1(function () { 2if (typeof window.SwymStorefrontLayoutAPI === 'undefined') { 3 window.SwymStorefrontLayoutAPI = {}; 4} 5if (typeof window.SwymStorefrontLayoutContext === 'undefined') { 6 window.SwymStorefrontLayoutContext = {}; 7} 8if(typeof window.SwymStorefrontLayoutExtensions === 'undefined'){ 9 window.SwymStorefrontLayoutExtensions = {}; 10} 11var SWYM_SHOP_API_KEY = 'd612f58ce0decfaba38849af3396fb5b'; 12function swymEscAttr(str) { 13 if (!str) return ''; 14 return String(str) 15 .replace(/&/g, '&') 16 .replace(/"/g, '"') 17 .replace(/</g, '<') 18 .replace(/>/g, '>') 19 .replace(/'/g, '''); 20} 21 22function isShopifyStoreRTLEnabled() { 23 // Check dir attributes 24 const htmlDir = document.documentElement.getAttribute('dir'); 25 const bodyDir = document.body.getAttribute('dir'); 26 27 // Check computed CSS direction 28 const htmlCSSDir = getComputedStyle(document.documentElement).direction; 29 const bodyCSSDir = getComputedStyle(document.body).direction; 30 31 // Optional: check for common theme class 32 const hasRTLClass = document.documentElement.classList.contains('rtl') || 33 document.body.classList.contains('rtl') || 34 document.querySelector('.rtl, .is-rtl') !== null; 35 36 return ( 37 htmlDir === 'rtl' || 38 bodyDir === 'rtl' || 39 htmlCSSDir === 'rtl' || 40 bodyCSSDir === 'rtl' || 41 hasRTLClass 42 ); 43} 44 45const isRTL = isShopifyStoreRTLEnabled(); 46if (isRTL) { 47 document.body.classList.add('swym-ccui-rtl'); 48} 49 50SwymStorefrontLayoutContext.ActionTypes = { 51 ManageListItem: 'ManageListItem', 52 ManageSflListItem: 'ManageSflListItem', 53 ManageSfyListItem: 'ManageSfyListItem', 54 AddToCollection: 'AddToCollection', 55 CreateCollection: 'CreateCollection', 56 EditCollection: 'EditCollection', 57 SaveCollection: 'SaveCollection', 58 ShareSingleWishlist: 'ShareSingleWishlist' 59} 60 61SwymStorefrontLayoutContext.NotificationStatusTypes = { 62 Success: 'success', 63 Error: 'error', 64 Warning: 'Warning', 65 Info: 'info', 66 Neutral: 'neutral', 67 Toast: 'toast' 68} 69 70SwymStorefrontLayoutContext.NotificationActionTypes = { 71 AddedToWishlist: 'added-to-wishlist', 72 AddToCollection: 'add-to-collection', 73 AddedToCollection: 'added-to-collection', 74 AddedToCart: 'added-to-cart' 75} 76 77SwymStorefrontLayoutContext.StorefrontLayoutUrls = Object.assign( 78 SwymStorefrontLayoutContext.StorefrontLayoutUrls || {}, 79 { 80 List: '#swym-list', 81 Collections: '#swym-collections', 82 CollectionList: '#swym-collection:' 83 } 84); 85 86SwymStorefrontLayoutContext.NotificationIconType = { 87 SuccessIcon: 'SuccessIcon', 88 RemoveIcon: 'RemoveIcon' 89} 90 91SwymStorefrontLayoutContext.LoaderViewType = { 92 WishlistListItem: 'wishlist-list-item', 93 CarouselListItem: 'carousel-list-item', 94 CollectionImages: 'collection-view-images', 95 SpinnerLoader: 'spinner-loader' 96} 97 98SwymStorefrontLayoutContext.StorefrontLayoutViewType = { 99 Wishlist: 'tabWishlist', 100 SaveForLater: 'tabSavedForLater', 101 SavedForYou: 'tabSavedForYou' 102} 103 104SwymStorefrontLayoutContext.Tabs = [SwymStorefrontLayoutContext.StorefrontLayoutViewType.Wishlist] 105 106SwymStorefrontLayoutContext.ListItemType = { 107 WishlistItem: 'WishlistItem', 108 CollectionItem: 'CollectionItem', 109 SaveForLaterItem: 'SaveForLaterItem', 110 SavedForYouItem: 'SavedForYouItem' 111} 112SwymStorefrontLayoutContext.CommonAddtoWL = false; 113SwymStorefrontLayoutContext.CommonRemovefromWL = false; 114 115/** 116 * Helper function to determine if multiple wishlists should be shown 117 * Returns an object with two flags: 118 * - shouldShowMultiple: true if carousel and collections should be displayed 119 * - canCreateLists: true if user can create new lists (feature flag is on) 120 */ 121SwymStorefrontLayoutContext.getMultiWishlistConfig = function() { 122 const isOldControlCentre = !('multiple-wishlist' in (window?.SwymEnabledCommonFeatures || {})); 123 const canCreateLists = isOldControlCentre 124 ? SwymStorefrontLayoutContext?.Settings?.EnableStorefrontLayoutCollection 125 : window?.SwymEnabledCommonFeatures?.['multiple-wishlist']; 126 127 // Show multiple wishlists if feature is enabled OR if user has more than 1 list 128 const hasMultipleLists = window.SwymStorefrontLayoutContext?.allLists?.length > 1; 129 const shouldShowMultiple = canCreateLists || hasMultipleLists; 130 131 return { 132 shouldShowMultiple, 133 canCreateLists 134 }; 135}; 136 137// The list name used by the SDK's auto-wishlist feature in multi-list mode 138const SFY_LIST_NAME = 'Saved For Me'; 139
140// Returns the dedicated "Saved For Me" list object in multi-list mode, or null in single-list mode. 141SwymStorefrontLayoutContext.getSfyList = function() { 142 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 143 if (!shouldShowMultiple) return null; 144 return (SwymStorefrontLayoutContext.allLists || []).find(l => l.lname === SFY_LIST_NAME) || null; 145}; 146 147/** 148 * Helper function to check if education layer (tooltips/toasts on collection and PDP pages) is enabled. 149 * Controlled via shop metafield `swym_wishlist.educationLayerEnabled`. 150 * Defaults to true (enabled) unless explicitly set to false. 151 */ 152SwymStorefrontLayoutContext.isEducationLayerEnabled = function() { 153 return window.SwymEducationLayerEnabled !== false; 154}; 155 156/** 157 * Returns whether the login nudge is enabled for this store. 158 * Defaults to true (enabled) unless explicitly set to false. 159 */ 160SwymStorefrontLayoutContext.isLoginNudgeEnabled = function() { 161 return window.SwymLoginNudgeEnabled !== false; 162}; 163 164window._SwymAJAXCart = async function () { 165 try { 166 const cartComponent = document.querySelector('cart-notification') || document.querySelector('cart-drawer'); 167 if (!cartComponent || typeof cartComponent.getSectionsToRender !== 'function') { 168 console.warn('[Cart Refresh] Cart component or getSectionsToRender is missing.'); 169 const modalPopup = document.querySelector("modal-popup"); 170 if(modalPopup && window.routes.cart) { 171 modalPopup.trigger("updateCart", { 172 callback: () => { 173 modalPopup.forwardEvent("openPopup", { 174 target: "CART", 175 url: window.routes.cart 176 }); 177 } 178 }); 179 return; 180 } 181 182 // Get current theme info 183 const currentThemeName = window?.Shopify?.theme?.schema_name?.trim(); 184 const currentThemeId = window?.Shopify?.theme?.theme_store_id; 185 186 if (!currentThemeName) return false; 187 188 // Use preset API to get theme event configuration 189 window._swat.fetchThemePreset({ 190 themeStoreId: currentThemeId, 191 schemaName: currentThemeName 192 }, (themeData) => { 193 if (themeData?.AJAXCartEvent?.[0]?.eventName) { 194 try { 195 // Get event details from preset API response 196 const event = themeData.AJAXCartEvent?.[0]; 197 198 // Create event config object only with defined properties 199 const eventConfig = {}; 200 if (event.bubbles !== undefined) { 201 eventConfig.bubbles = event.bubbles; 202 } 203 if (event.detail !== undefined) { 204 eventConfig.detail = event.detail; 205 } 206 207 // Create and dispatch custom event with only defined properties 208 const customEvent = new CustomEvent(event?.eventName, eventConfig); 209 210 // Dispatch event on appropriate target 211 const target = event.bubbles ? document.documentElement : document; 212 target.dispatchEvent(customEvent); 213 } catch (err) { 214 console.warn(`[Cart Refresh] Failed to dispatch event for theme ${currentThemeName}`, err); 215 } 216 } 217 }); 218 return; 219 } 220 221 const sectionsToRender = cartComponent.getSectionsToRender(); 222 const sectionIds = sectionsToRender.map(section => section.id).join(','); 223 if (!sectionIds) { 224 console.warn('[Cart Refresh] No section IDs found to render.'); 225 return; 226 } 227 228 const [sectionRes, cartDataRes] = await Promise.all([ 229 fetch(`/?sections=${sectionIds}`).then(res => res.ok ? res.json() : Promise.reject('Failed to fetch sections')), 230 fetch('/cart.js').then(res => res.ok ? res.json() : Promise.reject('Failed to fetch cart data')) 231 ]); 232 233 const parser = new DOMParser(); 234 235 // Update cart icon bubble 236 const cartIconBubble = document.getElementById('cart-icon-bubble'); 237 const iconHtmlDoc = parser.parseFromString(sectionRes['cart-icon-bubble'], 'text/html'); 238 const iconBubbleContent = iconHtmlDoc.getElementById('shopify-section-cart-icon-bubble')?.innerHTML; 239 if (cartIconBubble && iconBubbleContent) { 240 cartIconBubble.innerHTML = iconBubbleContent; 241 } 242 243 // Update cart drawer if it exists 244 const cartDrawer = document.querySelector('cart-drawer'); 245 if (cartDrawer) { 246 const drawerHtmlDoc = parser.parseFromString(sectionRes['cart-drawer'], 'text/html'); 247 const newDrawerContent = drawerHtmlDoc.querySelector('cart-drawer')?.innerHTML; 248 249 if (newDrawerContent !== undefined) { 250 cartDrawer.classList.toggle('is-empty', cartDataRes.item_count === 0); 251 cartDrawer.innerHTML = newDrawerContent; 252
253 const overlay = cartDrawer.querySelector('#CartDrawer-Overlay'); 254 if (overlay) { 255 overlay.addEventListener('click', cartDrawer.close?.bind(cartDrawer)); 256 } 257 } 258 } 259 } catch (error) { 260 console.error('[Cart Refresh] An error occurred:', error); 261 } 262}; 263 264/** 265 * Checks if the current user has permission to edit the given wishlist. 266 * @param {Object} list - The wishlist to check. 267 * @returns {boolean} - Returns true if the user can edit the list, otherwise false. 268 */ 269SwymStorefrontLayoutContext.checkUserCanEditList = (list) => { 270 if(!list){ 271 window._swat?.utils.warn('list not exist to check list access'); 272 return false; 273 } 274 if(list?.lty === 'sfl'){ 275 let canEditList = SwymStorefrontLayoutContext?.sflList?.lid === list?.lid; 276 return canEditList; 277 } 278 let canEditList = SwymStorefrontLayoutContext?.lists?.some((userList)=> userList?.lid === list?.lid); 279 return canEditList; 280} 281 282/** 283 * Formats a given price based on the store's money formatting settings. 284 * @param {number} value - The price value to format. 285 * @returns {string} - The formatted price string. 286 */ 287SwymStorefrontLayoutContext.FormatPrice = (value) => { 288 try { 289 if (window.SwymOverrideFormatMoneyFn) { 290 return window.SwymOverrideFormatMoneyFn(value); 291 } 292 if (window._swat.platform.formatMoney) { 293 if ( 294 window._swat.platform.currentMoneyFormat && 295 window._swat.platform.currentMoneyFormat() 296 ) { 297 return window._swat.platform.formatMoney( 298 value * 100, 299 window._swat.platform.currentMoneyFormat() 300 ); 301 } 302 } 303 } catch (err) { 304 window._swat?.utils.error("Error formatting price - " + value + err); 305 } 306 const currency = window._swat.currency; 307 return currency + window._swat.utils.padDecimal(value); 308} 309 310 311/** 312 * Retrieves the selected variant from a given item based on the provided variant ID. 313 * 314 * @param {Object} item - The item containing product data and variants. 315 * @param {string | number} variantId - The ID of the variant to find. 316 * @returns {Object | null} - The matching variant object if found, otherwise null. 317 */ 318SwymStorefrontLayoutContext.getSelectedVariant = (item, variantId) => { 319 return variantId? item?.productData?.variants?.find((variant) => variant?.id === variantId) || null:null; 320} 321 322/** 323 * Applies a transform function to listcontents across all context list references 324 * (DefaultList, collections, lists, allLists) to keep them in sync. 325 * @param {Function} transformFn - (listcontents: Array) => Array 326 */ 327SwymStorefrontLayoutContext.updateAllListContents = (transformFn) => { 328 if (SwymStorefrontLayoutContext.DefaultList) { 329 SwymStorefrontLayoutContext.DefaultList = { 330 ...SwymStorefrontLayoutContext.DefaultList, 331 listcontents: transformFn(SwymStorefrontLayoutContext.DefaultList.listcontents) 332 }; 333 } 334 ['collections', 'lists', 'allLists'].forEach(key => { 335 if (SwymStorefrontLayoutContext[key]) { 336 SwymStorefrontLayoutContext[key] = SwymStorefrontLayoutContext[key].map(list => ({ 337 ...list, 338 listcontents: transformFn(list.listcontents) 339 })); 340 } 341 }); 342}; 343 344/** 345 * Calculates the difference in hours between the current time and a given date-time string. 346 * @param {number} hours - The threshold hours to compare. 347 * @param {string} dateTimeString - The stored date-time string. 348 * @returns {boolean} - Returns true if the time difference exceeds the given hours, otherwise false. 349 */ 350SwymStorefrontLayoutExtensions.calcDifferenceInHours = (hours, dateTimeString) => { 351 try { 352 if (!dateTimeString) { 353 window._swat?.utils.warn("[WARNING] Date time string empty"); 354 return; 355 } 356 const storedDateTime = new Date(dateTimeString?.trim()); 357 const currentDateTime = new Date(); 358 359 const differenceInMs = Math.abs(currentDateTime - storedDateTime); 360 const differenceInHours = differenceInMs / (1000 * 60 * 60); 361 return differenceInHours > hours; 362 } catch (error) { 363 window._swat?.utils.warn("[Error/Warning] while performing health check", error); 364 return false; 365 } 366} 367 368 369/** 370 * Checks if a given container has a scrollbar and applies/removes a CSS class accordingly. 371 * @param {HTMLElement} container - The container element to check. 372 */ 373SwymStorefrontLayoutExtensions.checkContainerScrollbar = (container) => { 374 try{ 375 if(!container){ 376 window._swat?.utils.warn("container not available for scrollbar check"); 377 return; 378 } 379 if (container.scrollHeight > container.clientHeight) { 380 container.classList.add("swym-storefront-layout-container-has-scroll"); 381 } else { 382 container.classList.remove("swym-storefront-layout-container-has-scroll"); 383 } 384 }catch(error){ 385 window._swat?.utils.warn("invalid container for scrollbar check"); 386 } 387} 388 389/** 390 * Extracts computed font styles from heading elements (h1, h2, h3) with fallback to body. 391 * @returns {Object}
391 - An object containing font family and other relevant font properties. 392 */ 393SwymStorefrontLayoutExtensions.getHeadingFontStyles = () => { 394 try { 395 // Find the first available heading element (h1, h2, or h3) 396 const headingEl = document.querySelector('h1, h2, h3') || document.body; 397 const computedStyles = window.getComputedStyle(headingEl); 398 399 return { 400 fontFamily: computedStyles.fontFamily, 401 fontWeight: computedStyles.fontWeight, 402 letterSpacing: computedStyles.letterSpacing 403 }; 404 } catch (error) { 405 window._swat?.utils.warn("Error extracting heading font styles", error); 406 // Fallback to body styles if heading extraction fails 407 try { 408 const bodyStyles = window.getComputedStyle(document.body); 409 return { 410 fontFamily: bodyStyles.fontFamily, 411 fontWeight: bodyStyles.fontWeight, 412 letterSpacing: bodyStyles.letterSpacing 413 }; 414 } catch (fallbackError) { 415 window._swat?.utils.warn("Error extracting body font styles", fallbackError); 416 return {}; 417 } 418 } 419} 420 421/** 422 * Applies heading font styles to a given title element. 423 * @param {HTMLElement} titleElement - The title element to apply styles to. 424 */ 425SwymStorefrontLayoutExtensions.applyHeadingFontStyles = (titleElement) => { 426 try { 427 const headingFontStyles = SwymStorefrontLayoutExtensions?.getHeadingFontStyles(); 428 // When theme CSS mapping is active, typography is handled via CSS variables. 429 // Skip inline overrides for font-family, font-weight, and letter-spacing 430 // to avoid clobbering the CSS variable values with computed page styles. 431 const themeMappingActive = !!document.querySelector('[data-swym-theme-mapping]'); 432 433 if (titleElement && headingFontStyles) { 434 if (!themeMappingActive) { 435 // Apply font styles from heading elements to the title 436 if (headingFontStyles.fontFamily) { 437 titleElement.style.fontFamily = headingFontStyles.fontFamily; 438 } 439 if (headingFontStyles.fontWeight) { 440 titleElement.style.fontWeight = headingFontStyles.fontWeight; 441 } 442 if (headingFontStyles.letterSpacing) { 443 titleElement.style.letterSpacing = headingFontStyles.letterSpacing; 444 } 445 } 446 // Note: We don't apply fontSize and lineHeight as the title should maintain its own sizing 447 } 448 } catch (error) { 449 window._swat?.utils.warn(`Error applying heading font styles`, error); 450 } 451} 452 453SwymStorefrontLayoutExtensions.refreshWishList = () => { 454 try { 455 SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.updateList(); 456 } catch (error) { 457 window._swat?.utils.error('Failed to fetch wishlist', error); 458 } 459} 460 461SwymStorefrontLayoutExtensions.getShareIconSvg = () => { 462 return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true" focusable="false"> 463 <path d="M13.5 2.375C14.6669 2.375 15.625 3.33306 15.625 4.5C15.625 5.66694 14.6669 6.625 13.5 6.625C12.8556 6.625 12.2862 6.30928 11.8828 5.81445C11.7533 5.65562 11.5425 5.59393 11.3516 5.65137L11.2715 5.68359L6.77539 7.93262C6.5655 8.03762 6.45726 8.27438 6.51465 8.50195C6.58433 8.77824 6.625 8.86302 6.625 9C6.625 9.13698 6.58433 9.22176 6.51465 9.49805C6.45726 9.72562 6.5655 9.96238 6.77539 10.0674L11.2715 12.3164C11.4808 12.4211 11.7349 12.3669 11.8828 12.1855C12.2862 11.6907 12.8556 11.375 13.5 11.375C14.6669 11.375 15.625 12.3331 15.625 13.5C15.625 14.6669 14.6669 15.625 13.5 15.625C12.3331 15.625 11.375 14.6669 11.375 13.5C11.375 13.363 11.4157 13.2782 11.4854 13.002C11.5427 12.7744 11.4345 12.5376 11.2246 12.4326L6.72852 10.1836C6.54532 10.0919 6.32732 10.1214 6.17676 10.252L6.11719 10.3145C5.71385 10.8093 5.1444 11.125 4.5 11.125C3.33306 11.125 2.375 10.1669 2.375 9C2.375 7.83306 3.33306 6.875 4.5 6.875C5.1444 6.875 5.71385 7.19072 6.11719 7.68555C6.2651 7.86695 6.51918 7.92112 6.72852 7.81641L11.2246 5.56738C11.4345 5.46238 11.5427 5.22562 11.4854 4.99805C11.4157 4.72176 11.375 4.63698 11.375 4.5C11.375 3.33306 12.3331 2.375 13.5 2.375ZM13.5 11.5C12.3888 11.5 11.5 12.3888 11.5 13.5C11.5 14.6112 12.3888 15.5 13.5 15.5C14.6112 15.5 15.5 14.6112 15.5 13.5C15.5 12.3888 14.6112 11.5 13.5 11.5ZM4.5 7C3.38877 7 2.5 7.88877 2.5 9
463C2.5 10.1112 3.38877 11 4.5 11C5.61123 11 6.5 10.1112 6.5 9C6.5 7.88877 5.61123 7 4.5 7ZM13.5 2.5C12.3888 2.5 11.5 3.38877 11.5 4.5C11.5 5.61123 12.3888 6.5 13.5 6.5C14.6112 6.5 15.5 5.61123 15.5 4.5C15.5 3.38877 14.6112 2.5 13.5 2.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/> 464 </svg>`; 465} 466 467/** 468 * Returns the wishlist icon SVG markup. 469 * Currently uses a hardcoded heart icon. Dynamic icon support (heart/star/bookmark 470 * based on retailer config) will be added later. 471 * @param {'filled'|'unfilled'} state - Whether to return the filled or unfilled icon variant 472 * @param {object} [opts] - Optional overrides 473 * @param {string} [opts.color] - Icon stroke/fill color (default: 'currentColor') 474 * @param {string} [opts.width] - SVG width attribute (default: '17') 475 * @param {string} [opts.height] - SVG height attribute (default: '15') 476 * @returns {string} SVG markup string 477 */ 478SwymStorefrontLayoutExtensions.getWishlistIconSvg = (state = 'filled', opts = {}) => { 479 const color = opts.color || 'currentColor'; 480 const width = opts.width || '17'; 481 const height = opts.height || '15'; 482 483 if (state === 'unfilled') { 484 return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 17 16" fill="none" aria-hidden="true" focusable="false"> 485 <path d="M3.94617 0.518585C4.71879 0.439587 5.46423 0.623711 6.12292 0.918976C7.09924 1.35664 7.94118 2.06375 8.495 2.66019C8.69022 2.42887 8.92463 2.21448 9.18054 2.02249C9.66446 1.65956 10.2568 1.35316 10.8905 1.14944C12.1427 0.746943 13.6652 0.716791 14.8436 1.60062C16.0064 2.47285 16.4537 3.64555 16.3866 4.90921C16.3211 6.14189 15.7705 7.444 14.993 8.66409C13.4387 11.1032 10.8538 13.404 8.76746 14.4473L8.53113 14.5655L8.30066 14.4375C5.98745 13.1524 3.46501 10.932 1.9364 8.58597C1.17217 7.41299 0.633191 6.17363 0.521362 4.97757C0.407722 3.76125 0.739132 2.59768 1.69031 1.64651C2.39175 0.945176 3.16373 0.598678 3.94617 0.518585Z" fill="white" stroke="${color}"/> 486 </svg>`; 487 } 488 return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 17 15" fill="none" aria-hidden="true" focusable="false"> 489 <path d="M2.04363 2.00013C4.54368 -0.499876 7.71029 2.33346 8.54363 3.50012C9.21029 2.00012 12.5437 0.500119 14.5437 2.00013C18.6166 5.05486 12.5437 12.0001 8.54363 14.0001C4.04367 11.5001 -1.29179 5.3355 2.04363 2.00013Z" fill="${color}"/> 490 </svg>`; 491} 492 493SwymStorefrontLayoutExtensions.refreshSFLList = () => { 494 try { 495 SwymStorefrontLayoutAPI?.SwymSFLAsyncApis?.updateSflList(); 496 } catch (error) { 497 window._swat?.utils.error('Failed to fetch sfl list', error); 498 } 499} 500 501/** 502 * Add All To Cart — Control Centre UI only. 503 * 504 * Gated by the retailer setting `UI.ShowStorefrontLayoutAddAllToCart`. This is a 505 * separate key from the legacy UI's `UI.ShowAddAllToCart` so the two UIs can be 506 * switched on independently for a merchant. 507 */ 508SwymStorefrontLayoutExtensions.AddAllToCart = { 509 /** 510 * True when the merchant has enabled Add All To Cart for this UI. 511 * @returns {boolean} 512 */ 513 isEnabled() { 514 return window._swat?.retailerSettings?.UI?.ShowStorefrontLayoutAddAllToCart === true; 515 }, 516 517 /** 518 * Items from a list that can actually go into the cart: the selected variant is in 519 * stock and, when tag based actions are on, the product is not tagged to hide or 520 * disable add to cart. 521 * @param {object} list - Wishlist or collection with a `listcontents` array 522 * @returns {Array<object>} eligible list items 523 */ 524 getEligibleItems(list) { 525 const settings = window._swat?.retailerSettings; 526 const tagBasedActionEnabled = settings?.Wishlist?.TagBasedActionEnabled; 527 const hideAddToCartTag = settings?.UI?.HideAddToCartTag || 'swym-hide-addtocart'; 528 const disabledAddToCartTag = settings?.UI?.DisabledAddtoCartTag || 'swym-disabled-addtocart-with-text'; 529 530 return (list?.listcontents || []).filter((item) => { 531 const variantId = item?.cprops?.selectedVariantId || item?.epi; 532 const variant = SwymStorefrontLayoutContext?.getSelectedVariant(item, variantId); 533 if (variant && !variant.available) return false;
534 if (!tagBasedActionEnabled) return true; 535 const isTagged = (tag) => window._swat?.platform?.isTagAvailable(tag, item?.productData); 536 return !isTagged(hideAddToCartTag) && !isTagged(disabledAddToCartTag); 537 }); 538 }, 539 540 /** 541 * The drawer is a narrow column with a long scroll, so the button belongs in a 542 * sticky footer the way a cart drawer's checkout button does. The modal and the 543 * page are wide, where a full-width bar reads as a banner — there the button sits 544 * right aligned in the header row instead. 545 * @returns {'bottom'|'top'} which mount point this layout uses 546 */ 547 getPlacement() { 548 return SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer' ? 'bottom' : 'top'; 549 }, 550 551 /** 552 * Renders the button into whichever mount point suits the current layout, and 553 * wires its click handler. Renders nothing when the setting is off or the list 554 * has no item that can be added. 555 * @param {HTMLElement} root - Element holding the top and bottom mount points 556 * @param {object} list - Wishlist or collection the button acts on 557 */ 558 mount(root, list) { 559 if (!root) return; 560 const containers = root.querySelectorAll('.swym-storefront-layout-add-all-to-cart-container'); 561 containers.forEach((container) => { container.innerHTML = ''; }); 562 if (!this.isEnabled() || !this.getEligibleItems(list).length) return; 563 564 const container = root.querySelector(`.swym-storefront-layout-add-all-to-cart-${this.getPlacement()}`); 565 if (!container) return; 566 567 const cta = SwymStorefrontLayoutContext?.Strings?.addAllToCartCta || 'Add All To Cart'; 568 container.innerHTML = ` 569 <button class="swym-storefront-layout-add-all-to-cart-button" data-action="add-all-to-cart" aria-label="${swymEscAttr(cta)}">${cta}</button> 570 `; 571 const button = container.querySelector('[data-action="add-all-to-cart"]'); 572 button?.addEventListener('click', () => this.handleClick(button, list)); 573 }, 574 575 /** 576 * Click handler for the button — puts it in its loading state and starts the add. 577 * @param {HTMLElement} button 578 * @param {object} list 579 */ 580 handleClick(button, list) { 581 const eligibleItems = this.getEligibleItems(list); 582 if (!eligibleItems.length) { 583 this.showError(SwymStorefrontLayoutContext?.Strings?.addAllToCartEmptyText); 584 return; 585 } 586 587 const originalLabel = button.innerHTML; 588 button.innerHTML = SwymStorefrontLayoutContext?.Strings?.addingToCart || 'Adding To Cart'; 589 button.setAttribute('disabled', true); 590 this.addItemsToCart(eligibleItems, button, originalLabel); 591 }, 592 593 /** 594 * Adds the given items to the cart in a single request. 595 * @param {Array<object>} items 596 * @param {HTMLElement} button 597 * @param {string} originalLabel - Button label to restore on failure 598 */ 599 addItemsToCart(items, button, originalLabel) { 600 window._swat?.platform?.addAllToCart( 601 items.map(({ epi, empi, qty }) => ({ epi, empi, qty: qty || 1 })), 602 () => this.onAddSuccess(items, button, originalLabel), 603 (error) => { 604 // Shopify's /cart/add.js is atomic: one unaddable line rejects the whole payload 605 // with 422 and adds nothing. Its 422 body is only {status, message, description} — 606 // no variant id — so the failing line cannot be identified from the response. 607 // Retry the lines one at a time and let Shopify judge each on its own instead of 608 // parsing the message, which is merchant locale specific and would drop a 609 // "Shirt XL" when the error names "Shirt". 610 if (error?.status === 422 && items.length > 1) { 611 this.addItemsOneByOne(items, button, originalLabel); 612 return; 613 } 614 button.innerHTML = originalLabel; 615 button.removeAttribute('disabled'); 616 this.showError(error?.description || error?.detail || SwymStorefrontLayoutContext?.Strings?.addAllToCartErrorText); 617 } 618 ); 619 }, 620 621 /** 622 * Adds items one at a time, sequentially, keeping whichever lines Shopify accepts. 623 * Sequential rather than parallel because concurrent cart mutations race each other. 624 * @param {Array<object>} items 625 * @param {HTMLElement} button 626 * @param {string} originalLabel 627 */ 628 addItemsOneByOne(items, button, originalLabel) { 629 const addedItems = []; 630 let lastError = null; 631 632 const addNext = (index) => { 633 if (index >= items.length) { 634 if (addedItems.length) { 635 this.onAddSuccess(addedItems, button, originalLabel); 636 return; 637 } 638 button.innerHTML = originalLabel; 639 button.removeAttribute('disabled'); 640 this.showError(lastError?.description || lastError?.detail || SwymStorefrontLayoutContext?.Strings?.addAllToCartErrorText); 641 return; 642 } 643 644 const { epi, empi, qty } = items[index]; 645 window._swat?.platform?.addAllToCart( 646 [{ epi, empi, qty: qty || 1 }], 647 () => { addedItems.push(items[index]); addNext(index + 1); }, 648 (error) => { lastError = error; addNext(index + 1); } 649 ); 650 }; 651 652 addNext(0); 653 }, 654 655 /** 656 * Restores the button, notifies the shopper and refreshes the theme cart. 657 * @param {Array<object>} itemsAdded 658 * @param {HTMLElement} button 659 * @param {string} originalLabel 660 */ 661 onAddSuccess(itemsAdded, button, originalLabel) { 662 button.innerHTML = originalLabel; 663 button.removeAttribute('disabled'); 664 665 const successText = SwymStorefrontLayoutContext?.Strings?.addAllToCartSuccessText || ''; 666 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 667 message: window._swat?.utils?.renderTemplateString(successText, { addCount: itemsAdded.length }), 668 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 669 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes?.AddedToCart, 670 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 671 action: { 672 label: SwymStorefrontLayoutContext?.Strings?.notificationActionGoToCart, 673 onClick: () => { 674 window.location.href = `${window.Shopify.routes.root}cart`; 675 } 676 } 677 }, {
678 showActionButton: true, 679 showImage: false 680 }); 681 682 window._swat?.instrumentV3( 683 SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutAddedAllToCart, 684 { 685 "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutCartConversion, 686 itemCount: itemsAdded.length 687 } 688 ); 689 window?._SwymAJAXCart(); 690 }, 691 692 /** 693 * Shows an error notification for a failed add. 694 * @param {string} message 695 */ 696 showError(message) { 697 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 698 title: message || SwymStorefrontLayoutContext?.Strings?.addAllToCartErrorText, 699 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Error 700 }, { 701 showTitle: true, 702 showImage: false 703 }); 704 } 705} 706 707/** 708 * Theme CSS variable mapping — maps Shopify theme CSS variables to Swym's 709 * --swym-storefront-layout-* variables so the drawer inherits theme colors, 710 * typography, button styles, and input styles automatically. 711 * 712 * Supports Dawn-family and Horizon-family themes. Unknown themes fall back 713 * to the existing hardcoded :root values in storefront-layout.css. 714 * 715 * Runs once at init — injects a single <style> block, zero ongoing cost. 716 */ 717SwymStorefrontLayoutExtensions.ThemeCSSMapping = (() => { 718 /** 719 * Detects theme family using CSS variable feature detection. 720 * Dawn-family themes define --font-heading-family on :root. 721 * Horizon-family themes define --font-h2--family on :root. 722 * This approach works with custom-named / forked themes that still use 723 * the same CSS variable system as their parent theme. 724 * Falls back to name-based matching as a secondary check. 725 */ 726 const DAWN_THEMES = ['Dawn', 'Craft', 'Crave', 'Sense', 'Taste', 'Studio', 'Ride', 'Colorblock', 'Refresh', 'Origin', 'Publisher', 'Spotlight', 'Trade', 'Rise']; 727 const HORIZON_THEMES = ['Horizon', 'Ritual', 'Heritage', 'Atelier', 'Pitch', 'Tinker', 'Dwell', 'Vessel', 'Savor', 'Fabric']; 728 729 function getThemeFamily() { 730 try { 731 // Primary: CSS variable feature detection (works with custom-named themes) 732 const rootStyles = getComputedStyle(document.documentElement); 733 const hasDawnVars = rootStyles.getPropertyValue('--font-heading-family').trim(); 734 const hasHorizonVars = rootStyles.getPropertyValue('--font-h2--family').trim(); 735 if (hasDawnVars) return 'dawn'; 736 if (hasHorizonVars) return 'horizon'; 737 738 // Fallback: name-based matching 739 const themeName = window.Shopify?.theme?.name; 740 if (!themeName) return null; 741 if (DAWN_THEMES.some(t => themeName.includes(t))) return 'dawn'; 742 if (HORIZON_THEMES.some(t => themeName.includes(t))) return 'horizon'; 743 return null; 744 } catch (e) { 745 return null; 746 } 747 } 748 749 function getDawnThemeVariables() { 750 return ` 751 :root { 752 /* UI */ 753 --swym-theme-layout-ui-bg-color: rgb(var(--color-background)); 754 --swym-theme-layout-ui-text-color: rgba(var(--color-foreground), 0.75); 755 --swym-theme-layout-color-bg: rgb(var(--color-background)); 756 --swym-theme-layout-color-text: rgb(var(--color-foreground)); 757 --swym-theme-layout-color-text-default: rgb(var(--color-foreground)); 758 --swym-theme-layout-color-text-sub: rgba(var(--color-foreground), 0.55); 759 --swym-theme-layout-color-text-sub2: rgba(var(--color-foreground), 0.4); 760 --swym-theme-layout-color-border: rgba(var(--color-foreground), 0.15); 761 --swym-theme-layout-border-color: rgba(var(--color-foreground), 0.15); 762 763 /* TYPOGRAPHY */ 764 --swym-theme-layout-font-family-heading: var(--font-heading-family); 765 --swym-theme-layout-font-weight-heading: var(--font-heading-weight); 766 --swym-theme-layout-font-style-heading: var(--font-heading-style); 767 --swym-theme-layout-text-transform-heading: none; 768 --swym-theme-layout-letter-spacing-heading: calc(var(--font-heading-scale) * 0.06rem); 769 --swym-theme-layout-text-color-heading: rgb(var(--color-foreground)); 770 --swym-theme-layout-font-family-body: var(--font-body-family);
771 --swym-theme-layout-font-weight-body: var(--font-body-weight); 772 --swym-theme-layout-font-weight-body-bold: var(--font-body-weight-bold); 773 --swym-theme-layout-font-style-body: var(--font-body-style); 774 --swym-theme-layout-letter-spacing-body: 0.06rem; 775 --swym-theme-layout-text-transform-body: none; 776 777 /* BUTTONS — primary */ 778 --swym-theme-layout-button-border-color-primary: rgba(var(--color-button-text), calc(1 - var(--buttons-border-opacity, 1))); 779 --swym-theme-layout-button-border-radius: var(--buttons-radius, 0px); 780 --swym-theme-layout-button-border-radius-primary: var(--buttons-radius, 0px); 781 --swym-theme-layout-button-border-style-primary: solid; 782 --swym-theme-layout-button-border-width-primary: var(--buttons-border-width, 0px); 783 --swym-theme-layout-button-box-shadow-primary: var(--buttons-shadow-horizontal-offset, 0px) var(--buttons-shadow-vertical-offset, 0px) var(--buttons-shadow-blur-radius, 0px) rgba(var(--color-shadow, 0, 0, 0), var(--buttons-shadow-opacity, 0)); 784 --swym-theme-layout-button-color-bg-primary: rgba(var(--color-button), var(--alpha-button-background)); 785 --swym-theme-layout-button-color-text-primary: rgb(var(--color-button-text)); 786 --swym-theme-layout-button-font-family-primary: var(--font-body-family); 787 --swym-theme-layout-button-font-size-primary: 1.5rem; 788 --swym-theme-layout-button-font-style-primary: ; 789 --swym-theme-layout-button-font-weight-primary: var(--font-body-weight); 790 --swym-theme-layout-button-letter-spacing-primary: 0.1rem; 791 --swym-theme-layout-button-text-transform-primary: none; 792 793 /* BUTTONS — secondary */ 794 --swym-theme-layout-button-border-color-secondary: rgba(var(--color-secondary-button-text), var(--buttons-border-opacity, 1)); 795 --swym-theme-layout-button-border-radius-secondary: var(--buttons-radius, 0px); 796 --swym-theme-layout-button-border-style-secondary: solid; 797 --swym-theme-layout-button-border-width-secondary: var(--buttons-border-width, 0px); 798 --swym-theme-layout-button-box-shadow-secondary: var(--buttons-shadow-horizontal-offset, 0px) var(--buttons-shadow-vertical-offset, 0px) var(--buttons-shadow-blur-radius, 0px) rgba(var(--color-shadow, 0, 0, 0), var(--buttons-shadow-opacity, 0)); 799 --swym-theme-layout-button-color-bg-secondary: rgb(var(--color-secondary-button)); 800 --swym-theme-layout-button-color-text-secondary: rgb(var(--color-secondary-button-text)); 801 --swym-theme-layout-button-font-family-secondary: var(--font-body-family); 802 --swym-theme-layout-button-font-size-secondary: 1.5rem; 803 --swym-theme-layout-button-font-style-secondary: ; 804 --swym-theme-layout-button-font-weight-secondary: var(--font-body-weight); 805 --swym-theme-layout-button-letter-spacing-secondary: 0.1rem; 806 --swym-theme-layout-button-text-transform-secondary: none; 807 808 /* INPUTS */ 809 --swym-theme-layout-input-color-bg: rgb(var(--color-background)); 810 --swym-theme-layout-input-color-text: rgb(var(--color-foreground)); 811 --swym-theme-layout-input-border-color: rgba(var(--color-foreground), var(--inputs-border-opacity, 0.55)); 812 --swym-theme-layout-input-border-radius: var(--inputs-radius, 0px); 813 --swym-theme-layout-input-border-style: solid; 814 --swym-theme-layout-input-border-width: var(--inputs-border-width, 1px); 815 --swym-theme-layout-input-box-shadow: 0; 816 --swym-theme-layout-input-box-shadow-hover: 0 0 0 var(--inputs-border-width, 1px) rgba(var(--color-foreground), var(--inputs-border-opacity, 0.55)); 817 --swym-theme-layout-input-box-shadow-focus: 0 0 0 var(--inputs-border-width, 1px) rgba(var(--color-foreground), 1); 818 --swym-theme-layout-input-focus-border-color: rgba(var(--color-foreground), 1); 819 --swym-theme-layout-input-focus-outline-color: transparent; 820 --swym-theme-layout-input-focus-outline-offset: 0; 821 --swym-theme-layout-input-focus-outline-style: solid; 822 --swym-theme-layout-input-focus-outline-width: 0; 823 --swym-theme-layout-input-transition: box-shadow var(--duration-short, 100ms) ease; 824 } 825 `; 826 } 827 828 function getHorizonThemeVariables() { 829 return ` 830 :root { 831 /* UI */ 832 --swym-theme-layout-ui-bg-color: var(--color-background); 833 --swym-theme-layout-ui-text-color: var(--color-foreground); 834 --swym-theme-layout-color-bg: var(--color-background); 835 --swym-theme-layout-color-text: var(--color-foreground); 836 --swym-theme-layout-color-text-default: var(--color-foreground); 837 --swym-theme-layout-color-text-sub: color-mix(in srgb, var(--color-foreground) 55%, transparent); 838 --swym-theme-layout-color-text-sub2: color-mix(in srgb, var(--color-foreground) 40%, transparent); 839 --swym-theme-layout-color-border: color-mix(in srgb, var(--color-foreground) 15%, transparent); 840 --swym-theme-layout-border-color: color-mix(in srgb, var(--color-foreground) 15%, transparent); 841 842 /* TYPOGRAPHY */ 843 --swym-theme-layout-font-family-heading: var(--font-h2--family);
844 --swym-theme-layout-font-weight-heading: var(--font-h2--weight); 845 --swym-theme-layout-font-style-heading: var(--font-h2--style); 846 --swym-theme-layout-text-transform-heading: var(--font-h2--case); 847 --swym-theme-layout-letter-spacing-heading: var(--font-h2--letter-spacing); 848 --swym-theme-layout-text-color-heading: var(--font-h2--color); 849 --swym-theme-layout-font-family-body: var(--font-paragraph--family); 850 --swym-theme-layout-font-weight-body: var(--font-paragraph--weight); 851 --swym-theme-layout-font-weight-body-bold: bold; 852 --swym-theme-layout-font-style-body: var(--font-paragraph--style); 853 --swym-theme-layout-letter-spacing-body: var(--font-paragraph--letter-spacing); 854 --swym-theme-layout-text-transform-body: var(--font-paragraph--case); 855 856 /* BUTTONS — primary */ 857 --swym-theme-layout-button-border-color-primary: var(--color-primary-button-background); 858 --swym-theme-layout-button-border-radius: var(--style-border-radius-buttons-primary); 859 --swym-theme-layout-button-border-radius-primary: var(--style-border-radius-buttons-primary); 860 --swym-theme-layout-button-border-style-primary: solid; 861 --swym-theme-layout-button-border-width-primary: 0; 862 --swym-theme-layout-button-box-shadow-primary: inset 0 0 0 var(--style-border-width-primary) var(--color-primary-button-border); 863 --swym-theme-layout-button-color-bg-primary: var(--color-primary-button-background); 864 --swym-theme-layout-button-color-text-primary: var(--color-primary-button-text); 865 --swym-theme-layout-button-font-family-primary: var(--button-font-family-primary); 866 --swym-theme-layout-button-font-size-primary: var(--font-paragraph--size); 867 --swym-theme-layout-button-font-style-primary: var(--font-paragraph--style); 868 --swym-theme-layout-button-font-weight-primary: var(--font-paragraph--weight); 869 --swym-theme-layout-button-letter-spacing-primary: var(--font-paragraph--letter-spacing); 870 --swym-theme-layout-button-text-transform-primary: var(--button-text-case-primary); 871 872 /* BUTTONS — secondary */ 873 --swym-theme-layout-button-border-color-secondary: var(--color-secondary-button-background); 874 --swym-theme-layout-button-border-radius-secondary: var(--style-border-radius-buttons-secondary); 875 --swym-theme-layout-button-border-style-secondary: solid; 876 --swym-theme-layout-button-border-width-secondary: 0; 877 --swym-theme-layout-button-box-shadow-secondary: inset 0 0 0 var(--style-border-width-secondary) var(--color-secondary-button-border); 878 --swym-theme-layout-button-color-bg-secondary: var(--color-secondary-button-background); 879 --swym-theme-layout-button-color-text-secondary: var(--color-secondary-button-text); 880 --swym-theme-layout-button-font-family-secondary: var(--button-font-family-secondary); 881 --swym-theme-layout-button-font-size-secondary: var(--font-paragraph--size); 882 --swym-theme-layout-button-font-style-secondary: var(--font-paragraph--style); 883 --swym-theme-layout-button-font-weight-secondary: var(--font-paragraph--weight); 884 --swym-theme-layout-button-letter-spacing-secondary: var(--font-paragraph--letter-spacing); 885 --swym-theme-layout-button-text-transform-secondary: var(--button-text-case-secondary); 886 887 /* INPUTS */ 888 --swym-theme-layout-input-color-bg: var(--color-input-background); 889 --swym-theme-layout-input-color-text: var(--color-input-text); 890 --swym-theme-layout-input-border-color: var(--color-input-border); 891 --swym-theme-layout-input-border-radius: var(--style-border-radius-inputs); 892 --swym-theme-layout-input-border-style: solid; 893 --swym-theme-layout-input-border-width: var(--style-border-width-inputs); 894 --swym-theme-layout-input-box-shadow: 0; 895 --swym-theme-layout-input-box-shadow-hover: 0; 896 --swym-theme-layout-input-box-shadow-focus: 0; 897 --swym-theme-layout-input-focus-border-color: var(--color-input-border); 898 --swym-theme-layout-input-focus-outline-color: currentColor; 899 --swym-theme-layout-input-focus-outline-offset: var(--focus-outline-offset); 900 --swym-theme-layout-input-focus-outline-style: solid; 901 --swym-theme-layout-input-focus-outline-width: var(--focus-outline-width); 902 --swym-theme-layout-input-transition: none; 903 } 904 `; 905 } 906 907 function getBridgeVariables() { 908 return ` 909 #swym-storefront-layout-container { 910 /* UI */ 911 --swym-storefront-layout-ui-bg-color: var(--swym-theme-layout-ui-bg-color); 912 --swym-storefront-layout-ui-text-color: var(--swym-theme-layout-ui-text-color); 913 --swym-storefront-layout-color-bg: var(--swym-theme-layout-color-bg); 914 --swym-storefront-layout-color-text: var(--swym-theme-layout-color-text); 915 --swym-storefront-layout-color-text-default: var(--swym-theme-layout-color-text-default); 916 --swym-storefront-layout-color-text-sub: var(--swym-theme-layout-color-text-sub); 917 --swym-storefront-layout-color-text-sub2: var(--swym-theme-layout-color-text-sub2); 918 --swym-storefront-layout-color-border: var(--swym-theme-layout-color-border); 919 --swym-storefront-layout-border-color: var(--swym-theme-layout-border-color); 920 921 /* TYPOGRAPHY */ 922 --swym-storefront-layout-font-family-heading: var(--swym-theme-layout-font-family-heading);
923 --swym-storefront-layout-font-weight-heading: var(--swym-theme-layout-font-weight-heading); 924 --swym-storefront-layout-font-style-heading: var(--swym-theme-layout-font-style-heading); 925 --swym-storefront-layout-text-transform-heading: var(--swym-theme-layout-text-transform-heading); 926 --swym-storefront-layout-letter-spacing-heading: var(--swym-theme-layout-letter-spacing-heading); 927 --swym-storefront-layout-text-color-heading: var(--swym-theme-layout-text-color-heading); 928 --swym-storefront-layout-font-family-body: var(--swym-theme-layout-font-family-body); 929 --swym-storefront-layout-font-weight-body: var(--swym-theme-layout-font-weight-body); 930 --swym-storefront-layout-font-weight-body-bold: var(--swym-theme-layout-font-weight-body-bold); 931 --swym-storefront-layout-font-style-body: var(--swym-theme-layout-font-style-body); 932 --swym-storefront-layout-letter-spacing-body: var(--swym-theme-layout-letter-spacing-body); 933 934 /* BUTTONS — primary */ 935 --swym-storefront-layout-button-color-bg-primary: var(--swym-theme-layout-button-color-bg-primary); 936 --swym-storefront-layout-button-color-text-primary: var(--swym-theme-layout-button-color-text-primary); 937 --swym-storefront-layout-button-color-bg-secondary: var(--swym-theme-layout-button-color-bg-secondary); 938 --swym-storefront-layout-button-color-text-secondary: var(--swym-theme-layout-button-color-text-secondary); 939 --swym-storefront-layout-button-border-color-primary: var(--swym-theme-layout-button-border-color-primary); 940 --swym-storefront-layout-button-border-radius: var(--swym-theme-layout-button-border-radius); 941 --swym-storefront-layout-button-border-radius-primary: var(--swym-theme-layout-button-border-radius-primary); 942 --swym-storefront-layout-button-border-style-primary: var(--swym-theme-layout-button-border-style-primary); 943 --swym-storefront-layout-button-border-width-primary: var(--swym-theme-layout-button-border-width-primary); 944 --swym-storefront-layout-button-box-shadow-primary: var(--swym-theme-layout-button-box-shadow-primary); 945 --swym-storefront-layout-button-font-family-primary: var(--swym-theme-layout-button-font-family-primary); 946 --swym-storefront-layout-button-font-weight-primary: var(--swym-theme-layout-button-font-weight-primary); 947 --swym-storefront-layout-button-font-size-primary: var(--swym-theme-layout-button-font-size-primary); 948 --swym-storefront-layout-button-font-style-primary: var(--swym-theme-layout-button-font-style-primary); 949 --swym-storefront-layout-button-text-transform-primary: var(--swym-theme-layout-button-text-transform-primary); 950 --swym-storefront-layout-button-letter-spacing-primary: var(--swym-theme-layout-button-letter-spacing-primary); 951 952 /* BUTTONS — secondary */ 953 --swym-storefront-layout-button-border-color-secondary: var(--swym-theme-layout-button-border-color-secondary); 954 --swym-storefront-layout-button-border-radius-secondary: var(--swym-theme-layout-button-border-radius-secondary); 955 --swym-storefront-layout-button-border-style-secondary: var(--swym-theme-layout-button-border-style-secondary); 956 --swym-storefront-layout-button-border-width-secondary: var(--swym-theme-layout-button-border-width-secondary); 957 --swym-storefront-layout-button-box-shadow-secondary: var(--swym-theme-layout-button-box-shadow-secondary); 958 --swym-storefront-layout-button-font-family-secondary: var(--swym-theme-layout-button-font-family-secondary); 959 --swym-storefront-layout-button-font-weight-secondary: var(--swym-theme-layout-button-font-weight-secondary); 960 --swym-storefront-layout-button-font-style-secondary: var(--swym-theme-layout-button-font-style-secondary); 961 --swym-storefront-layout-button-text-transform-secondary: var(--swym-theme-layout-button-text-transform-secondary); 962 --swym-storefront-layout-button-letter-spacing-secondary: var(--swym-theme-layout-button-letter-spacing-secondary); 963 --swym-storefront-layout-button-font-size-secondary: var(--swym-theme-layout-button-font-size-secondary); 964 965 /* INPUTS */ 966 --swym-storefront-layout-input-color-bg: var(--swym-theme-layout-input-color-bg); 967 --swym-storefront-layout-input-color-text: var(--swym-theme-layout-input-color-text); 968 --swym-storefront-layout-input-border-color: var(--swym-theme-layout-input-border-color); 969 --swym-storefront-layout-input-border-radius: var(--swym-theme-layout-input-border-radius); 970 --swym-storefront-layout-input-border-style: var(--swym-theme-layout-input-border-style); 971 --swym-storefront-layout-input-border-width: var(--swym-theme-layout-input-border-width); 972 --swym-storefront-layout-input-box-shadow: var(--swym-theme-layout-input-box-shadow); 973 --swym-storefront-layout-input-box-shadow-hover: var(--swym-theme-layout-input-box-shadow-hover); 974 --swym-storefront-layout-input-box-shadow-focus: var(--swym-theme-layout-input-box-shadow-focus); 975 --swym-storefront-layout-input-focus-border-color: var(--swym-theme-layout-input-focus-border-color); 976 --swym-storefront-layout-input-focus-outline-color: var(--swym-theme-layout-input-focus-outline-color); 977 --swym-storefront-layout-input-focus-outline-offset: var(--swym-theme-layout-input-focus-outline-offset); 978 --swym-storefront-layout-input-focus-outline-style: var(--swym-theme-layout-input-focus-outline-style); 979 --swym-storefront-layout-input-focus-outline-width: var(--swym-theme-layout-input-focus-outline-width); 980 --swym-storefront-layout-input-transition: var(--swym-theme-layout-input-transition); 981 } 982 `; 983 } 984 985 function inject() { 986 try { 987 const family = getThemeFamily(); 988 if (!family) return; // Unknown theme — keep existing hardcoded :root values 989 990 // When block settings are enabled, the merchant controls colors via the 991 // theme editor. Skip the entire CSS mapping (theme vars + bridge) so 992 // block settings and :root defaults are used — matching pre-mapping behavior. 993 const blockSettingsEnabled = window.SwymStorefrontLayoutContext?.BlockSettingsEnabled || false; 994 if (blockSettingsEnabled) return; 995 996 const themeVars = family === 'dawn' ? getDawnThemeVariables() : getHorizonThemeVariables(); 997 const bridgeVars = getBridgeVariables(); 998 999 const style = document.createElement('style'); 1000 style.setAttribute('data-swym-theme-mapping', family); 1001 style.textContent = themeVars + bridgeVars; 1002 document.head.appendChild(style); 1003 } catch (e) { 1004 // Silently fail — fallback to hardcoded :root values 1005 } 1006 } 1007 1008 return { inject, getThemeFamily }; 1009})(); 1010 1011/** 1012 * Defines action codes for various storefront layout events. 1013 */ 1014SwymStorefrontLayoutExtensions.InstrumentActionCodes = { 1015 StorefrontLayoutInitialized: 701, 1016 StorefrontLayoutOpened: 702, 1017 StorefrontLayoutClosed: 703, 1018 StorefrontLayoutUserLogIn: 704, 1019 StorefrontLayoutCollectionRendered: 705, 1020 StorefrontLayoutWishlistItemsRendered: 706, 1021 StorefrontLayoutVariantChanged: 707, 1022 StorefrontLayoutItemAddedToCart: 708, 1023 StorefrontLayoutItemAddedToList: 709, 1024 StorefrontLayoutItemRemovedFromList: 710, 1025 StorefrontLayoutCollectionCreated: 711, 1026 StorefrontLayoutCollectionOpened: 712, 1027 StorefrontLayoutCollectionRenamed: 713, 1028 StorefrontLayoutCollectionDeleted: 714, 1029 StorefrontLayoutCollectionGridViewOpened: 715, 1030 StorefrontLayoutSFLInitialized: 716, 1031 StorefrontLayoutSFLItemsRendered: 717, 1032 StorefrontLayoutSFLItemAddedToList: 718, 1033 StorefrontLayoutSFLItemRemovedFromList: 719, 1034 StorefrontLayoutSFLItemMovedToCart: 720, 1035 StorefrontLayoutPageRendered: 721, 1036 StorefrontLayoutModalRendered: 722, 1037 StorefrontLayoutSideDrawerRendered: 723, 1038 // Saved For You 1039 StorefrontLayoutSFYTabViewed: 724, 1040 StorefrontLayoutSFYItemAddedToCart: 725, 1041 StorefrontLayoutSFYItemRemoved: 726, 1042 StorefrontLayoutSFYItemMovedToWishlist: 727, 1043 // AW Login Nudge 1044 StorefrontLayoutAWNudge1Shown: 728, 1045 StorefrontLayoutAWNudge1LoginClicked: 729, 1046 StorefrontLayoutAWNudge1Dismissed: 730, 1047 StorefrontLayoutAWNudge2Shown: 731, 1048 StorefrontLayoutAWNudge2LoginClicked: 732, 1049 StorefrontLayoutAWNudge2Dismissed: 733, 1050 // Explicit WL Login Nudge 1051 StorefrontLayoutExplicitNudge1Shown: 734, 1052 StorefrontLayoutExplicitNudge1LoginClicked: 735, 1053 StorefrontLayoutExplicitNudge1Dismissed: 736, 1054 StorefrontLayoutExplicitNudge2Shown: 737, 1055 StorefrontLayoutExplicitNudge2LoginClicked: 738, 1056 StorefrontLayoutExplicitNudge2Dismissed: 739, 1057 // Sign in with Shop 1058 StorefrontLayoutLoginNudgeShopLoginShown: 740, 1059 StorefrontLayoutWishlistUiShopLoginShown: 741, 1060 StorefrontLayoutLoginNudgeShopLoginSuccess: 742, 1061 StorefrontLayoutWishlistUiShopLoginSuccess: 743, 1062 // Add All To Cart 1063 StorefrontLayoutAddedAllToCart: 744, 1064 // Sign in with Shop (failure/fallback) — renumbered 744-748 -> 745-749, 744 collided 1065 // with the already-shipped Add All To Cart code above. 1066 StorefrontLayoutLoginNudgeShopLoginDeclined: 745, 1067 StorefrontLayoutWishlistUiShopLoginDeclined: 746, 1068 StorefrontLayoutLoginNudgeShopLoginFallback: 747, 1069 StorefrontLayoutWishlistUiShopLoginFallback: 748, 1070 StorefrontLayoutWishlistUiClassicLoginClicked: 749, 1071 // Education Layer 1072 EducationLayerToastShownCollections: 812, 1073 EducationLayerTooltipShownCollections: 813, 1074 EducationLayerTooltipDismissedCollections: 814, 1075 EducationLayerTooltipShownPDP: 815, 1076 EducationLayerTooltipDismissedPDP: 816, 1077 EducationLayerButtonTransitionShownPDP: 817, 1078 EducationLayerToastShownPDP: 818, 1079 EducationLayerToastCTAClicked: 819, 1080 EducationLayerSessionStopped: 820 1081}; 1082 1083/** 1084 * Defines UTM terms for tracking user engagement and interactions. 1085 */ 1086SwymStorefrontLayoutExtensions.InstrumentUtmTerms = { 1087 StorefrontLayoutUiEngagement: "storefront_layout_ui_engagement", 1088 StorefrontLayoutCollectionInteractions: "storefront_layout_collection_interactions", 1089 StorefrontLayoutItemInteractions: "storefront_layout_item_interactions", 1090 StorefrontLayoutCartConversion: "storefront_layout_cart_conversion", 1091 EducationLayerEngagement: "education", 1092 StorefrontLayoutAWLoginNudgeEngagement: "aw_login_nudge", 1093 StorefrontLayoutExplicitLoginNudgeEngagement: "explicit_login_nudge", 1094 StorefrontLayoutShopLoginNudgeEngagement: "nudge_shop_login_shown", 1095 StorefrontLayoutShopLoginWishlistUiEngagement: "wishlist_ui_shop_login_shown", 1096 StorefrontLayoutShopLoginNudgeSuccessEngagement: "nudge_shop_login_success", 1097 StorefrontLayoutShopLoginWishlistUiSuccessEngagement: "wishlist_ui_shop_login_success", 1098 StorefrontLayoutShopLoginNudgeDeclinedEngagement: "nudge_shop_login_declined", 1099 StorefrontLayoutShopLoginWishlistUiDeclinedEngagement: "wishlist_ui_shop_login_declined", 1100 StorefrontLayoutShopLoginNudgeFallbackEngagement: "nudge_shop_login_fallback", 1101 StorefrontLayoutShopLoginWishlistUiFallbackEngagement: "wishlist_ui_shop_login_fallback", 1102 StorefrontLayoutShopLoginWishlistUiClassicLoginClickedEngagement: "wishlist_ui_classic_login_clicked" 1103}; 1104 1105if (typeof window !== 'undefined') { 1106 if (!window.SwymCallbacks) { 1107 window.SwymCallbacks = []; 1108 } 1109 1110 let vsClosedListenerRegistered = false;
1111 1112 window.SwymCallbacks.push(function(swat) { 1113 if (vsClosedListenerRegistered) return; 1114 vsClosedListenerRegistered = true; 1115 1116 if (!swat || !swat.evtLayer) return; 1117 1118 swat.evtLayer.addEventListener('sw:vs:closed', (e) => { 1119 const detail = e?.detail || {}; 1120 const empi = detail.empi; 1121 const listId = detail.listId; 1122 1123 if (!empi || !listId) return; 1124 1125 const gridItem = window.SwymStorefrontLayoutExtensions?._vsActiveItem; 1126 if (gridItem && gridItem.item?.empi === empi && gridItem.list?.lid === listId) { 1127 if (!gridItem._variantConfirmed) { 1128 gridItem._variantConfirmed = true; 1129 gridItem.renderItem(); 1130 gridItem.initItemElements(); 1131 gridItem.addEventListeners(); 1132 } 1133 } 1134 1135 if (window.SwymStorefrontLayoutExtensions) { 1136 window.SwymStorefrontLayoutExtensions._vsActiveItem = null; 1137 } 1138 }); 1139 }); 1140} 1141 1142/** 1143 * SwymWishlistCore Component 1144 * 1145 * This component serves as the base class for Swym Wishlist Storefront Layout, 1146 * providing the core functionality shared across different layout implementations: 1147 * - Drawer 1148 * - Page Section 1149 * - Modal 1150 * 1151 * Key Responsibilities: 1152 * - Initializes the UI and sets up event listeners for wishlist interactions. 1153 * - Handles fetching and rendering of wishlist items and collections. 1154 * - Monitors hash changes to update the UI accordingly. 1155 * - Integrates with Swym's API for wishlist actions and notifications. 1156 * - Provides a structured foundation for extending different storefront layout types. 1157 * 1158 * The component can be extended by specific layout implementations such as: 1159 * - `SwymStorefrontLayoutAsDrawer` 1160 * - `SwymStorefrontLayoutAsPageSection` 1161 * - `SwymStorefrontLayoutAsModal` 1162 */ 1163class SwymWishlistCore extends HTMLElement { 1164 constructor() { 1165 super(); 1166 this.elements = {}; 1167 this.initUi(); 1168 } 1169 1170 initUi(){ 1171 this.setAttribute('id', 'swym-storefront-layout'); 1172 // Inject theme-aware CSS variables (Dawn/Horizon) — runs once, no-op for unknown themes 1173 SwymStorefrontLayoutExtensions.ThemeCSSMapping.inject(); 1174 window.addEventListener('hashchange', ()=>{ 1175 this.checkForSwymDrawerHash(); 1176 }); 1177 1178 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.LayoutInitialized, () => { 1179 SwymStorefrontLayoutContext?.swat.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutInitialized, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 1180 this.checkForSwymDrawerHash(); 1181 this.initElement(); 1182 SwymStorefrontLayoutContext?.swat.evtLayer.addEventListener(SwymStorefrontLayoutContext?.swat.JSEvents.StringsLoaded, (event) => { 1183 this.loadStrings(); 1184 }); 1185 1186 // Handle race condition: StringsLoaded may have fired before LayoutInitialized 1187 // (e.g. after login redirect when UI bundle is cached). Check if locale strings 1188 // are already present in retailerSettings and load them immediately if so. 1189 // setTimeout(0) defers the call until after all LayoutInitialized listeners have 1190 // fired, so the Tabs component's _layoutInitHandler can set up SwymStorefrontLayoutContext.Tabs 1191 // before reRenderStorefrontLayoutUI() recreates the DOM. 1192 const retailerStrings = window._swat?.retailerSettings?.Strings || {}; 1193 const stringsAlreadyLoaded = retailerStrings.ThemeAppStorefrontLayoutTitle || retailerStrings.ThemeAppStorefrontLayoutEmptyWishlistTitle; 1194 if (stringsAlreadyLoaded) { 1195 setTimeout(() => this.loadStrings(), 0); 1196 } 1197 1198 this.attachSwymEventListeners(SwymStorefrontLayoutContext.swat); 1199 this.instrumentLayoutType(); 1200 }); 1201 1202 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched, () => { 1203 this.checkForSwymDrawerHash(); 1204 }) 1205 } 1206 1207 instrumentLayoutType(){ 1208 let StorefrontLayoutType = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType; 1209 if(StorefrontLayoutType === 'as-drawer'){ 1210 SwymStorefrontLayoutContext?.swat.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSideDrawerRendered, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 1211 }else if(StorefrontLayoutType === 'as-modal'){ 1212 SwymStorefrontLayoutContext?.swat.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutModalRendered, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 1213 }else if(StorefrontLayoutType === 'as-section'){ 1214 SwymStorefrontLayoutContext?.swat.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutPageRendered, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 1215 } 1216 } 1217 1218 reRenderStorefrontLayoutUI() { 1219 this.reRenderUI(); 1220 this.initElement(); 1221 } 1222 1223 initElement(){ 1224 1225 SwymStorefrontLayoutExtensions.SwymStorefrontLayout = document.querySelector('swym-storefront-layout #swym-storefront-layout'); 1226 SwymStorefrontLayoutExtensions.SwymStorefrontLayoutActions = document.querySelector('#swym-storefront-layout-actions') || document.querySelector('#swym-storefront-layout-actions-target-page'); 1227 SwymStorefrontLayoutExtensions.SwymStorefrontLayoutActionTooltip = document.querySelector('#swym-storefront-layout-action-tooltip') || document.querySelector('#swym-storefront-layout-action-tooltip-target-page'); 1228 SwymStorefrontLayoutExtensions.Notification = document.querySelector('swym-storefront-layout-notification'); 1229 SwymStorefrontLayoutExtensions.collectionsListComponent = document.querySelector('swym-storefront-layout-collection-list'); 1230 1231 } 1232 1233 loadStrings() { 1234 try { 1235 const RetailerStrings = window._swat?.retailerSettings?.Strings || {}; 1236 const { 1237 ThemeAppStorefrontLayoutTitle, 1238 1239 ThemeAppStorefrontLayoutAddToCartCTA, 1240 ThemeAppStorefrontLayoutAddedToCartCTA, 1241 ThemeAppStorefrontLayoutAddAllToCartCTA, 1242 ThemeAppStorefrontLayoutAddAllToCartSuccessText, 1243 ThemeAppStorefrontLayoutAddAllToCartEmptyText, 1244 ThemeAppStorefrontLayoutAddAllToCartErrorText, 1245 ThemeAppStorefrontLayoutAddingToCartCTA, 1246 ThemeAppStorefrontLayoutSoldoutCTA, 1247 ThemeAppStorefrontLayoutViewCartCTA, 1248 ThemeAppStorefrontLayoutMoveToCartCTA, 1249 ThemeAppStorefrontLayoutMoveingToCartCTA, 1250 ThemeAppStorefrontLayoutRemoveSFLItemCTA, 1251 1252 ThemeAppStorefrontLayoutLoginHeading, 1253 ThemeAppStorefrontLayoutLoginMessage, 1254 ThemeAppStorefrontLayoutLoginButtonCTA,
1255 ThemeAppStorefrontLayoutLoggedUserWelcomeMessage, 1256 1257 ThemeAppStorefrontLayoutWishlistTitle, 1258 ThemeAppStorefrontLayoutWishlistInfo, 1259 ThemeAppStorefrontLayoutEmptyWishlistTitle, 1260 ThemeAppStorefrontLayoutEmptyWishlistDescription, 1261 1262 ThemeAppStorefrontLayoutCollectionsTitle, 1263 ThemeAppStorefrontLayoutEmptyCarouselCollectionsDescription, 1264 ThemeAppStorefrontLayoutEmptyCollectionsTitle, 1265 ThemeAppStorefrontLayoutEmptyCollectionsDescription, 1266 1267 ThemeAppStorefrontLayoutAddToCollectionsTitle, 1268 ThemeAppStorefrontLayoutAddToCollectionCTA, 1269 ThemeAppStorefrontLayoutCreateCollectionCTA, 1270 ThemeAppStorefrontLayoutSaveNewCollectionCTA, 1271 ThemeAppStorefrontLayoutRemoveItemCTA, 1272 ThemeAppStorefrontLayoutRenameCollectionCTA, 1273 ThemeAppStorefrontLayoutDeleteCollectionCTA, 1274 ThemeAppStorefrontLayoutShareCollectionCTA, 1275 ThemeAppStorefrontLayoutSaveCollectionCTA, 1276 ThemeAppStorefrontLayoutEditCollectionCTA, 1277 ThemeAppStorefrontLayoutShareCollectionTitle, 1278 ThemeAppStorefrontLayoutShareCollectionMessage, 1279 ThemeAppStorefrontLayoutSharedCollectionMessage, 1280 ThemeAppStorefrontLayoutUpdateCollectionTitle, 1281 1282 ThemeAppStorefrontLayoutErrorMessageListNameRequired, 1283 ThemeAppStorefrontLayoutErrorMessageListNameRequired3Char, 1284 ThemeAppStorefrontLayoutErrorMessageListNameAlreadyExist, 1285 1286 ThemeAppStorefrontLayoutNotificationMessageItemSaved, 1287 ThemeAppStorefrontLayoutNotificationMessageItemRemoved, 1288 ThemeAppStorefrontLayoutNotificationMessageAddedToCart, 1289 ThemeAppStorefrontLayoutNotificationMessageAddedToCollection, 1290 ThemeAppStorefrontLayoutNotificationMessageCollectionSaved, 1291 ThemeAppStorefrontLayoutNotificationMessageCollectionDeleted, 1292 ThemeAppStorefrontLayoutNotificationMessageCollectionUpdated, 1293 ThemeAppStorefrontLayoutNotificationMessageCollectionUnavailable, 1294 ThemeAppStorefrontLayoutNotificationMessageSFLItemSaved, 1295 ThemeAppStorefrontLayoutNotificationMessageSFLItemRemoved, 1296 ThemeAppStorefrontLayoutNotificationMessageMovedToCart, 1297 1298 ThemeAppStorefrontLayoutNotificationActionAddToCollection, 1299 ThemeAppStorefrontLayoutNotificationActionView, 1300 ThemeAppStorefrontLayoutNotificationActionViewCollection, 1301 ThemeAppStorefrontLayoutNotificationActionGoToCart, 1302 1303 ThemeAppStorefrontLayoutTextItem, 1304 ThemeAppStorefrontLayoutTextItems, 1305 1306 ThemeAppStorefrontLayoutTabWishlist, 1307 ThemeAppStorefrontLayoutTabSavedForLater, 1308 ThemeAppStorefrontLayoutSaveForLaterTitle, 1309 ThemeAppStorefrontLayoutEmptySavedForLaterTitle, 1310 ThemeAppStorefrontLayoutEmptySavedForLaterDescription, 1311 VariantSelectorBtnText, 1312 1313 ThemeAppStorefrontLayoutEmptySingleLoggedInTitle, 1314 ThemeAppStorefrontLayoutEmptySingleLoggedInDescription, 1315 ThemeAppStorefrontLayoutEmptySingleLoggedInBullet1, 1316 ThemeAppStorefrontLayoutEmptySingleLoggedInBullet2, 1317 ThemeAppStorefrontLayoutEmptySingleLoggedInBullet3, 1318 ThemeAppStorefrontLayoutEmptySingleLoggedInCTA, 1319 1320 ThemeAppStorefrontLayoutEmptyMultiLoggedInTitle, 1321 ThemeAppStorefrontLayoutEmptyMultiLoggedInDescription, 1322 ThemeAppStorefrontLayoutEmptyMultiLoggedInCreateTitle, 1323 ThemeAppStorefrontLayoutEmptyMultiLoggedInCreateDescription, 1324 ThemeAppStorefrontLayoutEmptyMultiLoggedInBrowseLabel, 1325 ThemeAppStorefrontLayoutEmptyMultiLoggedInCTA, 1326 ThemeAppStorefrontLayoutEmptyMultiLoggedInTag1, 1327 ThemeAppStorefrontLayoutEmptyMultiLoggedInTag2, 1328 ThemeAppStorefrontLayoutEmptyMultiLoggedInTag3, 1329 ThemeAppStorefrontLayoutEmptyMultiLoggedInTag4, 1330 ThemeAppStorefrontLayoutEmptyMultiLoggedInTag5, 1331 1332 ThemeAppStorefrontLayoutEmptyGuestTitle, 1333 ThemeAppStorefrontLayoutEmptyGuestDescription, 1334 ThemeAppStorefrontLayoutEmptyGuestSingleNotice, 1335 ThemeAppStorefrontLayoutEmptyGuestMultiNotice, 1336 ThemeAppStorefrontLayoutEmptyGuestLoginCTA, 1337 ThemeAppStorefrontLayoutEmptyGuestBrowseCTA, 1338 ThemeAppStorefrontLayoutEmptyGuestSignupText, 1339 ThemeAppStorefrontLayoutEmptyGuestSignupCTA, 1340 1341 ThemeAppStorefrontLayoutEmptySflTitle, 1342 ThemeAppStorefrontLayoutEmptySflDescription, 1343 ThemeAppStorefrontLayoutEmptySflStep1Title, 1344 ThemeAppStorefrontLayoutEmptySflStep1Description, 1345 ThemeAppStorefrontLayoutEmptySflStep2Title, 1346 ThemeAppStorefrontLayoutEmptySflStep2Description, 1347 ThemeAppStorefrontLayoutEmptySflLoggedInNotice, 1348 ThemeAppStorefrontLayoutEmptySflGuestNotice, 1349 ThemeAppStorefrontLayoutEmptySflGoToCartCTA, 1350 ThemeAppStorefrontLayoutEmptySflLoginCTA, 1351 ThemeAppStorefrontLayoutEmptySflSignupText, 1352 ThemeAppStorefrontLayoutEmptySflSignupCTA, 1353 1354 ThemeAppStorefrontLayoutTabSavedForYou, 1355 1356 // Drawer UI Polish — personalized titles and share footer (single wishlist mode) 1357 ThemeAppStorefrontLayoutPersonalizedTitle, 1358 ThemeAppStorefrontLayoutPersonalizedSfyTitle, 1359 ThemeAppStorefrontLayoutShareCTA, 1360 ThemeAppStorefrontLayoutShareThisListCTA, 1361 ThemeAppStorefrontLayoutLoginToSaveCTA, 1362
1363 // Auto-Wishlist login nudge strings (corner notification) 1364 ThemeAppStorefrontLayoutAwNudgeMessage, 1365 ThemeAppStorefrontLayoutAwNudgeMessageSub, 1366 ThemeAppStorefrontLayoutAwNudgeLoginCTA, 1367 ThemeAppStorefrontLayoutAwNudgeDismiss, 1368 } = RetailerStrings; 1369 1370 SwymStorefrontLayoutContext.Strings = { 1371 ...SwymStorefrontLayoutContext?.Strings, 1372 ...(ThemeAppStorefrontLayoutTitle ? { 'title': ThemeAppStorefrontLayoutTitle } : {}), 1373 ...(VariantSelectorBtnText ? { 'VariantSelectorBtnText': VariantSelectorBtnText } : {}), 1374 ...(ThemeAppStorefrontLayoutAddToCartCTA ? { 'addToCart': ThemeAppStorefrontLayoutAddToCartCTA } : {}), 1375 ...(ThemeAppStorefrontLayoutAddedToCartCTA ? { 'addedToCart': ThemeAppStorefrontLayoutAddedToCartCTA } : {}), 1376 addAllToCartCta: ThemeAppStorefrontLayoutAddAllToCartCTA || 'Add All To Cart', 1377 addAllToCartSuccessText: ThemeAppStorefrontLayoutAddAllToCartSuccessText || 'Successfully added {{addCount}} items to your cart.', 1378 addAllToCartEmptyText: ThemeAppStorefrontLayoutAddAllToCartEmptyText || 'There are no items that can be added to the cart.', 1379 addAllToCartErrorText: ThemeAppStorefrontLayoutAddAllToCartErrorText || 'Error adding all items to cart.', 1380 ...(ThemeAppStorefrontLayoutAddingToCartCTA ? { 'addingToCart': ThemeAppStorefrontLayoutAddingToCartCTA } : {}), 1381 ...(ThemeAppStorefrontLayoutSoldoutCTA ? { 'soldOut': ThemeAppStorefrontLayoutSoldoutCTA } : {}), 1382 ...(ThemeAppStorefrontLayoutViewCartCTA ? { 'viewCartCta': ThemeAppStorefrontLayoutViewCartCTA } : {}), 1383 ...(ThemeAppStorefrontLayoutMoveToCartCTA ? { 'moveToCartCta': ThemeAppStorefrontLayoutMoveToCartCTA } : {}), 1384 ...(ThemeAppStorefrontLayoutMoveingToCartCTA ? { 'movingToCartCta': ThemeAppStorefrontLayoutMoveingToCartCTA } : {}), 1385 ...(ThemeAppStorefrontLayoutRemoveSFLItemCTA ? { 'removeSflItemCta': ThemeAppStorefrontLayoutRemoveSFLItemCTA } : {}), 1386 1387 ...(ThemeAppStorefrontLayoutLoginHeading ? { 'loginHeading': ThemeAppStorefrontLayoutLoginHeading } : {}), 1388 ...(ThemeAppStorefrontLayoutLoginMessage ? { 'loginText': ThemeAppStorefrontLayoutLoginMessage } : {}), 1389 ...(ThemeAppStorefrontLayoutLoginButtonCTA ? { 'loginButtonText': ThemeAppStorefrontLayoutLoginButtonCTA } : {}), 1390 ...(ThemeAppStorefrontLayoutLoggedUserWelcomeMessage ? { 'loggedUserWelcomeMessage': ThemeAppStorefrontLayoutLoggedUserWelcomeMessage } : {}), 1391 1392 ...(ThemeAppStorefrontLayoutWishlistTitle ? { 'wishlistTitle': ThemeAppStorefrontLayoutWishlistTitle } : {}), 1393 ...(ThemeAppStorefrontLayoutWishlistInfo ? { 'wishlistInfo': ThemeAppStorefrontLayoutWishlistInfo } : {}), 1394 ...(ThemeAppStorefrontLayoutEmptyWishlistTitle ? { 'emptyWishlistTitle': ThemeAppStorefrontLayoutEmptyWishlistTitle } : {}), 1395 ...(ThemeAppStorefrontLayoutEmptyWishlistDescription ? { 'emptyWishlistDescription': ThemeAppStorefrontLayoutEmptyWishlistDescription } : {}), 1396 1397 ...(ThemeAppStorefrontLayoutCollectionsTitle ? { 'collectionTitle': ThemeAppStorefrontLayoutCollectionsTitle } : {}), 1398 ...(ThemeAppStorefrontLayoutEmptyCarouselCollectionsDescription ? { 'emptyCarouselCollectionText': ThemeAppStorefrontLayoutEmptyCarouselCollectionsDescription } : {}), 1399 ...(ThemeAppStorefrontLayoutEmptyCollectionsTitle ? { 'emptyCollectionText': ThemeAppStorefrontLayoutEmptyCollectionsTitle } : {}), 1400 ...(ThemeAppStorefrontLayoutEmptyCollectionsDescription ? { 'emptyCollectionDescription': ThemeAppStorefrontLayoutEmptyCollectionsDescription } : {}), 1401 1402 ...(ThemeAppStorefrontLayoutAddToCollectionsTitle ? { 'addToCollectionTitle': ThemeAppStorefrontLayoutAddToCollectionsTitle } : {}), 1403 ...(ThemeAppStorefrontLayoutAddToCollectionCTA ? { 'addToCollectionCta': ThemeAppStorefrontLayoutAddToCollectionCTA } : {}), 1404 ...(ThemeAppStorefrontLayoutCreateCollectionCTA ? { 'createCollectionCta': ThemeAppStorefrontLayoutCreateCollectionCTA } : {}), 1405 ...(ThemeAppStorefrontLayoutSaveNewCollectionCTA ? { 'saveNewCollectionCta': ThemeAppStorefrontLayoutSaveNewCollectionCTA } : {}), 1406 ...(ThemeAppStorefrontLayoutRemoveItemCTA ? { 'removeItemCta': ThemeAppStorefrontLayoutRemoveItemCTA } : {}), 1407 ...(ThemeAppStorefrontLayoutRenameCollectionCTA ? { 'renameCollectionCta': ThemeAppStorefrontLayoutRenameCollectionCTA } : {}), 1408 ...(ThemeAppStorefrontLayoutDeleteCollectionCTA ? { 'deleteCollectionCta': ThemeAppStorefrontLayoutDeleteCollectionCTA } : {}), 1409 ...(ThemeAppStorefrontLayoutShareCollectionCTA ? { 'shareCollectionCta': ThemeAppStorefrontLayoutShareCollectionCTA } : {}), 1410 ...(ThemeAppStorefrontLayoutSaveCollectionCTA ? { 'saveCollectionCta': ThemeAppStorefrontLayoutSaveCollectionCTA } : {}),
1411 ...(ThemeAppStorefrontLayoutEditCollectionCTA ? { 'editCollectionCta': ThemeAppStorefrontLayoutEditCollectionCTA } : {}), 1412 ...(ThemeAppStorefrontLayoutShareCollectionTitle ? { 'shareCollectionTitle': ThemeAppStorefrontLayoutShareCollectionTitle } : {}), 1413 ...(ThemeAppStorefrontLayoutShareCollectionMessage ? { 'shareCollectionMessage': ThemeAppStorefrontLayoutShareCollectionMessage } : {}), 1414 ...(ThemeAppStorefrontLayoutSharedCollectionMessage ? { 'sharedCollectionMessage': ThemeAppStorefrontLayoutSharedCollectionMessage } : {}), 1415 ...(ThemeAppStorefrontLayoutUpdateCollectionTitle ? { 'updateCollectionTitle': ThemeAppStorefrontLayoutUpdateCollectionTitle } : {}), 1416 1417 ...(ThemeAppStorefrontLayoutErrorMessageListNameRequired ? { 'errorMessageListNameRequired': ThemeAppStorefrontLayoutErrorMessageListNameRequired } : {}), 1418 ...(ThemeAppStorefrontLayoutErrorMessageListNameRequired3Char ? { 'errorMessageListNameRequire3Char': ThemeAppStorefrontLayoutErrorMessageListNameRequired3Char } : {}), 1419 ...(ThemeAppStorefrontLayoutErrorMessageListNameAlreadyExist ? { 'errorMessageListNameAlreadyExist': ThemeAppStorefrontLayoutErrorMessageListNameAlreadyExist } : {}), 1420 1421 ...(ThemeAppStorefrontLayoutNotificationMessageItemSaved ? { 'notificationMessageItemSaved': ThemeAppStorefrontLayoutNotificationMessageItemSaved } : {}), 1422 ...(ThemeAppStorefrontLayoutNotificationMessageItemRemoved ? { 'notificationMessageItemRemoved': ThemeAppStorefrontLayoutNotificationMessageItemRemoved } : {}), 1423 ...(ThemeAppStorefrontLayoutNotificationMessageAddedToCart ? { 'notificationMessageAddedToCart': ThemeAppStorefrontLayoutNotificationMessageAddedToCart } : {}), 1424 ...(ThemeAppStorefrontLayoutNotificationMessageAddedToCollection ? { 'notificationMessageAddedToCollection': ThemeAppStorefrontLayoutNotificationMessageAddedToCollection } : {}), 1425 ...(ThemeAppStorefrontLayoutNotificationMessageCollectionSaved ? { 'notificationMessageCollectionSaved': ThemeAppStorefrontLayoutNotificationMessageCollectionSaved } : {}), 1426 ...(ThemeAppStorefrontLayoutNotificationMessageCollectionDeleted ? { 'notificationMessageCollectionDeleted': ThemeAppStorefrontLayoutNotificationMessageCollectionDeleted } : {}), 1427 ...(ThemeAppStorefrontLayoutNotificationMessageCollectionUpdated ? { 'notificationMessageCollectionUpdated': ThemeAppStorefrontLayoutNotificationMessageCollectionUpdated } : {}), 1428 ...(ThemeAppStorefrontLayoutNotificationMessageCollectionUnavailable ? { 'notificationMessageCollectionUnavailable': ThemeAppStorefrontLayoutNotificationMessageCollectionUnavailable } : {}), 1429 ...(ThemeAppStorefrontLayoutNotificationMessageSFLItemSaved ? { 'notificationMessageSFLItemSaved': ThemeAppStorefrontLayoutNotificationMessageSFLItemSaved } : {}), 1430 ...(ThemeAppStorefrontLayoutNotificationMessageSFLItemRemoved ? { 'notificationMessageSFLItemRemoved': ThemeAppStorefrontLayoutNotificationMessageSFLItemRemoved } : {}), 1431 ...(ThemeAppStorefrontLayoutNotificationMessageMovedToCart ? { 'notificationMessageMovedToCart': ThemeAppStorefrontLayoutNotificationMessageMovedToCart } : {}), 1432 1433 ...(ThemeAppStorefrontLayoutNotificationActionAddToCollection ? { 'notificationActionAddToCollection': ThemeAppStorefrontLayoutNotificationActionAddToCollection } : {}), 1434 ...(ThemeAppStorefrontLayoutNotificationActionView ? { 'notificationActionView': ThemeAppStorefrontLayoutNotificationActionView } : {}), 1435 ...(ThemeAppStorefrontLayoutNotificationActionViewCollection ? { 'notificationActionViewCollection': ThemeAppStorefrontLayoutNotificationActionViewCollection } : {}), 1436 ...(ThemeAppStorefrontLayoutNotificationActionGoToCart ? { 'notificationActionGoToCart': ThemeAppStorefrontLayoutNotificationActionGoToCart } : {}), 1437 1438 ...(ThemeAppStorefrontLayoutTextItem ? { 'item': ThemeAppStorefrontLayoutTextItem } : {}), 1439 ...(ThemeAppStorefrontLayoutTextItems ? { 'items': ThemeAppStorefrontLayoutTextItems } : {}), 1440 1441 ...(ThemeAppStorefrontLayoutTabWishlist ? { 'tabWishlist': ThemeAppStorefrontLayoutTabWishlist } : {}), 1442 ...(ThemeAppStorefrontLayoutTabSavedForLater ? { 'tabSavedForLater': ThemeAppStorefrontLayoutTabSavedForLater } : {}), 1443 tabSavedForYou: ThemeAppStorefrontLayoutTabSavedForYou || RetailerStrings?.AutoWishlistedTabText || 'Saved For You', 1444 ...(ThemeAppStorefrontLayoutSaveForLaterTitle ? { 'savedForLaterTitle': ThemeAppStorefrontLayoutSaveForLaterTitle } : {}), 1445 ...(ThemeAppStorefrontLayoutEmptySavedForLaterTitle ? { 'emptySavedForLaterTitle': ThemeAppStorefrontLayoutEmptySavedForLaterTitle } : {}), 1446 ...(ThemeAppStorefrontLayoutEmptySavedForLaterDescription ? { 'emptySavedForLaterDescription': ThemeAppStorefrontLayoutEmptySavedForLaterDescription } : {}), 1447 1448 ...(ThemeAppStorefrontLayoutEmptySingleLoggedInTitle ? { 'emptySingleLoggedInTitle': ThemeAppStorefrontLayoutEmptySingleLoggedInTitle } : {}), 1449 ...(ThemeAppStorefrontLayoutEmptySingleLoggedInDescription ? { 'emptySingleLoggedInDescription': ThemeAppStorefrontLayoutEmptySingleLoggedInDescription } : {}), 1450 ...(ThemeAppStorefrontLayoutEmptySingleLoggedInBullet1 ? { 'emptySingleLoggedInBullet1': ThemeAppStorefrontLayoutEmptySingleLoggedInBullet1 } : {}), 1451 ...(ThemeAppStorefrontLayoutEmptySingleLoggedInBullet2 ? { 'emptySingleLoggedInBullet2': ThemeAppStorefrontLayoutEmptySingleLoggedInBullet2 } : {}), 1452 ...(ThemeAppStorefrontLayoutEmptySingleLoggedInBullet3 ? { 'emptySingleLoggedInBullet3': ThemeAppStorefrontLayoutEmptySingleLoggedInBullet3 } : {}), 1453 ...(ThemeAppStorefrontLayoutEmptySingleLoggedInCTA ? { 'emptySingleLoggedInCTA': ThemeAppStorefrontLayoutEmptySingleLoggedInCTA } : {}), 1454 1455 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInTitle ? { 'emptyMultiLoggedInTitle': ThemeAppStorefrontLayoutEmptyMultiLoggedInTitle } : {}), 1456 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInDescription ? { 'emptyMultiLoggedInDescription': ThemeAppStorefrontLayoutEmptyMultiLoggedInDescription } : {}), 1457 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInCreateTitle ? { 'emptyMultiLoggedInCreateTitle': ThemeAppStorefrontLayoutEmptyMultiLoggedInCreateTitle } : {}), 1458 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInCreateDescription ? { 'emptyMultiLoggedInCreateDescription': ThemeAppStorefrontLayoutEmptyMultiLoggedInCreateDescription } : {}), 1459 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInBrowseLabel ? { 'emptyMultiLoggedInBrowseLabel': ThemeAppStorefrontLayoutEmptyMultiLoggedInBrowseLabel } : {}), 1460 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInCTA ? { 'emptyMultiLoggedInCTA': ThemeAppStorefrontLayoutEmptyMultiLoggedInCTA } : {}), 1461 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInTag1 ? { 'emptyMultiLoggedInTag1': ThemeAppStorefrontLayoutEmptyMultiLoggedInTag1 } : {}), 1462 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInTag2 ? { 'emptyMultiLoggedInTag2': ThemeAppStorefrontLayoutEmptyMultiLoggedInTag2 } : {}), 1463 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInTag3 ? { 'emptyMultiLoggedInTag3': ThemeAppStorefrontLayoutEmptyMultiLoggedInTag3 } : {}), 1464 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInTag4 ? { 'emptyMultiLoggedInTag4': ThemeAppStorefrontLayoutEmptyMultiLoggedInTag4 } : {}), 1465 ...(ThemeAppStorefrontLayoutEmptyMultiLoggedInTag5 ? { 'emptyMultiLoggedInTag5': ThemeAppStorefrontLayoutEmptyMultiLoggedInTag5 } : {}), 1466 1467 ...(ThemeAppStorefrontLayoutEmptyGuestTitle ? { 'emptyGuestTitle': ThemeAppStorefrontLayoutEmptyGuestTitle } : {}), 1468 ...(ThemeAppStorefrontLayoutEmptyGuestDescription ? { 'emptyGuestDescription': ThemeAppStorefrontLayoutEmptyGuestDescription } : {}), 1469 ...(ThemeAppStorefrontLayoutEmptyGuestSingleNotice ? { 'emptyGuestSingleNotice': ThemeAppStorefrontLayoutEmptyGuestSingleNotice } : {}), 1470 ...(ThemeAppStorefrontLayoutEmptyGuestMultiNotice ? { 'emptyGuestMultiNotice': ThemeAppStorefrontLayoutEmptyGuestMultiNotice } : {}), 1471 ...(ThemeAppStorefrontLayoutEmptyGuestLoginCTA ? { 'emptyGuestLoginCTA': ThemeAppStorefrontLayoutEmptyGuestLoginCTA } : {}), 1472 ...(ThemeAppStorefrontLayoutEmptyGuestBrowseCTA ? { 'emptyGuestBrowseCTA': ThemeAppStorefrontLayoutEmptyGuestBrowseCTA } : {}), 1473 ...(ThemeAppStorefrontLayoutEmptyGuestSignupText ? { 'emptyGuestSignupText': ThemeAppStorefrontLayoutEmptyGuestSignupText } : {}), 1474 ...(ThemeAppStorefrontLayoutEmptyGuestSignupCTA ? { 'emptyGuestSignupCTA': ThemeAppStorefrontLayoutEmptyGuestSignupCTA } : {}), 1475 1476 ...(ThemeAppStorefrontLayoutEmptySflTitle ? { 'emptySflTitle': ThemeAppStorefrontLayoutEmptySflTitle } : {}), 1477 ...(ThemeAppStorefrontLayoutEmptySflDescription ? { 'emptySflDescription': ThemeAppStorefrontLayoutEmptySflDescription } : {}), 1478 ...(ThemeAppStorefrontLayoutEmptySflStep1Title ? { 'emptySflStep1Title': ThemeAppStorefrontLayoutEmptySflStep1Title } : {}), 1479 ...(ThemeAppStorefrontLayoutEmptySflStep1Description ? { 'emptySflStep1Description': ThemeAppStorefrontLayoutEmptySflStep1Description } : {}), 1480 ...(ThemeAppStorefrontLayoutEmptySflStep2Title ? { 'emptySflStep2Title': ThemeAppStorefrontLayoutEmptySflStep2Title } : {}), 1481 ...(ThemeAppStorefrontLayoutEmptySflStep2Description ? { 'emptySflStep2Description': ThemeAppStorefrontLayoutEmptySflStep2Description } : {}), 1482 ...(ThemeAppStorefrontLayoutEmptySflLoggedInNotice ? { 'emptySflLoggedInNotice': ThemeAppStorefrontLayoutEmptySflLoggedInNotice } : {}), 1483 ...(ThemeAppStorefrontLayoutEmptySflGuestNotice ? { 'emptySflGuestNotice': ThemeAppStorefrontLayoutEmptySflGuestNotice } : {}), 1484 ...(ThemeAppStorefrontLayoutEmptySflGoToCartCTA ? { 'emptySflGoToCartCTA': ThemeAppStorefrontLayoutEmptySflGoToCartCTA } : {}), 1485 ...(ThemeAppStorefrontLayoutEmptySflLoginCTA ? { 'emptySflLoginCTA': ThemeAppStorefrontLayoutEmptySflLoginCTA } : {}), 1486 ...(ThemeAppStorefrontLayoutEmptySflSignupText ? { 'emptySflSignupText': ThemeAppStorefrontLayoutEmptySflSignupText } : {}), 1487 ...(ThemeAppStorefrontLayoutEmptySflSignupCTA ? { 'emptySflSignupCTA': ThemeAppStorefrontLayoutEmptySflSignupCTA } : {}), 1488
1489 // Auto-Wishlist login nudge strings 1490 // Fallback chain: merchant Liquid override → SDK locale strings (strings/*.js via retailerSettings) → empty string 1491 awNudgeMessage: ThemeAppStorefrontLayoutAwNudgeMessage || RetailerStrings?.ThemeAppStorefrontLayoutAwNudgeMessage || '', 1492 awNudgeMessageSub: ThemeAppStorefrontLayoutAwNudgeMessageSub || RetailerStrings?.ThemeAppStorefrontLayoutAwNudgeMessageSub || '', 1493 awNudgeLoginCTA: ThemeAppStorefrontLayoutAwNudgeLoginCTA || RetailerStrings?.ThemeAppStorefrontLayoutAwNudgeLoginCTA || '', 1494 awNudgeDismiss: ThemeAppStorefrontLayoutAwNudgeDismiss || RetailerStrings?.ThemeAppStorefrontLayoutAwNudgeDismiss || '', 1495 // Explicit WL login nudge — only bold message differs; sub-message, CTA and dismiss reuse AW strings 1496 explicitNudgeMessage: RetailerStrings?.ThemeAppStorefrontLayoutExplicitNudgeMessage || '', 1497 1498 // Drawer UI Polish — personalized titles and share footer 1499 personalizedTitle: ThemeAppStorefrontLayoutPersonalizedTitle || RetailerStrings?.ThemeAppStorefrontLayoutPersonalizedTitle || "{{name}}'s Wishlist", 1500 personalizedSfyTitle: ThemeAppStorefrontLayoutPersonalizedSfyTitle || RetailerStrings?.ThemeAppStorefrontLayoutPersonalizedSfyTitle || 'Saved For {{name}}', 1501 shareCta: ThemeAppStorefrontLayoutShareCTA || RetailerStrings?.ThemeAppStorefrontLayoutShareCTA || 'Share', 1502 shareThisListCta: ThemeAppStorefrontLayoutShareThisListCTA || RetailerStrings?.ThemeAppStorefrontLayoutShareThisListCTA || 'Share This List', 1503 loginToSaveCta: ThemeAppStorefrontLayoutLoginToSaveCTA || RetailerStrings?.ThemeAppStorefrontLayoutLoginToSaveCTA || 'Login to Save', 1504 1505 } 1506 SwymStorefrontLayoutContext.stringsLoaded = true; 1507 this.reRenderStorefrontLayoutUI(); 1508 } catch (error) { 1509 window._swat?.utils.error(error); 1510 } 1511 } 1512 1513 async checkForSwymDrawerHash() { 1514 const { StorefrontLayoutType, StorefrontLayoutAsSectionPageURL } = SwymStorefrontLayoutContext?.Settings; 1515 const currentHash = window.location.hash; 1516 const swymOpenParam = new URLSearchParams(window.location.search).get('swymOpen'); 1517 const shouldOpenFromQuery = swymOpenParam === 'wishlist'; 1518 1519 if(!SwymStorefrontLayoutExtensions.SwymStorefrontLayout){ 1520 SwymStorefrontLayoutExtensions.SwymStorefrontLayout = document.querySelector('swym-storefront-layout #swym-storefront-layout'); 1521 } 1522 1523 if(!currentHash && !shouldOpenFromQuery && SwymStorefrontLayoutExtensions.SwymStorefrontLayout?.isOpen){ 1524 SwymStorefrontLayoutExtensions.SwymStorefrontLayout?.close?.(); 1525 return; 1526 } 1527 1528 const openWishlistPage = () => { 1529 if (StorefrontLayoutType === 'as-drawer' || StorefrontLayoutType === 'as-modal') { 1530 SwymStorefrontLayoutExtensions?.SwymStorefrontLayout?.open(); 1531 } else if (StorefrontLayoutType === 'as-section') { 1532 const normalize = s => s.replace(/\/+$/, ''); 1533 const ensureLeadingSlash = s => (s?.startsWith('/') ? s : `/${s || ''}`); 1534 const localeRoot = window.Shopify?.routes?.root || '/'; 1535 const formattedLocale = localeRoot === '/' ? '' : localeRoot.replace(/\/$/, ''); 1536 const sectionPath = ensureLeadingSlash(StorefrontLayoutAsSectionPageURL); 1537 const expected = normalize(`${formattedLocale}${sectionPath}`); 1538 const current = normalize(window.location.pathname); 1539 1540 if (current !== expected) { 1541 const redirectUrl = `${window.location.origin}${formattedLocale}${sectionPath}${window.location.search}`; 1542 window.location.replace(redirectUrl); 1543 } 1544 } 1545 }; 1546 1547 if (currentHash === SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.List || shouldOpenFromQuery) { 1548 SwymStorefrontLayoutExtensions?.collectionsListComponent?.closeCollectinListView(); 1549 openWishlistPage(); 1550 } else if (currentHash.startsWith(SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.CollectionList)) { 1551 const collectionId = currentHash.slice(SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.CollectionList.length); 1552 1553 if (collectionId && SwymStorefrontLayoutContext?.collections) { 1554 const selectedCollection = SwymStorefrontLayoutContext?.collections?.find(collection => collection.lid === collectionId); 1555 1556 if (selectedCollection) { 1557 const { shouldShowMultiple, canCreateLists } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 1558 SwymStorefrontLayoutExtensions?.collectionsListComponent?.setData({ 1559 list: selectedCollection, 1560 collections: SwymStorefrontLayoutContext?.collections, 1561 canCreateLists: canCreateLists, 1562 shouldShowMultiple: shouldShowMultiple 1563 }); 1564 SwymStorefrontLayoutExtensions?.collectionsListComponent?.openCollectionListView(); 1565 } else { 1566 try{ 1567 const sharedListDetails = await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.fetchListDetails(collectionId); 1568 if(sharedListDetails?.list){ 1569 let [list] = await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.fetchProductDataForLists([sharedListDetails.list], true) || []; 1570 if(list){ 1571 const { shouldShowMultiple, canCreateLists } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 1572 SwymStorefrontLayoutExtensions?.collectionsListComponent?.setData({ 1573 list, 1574 collections: SwymStorefrontLayoutContext?.collections, 1575 canCreateLists: canCreateLists, 1576 shouldShowMultiple: shouldShowMultiple 1577 }); 1578 SwymStorefrontLayoutExtensions?.collectionsListComponent?.openCollectionListView(); 1579 }else{ 1580 throw 'Failed to get product data for shared list'; 1581 } 1582 }else{ 1583 throw 'Collection Not Available'; 1584 } 1585 }catch(e){ 1586 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1587 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageCollectionUnavailable, 1588 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Error, 1589 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration 1590 }); 1591 window._swat?.utils.error(`Collection detail not found for ID: ${collectionId}. It may have been deleted or is unavailable.`,e); 1592 } 1593 } 1594 1595 openWishlistPage(); 1596 } 1597 } 1598 } 1599 1600 /** 1601 * Attaches event listeners to Swym's event layer for handling various storefront layout interactions. 1602 * 1603 * Key Features: 1604 * - Refreshes the wishlist and save-for-later (SFL) lists when customer information is updated. 1605 * - Handles events for adding and removing items from the wishlist and SFL. 1606 * - Displays notifications for user actions such as adding to wishlist, remov
1606ing items, or moving items to the cart. 1607 * - Tracks user interactions using Swym's instrumentation for analytics. 1608 * - Supports multi-list and collection features based on retailer settings. 1609 * 1610 * Events Handled: 1611 * - `customerInfoRefreshed`: Refreshes wishlist and SFL lists. 1612 * - `addedToWishlist`: Updates the wishlist and displays a notification when an item is added. 1613 * - `removedFromWishlist`: Updates the wishlist and displays a notification when an item is removed. 1614 * - `addedToSFL`: Updates the SFL list and displays a notification when an item is added. 1615 * - `removedFromSFL`: Updates the SFL list and displays a notification when an item is removed. 1616 * - `movedToCartFromSFL`: Refreshes the SFL list when an item is moved to the cart. 1617 * 1618 * Dependencies: 1619 * - SwymStorefrontLayoutExtensions: Provides methods for refreshing lists and managing notifications. 1620 * - SwymStorefrontLayoutContext: Provides context for settings, strings, and instrumentation codes. 1621 * - Swym's event layer (`swat.evtLayer`): Listens for and triggers events. 1622 */ 1623 1624 attachSwymEventListeners(swat) { 1625 1626 swat?.evtLayer?.addEventListener(swat?.JSEvents?.customerInfoRefreshed, ()=>{ 1627 SwymStorefrontLayoutExtensions?.refreshWishList(); 1628 if(window._swat?.retailerSettings?.SFL?.SFLFeatureEnabled){ 1629 SwymStorefrontLayoutExtensions?.refreshSFLList(); 1630 } 1631 }) 1632 1633 //Wishlist Events 1634 swat?.evtLayer?.addEventListener(swat?.JSEvents?.addedToWishlist,
1634(event) => { 1635 if (SwymStorefrontLayoutContext._sfyMoveToWishlist) return; 1636 SwymStorefrontLayoutExtensions?.refreshWishList(); 1637 1638 let image = event?.detail?.d?.iu || null; 1639 let { epi, empi } = event?.detail?.d || {}; 1640 let product = event?.detail.d; 1641 1642 const { shouldShowMultiple, canCreateLists } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 1643 1644 swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutItemAddedToList, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi }); 1645 if(SwymStorefrontLayoutContext?.swat?.ui?.uiRef?.settings?.UI?.WishlistShowNotification && !SwymStorefrontLayoutContext.CommonAddtoWL){ 1646 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1647 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageItemSaved, 1648 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 1649 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes.AddedToWishlist, 1650 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1651 product, 1652 image 1653 },{ 1654 showImage: true, 1655 showProgress: true 1656 }); 1657 1658 setTimeout(()=>{ 1659 if(SwymStorefrontLayoutExtensions?.Notification?.notificationData?.actionType === SwymStorefrontLayoutContext?.NotificationActionTypes?.AddedToWishlist){ 1660 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1661 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 1662 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes.AddedToWishlist, 1663 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1664 product, 1665 image, 1666 action: { 1667 label: canCreateLists ? SwymStorefrontLayoutContext?.Strings?.notificationActionAddToCollection : SwymStorefrontLayoutContext?.Strings?.notificationActionView, 1668 onClick: () => { 1669 if(canCreateLists){ 1670 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutPageActions?.setData({ 1671 listId: SwymStorefrontLayoutContext?.DefaultList?.lid, 1672 list: SwymStorefrontLayoutContext?.DefaultList, 1673 item: product, 1674 collections: SwymStorefrontLayoutContext?.collections, 1675 actionType: SwymStorefrontLayoutContext?.ActionTypes.AddToCollection 1676 }); 1677 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutPageActions?.openDrawer(); 1678 }else{ 1679 window.location.hash = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.List; 1680 } 1681 } 1682 } 1683 },{ 1684 showImage: true, 1685 showActionButton: true, 1686 showTitle: true 1687 }); 1688 } 1689 },3000); 1690 } 1691 if(SwymStorefrontLayoutContext.CommonAddtoWL && !SwymStorefrontLayoutContext.CommonRemovefromWL) { 1692 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1693 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageItemSaved, 1694 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 1695 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes.AddedToWishlist, 1696 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1697 product, 1698 image 1699 },{ 1700 showImage: true, 1701 showProgress: true 1702 }); 1703 SwymStorefrontLayoutContext.CommonAddtoWL = false 1704 1705 setTimeout(() => { 1706 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1707 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 1708 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes.AddedToWishlist, 1709 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1710 product, 1711 image, 1712 action: { 1713 label: SwymStorefrontLayoutContext?.Strings?.notificationActionView, 1714 onClick: () => { 1715 _swat.ui.open(); 1716 } 1717 } 1718 },{ 1719 showImage: true, 1720 showActionButton: true, 1721 showTitle: true 1722 }); 1723 }, 3000) 1724 } 1725 }); 1726
1727 swat?.evtLayer?.addEventListener(swat?.JSEvents?.removedFromWishlist, (event) => { 1728 if (SwymStorefrontLayoutContext._sfyMoveToWishlist) return; 1729 1730 let image = event?.detail?.d?.iu || null; 1731 let { epi, empi } = event?.detail?.d || {}; 1732 const product = event?.detail.d; 1733 if( epi && empi){ 1734 // Use the new helper to determine behavior 1735 const { shouldShowMultiple, canCreateLists } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 1736 1737 // If showing multiple lists (either feature enabled OR multiple lists exist), do a full refresh 1738 if (shouldShowMultiple) { 1739 SwymStorefrontLayoutExtensions?.refreshWishList(); 1740 } else { 1741 // Single wishlist: Update local state instead of refetching to avoid price double-formatting issue 1742 if (SwymStorefrontLayoutContext.DefaultList) { 1743 SwymStorefrontLayoutContext.DefaultList.listcontents = 1744 (SwymStorefrontLayoutContext.DefaultList.listcontents || []).filter(item => item.epi !== epi); 1745 } 1746 if (SwymStorefrontLayoutContext.collections) { 1747 SwymStorefrontLayoutContext.collections = (SwymStorefrontLayoutContext.collections || []).map(list => ({ 1748 ...list, 1749 listcontents: (list.listcontents || []).filter(item => item.epi !== epi) 1750 })); 1751 } 1752 if (SwymStorefrontLayoutContext.lists) { 1753 SwymStorefrontLayoutContext.lists = (SwymStorefrontLayoutContext.lists || []).map(list => ({ 1754 ...list, 1755 listcontents: (list.listcontents || []).filter(item => item.epi !== epi) 1756 })); 1757 } 1758 1759 // Dispatch WishlistFetched event to update UI components 1760 if(SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched){ 1761 var wishlistFetchedEvent = new CustomEvent(SwymStorefrontLayoutContext.CustomEvents.WishlistFetched, { 1762 detail: { 1763 defaultList: SwymStorefrontLayoutContext.DefaultList, 1764 collections: SwymStorefrontLayoutContext.collections, 1765 lists: SwymStorefrontLayoutContext.lists 1766 } 1767 }); 1768 document.dispatchEvent(wishlistFetchedEvent); 1769 } 1770 } 1771 1772 swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutItemRemovedFromList, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi }); 1773 } 1774 if((SwymStorefrontLayoutContext.CommonRemovefromWL && SwymStorefrontLayoutContext.CommonAddtoWL) || SwymStorefrontLayoutContext.CommonRemovefromWL) { 1775 if(SwymStorefrontLayoutContext.CommonRemovefromWL && !SwymStorefrontLayoutContext.CommonAddtoWL) { 1776 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1777 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageItemRemoved, 1778 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Toast, 1779 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1780 image 1781 }); 1782 SwymStorefrontLayoutContext.CommonRemovefromWL = false; 1783 setTimeout(() => { 1784 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1785 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 1786 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes.AddedToWishlist, 1787 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1788 product, 1789 image, 1790 action: { 1791 label: SwymStorefrontLayoutContext?.Strings?.notificationActionView, 1792 onClick: () => { 1793 _swat.ui.open(); 1794 } 1795 } 1796 },{ 1797 showImage: true, 1798 showActionButton: true, 1799 showTitle: true 1800 }); 1801 }, 3000) 1802 } 1803 return 1804 } 1805 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1806 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageItemRemoved, 1807 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Toast, 1808 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1809 image 1810 }); 1811 }); 1812 1813 //SFL Events 1814 if(window._swat?.retailerSettings?.SFL?.SFLFeatureEnabled){ 1815 swat.evtLayer.addEventListener(swat.JSEvents.addedToSFL, (event) => { 1816 let image = event?.detail?.d?.iu || null; 1817 let title = event?.detail?.d?.dt || null; 1818 let { epi, empi } = event?.detail?.d || {}; 1819 window._swat.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFLItemAddedToList, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi }); 1820 1821 SwymStorefrontLayoutExtensions?.refreshSFLList(); 1822 1823 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1824 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageSFLItemSaved, 1825 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 1826 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1827 image, 1828 title 1829 },{ 1830 showProgress: true, 1831 showTitle: true 1832 }); 1833 }); 1834 1835 swat.evtLayer.addEventListener(swat.JSEvents.removedFromSFL, (event) => { 1836 let image = event?.detail?.d?.iu || null; 1837 let title = event?.detail?.d?.dt || null; 1838 let { epi, empi } = event?.detail?.d || {}; 1839 window._swat.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFLItemRemovedFromList, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi }); 1840 SwymStorefrontLayoutExtensions?.refreshSFLList(); 1841 1842 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 1843 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageSFLItemRemoved, 1844 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 1845 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 1846 image, 1847 title 1848 },{ 1849 showProgress: true, 1850 showTitle: true 1851 }); 1852 }); 1853 1854 swat.evtLayer.addEventListener(swat.JSEvents.movedToCartFromSFL, (event) => { 1855 swat.utils.debounce(()=>{ 1856 SwymStorefrontLayoutExtensions?.refreshSFLList(); 1857 }, 1000)(); 1858 }); 1859 1860 } 1861 } 1862 1863} 1864 1865/** 1866 * SwymStorefrontLayoutAsModal 1867 *
1868 * This class extends SwymWishlistCore to render a modal-based storefront layout for the Swym wishlist. 1869 * It manages UI rendering, event listeners, and open/close interactions. 1870 * 1871 * Features: 1872 * - Displays a modal layout for wishlist functionality. 1873 * - Supports collection carousel based on SwymStorefrontLayoutContext settings. 1874 * - Provides a close button and backdrop click handling to close the modal. 1875 * - Tracks UI engagement using Swym's instrumentation. 1876 * 1877 * Methods: 1878 * - renderUI(): Generates the modal's HTML structure. 1879 * - initUIElement(): Initializes UI elements and event listeners. 1880 * - attachDrawerEventListner(): Binds click events to close the modal. 1881 * - open(): Opens the modal and updates the UI state. 1882 * - close(): Closes the modal, removes body scroll lock, and updates history state. 1883 * - reRenderUI(): Re-renders the UI while preserving the open state. 1884 * 1885 * Usage: 1886 * <swym-storefront-layout-as-modal></swym-storefront-layout-as-modal> 1887 */ 1888 class SwymStorefrontLayoutAsModal extends SwymWishlistCore { 1889 constructor() { 1890 super(); 1891 this.renderUI(); 1892 } 1893 1894 renderUI() { 1895 this.innerHTML = ` 1896 <div id="swym-storefront-layout-container" class="swym-storefront-layout-drawer swym-storefront-layout-hide-view" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="swym-wishlist-title" aria-hidden="true" inert> 1897 <div class="swym-storefront-layout-backdrop"></div> 1898 <div class="swym-storefront-layout-layout"> 1899 <swym-storefront-layout-tabs></swym-storefront-layout-tabs> 1900 <button id="swym-storefront-layout-close-drawer-button" aria-label="${SwymStorefrontLayoutContext?.Strings?.closeDrawer || 'Close'}" class="swym-storefront-layout-close-button"> 1901 <svg xmlns="http://www.w3.org/2000/svg" width="27" height="27" viewBox="0 0 27 27" fill="none" aria-hidden="true"> 1902 <path d="M0 13.5C0 6.04416 6.04416 0 13.5 0C20.9558 0 27 6.04416 27 13.5C27 20.9558 20.9558 27 13.5 27C6.04416 27 0 20.9558 0 13.5Z" fill="none" fill-opacity="0.5"/> 1903 <path d="M16.0245 17.8326C16.2477 18.0558 16.6095 18.0558 16.8326 17.8326C17.0558 17.6095 17.0558 17.2477 16.8326 17.0245L13.8081 14L16.8326 10.9755C17.0558 10.7523 17.0558 10.3905 16.8326 10.1674C16.6095 9.94421 16.2477 9.94421 16.0245 10.1674L13 13.1919L9.97549 10.1674C9.75233 9.94421 9.39052 9.94421 9.16737 10.1674C8.94421 10.3905 8.94421 10.7523 9.16737 10.9755L12.1919 14L9.16737 17.0245C8.94421 17.2477 8.94421 17.6095 9.16737 17.8326C9.39052 18.0558 9.75233 18.0558 9.97549 17.8326L13 14.8081L16.0245 17.8326Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/> 1904 </svg> 1905 </button> 1906 <swym-storefront-layout-tab-content class="swym-storefront-layout-container swym-storefront-layout-scrollable"></swym-storefront-layout-tab-content> 1907 <swym-storefront-layout-login-user showLogin="true"></swym-storefront-layout-login-user> 1908 </div> 1909 </div> 1910 `; 1911 1912 this.initUIElement(); 1913 } 1914 1915 initUIElement() { 1916 this.elements = { 1917 drawer: this.querySelector('.swym-storefront-layout-drawer'), 1918 drawerCloseButton: this.querySelector('#swym-storefront-layout-close-drawer-button'), 1919 drawerbackdrop: this.querySelector('.swym-storefront-layout-backdrop'), 1920 collectionList: this.querySelector('#swym-storefront-layout-collection-list'), 1921 } 1922 1923 1924 // Strengthen notification accessibility with polite live region 1925 try { 1926 const notificationEl = document.querySelector('swym-storefront-layout-notification'); 1927 if (notificationEl) { 1928 notificationEl.setAttribute('aria-live', 'polite'); 1929 } 1930 } catch (e) { 1931 window._swat?.utils.warn('Unable to set notification aria-live', e); 1932 } 1933 1934 // Announce list updates for screen readers 1935 try { 1936 document.querySelectorAll('.swym-storefront-layout-total-list-items-count').forEach((itemsCountEl) => { 1937 itemsCountEl.setAttribute('aria-live', 'polite'); 1938 }); 1939 } catch (e) { 1940 window._swat?.utils.warn('Unable to set items count aria-l
1940ive', e); 1941 } 1942 this.attachDrawerEventListner(); 1943 1944 } 1945 1946 getDeepFocusableElements(root) { 1947 const focusableSelector = 1948 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; 1949 let elements = Array.from(root.querySelectorAll(focusableSelector)); 1950 for (const el of elements) { 1951 if (el.shadowRoot) { 1952 elements = elements.concat( 1953 this.getDeepFocusableElements(el.shadowRoot) 1954 ); 1955 } 1956 } 1957 return elements; 1958 } 1959 1960 trapFocus = (e) => { 1961 const isTabPressed = e.key === "Tab" || e.keyCode === 9; 1962 if (!isTabPressed) return; 1963 1964 const modal = this.elements.drawer; 1965 const focusableElements = this.getDeepFocusableElements(modal); 1966 1967 if (focusableElements.length === 0) return; 1968 1969 const first = focusableElements[0]; 1970 const last = focusableElements[focusableElements.length - 1]; 1971 1972 if (e.shiftKey) { 1973 if ( 1974 document.activeElement === first || 1975 !modal.contains(document.activeElement) 1976 ) { 1977 e.preventDefault(); 1978 last.focus(); 1979 } 1980 } else { 1981 if ( 1982 document.activeElement === last || 1983 !modal.contains(document.activeElement) 1984 ) { 1985 e.preventDefault(); 1986 first.focus(); 1987 } 1988 } 1989 }; 1990 1991 attachDrawerEventListner() { 1992 this.elements.drawerCloseButton.addEventListener('click', this.close.bind(this)); 1993 this.elements.drawerbackdrop.addEventListener('click', this.close.bind(this)); 1994 1995 // Close on ESC (focus trap is handled in open/close methods) 1996 const keydownHandler = (e) => { 1997 const isOpen = this.elements?.drawer && !this.elements.drawer.classList.contains('swym-storefront-layout-hide-view'); 1998 if (!isOpen) return; 1999 if (e.key === 'Escape') { 2000 e.preventDefault(); 2001 this.close(); 2002 } 2003 }; 2004 document.addEventListener('keydown', keydownHandler); 2005 } 2006 2007 open() { 2008 this.isOpen = true; 2009 this.previouslyFocused = document.activeElement; 2010 2011 if (this.elements?.drawer) { 2012 this.elements.drawer.classList.remove( 2013 "swym-storefront-layout-hide-view" 2014 ); 2015 this.elements.drawer.removeAttribute("aria-hidden"); 2016 this.elements.drawer.removeAttribute("inert"); 2017 document.body.classList.add("swym-storefront-layout-body-no-scroll"); 2018 2019 this.elements.drawer.focus(); 2020 document.addEventListener("keydown", this.trapFocus, true); 2021 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutOpened, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 2022 if (SwymStorefrontLayoutContext?.CustomEvents?.Opened) { 2023 document.dispatchEvent(new CustomEvent(SwymStorefrontLayoutContext.CustomEvents.Opened)); 2024 } 2025 } 2026 } 2027 2028 close() { 2029 this.isOpen = false; 2030 2031 if (this.elements?.drawer) { 2032 this.elements.drawer.classList.add("swym-storefront-layout-hide-view"); 2033 this.elements.drawer.setAttribute("aria-hidden", "true"); 2034 this.elements.drawer.setAttribute("inert", ""); 2035 document.body.classList.remove("swym-storefront-layout-body-no-scroll"); 2036 2037 document.removeEventListener("keydown", this.trapFocus, true); 2038 2039 if (this.previouslyFocused && typeof this.previouslyFocused.focus === "function") { 2040 this.previouslyFocused.focus(); 2041 } 2042 2043 history.replaceState(null, '', window.location.pathname + window.location.search); 2044 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutClosed, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 2045 } 2046 } 2047 2048 reRenderUI() { 2049 this.renderUI(); 2050 if (this.isOpen) this.open(); 2051 } 2052 } 2053 2054/** 2055 * SwymStorefrontLayoutAsSection 2056 * 2057 * This class extends SwymWishlistCore to render a storefront wishlist layout as a section within a specified container. 2058 * It dynamically injects the wishlist UI into the provided container element. 2059 * 2060 * Features: 2061 * - Renders the wishlist section inside a container specified by the `container-id` attribute. 2062 * - Supports collection carousel based on SwymStorefrontLayoutContext settings. 2063 * - Displays wishlist items and user login UI. 2064 * - Handles rendering errors gracefully and logs warnings if an invali
2064d container ID is provided. 2065 * 2066 * Methods: 2067 * - renderUI(): Generates the section-based wishlist UI and injects it into the specified container. 2068 * - reRenderUI(): Re-renders the UI to reflect any updates. 2069 * 2070 * Usage: 2071 * <swym-storefront-layout-as-section container-id="your-container-id"></swym-storefront-layout-as-section> 2072 */ 2073 2074class SwymStorefrontLayoutAsSection extends SwymWishlistCore { 2075 constructor() { 2076 super(); 2077 this.containerId = this.getAttribute('container-id'); 2078 this.renderUI(); 2079 } 2080 2081 renderUI() { 2082 let { StorefrontLayoutType } = SwymStorefrontLayoutContext?.Settings; 2083 if(StorefrontLayoutType === 'as-section'){ 2084 this.listContainerToRender = null; 2085 try { 2086 this.listContainerToRender = document.getElementById(this.containerId); 2087 2088 // Only proceed if the container exists in the DOM 2089 if (!this.listContainerToRender) { 2090 // Container doesn't exist yet - this can happen during page transitions or layout changes 2091 // We'll skip rendering for now and wait for the container to be available 2092 return; 2093 } 2094 2095 this.listContainerToRender.innerHTML = ` 2096 <div id="swym-storefront-layout-container"> 2097 <div id="swym-storefront-layout-section-container" class="swym-storefront-layout-layout"> 2098 <swym-storefront-layout-tabs></swym-storefront-layout-tabs> 2099 <swym-storefront-layout-login-user showLogin="true"></swym-storefront-layout-login-user> 2100 <swym-storefront-layout-tab-content class="swym-storefront-layout-container"></swym-storefront-layout-tab-content> 2101 </div> 2102 </div> 2103 ` 2104 2105 this.listContainerToRender.classList.add('swym-storefront-layout-root-component'); 2106 2107 } catch (error) { 2108 window._swat?.utils.warn('Invalid List Container Selector ', this.containerId); 2109 } 2110 } 2111 } 2112 2113 reRenderUI() { 2114 this.renderUI(); 2115 } 2116 2117 close() { 2118 if (document.referrer && history.length > 1) { 2119 try { 2120 const referrerOrigin = new URL(document.referrer).origin; 2121 if (referrerOrigin === window.location.origin) { 2122 history.back(); 2123 return; 2124 } 2125 } catch (e) { /* invalid referrer URL, fall through */ } 2126 } 2127 window.location.href = window.Shopify?.routes?.root || '/'; 2128 } 2129 2130} 2131 2132/** 2133 * SwymStorefrontLayoutAsDrawer 2134 * 2135 * This class extends SwymWishlistCore to implement a drawer-style storefront wishlist layout. 2136 * It provides an interactive UI for users to view and manage their wishlist within a side drawer. 2137 * 2138 * Features: 2139 * - Renders a wishlist drawer that slides in/out based on user interaction. 2140 * - Supports collection carousel display based on SwymStorefrontLayoutContext settings. 2141 * - Includes login UI for authentication-related actions. 2142 * - Implements event listeners for closing the drawer via a close button or backdrop click. 2143 * - Triggers instrumentation events for UI engagement tracking. 2144 * 2145 * Methods: 2146 * - renderUI(): Generates the wishlist drawer UI and injects it into the component. 2147 * - initUIElement(): Caches DOM elements required for interactions. 2148 * - attachDrawerEventListner(): Attaches event listeners for closing the drawer. 2149 * - open(): Opens the drawer and prevents background scrolling. 2150 * - close(): Closes the drawer, restores scrolling, and updates browser history. 2151 * - reRenderUI(): Re-renders the UI and retains the open state if applicable. 2152 * 2153 * Usage: 2154 * <swym-storefront-layout-as-drawer></swym-storefront-layout-as-drawer> 2155 */ 2156 class SwymStorefrontLayoutAsDrawer extends SwymWishlistCore { 2157 constructor() { 2158 super(); 2159 this.renderUI(); 2160 } 2161 2162 renderUI() { 2163 this.innerHTML = ` 2164 <div id="swym-storefront-layout-container" class="swym-storefront-layout-drawer swym-storefront-layout-hide-view swym-storefront-layout-drawer-position-${SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutDrawerPosition}" role="dialog" aria-modal="true" aria-labelledby="swym-wishlist-title" tabindex="-1" aria-hidden="true" inert> 2165 <div class="swym-storefront-layout-backdrop"></div> 2166 <div class="swym-storefront-layout-layout"> 2167 <button id="swym-storefront-layout-close-drawer-button" aria-label="${SwymStorefrontLayoutContext?.Strings?.closeDrawer || 'Close'}" class="swym-storefront-layout-close-button"> 2168 <svg xmlns="http://www.w3.org/2000/svg" width="27" height="27" viewB
2168ox="0 0 27 27" fill="none" aria-hidden="true"> 2169 <path d="M0 13.5C0 6.04416 6.04416 0 13.5 0C20.9558 0 27 6.04416 27 13.5C27 20.9558 20.9558 27 13.5 27C6.04416 27 0 20.9558 0 13.5Z" fill="none" fill-opacity="0.5"/> 2170 <path d="M16.0245 17.8326C16.2477 18.0558 16.6095 18.0558 16.8326 17.8326C17.0558 17.6095 17.0558 17.2477 16.8326 17.0245L13.8081 14L16.8326 10.9755C17.0558 10.7523 17.0558 10.3905 16.8326 10.1674C16.6095 9.94421 16.2477 9.94421 16.0245 10.1674L13 13.1919L9.97549 10.1674C9.75233 9.94421 9.39052 9.94421 9.16737 10.1674C8.94421 10.3905 8.94421 10.7523 9.16737 10.9755L12.1919 14L9.16737 17.0245C8.94421 17.2477 8.94421 17.6095 9.16737 17.8326C9.39052 18.0558 9.75233 18.0558 9.97549 17.8326L13 14.8081L16.0245 17.8326Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/> 2171 </svg> 2172 </button> 2173 <swym-storefront-layout-tab-content class="swym-storefront-layout-container swym-storefront-layout-scrollable"></swym-storefront-layout-tab-content> 2174 <swym-storefront-layout-login-user showLogin="true"></swym-storefront-layout-login-user> 2175 <swym-storefront-layout-actions id="swym-storefront-layout-actions"></swym-storefront-layout-actions> 2176 <swym-storefront-layout-tabs></swym-storefront-layout-tabs> 2177 </div> 2178 </div> 2179 `; 2180 this.initUIElement(); 2181 } 2182 2183 initUIElement() { 2184 this.elements = { 2185 drawer: this.querySelector('.swym-storefront-layout-drawer'), 2186 drawerCloseButton: this.querySelector('#swym-storefront-layout-close-drawer-button'), 2187 drawerbackdrop: this.querySelector('.swym-storefront-layout-backdrop'), 2188 collectionList: this.querySelector('#swym-storefront-layout-collection-list') 2189 } 2190 this.attachDrawerEventListner(); 2191 } 2192 2193 attachDrawerEventListner() { 2194 this.elements.drawerCloseButton.addEventListener('click', this.close.bind(this)); 2195 this.elements.drawerbackdrop.addEventListener('click', this.close.bind(this)); 2196 2197 // Close on ESC (focus trap is handled in open/close methods) 2198 const keydownHandler = (e) => { 2199 const isOpen = this.elements?.drawer && !this.elements.drawer.classList.contains('swym-storefront-layout-hide-view'); 2200 if (!isOpen) return; 2201 if (e.key === 'Escape') { 2202 e.preventDefault(); 2203 this.close(); 2204 } 2205 }; 2206 document.addEventListener('keydown', keydownHandler); 2207 } 2208 2209 getFocusableElements(root) { 2210 const selectors = 2211 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'; 2212 const elements = Array.from(root.querySelectorAll(selectors)); 2213 2214 return elements.filter( 2215 (el) => el.offsetParent !== null || el instanceof SVGElement 2216 ); 2217 } 2218 2219 trapFocus = (e) => { 2220 if (e.key !== "Tab") return; 2221 2222 const focusableEls = this.getFocusableElements(this.elements.drawer); 2223 if (focusableEls.length === 0) return; 2224 2225 const first = focusableEls[0]; 2226 const last = focusableEls[focusableEls.length - 1]; 2227 2228 if (e.shiftKey) { 2229 if (document.activeElement === first || !this.elements.drawer.contains(document.activeElement)) { 2230 e.preventDefault(); 2231 last.focus(); 2232 } 2233 } else { 2234 if (document.activeElement === last || !this.elements.drawer.contains(document.activeElement)) { 2235 e.preventDefault(); 2236 first.focus(); 2237 } 2238 } 2239 }; 2240 2241 open() { 2242 this.isOpen = true; 2243 this.previouslyFocused = document.activeElement; 2244 2245 if (this.elements?.drawer) { 2246 this.elements.drawer.classList.remove("swym-storefront-layout-hide-view"); 2247 this.elements.drawer.removeAttribute("aria-hidden"); 2248 this.elements.drawer.removeAttribute("inert"); 2249 document.body.classList.add("swym-storefront-layout-body-no-scroll"); 2250 2251 // Set focus to the drawer itself 2252 this.elements.drawer.focus(); 2253 2254 // Trap focus inside the drawer 2255 document.addEventListener("keydown", this.trapFocus, true); 2256 2257 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutOpened, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 2258 if (SwymStorefrontLayoutContext?.CustomEvents?.Opened) { 2259 document.dispatchEvent(new CustomEvent(SwymStorefrontLayoutContext.CustomEvents.Opened)); 2260 } 2261 } 2262 } 2263 2264 close() { 2265 this.isOpen = false;
2266 2267 if (this.elements?.drawer) { 2268 this.elements.drawer.classList.add("swym-storefront-layout-hide-view"); 2269 this.elements.drawer.setAttribute("aria-hidden", "true"); 2270 this.elements.drawer.setAttribute("inert", ""); 2271 document.body.classList.remove("swym-storefront-layout-body-no-scroll"); 2272 2273 document.removeEventListener("keydown", this.trapFocus, true); 2274 2275 // Return focus to the previously focused element 2276 if (this.previouslyFocused && typeof this.previouslyFocused.focus === "function") { 2277 this.previouslyFocused.focus(); 2278 } 2279 2280 history.replaceState(null, '', window.location.pathname + window.location.search); 2281 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutClosed, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 2282 } 2283 } 2284 2285 reRenderUI() { 2286 this.renderUI(); 2287 if (this.isOpen) this.open(); 2288 } 2289 } 2290 2291/** 2292 * SwymStorefrontLayoutCollectionCarousel 2293 * 2294 * This custom Web Component is responsible for rendering a carousel of collection lists 2295 * within the storefront layout. It dynamically updates based on the provided collection 2296 * data and offers smooth scrolling functionality with navigation buttons. 2297 * 2298 * Features: 2299 * - Dynamically renders collections with images and item counts. 2300 * - Implements smooth scrolling with previous/next buttons. 2301 * - Updates button visibility based on scroll position. 2302 * - Tracks user interactions using Swym's instrumentation. 2303 * - Provides an action button for each collection with a tooltip menu. 2304 * - Supports localization via `SwymStorefrontLayoutContext?.Strings`. 2305 * 2306 * Usage: 2307 * - Call `setData({ collections })` to populate the carousel with collection data. 2308 * 2309 * Dependencies: 2310 * - `window.SwymStorefrontLayoutContext` for localized strings and configurations. 2311 * - `window._swat?.instrumentV3` for tracking user interactions. 2312 * - `SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip` for managing action tooltips. 2313 * 2314 */ 2315class SwymStorefrontLayoutCollectionCarousel extends HTMLElement { 2316 constructor() { 2317 super(); 2318 this.collections = []; 2319 2320 // Check multi-wishlist config to determine initial visibility 2321 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 2322 this.shouldShowMultiple = shouldShowMultiple; 2323 2324 this.renderUI(); 2325 this.elements = { 2326 title: this.querySelector('.swym-storefront-layout-collection-carousel-title'), 2327 carouselContainer: this.querySelector('.swym-storefront-layout-carousel-container'), 2328 carousel: this.querySelector('#swym-storefront-layout-collection-carousel-items-container'), 2329 carouselPrevButton: this.querySelector('.swym-storefront-layout-carousel-prev-btn'), 2330 carouselNextButton: this.querySelector('.swym-storefront-layout-carousel-next-btn') 2331 } 2332 2333 // Hide carousel initially if multi-wishlist should not be shown 2334 if (!this.shouldShowMultiple) { 2335 this.style.display = 'none'; 2336 } 2337 2338 this.attachEventListner(); 2339 } 2340 2341 setData({ collections, canCreateLists = true, shouldShowMultiple = false }) { 2342 this.collections = collections; 2343 this.canCreateLists = canCreateLists; 2344 this.shouldShowMultiple = shouldShowMultiple; 2345 this.renderCollections(); 2346 } 2347 2348 renderUI() { 2349 this.innerHTML = ` 2350 <div class="swym-storefront-layout-collection-carousel-container"> 2351 <div class="swym-storefront-layout-collection-carousel-title" role="heading" aria-level="2">${SwymStorefrontLayoutContext?.Strings?.collectionTitle}</div> 2352 <div class="swym-storefront-layout-carousel-container" role="region" aria-label="${SwymStorefrontLayoutContext?.Strings?.collectionTitle}"> 2353 <button class="swym-storefront-layout-carousel-btn swym-storefront-layout-carousel-prev-btn" aria-label="${SwymStorefrontLayoutContext?.Strings?.scrollLeft || 'Scroll Left'}"> 2354 <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="swym-storefront-layout-scroll-button-icon" aria-hidden="true"><path d="m13.333 8 8 8-8 8" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"></path></svg> 2355 </button> 2356 <div id="swym-storefront-layout-collection-carousel-items-container" class="swym-storefront-layout-carousel swym-storefront-layout-scrollable" role="list" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.collectionTitle || 'Collections'}"> 2357 <swym-storefront-layout-loader loading="true" loadertype="${SwymStorefrontLayoutContext?.LoaderViewType.CarouselListItem}" width="100%" height="100px"></swym-storefront-layout-loader> 2358 </div> 2359 <button class="swym-storefront-layout-carousel-btn swym-storefront-layout-carousel-next-btn" aria-label="${SwymStorefrontLayoutContext?.Strings?.scrollRight || 'Scroll Right'}"> 2360 <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="swym-storefront-layout-scroll-button-icon" aria-hidden="true"><path d="m13.333 8 8 8-8 8" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"></path></svg> 2361 </button> 2362 </div> 2363 </div> 2364 ` 2365 } 2366 2367 attachEventListner() { 2368 2369 this.elements.carouselPrevButton.addEventListener("click", () => { 2370 this.elements.carousel.scrollBy({ 2371 left: -320, 2372 behavior: "smooth", 2373 }); 2374 }); 2375 2376 this.elements.carouselNextButton.addEventListener("click", () => { 2377 this.elements.carousel.scrollBy({ 2378 left: 320, 2379 behavior: "smooth", 2380 }); 2381 }); 2382 2383 this.elements.carousel.addEventListener("scroll", () => this.updateCarouselButtonVisibility()); 2384 2385 } 2386 2387 renderCollections() { 2388 const collectionsContainer = this.querySelector('#swym-storefront-layout-collection-carousel-items-container'); 2389 if (collectionsContainer) { 2390 2391 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutCollectionRendered, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 2392 2393 // Show carousel if shouldShowMultiple is true (feature enabled or multiple lists exist) 2394 if (!this.shouldShowMultiple) { 2395 this.style.display = 'none'; 2396 return; 2397 } 2398 2399 // Show the carousel
2400 this.style.display = ''; 2401 2402 if (this.collections && this.collections.length) { 2403 this.elements.carouselContainer.classList.add('swym-storefront-layout-has-items'); 2404 collectionsContainer.innerHTML = this.collections 2405 .map( 2406 (collection) => { 2407 let { lname, listcontents, lid } = collection; 2408 2409 const listImagesHtml = Array.from({ length: 3 }) 2410 .map((_, i) => { 2411 let item = listcontents[i]; 2412 return ` 2413 <div class="${i === 0 ? 'swym-storefront-layout-collection-grid-item-primary-image' : 'swym-storefront-layout-collection-grid-item-secondary-image'} "> 2414 ${item?.iu ? `<img src="${item ? item.iu : null}" alt="${item?.dt ? swymEscAttr(item?.dt) : 'product Image'}" class="swym-storefront-layout-collection-grid-item-image" />` : `<div class="swym-storefront-layout-collection-grid-item-image" ></div>`} 2415 </div> 2416 `; 2417 }) 2418 .join(""); 2419 2420 const count = listcontents.length; 2421 2422 return ` 2423 <div class="swym-storefront-layout-collection-grid-item" data-lid="${lid}" role="listitem" aria-label="${lname}"> 2424 <div class="swym-storefront-layout-collection-grid-item-image-container"> 2425 ${listImagesHtml} 2426 </div> 2427 <div class="swym-storefront-layout-collection-grid-item-info-container"> 2428 <div class="swym-storefront-layout-collection-grid-item-name">${lname}</div> 2429 <div class="swym-storefront-layout-collection-grid-item-count">${count} ${count === 1 ? SwymStorefrontLayoutContext?.Strings?.item : SwymStorefrontLayoutContext?.Strings?.items}</div> 2430 </div> 2431 <div class="swym-storefront-layout-collection-grid-item-option-button" role="button" tabindex="0" aria-haspopup="dialog" aria-expanded="false" aria-label="More options for ${lname}"> 2432 <svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13" fill="none" aria-hidden="true" focusable="false"> 2433 <circle cx="1.5" cy="1.50014" r="1.5" fill="#B1B7C3"/> 2434 <circle cx="1.5" cy="6.50014" r="1.5" fill="#B1B7C3"/> 2435 <circle cx="1.5" cy="11.5001" r="1.5" fill="#B1B7C3"/> 2436 </svg> 2437 </div> 2438 </div> 2439 ` 2440 } 2441 ) 2442 .join(''); 2443 this.updateCarouselButtonVisibility(); 2444 this.attachEventListnerToCollectionGrid(); 2445 } else { 2446 // Show empty state when shouldShowMultiple is true but no collections 2447 this.elements.carouselContainer.classList.remove('swym-storefront-layout-has-items'); 2448 collectionsContainer.innerHTML = ` 2449 <div class="swym-storefront-layout-collection-empty">${SwymStorefrontLayoutContext?.Strings?.emptyCarouselCollectionText}</div>`; 2450 } 2451 2452 let collectionCount = this.collections?.length; 2453 if(this.elements.title){ 2454 this.elements.title.innerHTML = `${SwymStorefrontLayoutContext?.Strings?.collectionTitle} ${collectionCount?`(${collectionCount})`:''}`; 2455 } 2456 } 2457 } 2458 2459 2460 updateCarouselButtonVisibility() { 2461 const scrollLeft = this.elements.carousel.scrollLeft; 2462 const maxScrollLeft = this.elements.carousel.scrollWidth - this.elements.carousel.clientWidth; 2463 2464 this.elements.carouselPrevButton.style.display = scrollLeft > 0 ? "flex" : "none"; 2465 this.elements.carouselNextButton.style.display = scrollLeft < maxScrollLeft ? "flex" : "none"; 2466 } 2467 2468 attachEventListnerToCollectionGrid() { 2469 this.collections.forEach((collection) => { 2470 const collectionItem = this.elements.carousel.querySelector( 2471 `[data-lid="${collection.lid}"]` 2472 ); 2473 collectionItem?.addEventListener("click", (event) => { 2474 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutCollectionOpened, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 2475 window.location.hash = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.CollectionList + collection.lid; 2476 }); 2477 2478 let collectionItemActionButton = collectionItem.querySelector('.swym-storefront-layout-collection-grid-item-option-button'); 2479 collectionItemActionButton?.addEventListener("click", (event) => { 2480 event.preventDefault(); 2481 event.stopPropagation(); 2482 if(SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.isTooltipOpen && SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.currentTarget === event.target){ 2483 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.closeTooltip(); 2484 collectionItemActionButton?.setAttribute('aria-expanded', 'false'); 2485 }else{ 2486 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.setData({ 2487 listId: collection.lid, 2488 list: collection, 2489 collections: this.collections, 2490 actionType: SwymStorefrontLayoutContext?.ActionTypes.EditCollection 2491 }); 2492 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.showOnTarget(event.target); 2493 collectionItemActionButton?.setAttribute('aria-expanded', 'true'); 2494 } 2495 }) 2496 2497 // Keyboard support for option button 2498 collectionItemActionButton?.addEventListener('keydown', (e) => { 2499 if (e.key === 'Enter' || e.key === ' ') { 2500 e.preventDefault(); 2501 collectionItemActionButton.click(); 2502 } 2503 }); 2504 2505 }) 2506 } 2507} 2508 2509/** 2510 * SwymStorefrontLayoutWishlistItem 2511 * 2512 * This custom web component represents an item in the Swym Storefront Layout Wishlist. 2513 * It dynamically renders product details, pricing, and actions such as adding to cart 2514 * or managing wishlist options. It also handles variant selection and user interactions 2515 * based on the store's configuration settings. 2516 * 2517 * Key Features: 2518 * - Displays product title, image, price, and compare-at price. 2519 * - Supports multiple product variants and allows selection. 2520 * - Handles "Add to Cart" functionality with visual feedback. 2521 * - Manages user interactions for wishlist options and collections. 2522 * - Ensures compatibility with store localization settings. 2523 * 2524 * Dependencies: 2525 * - SwymStorefrontLayoutContext: Provides store settings, strings, and utilities. 2526 * - SwymStorefrontLayoutExtensions: Handles tooltips and notifications. 2527 * - _swat.platform: Manages cart operations. 2528 * 2529 * Usage: 2530 * This component should be used within the Swym Storefront Layout to display 2531 * wishlist items effectively while maintaining a seamless user experience. 2532 */ 2533class SwymStorefrontLayoutWishlistItem extends HTMLElement{ 2534 constructor() { 2535 super(); 2536 this.item = null; 2537 this.list = null; 2538 this.sflList = null; 2539 this.selectedVariant = null; 2540 this.collections = null; 2541 this.listItemType = SwymStorefrontLayoutContext?.ListItemType?.WishlistItem; 2542 this.quantityUpdateTimeout = null; 2543 this._listenerController = null; 2544 } 2545 2546 disconnectedCallback() { 2547 this._listenerController?.abort(); 2548 this._listenerController = null; 2549 if (this.quantityUpdateTimeout) { 2550 clearTimeout(this.quantityUpdateTimeout); 2551 this.quantityUpdateTimeout = null; 2552 } 2553 } 2554 2555 setData({ item, list, sflList, listItemType, collections, canCreateLists = true }) { 2556 this.item = item; 2557 this.listItemType = listItemType; 2558 this.list = list; 2559 this.sflList = sflList; 2560 this.collections = collections; 2561 this.canCreateLists = canCreateLists; 2562 2563 this.selectedVariantId = this.item?.cprops?.selectedVariantId || this.item?.epi; 2564 this.selectedVariant = SwymStorefrontLayoutContext?.getSelectedVariant(this.item, this.selectedVariantId); 2565 2566 this.dataset.epi = this.selectedVariant?.id || this.item?.epi; 2567 this.dataset.empi = this.item?.empi; 2568 this.dataset.du = this.item?.['du-mkt'] || this.item?.du; 2569 2570 this.renderItem(); 2571 this.initItemElements(); 2572 this.addEventListeners(); 2573 } 2574 2575 getProductImage() { 2576 const productImage = this.selectedVariant?.featured_image; 2577 const imageUrl = typeof productImage === 'string' ? productImage : productImage?.src; 2578 return ( 2579 imageUrl || 2580 this.item?.productData?.featured_image || 2581 this.item?.iu 2582 ); 2583 } 2584 2585 getFormattedPrice() { 2586 let price = this.selectedVariant?.price ?? this.item?.productData?.price; 2587 if (price === undefined || price === null) { 2588 price = this.item?.pr; 2589 } 2590 return price !== undefined && price !== null 2591 ? SwymStorefrontLayoutContext?.FormatPrice(price) 2592 : ''; 2593 } 2594 getFormattedComparePrice() { 2595 let price = this.selectedVariant?.compare_at_price ?? this.item?.productData?.compare_at_price; 2596 if (price === undefined || price === null) { 2597 price = this.item?.op; 2598 } 2599 return price !== undefined && price !== null 2600 ? SwymStorefrontLayoutContext?.FormatPrice(price) 2601 : ''; 2602 } 2603 2604 getButtonState() { 2605 const isInCart = _swat.platform.isInDeviceCart(this.selectedVariant?.id); 2606 if(this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SaveForLaterItem){ 2607 return this.selectedVariant?.available 2608 ? SwymStorefrontLayoutContext?.Strings?.moveToCartCta 2609 : SwymStorefrontLayoutContext?.Strings?.soldOut; 2610 }else{ 2611 return this.selectedVariant?.available 2612 ? isInCart 2613 ? SwymStorefrontLayoutContext?.Strings?.addedToCart 2614 : SwymStorefrontLayoutContext?.Strings?.addToCart 2615 : SwymStorefrontLayoutContext?.Strings?.soldOut; 2616 } 2617 } 2618 2619 renderVariants() { 2620 if (SwymStorefrontLayoutContext?.Settings?.EnableStorefrontLayoutVariantSelector && this.item?.productData?.variants?.length > 1) { 2621 return `
2622 <span class="swym-storefront-layout-price-devider">|</span> 2623 <div class="swym-storefront-layout-grid-item-variants-container"> 2624 ${this.item.productData.options 2625 .map( 2626 (option, index) => ` 2627 ${index==0?'':'<span class="swym-storefront-layout-variant-devider"></span>'} 2628 <div class="swym-storefront-layout-variant"> 2629 ${option.values 2630 .map( 2631 (value) => 2632 this.selectedVariant?.[`option${index + 1}`] === value?value:'' 2633 ) 2634 .join('')} 2635 </div> 2636 ` 2637 ) 2638 .join('')} 2639 </div> 2640 `; 2641 } 2642 return ''; 2643 } 2644 2645 renderVariantSelector() { 2646 if (SwymStorefrontLayoutContext?.Settings?.EnableStorefrontLayoutVariantSelector && this.item?.productData?.variants?.length > 1) { 2647 return ` 2648 <div class="swym-storefront-layout-grid-item-variant-selector-container"> 2649 ${this.item.productData.options 2650 .map( 2651 (option, index) => ` 2652 ${option.values && option.values.length>1 ? ` 2653 <select class="swym-storefront-layout-variant-selector"> 2654 <option value="${option.name}" disabled>${option.name}</option> 2655 ${option.values 2656 .map( 2657 (value) => 2658 `<option value="${value}" ${ 2659 this.selectedVariant[`option${index + 1}`] === value ? 'selected' : '' 2660 }>${value}</option>` 2661 ) 2662 .join('')} 2663 </select> 2664 `:''} 2665 ` 2666 ) 2667 .join('')} 2668 </div> 2669 `; 2670 } 2671 return ''; 2672 } 2673 renderOptionButton() { 2674 // Get multi-wishlist configuration 2675 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 2676 const isDrawer = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer'; 2677 2678 // Drawer UI Polish: In drawer + single wishlist mode, show trash icon instead of 3-dot menu 2679 // Clicking opens tooltip with "Remove from wishlist" confirmation (no direct delete) 2680 if (isDrawer && !shouldShowMultiple) { 2681 if (this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SavedForYouItem || 2682 SwymStorefrontLayoutContext?.checkUserCanEditList(this.list || this.sflList)) { 2683 const ariaLabel = this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SavedForYouItem 2684 ? (SwymStorefrontLayoutContext?.Strings?.removeItemCta || 'Remove from Saved For You') 2685 : (SwymStorefrontLayoutContext?.Strings?.removeItemCta || 'Remove item'); 2686 return ` 2687 <button class="swym-storefront-layout-grid-item-option-button swym-storefront-layout-delete-btn" tabindex="0" role="button" aria-label="${ariaLabel}" aria-haspopup="dialog" aria-expanded="false"> 2688 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true" focusable="false"> 2689 <path d="M11.5 9V13.25M8.5 9V13.25M5.75 12.2V6H14.25C14.25 8.421 14.25 9.779 14.25 12.2C14.25 13.053 14.25 13.647 14.212 14.11C14.175 14.563 14.106 14.824 14.005 15.021C13.7822 15.4476 13.4408 15.8008 13.022 16.038C12.825 16.138 12.564 16.208 12.111 16.245C11.648 16.282 11.054 16.283 10.201 16.283H9.801C8.948 16.283 8.354 16.283 7.891 16.245C7.438 16.208 7.177 16.139 6.98 16.038C6.56083 15.8009 6.21911 15.4478 5.996 15.021C5.896 14.824 5.826 14.563 5.789 14.11C5.75 13.647 5.75 13.053 5.75 12.2Z" stroke="currentColor" stroke-linecap="round"/> 2690 <path d="M4.25 6H15.75M8 5.25C8 4.71957 8.21071 4.21086 8.58579 3.83579C8.96086 3.46071 9.46957 3.25 10 3.25C10.5304 3.25 11.0391 3.46071 11.4142 3.83579C11.7893 4.21086 12 4.71957 12 5.25" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/> 2691 </svg> 2692 </button> 2693 `; 2694 } 2695 return ''; 2696 } 2697 2698 // SavedForYouItem only supports remove — show option button for that 2699 if (this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SavedForYouItem) { 2700 return ` 2701 <button class="swym-storefront-layout-grid-item-option-button" tabindex="0" role="button" aria-label="Remove from Saved For You"> 2702 <svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13" fill="currentcolor" aria-hidden="true" focusable="false"> 2703 <circle cx="1.5" cy="1.50014" r="1.5" fill="#B1B7C3"/> 2704 <circle cx="1.5" cy="6.50014" r="1.5" fill="#B1B7C3"/> 2705 <circle cx="1.5" cy="11.5001" r="1.5" fill="#B1B7C3"/> 2706 </svg> 2707 </button> 2708 `; 2709 } 2710 2711 // Show options button if: 2712 // 1. Multiple lists should be shown (feature enabled OR multiple lists exist), OR 2713 // 2. User can edit the list (for delete/rename options) 2714 if ( 2715 shouldShowMultiple || 2716 SwymStorefrontLayoutContext?.checkUserCanEditList( 2717 this.list || this.sflList 2718 ) 2719 ) { 2720 return ` 2721 <button class="swym-storefront-layout-grid-item-option-button" tabindex="0" role="button" aria-label="Open item options" aria-haspopup="dialog" aria-expanded="false"> 2722 <svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13" fill="currentcolor" aria-hidden="true" focusable="false"> 2723 <circle cx="1.5" cy="1.50014" r="1.5" fill="#B1B7C3"/> 2724 <circle cx="1.5" cy="6.50014" r="1.5" fill="#B1B7C3"/> 2725 <circle cx="1.5" cy="11.5001" r="1.5" fill="#B1B7C3"/> 2726 </svg> 2727 </button> 2728 `; 2729 } 2730 return ""; 2731 } 2732 isTagAvailable(tag, product) { 2733 return window._swat && window._swat.platform?.isTagAvailable( 2734 tag, 2735 product 2736 ); 2737 } 2738 2739 getDefaultValue(val, defaultValue) { 2740 return val ? val : defaultValue; 2741 } 2742 2743 getMinQuantity() { 2744 return window._swat?.retailerSettings?.UI?.MinimumQuantity || 1; 2745 } 2746 2747 getMaxQuantity() { 2748 return window._swat?.retailerSettings?.UI?.MaximumQuantity; 2749 } 2750 2751 renderItem() { 2752 if(_swat.marketsEnabled && this.item?.['du-mkt'] === null ){ 2753 return; 2754 } 2755 const settings = window._swat.retailerSettings; 2756 const IsTagBasedActionEnabled = settings?.Wishlist?.TagBasedActionEnabled; 2757 2758 const hideAddtoCartTag = this.getDefaultValue(settings?.UI?.HideAddToCartTag, "swym-hide-addtocart"); 2759 const disabledAddtoCartTag = this.getDefaultValue(settings?.UI?.DisabledAddtoCartTag, "swym-disabled-addtocart-with-text"); 2760 const hideProductPriceTag = this.getDefaultValue(settings?.UI?.HideProductPriceTag, "swym-hide-productprice"); 2761 2762 const productInfo = this.item?.productData; 2763 2764 const isHidden = IsTagBasedActionEnabled && this.isTagAvailable(hideAddtoCartTag, productInfo); 2765 const isDisabled = IsTagBasedActionEnabled && this.isTagAvailable(disabledAddtoCartTag, productInfo); 2766 const hidePrice = IsTagBasedActionEnabled && this.isTagAvailable(hideProductPriceTag, productInfo); 2767 2768 // Use du-mkt if available (market-specific URL), otherwise fall back to du 2769 let localizedUrl = this.item?.['du-mkt'] || this.item?.du || ''; 2770 2771 // Append variant parameter if not already present 2772 if (localizedUrl && this.item?.epi) { 2773 const urlObj = new URL(localizedUrl, window.location.origin); 2774 if (!urlObj.searchParams.has('variant')) { 2775 urlObj.searchParams.set('variant', this.item.epi); 2776 localizedUrl = urlObj.toString(); 2777 } 2778 } 2779
2780 const noVariant = this.selectedVariant?.title === "Default Title"; 2781 const matchingItem = this.list?.listcontents.find(item => item.epi === this.selectedVariant?.id); 2782 const isAutoWishlist = this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SavedForYouItem 2783 && matchingItem?.source === "auto-wishlist"; 2784 const isFirstVariant = matchingItem?.productData?.variants?.[0]?.id === this.selectedVariant?.id; 2785 2786 let price = this.getFormattedPrice(); 2787 let comparePrice = this.getFormattedComparePrice(); 2788 2789 this.innerHTML = ` 2790 <div class="swym-storefront-layout-grid-item"> 2791 <a href="${localizedUrl}" class="swym-storefront-layout-grid-item-image-container"> 2792 <img src="${this.getProductImage()}" alt="${swymEscAttr(this.item?.productData?.title || this.item?.dt) || 'Product image'}" class="swym-storefront-layout-grid-item-image" /> 2793 </a> 2794 <div class="swym-storefront-layout-grid-item-content"> 2795 <a href="${localizedUrl}" class="swym-storefront-layout-grid-item-title">${this.item?.productData?.title || this.item?.dt || ''}</a> 2796 <div class="swym-storefront-layout-grid-item-price-variant"> 2797 ${!hidePrice ? `<span class="swym-storefront-layout-grid-item-final-price">${price}</span>` : ''} 2798 ${comparePrice && comparePrice !== price && !hidePrice && this.selectedVariant?.compare_at_price ? `<span class="swym-storefront-layout-grid-item-compare-price">${comparePrice}</span>`: ''} 2799${this.renderVariants()} 2800 </div> 2801 <div class="swym-storefront-layout-grid-item-action-container"> 2802 ${!isHidden && this.isSfsQuantityFeatureEnabled() ? `<div class="swym-storefront-layout-quantity-container"> 2803 <div class="swym-storefront-layout-quantity-selector"> 2804 <button type="button" class="swym-storefront-layout-quantity-btn swym-storefront-layout-quantity-minus" data-action="quantity-decrease" aria-label="${SwymStorefrontLayoutContext?.Strings?.decreaseQuantity || 'Decrease quantity'}">−</button> 2805 <input type="number" class="swym-storefront-layout-quantity-input" 2806 value="${this.item.qty || this.getMinQuantity()}" min="${this.getMinQuantity()}" ${this.getMaxQuantity() ? `max="${this.getMaxQuantity()}"` : ''} step="1" data-action="quantity-change"> 2807 <button type="button" class="swym-storefront-layout-quantity-btn swym-storefront-layout-quantity-plus" data-action="quantity-increase" aria-label="${SwymStorefrontLayoutContext?.Strings?.increaseQuantity || 'Increase quantity'}">+</button> 2808 </div> 2809 </div>` : ''} 2810 ${isAutoWishlist && isFirstVariant && !noVariant && !isHidden && !this._variantConfirmed ? 2811 `<button class="swym-variant-selector"> 2812 ${window.SwymStorefrontLayoutContext?.Strings?.VariantSelectorBtnText} 2813 <svg xmlns="http://www.w3.org/2000/svg" width="7" height="11" viewB
2813ox="0 0 7 11" fill="none"> 2814 <path d="M1 9.5L5.44444 5.5L1 1.5" stroke="currentColor" stroke-width="1.5"/> 2815 </svg> 2816 </button>` : ``} 2817 ${isAutoWishlist && !isHidden && ( 2818 !isFirstVariant || noVariant || (isFirstVariant && !noVariant && this._variantConfirmed) 2819 ) ? 2820 `<div class="swym-sfy-action-row"> 2821 <button class="swym-storefront-layout-grid-item-add-to-cart-button" data-action="add-to-cart" 2822 ${!this.selectedVariant?.available || isDisabled ? 'disabled' : ''}> 2823 ${this.getButtonState()} 2824 </button> 2825 <button class="swym-sfy-add-to-wishlist-btn ${SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer' ? 'swym-sfy-heart-btn' : ''}" data-action="add-to-wishlist" aria-label="${SwymStorefrontLayoutContext?.Strings?.sfyAddToWishlistCta || 'Add to Wishlist'}"> 2826 ${SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer' ? (SwymStorefrontLayoutExtensions?.getWishlistIconSvg?.('unfilled', { width: '17', height: '16' }) || '') : (SwymStorefrontLayoutContext?.Strings?.sfyAddToWishlistCta || 'Add to Wishlist')} 2827 </button> 2828 </div>` : "" 2829 } 2830 ${isAutoWishlist && !isHidden && isFirstVariant && !noVariant && !this._variantConfirmed ? 2831 `<button class="swym-sfy-add-to-wishlist-btn ${SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer' ? 'swym-sfy-heart-btn' : ''}" data-action="add-to-wishlist" aria-label="${SwymStorefrontLayoutContext?.Strings?.sfyAddToWishlistCta || 'Add to Wishlist'}"> 2832 ${SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer' ? (SwymStorefrontLayoutExtensions?.getWishlistIconSvg?.('unfilled', { width: '17', height: '16' }) || '') : (SwymStorefrontLayoutContext?.Strings?.sfyAddToWishlistCta || 'Add to Wishlist')} 2833 </button>` : "" 2834 } 2835 ${!isAutoWishlist && !isHidden ? 2836 `<button class="swym-storefront-layout-grid-item-add-to-cart-button" data-action="add-to-cart" 2837 ${!this.selectedVariant?.available || isDisabled ? 'disabled' : ''}> 2838 ${this.getButtonState()} 2839 </button>`: "" 2840 } 2841 </div> 2842 </div> 2843 ${this.renderOptionButton()} 2844 </div> 2845 `; 2846 2847 } 2848 2849 initItemElements(){ 2850 this.elements = { 2851 AddToCartButton: this.querySelector('[data-action="add-to-cart"]'), 2852 AddToWishlistButton: this.querySelector('[data-action="add-to-wishlist"]'), 2853 Image: this.querySelector('.swym-storefront-layout-grid-item-image'), 2854 Price: this.querySelector('.swym-storefront-layout-grid-item-final-price'), 2855 ComparePrice: this.querySelector('.swym-storefront-layout-grid-item-compare-price'), 2856 OptionButton: this.querySelector('.swym-storefront-layout-grid-item-option-button'), 2857 sflItemsContainer : document.querySelector(`swym-storefront-layout-sfl-container .swym-storefront-layout-items-container`), 2858 swymVariantSelector: this.querySelector('.swym-variant-selector'), 2859 QuantityInput: this.querySelector('.swym-storefront-layout-quantity-input'), 2860 QuantityMinusBtn: this.querySelector('[data-action="quantity-decrease"]'), 2861 QuantityPlusBtn: this.querySelector('[data-action="quantity-increase"]') 2862 } 2863 2864 // Initialize button states (only if SFS quantity feature is enabled) 2865 if (this.isSfsQuantityFeatureEnabled()) { 2866 this.updateQuantityButtonStates(); 2867 } 2868 } 2869 2870 addEventListeners() { 2871 // Re-entrant calls (e.g. after variant-selector confirmation) must tear down 2872 // the previous set of listeners first, otherwise they accumulate on every re-render. 2873 this._listenerController?.abort(); 2874 this._listenerController = new AbortController(); 2875 const { signal } = this._listenerController; 2876 2877 this.elements.AddToCartButton?.addEventListener('click', (event) => { 2878 if(this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SaveForLaterItem){ 2879 this.handleMoveToCart(event); 2880 }else{ 2881 this.handleAddToCart(event); 2882 } 2883 }, { signal }); 2884 this.elements.OptionButton?.addEventListener('click', (event) => { 2885 if(SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.isTooltipOpen && SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.currentTarget === event.target){ 2886 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.closeTooltip(); 2887 this.elements.OptionButton?.setAttribute('aria-expanded', 'false'); 2888 }else{ 2889 if(this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.WishlistItem || this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.CollectionItem){ 2890 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.setData({ 2891 listId: this.list.lid, 2892 list: this.list, 2893 item: this.item, 2894 selectedVariantId: this.selectedVariant?.id, 2895 collections: this.collections, 2896 actionType: SwymStorefrontLayoutContext?.ActionTypes.ManageListItem, 2897 canCreateLists: this.canCreateLists 2898 }); 2899 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.showOnTarget(event.target); 2900 }else if(this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SaveForLaterItem){ 2901 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.setData({ 2902 listId: this.sflList?.lid, 2903 list: this.sflList, 2904 item: this.item, 2905 selectedVariantId: this.selectedVariant?.id, 2906 collections: this.collections, 2907 actionType: SwymStorefrontLayoutContext?.ActionTypes.ManageSflListItem, 2908 canCreateLists: this.canCreateLists 2909 }); 2910 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.showOnTarget(event.target); 2911 }else if(this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SavedForYouItem){ 2912 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.setData({ 2913 listId: this.list?.lid, 2914 list: this.list, 2915 item: this.item, 2916 selectedVariantId: this.selectedVariant?.id, 2917 collections: this.collections, 2918 actionType: SwymStorefrontLayoutContext?.ActionTypes.ManageSfyListItem, 2919 canCreateLists: this.canCreateLists 2920 }); 2921 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.showOnTarget(event.target); 2922 } 2923 this.elements.OptionButton?.setAttribute('aria-expanded', 'true'); 2924 } 2925 }, { signal }); 2926 // Keyboard support for options button 2927 this.elements.OptionButton?.addEventListener('keydown', (e) => { 2928 if (e.key === 'Enter' || e.key === ' ') { 2929 e.preventDefault(); 2930 this.elements.OptionButton.click(); 2931 } 2932 }, { signal }); 2933 this.elements.AddToWishlistButton?.addEventListener('click', (event) => { 2934 event.preventDefault(); 2935 this.handleMoveToWishlist(); 2936 }, { signal }); 2937 this.elements.AddToWishlistButton?.addEventListener('keydown', (e) => { 2938 if (e.key === 'Enter' || e.key === ' ') { 2939 e.preventDefault(); 2940 this.handleMoveToWishlist(); 2941 } 2942 }, { signal }); 2943 const variantSelectors = this.querySelectorAll('.swym-storefront-layout-variant-selector'); 2944 variantSelectors?.forEach((selector) => 2945 selector.addEventListener('change', () => this.handleVariantSelectionChange(), { signal }) 2946 ); 2947 this.elements.swymVariantSelector?.addEventListener('click', (event) => { 2948 if (!window.SwymStorefrontLayoutExtensions) { 2949 window.SwymStorefrontLayoutExtensions = {}; 2950 } 2951 window.SwymStorefrontLayoutExtensions._vsActiveItem = this; 2952 2953 window._swat.evtLayer.dispatchEvent(new CustomEvent("sw:renderVS", { 2954 detail: { 2955 variant: this.selectedVariant, 2956 lid: this.item.lid, 2957 product: { 2958 epi: this.item.epi, 2959 empi: this.item.empi, 2960 dt: this.item.dt, 2961 variants: this.item.productData.variants, 2962 du: this.item?.['du-mkt'] || this.item.du, 2963 options: this.item.productData.options 2964 } 2965 } 2966 })) 2967 }, { signal }); 2968 2969 // Add quantity input event listeners (only if SFS quantity feature is enabled) 2970 if (this.isSfsQuantityFeatureEnabled()) { 2971 this.elements.QuantityInput?.addEventListener('change', (event) => { 2972 this.handleQuantityChange(event); 2973 }, { signal }); 2974 2975 this.elements.QuantityInput?.addEventListener('blur', (event) => { 2976 this.validateQuantityInput(event); 2977 }, { signal }); 2978 2979 // Add plus/minus button event listeners 2980 this.elements.QuantityMinusBtn?.addEventListener('click', (event) => { 2981 this.handleQuantityDecrease(event); 2982 }, { signal }); 2983
2984 this.elements.QuantityPlusBtn?.addEventListener('click', (event) => { 2985 this.handleQuantityIncrease(event); 2986 }, { signal }); 2987 } 2988 } 2989 2990 async handleDirectRemove() { 2991 try { 2992 const { epi, empi, du } = this.item; 2993 const lid = (this.list || this.sflList)?.lid; 2994 2995 if (this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SavedForYouItem) { 2996 // SFY item: optimistic UI update + API call (same pattern as addSfyOptionEventListeners) 2997 SwymStorefrontLayoutContext.updateAllListContents((listcontents = []) => 2998 listcontents.filter(item => String(item.epi) !== String(epi)) 2999 ); 3000 if (SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched) { 3001 document.dispatchEvent(new CustomEvent(SwymStorefrontLayoutContext.CustomEvents.WishlistFetched, { 3002 detail: { 3003 defaultList: SwymStorefrontLayoutContext.DefaultList, 3004 collections: SwymStorefrontLayoutContext.collections, 3005 lists: SwymStorefrontLayoutContext.lists 3006 } 3007 })); 3008 } 3009 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3010 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageItemRemoved, 3011 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Toast, 3012 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 3013 image: this.item?.iu 3014 }); 3015 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.removeProductFromList({ epi, empi, du }, lid); 3016 SwymStorefrontLayoutExtensions?.refreshWishList(); 3017 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFYItemRemoved, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi }); 3018 } else if (this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SaveForLaterItem) { 3019 // SFL item: use removeProductFromSFL (same pattern as addSflOptionEventListeners) 3020 let selectedEpi = this.selectedVariant?.id || epi; 3021 await SwymStorefrontLayoutAPI?.SwymSFLAsyncApis?.removeProductFromSFL({ epi: selectedEpi, empi, du }, SwymStorefrontLayoutContext?.sflListId); 3022 this.remove(); 3023 window._swat?.triggerSwymEvent(window._swat?.JSEvents?.removedFromSFL, this.item); 3024 } else { 3025 // Regular wishlist item: use same pattern as removeFromListBtn in addOptionEventListeners 3026 let selectedEpi = this.selectedVariant?.id || epi; 3027 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.removeProductFromList({ epi: selectedEpi, empi, du }, lid); 3028 this.remove(); 3029 SwymStorefrontLayoutExtensions?.refreshWishList(); 3030 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutItemRemovedFromList, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi: selectedEpi, empi }); 3031 } 3032 } catch (error) { 3033 window._swat?.utils.error('Failed to remove product:', error); 3034 } 3035 } 3036 3037 async handleMoveToWishlist() { 3038 try { 3039 const { empi, du } = this.item; 3040 const epi = this.selectedVariant?.id || this.item.epi; 3041 const lid = this.list?.lid; 3042 3043 // Check multi-wishlist config 3044 const { shouldShowMultiple, canCreateLists } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 3045 3046 if (shouldShowMultiple) { 3047 // Multi-list: open AddToCollection drawer so user picks the target list 3048 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActions?.setData({ 3049 listId: lid, 3050 list: this.list, 3051 item: this.item, 3052 collections: SwymStorefrontLayoutContext?.collections || [], 3053 actionType: SwymStorefrontLayoutContext?.ActionTypes.AddToCollection, 3054 canCreateLists: canCreateLists 3055 }); 3056 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActions?.openDrawer(); 3057 return; 3058 } 3059 3060 // Single list: move to default wishlist directly 3061 const atwBtn = this.querySelector('[data-action="add-to-wis
3061hlist"]'); 3062 if (atwBtn) { 3063 atwBtn.innerHTML = SwymStorefrontLayoutContext?.Strings?.addingToWishlist || 'Adding...'; 3064 atwBtn.setAttribute('disabled', 'true'); 3065 } 3066 3067 // Instant local state update across ALL context references 3068 SwymStorefrontLayoutContext.updateAllListContents((listcontents = []) => 3069 listcontents.map(item => 3070 String(item.epi) === String(epi) ? { ...item, source: undefined } : item 3071 ) 3072 ); 3073 3074 // Switch to Wishlist tab and update UI immediately 3075 SwymStorefrontLayoutContext.SelectedLayoutView = SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.Wishlist; 3076 if (SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched) { 3077 document.dispatchEvent(new CustomEvent(SwymStorefrontLayoutContext.CustomEvents.WishlistFetched, { 3078 detail: { 3079 defaultList: SwymStorefrontLayoutContext.DefaultList, 3080 collections: SwymStorefrontLayoutContext.collections, 3081 lists: SwymStorefrontLayoutContext.lists 3082 } 3083 })); 3084 } 3085 3086 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3087 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageItemSaved, 3088 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Toast, 3089 iconType: SwymStorefrontLayoutContext?.NotificationIconType?.SuccessIcon 3090 }); 3091 3092 // Fire API calls THEN refresh; suppress duplicate notifications from SDK event listeners 3093 SwymStorefrontLayoutContext._sfyMoveToWishlist = true; 3094 try { 3095 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.removeProductFromList({ epi, empi, du }, lid); 3096 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.addProductToLists({ epi, empi, du }, [lid]); 3097 } finally { 3098 SwymStorefrontLayoutContext._sfyMoveToWishlist = false; 3099 SwymStorefrontLayoutExtensions?.refreshWishList(); 3100 } 3101 3102 window._swat?.instrumentV3( 3103 SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFYItemMovedToWishlist, 3104 { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi } 3105 ); 3106 } catch (err) { 3107 window._swat?.utils.error('[SFY] Move to wishlist failed', err); 3108 } 3109 } 3110 3111 handleAddToCart(event) { 3112 event.preventDefault(); 3113 let button = event.target; 3114 3115 let { empi, epi, du } = this.dataset; 3116 let quantity = this.getCurrentQuantity(); 3117 3118 let originalButtonState = button.innerHTML; 3119 button.innerHTML = SwymStorefrontLayoutContext?.Strings?.addingToCart; 3120 button.setAttribute('disabled', true); 3121 3122 _swat.api.replayAddToCart( 3123 { empi: +empi, du: du, qty: quantity }, 3124 +epi, 3125 (success) => { 3126 button.innerHTML = SwymStorefrontLayoutContext?.Strings?.addedToCart; 3127 button.removeAttribute('disabled'); 3128 3129 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3130 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageAddedToCart, 3131 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 3132 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes.AddedToCart, 3133 image: this.item.iu, 3134 product: this.item, 3135 title: this.item?.productData?.title || this.item?.dt, 3136 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 3137 action: { 3138 label: SwymStorefrontLayoutContext?.Strings?.notificationActionGoToCart, 3139 onClick: () => { 3140 window.location.href = `${window.Shopify.routes.root}cart`; 3141 } 3142 } 3143 }, { 3144 showActionButton: true, 3145 showTitle: true 3146 }); 3147 3148 if (this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SavedForYouItem) { 3149 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFYItemAddedToCart, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutCartConversion, epi, empi }); 3150 } else { 3151 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutItemAddedToCart, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutCartConversion, epi, empi }); 3152 } 3153 window?._SwymAJAXCart(); 3154 }, 3155 (error) => { 3156 button.innerHTML = originalButtonState; 3157 button.removeAttribute('disabled'); 3158 window._swat?.utils.warn('Error :', error.description); 3159 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3160 title: error.description, 3161 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Error 3162 },{ 3163 showTitle: true, 3164 showImage: false 3165 }); 3166 } 3167 ); 3168 } 3169 3170 handleMoveToCart(event) { 3171 event.preventDefault(); 3172 let button = event.target; 3173 3174 let { empi, epi, du } = this.dataset; 3175 let quantity = this.getCurrentQuantity(); 3176 3177 let originalButtonState = button.innerHTML; 3178 button.innerHTML = SwymStorefrontLayoutContext?.Strings?.movingToCartCta; 3179 button.setAttribute('disabled', true); 3180 3181 window._swat?.api.replayAddToCart( 3182 { empi: +empi, du: du, qty: quantity }, 3183 +epi, 3184 async (success) => { 3185 try{ 3186 await SwymStorefrontLayoutAPI?.SwymSFLAsyncApis?.removeProductFromSFL({ epi, empi, du }, SwymStorefrontLayoutContext.sflListId); 3187 let elementDeleted = this.elements.sflItemsContainer?.querySelector(`[data-epi="${epi}"]`); 3188 elementDeleted?.remove(); 3189 window._swat?.triggerSwymEvent(window._swat?.JSEvents?.movedToCartFromSFL, this.item); 3190 3191 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3192 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageMovedToCart, 3193 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 3194 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes.AddedToCart, 3195 image: this.item.iu, 3196 product: this.item, 3197 title: this.item?.productData?.title || this.item?.dt, 3198 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 3199 action: { 3200 label: SwymStorefrontLayoutContext?.Strings?.notificationActionGoToCart, 3201 onClick: () => { 3202 window.location.href = `${window.Shopify.routes.root}cart`; 3203 } 3204 } 3205 }, { 3206 showActionButton: true, 3207 showTitle: true 3208 }); 3209 3210 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFLItemMovedToCart, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutCartConversion, epi, empi }); 3211 window?._SwymAJAXCart(); 3212 }catch(error){ 3213 button.innerHTML = originalButtonState; 3214 button.removeAttribute('disabled'); 3215 window._swat?.utils.warn('Error removing SFL Items:', error); 3216 } 3217 3218 }, 3219 (error) => { 3220 button.innerHTML = originalButtonState; 3221 button.removeAttribute('disabled'); 3222 window._swat?.utils.warn('Error :', error.description); 3223 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3224 title: error.description, 3225 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Error 3226 },{ 3227 showTitle: true, 3228 showImage: false 3229 }); 3230 } 3231 ); 3232 } 3233 3234 async handleVariantSelectionChange() { 3235 3236 let selectedOptions = {}; 3237 const variantSelectors = this.querySelectorAll(".swym-storefront-layout-variant-selector"); 3238
3239 variantSelectors.forEach((selector) => { 3240 const selectedValue = selector.value?.trim(); 3241 const optionIndex = selector.dataset.optionindex; 3242 3243 if (selectedValue && optionIndex) { 3244 selectedOptions[`option${optionIndex}`] = selectedValue; 3245 } 3246 }); 3247 3248 const selectedVariant = this.item.productData.variants.find((variant) => 3249 Object.entries(selectedOptions).every(([key, value]) => variant[key] === value) 3250 ); 3251 3252 this.selectedVariant = selectedVariant; 3253 this.dataset.epi = selectedVariant.id; 3254 this.item.cprops = this.item.cprops ?? {}; 3255 this.item.cprops.selectedVariantId = selectedVariant.id; 3256 3257 if (selectedVariant) { 3258 const buttonText = this.getButtonState(); 3259 this.elements.AddToCartButton.textContent = buttonText; 3260 // The button relies on its text content for its accessible name 3261 if (selectedVariant.available) { 3262 this.elements.AddToCartButton.removeAttribute('disabled'); 3263 } else { 3264 this.elements.AddToCartButton.setAttribute('disabled', true); 3265 } 3266 3267 this.elements.Price.textContent =this.getFormattedPrice(); 3268 this.elements.ComparePrice.textContent = this.getFormattedComparePrice(); 3269 3270 const productImage = selectedVariant?.featured_image?.src || this.item.productData?.featured_image || item.iu; 3271 this.elements.Image.src = productImage; 3272 3273 let canEdit = (this.list?.userinfo?.em === SwymStorefrontLayoutContext?.SwymCustomerData?.email) || this.list?.canEdit; 3274 3275 if(SwymStorefrontLayoutContext?.checkUserCanEditList(this.list)){ 3276 let { epi, empi, du } = this.item; 3277 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutVariantChanged, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi }); 3278 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.updateProductFromList({ epi, empi, du, cprops: { selectedVariantId: selectedVariant.id } }, this.list.lid); 3279 } 3280 } else { 3281 window._swat?.utils.warn("No matching variant found for the selected options:", selectedOptions); 3282 } 3283 } 3284 3285 validateQuantityInput(event) { 3286 if (!this.isSfsQuantityFeatureEnabled()) return; 3287 3288 const input = event.target; 3289 let value = parseInt(input.value); 3290 const minQty = this.getMinQuantity(); 3291 const maxQty = this.getMaxQuantity(); 3292 const quantitySelector = input.closest('.swym-storefront-layout-quantity-selector'); 3293 3294 let hasError = false; 3295 3296 // Check if value is within bounds 3297 if (isNaN(value) || value < minQty || (maxQty && value > maxQty)) { 3298 hasError = true; 3299 } 3300 3301 // Add or remove error styling 3302 if (quantitySelector) { 3303 if (hasError) { 3304 quantitySelector.classList.add('swym-quantity-error'); 3305 // Don't auto-correct the value, keep the invalid value visible 3306 3307 // Show error toast notification 3308 const minQty = this.getMinQuantity(); 3309 const maxQty = this.getMaxQuantity(); 3310 let errorMessage = ''; 3311 3312 if (isNaN(value) || value < minQty) { 3313 errorMessage = `Minimum allowed quantity is ${minQty}`; 3314 } else if (maxQty && value > maxQty) { 3315 errorMessage = `Maximum allowed quantity is ${maxQty}`; 3316 } 3317 3318 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3319 title: errorMessage, 3320 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Error, 3321 duration: 3000 3322 }, { 3323 showTitle: true, 3324 showImage: false 3325 }); 3326 } else { 3327 quantitySelector.classList.remove('swym-quantity-error'); 3328 // Only update the item quantity if it's valid 3329 this.item.qty = value; 3330 // Debounce the backend update for valid values 3331 this.debouncedUpdateItemQuantity(value); 3332 } 3333 } 3334 3335 // Update button states 3336 this.updateQuantityButtonStates(); 3337 } 3338 3339 handleQuantityChange(event) { 3340 if (!this.isSfsQuantityFeatureEnabled()) return; 3341 3342 const input = event.target; 3343 const newQuantity = parseInt(input.value); 3344 const minQty = this.getMinQuantity(); 3345 const maxQty = this.getMaxQuantity(); 3346 3347 // Only update if the quantity is valid 3348 if (!isNaN(newQuantity) && newQuantity >= minQty && (!maxQty || newQuantity <= maxQty)) { 3349 // Update the item's quantity 3350 this.item.qty = newQuantity; 3351 3352 // Debounce the backend update 3353 this.debouncedUpdateItemQuantity(newQuantity); 3354 } 3355 3356 // Always update button states 3357 this.updateQuantityButtonStates(); 3358 } 3359 3360 async updateItemQuantity(quantity) { 3361 try { 3362 const minQty = this.getMinQuantity(); 3363 const maxQty = this.getMaxQuantity(); 3364 3365 // Validate quantity before updating 3366 if (quantity < minQty || (maxQty && quantity > maxQty)) { 3367 window._swat?.utils.warn(`Quantity ${quantity} is outside allowed range (${minQty}${maxQty ? `-${maxQty}` : '+'})`); 3368 return; 3369 } 3370 3371 if (this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.WishlistItem || 3372 this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.CollectionItem) { 3373 3374 // Update wishlist item quantity 3375 const updatedItem = { epi: this.item?.epi, empi: this.item?.empi, du: this.item?.du, qty: quantity }; 3376 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.updateProductFromList(updatedItem, this.list.lid); 3377 3378 } else if (this.listItemType === SwymStorefrontLayoutContext?.ListItemType?.SaveForLaterItem) { 3379 3380 // Update SFL item quantity 3381 const updatedItem = { epi: this.item?.epi, empi: this.item?.empi, du: this.item?.du, qty: quantity }; 3382 await SwymStorefrontLayoutAPI?.SwymSFLAsyncApis?.updateProductInSFL(updatedItem, SwymStorefrontLayoutContext.sflListId); 3383 3384 } 3385 } catch (error) { 3386 window._swat?.utils.warn('Error updating item quantity:', error); 3387 // Revert the input value on error 3388 if (this.elements.QuantityInput) { 3389 this.elements.QuantityInput.value = this.item.qty || this.getMinQuantity(); 3390 } 3391 } 3392 } 3393 3394 getCurrentQuantity() { 3395 if (!this.isSfsQuantityFeatureEnabled()) { 3396 return this.item.qty || 1; 3397 } 3398 return parseInt(this.elements.QuantityInput?.value) || this.item.qty || 1; 3399 } 3400 3401 debouncedUpdateItemQuantity(quantity) { 3402 // Clear any existing timeout 3403 if (this.quantityUpdateTimeout) { 3404 clearTimeout(this.quantityUpdateTimeout); 3405 } 3406 3407 // Set a new timeout for 500ms 3408 this.quantityUpdateTimeout = setTimeout(() => { 3409 this.updateItemQuantity(quantity); 3410 }, 500); 3411 } 3412 3413 isSfsQuantityFeatureEnabled() { 3414 try { 3415 const retailerSettings = window._swat?.retailerSettings; 3416 if (!retailerSettings) return false;
3417 3418 // Check if WishlistPlusPDPButtonAPI is available and use its SFS check 3419 if (typeof window.WishlistPlusPDPButtonAPI?.isSfsFeatureEnabled === 'function') { 3420 return window.WishlistPlusPDPButtonAPI.isSfsFeatureEnabled('WishlistQuantity', retailerSettings); 3421 } 3422 3423 // Fallback: Check if SFS/EngageUI is enabled 3424 const SFSEnabled = retailerSettings?.General?.EngageUI?.Enabled; 3425 const features = retailerSettings?.General?.EngageUI?.Features; 3426 const quantityFeatureEnabled = features?.some( 3427 (feature) => feature?.featureName === 'WishlistQuantity' && feature?.enabled === true 3428 ); 3429 3430 return SFSEnabled && quantityFeatureEnabled; 3431 } catch (error) { 3432 window._swat?.utils.warn('Error checking SFS quantity feature:', error); 3433 return false; 3434 } 3435 } 3436 3437 updateQuantityButtonStates() { 3438 if (!this.isSfsQuantityFeatureEnabled()) return; 3439 3440 const currentQty = this.getCurrentQuantity(); 3441 const minQty = this.getMinQuantity(); 3442 const maxQty = this.getMaxQuantity(); 3443 const quantitySelector = this.elements.QuantityInput?.closest('.swym-storefront-layout-quantity-selector'); 3444 const hasError = quantitySelector?.classList.contains('swym-quantity-error'); 3445 3446 // Update minus button state 3447 if (this.elements.QuantityMinusBtn) { 3448 if (hasError || currentQty <= minQty) { 3449 this.elements.QuantityMinusBtn.disabled = true; 3450 } else { 3451 this.elements.QuantityMinusBtn.disabled = false; 3452 } 3453 } 3454 3455 // Update plus button state 3456 if (this.elements.QuantityPlusBtn) { 3457 if (hasError || (maxQty && currentQty >= maxQty)) { 3458 this.elements.QuantityPlusBtn.disabled = true; 3459 } else { 3460 this.elements.QuantityPlusBtn.disabled = false; 3461 } 3462 } 3463 } 3464 3465 handleQuantityDecrease(event) { 3466 if (!this.isSfsQuantityFeatureEnabled()) return; 3467 3468 event.preventDefault(); 3469 const currentQty = this.getCurrentQuantity(); 3470 const minQty = this.getMinQuantity(); 3471 if (currentQty > minQty) { 3472 const newQty = currentQty - 1; 3473 this.elements.QuantityInput.value = newQty; 3474 3475 // Update the item's quantity immediately for UI responsiveness 3476 this.item.qty = newQty; 3477 3478 // Update button states immediately 3479 this.updateQuantityButtonStates(); 3480 3481 // Debounce the backend update 3482 this.debouncedUpdateItemQuantity(newQty); 3483 } 3484 } 3485 3486 handleQuantityIncrease(event) { 3487 if (!this.isSfsQuantityFeatureEnabled()) return; 3488 3489 event.preventDefault(); 3490 const currentQty = this.getCurrentQuantity(); 3491 const maxQty = this.getMaxQuantity(); 3492 if (!maxQty || currentQty < maxQty) { 3493 const newQty = currentQty + 1; 3494 this.elements.QuantityInput.value = newQty; 3495 3496 // Update the item's quantity immediately for UI responsiveness 3497 this.item.qty = newQty; 3498 3499 // Update button states immediately 3500 this.updateQuantityButtonStates(); 3501 3502 // Debounce the backend update 3503 this.debouncedUpdateItemQuantity(newQty); 3504 } 3505 } 3506 3507} 3508 3509/** 3510 * SwymStorefrontLayoutDefaultWishList 3511 * 3512 * This custom HTML element represents the default wishlist layout in the storefront. 3513 * It handles rendering wishlist items dynamically and updating the UI when new wishlist data is set. 3514 * 3515 * Features: 3516 * - Initializes with a loading state until wishlist data is available. 3517 * - Fetches product details for wishlist items and updates the UI. 3518 * - Displays an empty state when no wishlist items exist. 3519 * - Uses Swym API and instrumentation to track interactions. 3520 * 3521 * Usage: 3522 * <swym-storefront-layout-default-wishlist></swym-storefront-layout-default-wishlist> 3523 */ 3524class SwymStorefrontLayoutDefaultWishList extends HTMLElement { 3525 constructor() { 3526 super(); 3527 this.wishlist = null; 3528 this.collections = null; 3529 this.renderUI(); 3530 this.elements = { 3531 title: this.querySelector('.swym-storefront-layout-default-list-title'), 3532 itemsContainer: this.querySelector('#swym-storefront-layout-items-container') 3533 } 3534 } 3535 3536 handleAddToCart(event, productDetail) { 3537 event.preventDefault(); 3538 let button = event.target; 3539 3540 let empi = productDetail.empi; //8445423517851; 3541 let epi = productDetail.epi; //46212304076955; 3542 let du = productDetail.du; //"https://swym-test-onboarding-2-0.myshopify.com/products/the-collection-snowboard-hydrogen" 3543 3544 let originalButtonState = button.innerHTML; 3545 button.innerHTML = SwymStorefrontLayoutContext?.Strings?.addingToCart;
3546 button.setAttribute('disabled', true); 3547 3548 _swat.api.replayAddToCart( 3549 { empi: +empi, du: du, qty: 1 }, 3550 +epi, 3551 (success) => { 3552 button.innerHTML = SwymStorefrontLayoutContext?.Strings?.addedToCart; 3553 button.removeAttribute('disabled'); 3554 3555 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3556 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageAddedToCart, 3557 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 3558 actionType: SwymStorefrontLayoutContext?.NotificationActionTypes.AddedToCart, 3559 image: "", 3560 product: "", 3561 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 3562 action: { 3563 label: SwymStorefrontLayoutContext?.Strings?.notificationActionGoToCart, 3564 onClick: () => { 3565 window.location.href = `${window.Shopify.routes.root}cart`; 3566 } 3567 } 3568 }, { 3569 showActionButton: true, 3570 showTitle: true 3571 }); 3572 }, 3573 (error) => { 3574 button.innerHTML = originalButtonState; 3575 button.removeAttribute('disabled'); 3576 window._swat?.utils.warn('Error :', error.description); 3577 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 3578 title: error.description, 3579 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Error 3580 },{ 3581 showTitle: true, 3582 showImage: false 3583 }); 3584 } 3585 ); 3586 } 3587 3588 renderUI() { 3589 if(window.Shopify.designMode && !window.SwymCurrentStorePath) { 3590 const swymMockProductData = window.SwymSampleProduct; 3591 3592 // Extract and clean up the IDs 3593 const swymMockProductId = swymMockProductData?.id?.replace('gid://shopify/Product/', ''); 3594 const swymMockVariantId = swymMockProductData?.variants?.edges?.[0]?.node?.id?.replace('gid://shopify/ProductVariant/', ''); 3595 3596 // Construct the product URL using Shopify.shop 3597 const swymMockProductUrl = `https://${window.Shopify.shop}/products/${swymMockProductData?.handle}`; 3598 3599 window.SwymParsedSampleProduct = { 3600 image: swymMockProductData?.variants?.edges?.[0]?.node?.image, 3601 empi: swymMockProductId, 3602 epi: swymMockVariantId, 3603 du: swymMockProductUrl, 3604 title: swymMockProductData?.variants?.edges?.[0]?.node?.title, 3605 price: `$${swymMockProductData?.variants?.edges?.[0]?.node?.price}` 3606 }; 3607 3608 this.innerHTML = `<div id="swym-storefront-layout-items-container" class="swym-storefront-layout-items-container"><div class="swym-storefront-layout-grid-item"> 3609 <a href="#" class="swym-storefront-layout-grid-item-image-container"> 3610 <img src="${window.SwymParsedSampleProduct?.image}" alt="${window.SwymParsedSampleProduct?.title ? window.SwymParsedSampleProduct.title : 'Product image'}" class="swym-storefront-layout-grid-item-image" /> 3611 </a> 3612 <div class="swym-storefront-layout-grid-item-content"> 3613 <a href="${window.SwymParsedSampleProduct?.du}" class="swym-storefront-layout-grid-item-title">${window.SwymParsedSampleProduct.title || ''}</a> 3614 <div class="swym-storefront-layout-grid-item-price-variant"> 3615 <span class="swym-storefront-layout-grid-item-final-price">${window.SwymParsedSampleProduct?.price}</span> 3616 </div> 3617 <div class="swym-storefront-layout-grid-item-action-container"> 3618 <button class="swym-storefront-layout-grid-item-add-to-cart-button" data-action="add-to-cart"> 3619 ${SwymStorefrontLayoutContext?.Strings?.addToCart || 'Add to cart'} 3620 </button> 3621 </div> 3622 </div> 3623 </div></div>` 3624 3625 const addToCartBtn = this.querySelector(".swym-storefront-layout-grid-item-add-to-cart-button"); 3626 if (addToCartBtn) { 3627 addToCartBtn.addEventListener("click", (e) => { 3628 this.handleAddToCart(e, window.SwymParsedSampleProduct) 3629 }); 3630 } 3631 return; 3632 } 3633 3634 const { shouldShowMultiple, canCreateLists } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 3635 3636 this.innerHTML = ` 3637 <div class="swym-storefront-layout-default-list-container"> 3638 ${shouldShowMultiple ? ` 3639 <div class="swym-storefront-layout-default-list-title-container"> 3640 <div class="swym-storefront-layout-default-list-title">${SwymStorefrontLayoutContext?.Strings?.wishlistTitle}</div> 3641 <div class="swym-storefront-layout-default-list-info">${SwymStorefrontLayoutContext?.Strings?.wishlistInfo}</div> 3642 </div> 3643 ` : ''} 3644 <div class="swym-storefront-layout-add-all-to-cart-container swym-storefront-layout-add-all-to-cart-top"></div> 3645 <div id="swym-storefront-layout-items-container" class="swym-storefront-layout-items-container"> 3646 <swym-storefront-layout-loader loading="true" loadertype="${SwymStorefrontLayoutContext?.LoaderViewType.WishlistListItem}" buttonCta="${SwymStorefrontLayoutContext?.Strings?.addToCart}" height="100px"></swym-storefront-layout-loader> 3647 </div> 3648 <div class="swym-storefront-layout-add-all-to-cart-container swym-storefront-layout-add-all-to-cart-bottom"></div> 3649 </div> 3650 ` 3651 } 3652 3653 setData({ wishlist, collections, canCreateLists = true }) { 3654 this.wishlist = wishlist; 3655 this.collections = collections; 3656 this.canCreateLists = canCreateLists; 3657 if (this.elements?.itemsContainer?.dataset && this.wishlist?.lid) { 3658 this.elements.itemsContainer.dataset.lid = this.wishlist.lid; 3659 } 3660 this.renderWishlist(); 3661 } 3662 3663 async renderWishlist() { 3664 SwymStorefrontLayoutExtensions?.AddAllToCart?.mount(this, this.wishlist); 3665 if (this.wishlist?.listcontents?.length && this.elements.itemsContainer) { 3666 3667 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistItemsRendered, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 3668 3669 const wishlistedProducts = this.wishlist?.listcontents; 3670 const fragment = document.createDocumentFragment(); 3671
3672 wishlistedProducts.forEach((item) => { 3673 const wishlistItem = document.createElement('template'); 3674 wishlistItem.innerHTML = `<swym-storefront-layout-item id="swym-storefront-layout-item-${item.empi}-${item.epi}" ></swym-storefront-layout-item>`; 3675 fragment.appendChild(wishlistItem.content); 3676 }); 3677 3678 this.elements.itemsContainer.innerHTML = ''; 3679 this.elements.itemsContainer.appendChild(fragment); 3680 3681 wishlistedProducts.forEach((item) => { 3682 let wishlistItemElement = document.querySelector(`#swym-storefront-layout-item-${item.empi}-${item.epi}`); 3683 wishlistItemElement?.setData({ 3684 item, 3685 listItemType: SwymStorefrontLayoutContext?.ListItemType?.WishlistItem, 3686 list: this.wishlist, 3687 collections: this.collections, 3688 canCreateLists: this.canCreateLists 3689 }) 3690 }); 3691 this.elements.itemsContainer.classList.remove('swym-storefront-layout-items-has-empty'); 3692 }else{ 3693 const isLoggedIn = SwymStorefrontLayoutContext?.isShopperLoggedIn; 3694 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 3695 const rootUrl = (window.Shopify?.routes?.root || '/').replace(/\/$/, ''); 3696 const accountUrl = `${rootUrl}/account`; 3697 const homeUrl = window.Shopify?.routes?.root || '/'; 3698 3699 let emptyStateHtml = ''; 3700 let noticeText = ''; 3701 3702 if (!shouldShowMultiple && isLoggedIn) { 3703 // State 1: Single wishlist – Logged in (new rich design) 3704 emptyStateHtml = ` 3705 <div class="swym-storefront-layout-empty-container swym-storefront-layout-empty-state-single-logged"> 3706 <div class="swym-storefront-layout-empty-wishlist-container"> 3707 <div class="swym-storefront-layout-empty-wishlist-content"> 3708 <div class="swym-storefront-layout-empty-state-icon"> 3709 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none"> 3710 <circle cx="24" cy="24" r="24" fill="#F5F5F5"/> 3711 <path d="M20 18H18C17.4696 18 16.9609 18.2107 16.5858 18.5858C16.2107 18.9609 16 19.4696 16 20V32C16 32.5304 16.2107 33.0391 16.5858 33.4142C16.9609 33.7893 17.4696 34 18 34H30C30.5304 34 31.0391 33.7893 31.4142 33.4142C31.7893 33.0391 32 32.5304 32 32V20C32 19.4696 31.7893 18.9609 31.4142 18.5858C31.0391 18.2107 30.5304 18 30 18H28M20 18V16C20 15.4696 20.2107 14.9609 20.5858 14.5858C20.9609 14.2107 21.4696 14 22 14H26C26.5304 14 27.0391 14.2107 27.4142 14.5858C27.7893 14.9609 28 15.4696 28 16V18M20 18H28M24 24V28M22 26H26" stroke="#9B9B9B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 3712 </svg> 3713 <span class="swym-storefront-layout-empty-state-badge"> 3714 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none"> 3715 <circle cx="7" cy="7" r="7" fill="#E8A0BF"/> 3716 <path d="M7 4V10M4 7H10" stroke="white" stroke-width="1.5" stroke-linecap="round"/> 3717 </svg> 3718 </span> 3719 </div> 3720 <div class="swym-storefront-layout-empty-wishlist-title">${SwymStorefrontLayoutContext?.Strings?.emptySingleLoggedInTitle || ''}</div> 3721 <div class="swym-storefront-layout-empty-wishlist-description">${SwymStorefrontLayoutContext?.Strings?.emptySingleLoggedInDescription || ''}</div> 3722 <div class="swym-storefront-layout-empty-state-card"> 3723 <ul class="swym-storefront-layout-empty-state-bullet-list"> 3724 <li>${SwymStorefrontLayoutContext?.Strings?.emptySingleLoggedInBullet1 || ''}</li> 3725 <li>${SwymStorefrontLayoutContext?.Strings?.emptySingleLoggedInBullet2 || ''}</li> 3726 <li>${SwymStorefrontLayoutContext?.Strings?.emptySingleLoggedInBullet3 || ''}</li> 3727 </ul> 3728 </div> 3729 <hr class="swym-storefront-layout-empty-state-divider" /> 3730 <a href="${homeUrl}" class="swym-storefront-layout-empty-state-cta-button swym-storefront-layout-empty-state-start-browsing"> 3731 ${SwymStorefrontLayoutContext?.Strings?.emptySingleLoggedInCTA || ''} 3732 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9" fill="none"><path d="M13.8536 4.85368C14.0488 4.65841 14.0488 4.34183 13.8536 4.14657L10.6716 0.964589C10.4763 0.769327 10.1597 0.769327 9.96447 0.964589C9.7692 1.15985 9.7692 1.47643 9.96447 1.6717L12.7929 4.50012L9.96447 7.32855C9.7692 7.52381 9.7692 7.84039 9.96447 8.03566C10.1597 8.23092 10.4763 8.23092 10.6716 8.03566L13.8536 4.85368ZM0.5 5.00012L13.5 5.00012L13.5 4.00012L0.5 4.00012L0.5 5.00012Z" fill="currentColor"/></svg> 3733 </a> 3734 </div> 3735 </div> 3736 </div> 3737 `; 3738 } else if (!shouldShowMultiple && !isLoggedIn) { 3739 // State 2: Single wishlist – Guest (new rich design) 3740 noticeText = SwymStorefrontLayoutContext?.Strings?.emptyGuestSingleNotice || ''; 3741 3742 emptyStateHtml = ` 3743 <div class="swym-storefront-layout-empty-container swym-storefront-layout-empty-state-guest"> 3744 <div class="swym-storefront-layout-empty-wishlist-container"> 3745 <div class="swym-storefront-layout-empty-wishlist-content"> 3746 <div class="swym-storefront-layout-empty-state-icon"> 3747 <svg xmlns="http://www.w3.org/2000/svg" width="56" height="56" viewB
3747ox="0 0 56 56" fill="none"> 3748 <circle cx="28" cy="28" r="28" fill="#F5F3FF"/> 3749 <path d="M28 38L17.55 28.03C16.6264 27.0846 16.0453 25.8579 15.8955 24.5427C15.7457 23.2275 16.0356 21.8986 16.72 20.766C17.4045 19.6334 18.4445 18.7606 19.6772 18.2842C20.9099 17.8079 22.2674 17.7551 23.533 18.134C24.468 18.4101 25.3178 18.916 26.005 19.604L28 21.6L29.995 19.604C30.6822 18.916 31.532 18.4101 32.467 18.134C33.7326 17.7551 35.0901 17.8079 36.3228 18.2842C37.5555 18.7606 38.5955 19.6334 39.28 20.766C39.9644 21.8986 40.2543 23.2275 40.1045 24.5427C39.9547 25.8579 39.3736 27.0846 38.45 28.03L28 38Z" stroke="#8B7EC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="#C8B8F0"/> 3750 </svg> 3751 </div> 3752 <div class="swym-storefront-layout-empty-wishlist-title">${SwymStorefrontLayoutContext?.Strings?.emptyGuestTitle || ''}</div> 3753 <div class="swym-storefront-layout-empty-wishlist-description">${SwymStorefrontLayoutContext?.Strings?.emptyGuestDescription || ''}</div> 3754 <div class="swym-storefront-layout-empty-state-notice"> 3755 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"> 3756 <circle cx="9" cy="9" r="8" stroke="#B45309" stroke-width="1.2"/> 3757 <path d="M9 5V9L11 11" stroke="#B45309" stroke-width="1.2" stroke-linecap="round"/> 3758 </svg> 3759 <span class="swym-storefront-layout-empty-state-notice-text"></span> 3760 </div> 3761 <hr class="swym-storefront-layout-empty-state-divider" /> 3762 <a href="${accountUrl}" class="swym-storefront-layout-empty-state-cta-button swym-storefront-layout-empty-state-login-cta"> 3763 ${SwymStorefrontLayoutContext?.Strings?.emptyGuestLoginCTA || ''} 3764 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9" fill="none"><path d="M13.8536 4.85368C14.0488 4.65841 14.0488 4.34183 13.8536 4.14657L10.6716 0.964589C10.4763 0.769327 10.1597 0.769327 9.96447 0.964589C9.7692 1.15985 9.7692 1.47643 9.96447 1.6717L12.7929 4.50012L9.96447 7.32855C9.7692 7.52381 9.7692 7.84039 9.96447 8.03566C10.1597 8.23092 10.4763 8.23092 10.6716 8.03566L13.8536 4.85368ZM0.5 5.00012L13.5 5.00012L13.5 4.00012L0.5 4.00012L0.5 5.00012Z" fill="currentColor"/></svg> 3765 </a> 3766 <button class="swym-storefront-layout-empty-state-cta-button swym-storefront-layout-empty-state-browse-cta" data-action="close-drawer"> 3767 ${SwymStorefrontLayoutContext?.Strings?.emptyGuestBrowseCTA || ''} 3768 </button> 3769 <div class="swym-storefront-layout-empty-state-signup-text"> 3770 ${SwymStorefrontLayoutContext?.Strings?.emptyGuestSignupText || ''} <a href="${accountUrl}" class="swym-storefront-layout-empty-state-signup-link">${SwymStorefrontLayoutContext?.Strings?.emptyGuestSignupCTA || ''}</a> 3771 </div> 3772 </div> 3773 </div> 3774 </div> 3775 `; 3776 } else { 3777 // Multi wishlist – old simple empty state (logged in or guest) 3778 emptyStateHtml = ` 3779 <div class="swym-storefront-layout-empty-container"> 3780 <div class="swym-storefront-layout-empty-wishlist-container"> 3781 <div class="swym-storefront-layout-empty-wishlist-content"> 3782 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="86" height="85" viewBox="0 0 86 85" fill="none"> 3783 <rect width="59.5441" height="78.6833" transform="translate(0.710449 6.1748) rotate(-5.46887)" fill="white"/> 3784 <rect x="0.710938" y="6.1748" width="59.5441" height="78.6833" rx="1.21519" transform="rotate(-5.46887 0.710938 6.1748)" fill="#FDFDFD"/> 3785 <rect x="0.983626" y="6.39984" width="59.0441" height="78.1833" rx="0.965186" transform="rotate(-5.46887 0.983626 6.39984)" stroke="black" stroke-opacity="0.1" stroke-width="0.5"/> 3786 <rect x="5.57364" y="10.7446" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 5.57364 10.7446)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/>
3787 <rect x="13.0841" y="11.0938" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 13.0841 11.0938)" fill="#D0D0D0"/> 3788 <rect x="6.44223" y="19.8169" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 6.44223 19.8169)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 3789 <rect x="13.9527" y="20.166" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 13.9527 20.166)" fill="#D0D0D0"/> 3790 <rect x="7.31107" y="28.8892" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 7.31107 28.8892)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 3791 <rect x="14.8214" y="29.2383" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 14.8214 29.2383)" fill="#D0D0D0"/> 3792 <rect x="8.17942" y="37.9619" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 8.17942 37.9619)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 3793 <rect x="15.6899" y="38.311" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 15.6899 38.311)" fill="#D0D0D0"/> 3794 <rect x="9.04825" y="47.0342" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 9.04825 47.0342)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 3795 <rect x="16.5587" y="47.3833" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 16.5587 47.3833)" fill="#D0D0D0"/> 3796 <rect x="9.91666" y="56.1069" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 9.91666 56.1069)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 3797 <rect x="17.4271" y="56.4561" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 17.4271 56.4561)" fill="#D0D0D0"/> 3798 <rect x="10.7854" y="65.1792" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 10.7854 65.1792)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 3799 <rect x="18.2959" y="65.5283" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 18.2959 65.5283)" fill="#D0D0D0"/> 3800 <rect x="11.6538" y="74.2515" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 11.6538 74.2515)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 3801 <rect x="19.1643" y="74.6006" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 19.1643 74.6006)" fill="#D0D0D0"/> 3802 <rect x="55.0288" y="35.834" width="31.0853" height="31.0853" transform="rotate(13.2248 55.0288 35.834)" fill="url(#pattern0_489_1797)"/> 3803 <defs> 3804 <pattern id="pattern0_489_1797" patternContentUnits="objectBoundingBox" width="1" height="1"> 3805 <use xlink:href="#image0_489_1797" transform="scale(0.0104167)"/> 3806 </pattern> 3807 <image id="image0_489_1797" width="96" height="96" xlink:href="data:image/png;base64,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"/> 3808 </defs> 3809 </svg> 3810 <div class="swym-storefront-layout-empty-wishlist-title">${SwymStorefrontLayoutContext?.Strings?.emptyWishlistTitle}</div> 3811 <div class="swym-storefront-layout-empty-wishlist-description">${SwymStorefrontLayoutContext?.Strings?.emptyWishlistDescription}</div> 3812 </div> 3813 </div> 3814 </div> 3815 `; 3816 } 3817 3818 this.elements.itemsContainer.innerHTML = emptyStateHtml; 3819 this.elements.itemsContainer.classList.add('swym-storefront-layout-items-has-empty'); 3820 3821 // Safely set notice text as innerHTML (controlled locale string with <strong> tags) 3822 const noticeEl = this.elements.itemsContainer.querySelector('.swym-storefront-layout-empty-state-notice-text'); 3823 if (noticeEl) { 3824 noticeEl.innerHTML = noticeText || ''; 3825 } 3826 3827 // Attach event listener for "Keep browsing first" button (closes the drawer) 3828 const browseBtn = this.elements.itemsContainer.querySelector('[data-action="close-drawer"]'); 3829 if (browseBtn) { 3830 browseBtn.addEventListener('click', () => { 3831 const layout = SwymStorefrontLayoutExtensions.SwymStorefrontLayout 3832 || document.querySelector('swym-storefront-layout #swym-storefront-layout'); 3833 layout?.close?.(); 3834 }); 3835 } 3836 3837 // Attach event listener for multi-logged-in "+ Create a new list" card
3837in items empty state 3838 const createCardItems = this.elements.itemsContainer.querySelector('[data-action="create-collection"]'); 3839 if (createCardItems) { 3840 createCardItems.addEventListener('click', () => { 3841 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActions?.setData({ 3842 listId: SwymStorefrontLayoutContext?.DefaultList?.lid, 3843 list: SwymStorefrontLayoutContext?.DefaultList, 3844 item: { iu: '', dt: '' }, 3845 collections: SwymStorefrontLayoutContext?.collections || [], 3846 actionType: SwymStorefrontLayoutContext?.ActionTypes.AddToCollection, 3847 canCreateLists: true 3848 }); 3849 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActions?.openDrawer(); 3850 }); 3851 } 3852 } 3853 3854 let wishlistCount = (this.wishlist?.listcontents || []).filter(item => item.source !== 'auto-wishlist').length; 3855 if(this.elements.title){ 3856 this.elements.title.innerHTML = `${SwymStorefrontLayoutContext?.Strings?.wishlistTitle} ${wishlistCount?`(${wishlistCount})`:''}`; 3857 } 3858 } 3859 3860 3861} 3862 3863/** 3864 * SwymStorefrontLayoutCollectionList 3865 * 3866 * A custom HTML element representing a collection list within the Swym Storefront Layout. 3867 * This component is responsible for rendering a list of collections, displaying wishlist items, 3868 * handling user interactions, and managing collection data. 3869 * 3870 * Features: 3871 * - Initializes and renders the UI dynamically. 3872 * - Fetches and displays wishlist items for a collection. 3873 * - Handles user permissions for editing or saving collections. 3874 * - Provides UI elements like back navigation, action buttons, and collection details. 3875 * 3876 * Usage: 3877 * ```html 3878 * <swym-storefront-layout-collection-list></swym-storefront-layout-collection-list> 3879 * ``` 3880 * JavaScript: 3881 * ```js 3882 * const collectionListElement = document.createElement('swym-storefront-layout-collection-list'); 3883 * collectionListElement.setData({ list: myList, collections: myCollections }); 3884 * document.body.appendChild(collectionListElement); 3885 * ``` 3886 */ 3887class SwymStorefrontLayoutCollectionList extends HTMLElement { 3888 constructor() { 3889 super(); 3890 this.collectionList = null; 3891 this.collections = null; 3892 } 3893 initUI(){ 3894 this.renderUI(); 3895 this.elements = { 3896 backButton: this.querySelector('.swym-storefront-layout-collection-list-back-button'), 3897 itemsContainer: this.querySelector('#swym-storefront-layout-collection-list-items-container'), 3898 collectionCount: this.querySelector('.swym-storefront-layout-collection-items-count'), 3899 title: this.querySelector('.swym-storefront-layout-collection-title'), 3900 swymListActionButton: this.querySelector('.swym-storefront-layout-collection-option-button'), 3901 userAccessInfo: this.querySelector('#swym-storefront-layout-collection-list-access-info'), 3902 collectionImages: this.querySelector('#swym-storefront-layout-collection-list-images-container') 3903 } 3904 this.addEventListenerToView(); 3905 } 3906 3907 setData({ list, collections, canCreateLists = true, shouldShowMultiple = true }) { 3908 this.initUI(); 3909 this.renderLoader(); 3910 this.collectionList = list; 3911 this.collections = collections; 3912 this.canCreateLists = canCreateLists; 3913 this.shouldShowMultiple = shouldShowMultiple; 3914 if(this.collectionList){ 3915 this.elements.itemsContainer.dataset.lid = this.collectionList.lid; 3916 this.canEdit = SwymStorefrontLayoutContext?.checkUserCanEditList(this.collectionList); 3917 this.renderCollectionlist(); 3918 } 3919 } 3920 3921 renderUI() { 3922 this.innerHTML = ` 3923 <div class="swym-storefront-layout-collection-list-container"> 3924 <div class="swym-storefront-layout-collection-list-header"> 3925 <div id="swym-storefront-layout-collection-list-images-container"> 3926 </div> 3927 <div class="swym-storefront-layout-collection-list-header-content"> 3928 <div class="swym-storefront-layout-collection-list-back-button"> 3929 <svg data-v-bfb713a3="" data-v-326364ff="" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg" class="base-icon back-icon" aria-hidden="true"><path d="M15.44 7a.96.96 0 0 0-.96-.96H3.951l3.903-3.638A.96.96 0 1 0 6.544 1L.856 6.297a.963.963 0 0 0 0 1.404L6.543 13a.96.96 0 1 0 1.31-1.404L3.952 7.96H14.48a.96.96 0 0 0 .96-.96Z" fill="currentColor"></path></svg> 3930 </div> 3931 <div class="swym-storefront-layout-collection-title-container"> 3932 <div class="swym-storefront-layout-collection-title"></div> 3933 <div class="swym-storefront-layout-collection-items-count"></div> 3934 <div class="swym-storefront-layout-collection-option-button"> 3935 <svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13" fill="currentcolor"> 3936 <circle cx="1.5" cy="1.50014" r="1.5" fill="currentColor"></circle> 3937 <circle cx="1.5" cy="6.50014" r="1.5" fill="currentColor"></circle> 3938 <circle cx="1.5" cy="11.5001" r="1.5" fill="currentColor"></circle> 3939 </svg> 3940 </div> 3941 </div> 3942 </div> 3943 </div> 3944 <div class="swym-storefront-layout-collection-list-content swym-storefront-layout-scrollable"> 3945 <div id="swym-storefront-layout-collection-list-access-info"></div> 3946 <div class="swym-storefront-layout-add-all-to-cart-container swym-storefront-layout-add-all-to-cart-top"></div> 3947 <div id="swym-storefront-layout-collection-list-items-container" class="swym-storefront-layout-items-container"> 3948 </div> 3949 <div class="swym-storefront-layout-add-all-to-cart-container swym-storefront-layout-add-all-to-cart-bottom"></div> 3950 </div> 3951 <swym-storefront-layout-login-user showLogin="true"></swym-storefront-layout-login-user> 3952 </div> 3953 ` 3954 } 3955 3956 addEventListenerToView() { 3957 this.elements.backButton.addEventListener('click', () => { 3958 window.location.hash = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.List; 3959 this.closeCollectinListView() 3960 }); 3961 3962 this.elements.swymListActionButton?.addEventListener('click', (event) => { 3963 event.preventDefault(); 3964 event.stopPropagation(); 3965 if(SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.isTooltipOpen && SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.currentTarget === event.target){ 3966 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.closeTooltip(); 3967 }else{ 3968 if(this.canEdit){ 3969 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.setData({ 3970 listId: this.collectionList.lid, 3971 list: this.collectionList, 3972 collections: this.collections, 3973 actionType: SwymStorefrontLayoutContext?.ActionTypes.EditCollection 3974 }); 3975 }else{ 3976 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.setData({ 3977 listId: this.collectionList.lid, 3978 list: this.collectionList, 3979 collections: this.collections, 3980 actionType: SwymStorefrontLayoutContext?.ActionTypes.SaveCollection 3981 }); 3982 } 3983 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.showOnTarget(event.target); 3984 } 3985 }); 3986 } 3987 3988 async renderCollectionlist() { 3989 SwymStorefrontLayoutExtensions?.AddAllToCart?.mount(this, this.collectionList); 3990 if (this.collectionList && this.collectionList.listcontents && this.elements.itemsContainer) { 3991 3992 this.elements.title.innerHTML = `<div class="swym-storefront-layout-collection-title">${this.collectionList.lname}</div>`; 3993 3994 let itemsCount = this.collectionList?.listcontents?.length || 0; 3995 this.elements.collectionCount.innerHTML = itemsCount?`${itemsCount} ${itemsCount<=1? SwymStorefrontLayoutContext?.Strings?.item : SwymStorefrontLayoutContext?.Strings?.items }`:''; 3996 3997 const wishlistedProducts = this.collectionList.listcontents; 3998 const fragment = document.createDocumentFragment(); 3999 4000 this.elements.collectionImages.innerHTML = ` 4001 <div class="swym-storefront-layout-collection-list-images"> 4002 ${wishlistedProducts.slice(0, 5).map((item, index) => 4003 item 4004 ? `<img class="swym-storefront-layout-collection-list-image swym-storefront-layout-collection-list-image-${index}" src="${item.iu}" alt="Wishlist Image">` 4005 : `` 4006 ).join('')} 4007 </div> 4008 `; 4009 4010 4011 4012 if(itemsCount){
4013 wishlistedProducts.forEach((item) => { 4014 const wishlistItem = document.createElement('template'); 4015 wishlistItem.innerHTML = `<swym-storefront-layout-item id="swym-storefront-layout-collection-item-${item.empi}-${item.epi}" ></swym-storefront-layout-item>`; 4016 fragment.appendChild(wishlistItem.content); 4017 }); 4018 4019 this.elements.itemsContainer.innerHTML = ''; 4020 this.elements.itemsContainer.appendChild(fragment); 4021 4022 wishlistedProducts.forEach((item) => { 4023 let wishlistItemElement = document.querySelector(`#swym-storefront-layout-collection-item-${item.empi}-${item.epi}`); 4024 wishlistItemElement?.setData({ 4025 item, 4026 listItemType: SwymStorefrontLayoutContext?.ListItemType?.CollectionItem, 4027 list: this.collectionList, 4028 collections: this.collections, 4029 canCreateLists: this.canCreateLists 4030 }) 4031 }); 4032 }else{ 4033 this.renderEmptyView(); 4034 } 4035 4036 let isUserLogged = SwymStorefrontLayoutContext?.isShopperLoggedIn; 4037 if(isUserLogged && !!this.collectionList?.userinfo?.em && this.collectionList?.userinfo?.em != SwymStorefrontLayoutContext?.SwymCustomerData?.email){ 4038 let email = this.collectionList?.userinfo?.em; 4039 this.elements.userAccessInfo.innerHTML = window._swat?.utils?.renderTemplateString( SwymStorefrontLayoutContext?.Strings?.sharedCollectionMessage, { email }); 4040 }else{ 4041 this.elements.userAccessInfo.innerHTML = ``; 4042 } 4043 4044 this.openCollectionListView(); 4045 } 4046 } 4047 4048 renderEmptyView(){ 4049 this.elements.itemsContainer.innerHTML = ` 4050 <div class="swym-storefront-layout-empty-container"> 4051 <div class="swym-storefront-layout-empty-wishlist-container"> 4052 <div class="swym-storefront-layout-empty-wishlist-content"> 4053 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="86" height="85" viewBox="0 0 86 85" fill="none"> 4054 <rect width="59.5441" height="78.6833" transform="translate(0.710449 6.1748) rotate(-5.46887)" fill="white"/> 4055 <rect x="0.710938" y="6.1748" width="59.5441" height="78.6833" rx="1.21519" transform="rotate(-5.46887 0.710938 6.1748)" fill="#FDFDFD"/> 4056 <rect x="0.983626" y="6.39984" width="59.0441" height="78.1833" rx="0.965186" transform="rotate(-5.46887 0.983626 6.39984)" stroke="black" stroke-opacity="0.1" stroke-width="0.5"/> 4057 <rect x="5.57364" y="10.7446" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 5.57364 10.7446)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/>
4058 <rect x="13.0841" y="11.0938" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 13.0841 11.0938)" fill="#D0D0D0"/> 4059 <rect x="6.44223" y="19.8169" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 6.44223 19.8169)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 4060 <rect x="13.9527" y="20.166" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 13.9527 20.166)" fill="#D0D0D0"/> 4061 <rect x="7.31107" y="28.8892" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 7.31107 28.8892)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 4062 <rect x="14.8214" y="29.2383" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 14.8214 29.2383)" fill="#D0D0D0"/> 4063 <rect x="8.17942" y="37.9619" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 8.17942 37.9619)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 4064 <rect x="15.6899" y="38.311" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 15.6899 38.311)" fill="#D0D0D0"/> 4065 <rect x="9.04825" y="47.0342" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 9.04825 47.0342)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 4066 <rect x="16.5587" y="47.3833" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 16.5587 47.3833)" fill="#D0D0D0"/> 4067 <rect x="9.91666" y="56.1069" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 9.91666 56.1069)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 4068 <rect x="17.4271" y="56.4561" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 17.4271 56.4561)" fill="#D0D0D0"/> 4069 <rect x="10.7854" y="65.1792" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 10.7854 65.1792)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 4070 <rect x="18.2959" y="65.5283" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 18.2959 65.5283)" fill="#D0D0D0"/> 4071 <rect x="11.6538" y="74.2515" width="5.16454" height="5.16454" rx="0.455695" transform="rotate(-5.46887 11.6538 74.2515)" fill="#FBFBFB" stroke="#D0D0D0" stroke-width="0.303797"/> 4072 <rect x="19.1643" y="74.6006" width="44.6581" height="3.03797" rx="0.607593" transform="rotate(-5.46887 19.1643 74.6006)" fill="#D0D0D0"/> 4073 <rect x="55.0288" y="35.834" width="31.0853" height="31.0853" transform="rotate(13.2248 55.0288 35.834)" fill="url(#swym-empty-collection-pattern)"/> 4074 <defs> 4075 <pattern id="swym-empty-collection-pattern" patternContentUnits="objectBoundingBox" width="1" height="1"> 4076 <use xlink:href="#swym-empty-collection-image" transform="scale(0.0104167)"/> 4077 </pattern> 4078 <image id="swym-empty-collection-image" width="96" height="96" xlink:href="data:image/png;base64,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"/> 4079 </defs> 4080 </svg> 4081 <div class="swym-storefront-layout-empty-wishlist-title">${SwymStorefrontLayoutContext?.Strings?.emptyCollectionText}</div> 4082 <div class="swym-storefront-layout-empty-wishlist-description">${SwymStorefrontLayoutContext?.Strings?.emptyCollectionDescription}</div> 4083 </div> 4084 </div> 4085 </div> 4086 `; 4087 this.elements.itemsContainer.classList.add('swym-storefront-layout-items-has-empty'); 4088 } 4089 4090 renderLoader(){ 4091 this.elements.collectionImages.innerHTML = `<swym-storefront-layout-loader loading="true" loadertype="${SwymStorefrontLayoutContext?.LoaderViewType.CollectionImages}" height="80%"></swym-storefront-layout-loader>`; 4092 this.elements.itemsContainer.innerHTML = `<swym-storefront-layout-loader loading="true" loadertype="${SwymStorefrontLayoutContext?.LoaderViewType.WishlistListItem}" height="80%"></swym-storefront-layout-loader>`; 4093 } 4094 4095 clearUI(){ 4096 this.innerHTML = ''; 4097 } 4098 4099 openCollectionListView() { 4100 this.classList.add('swym-storefront-layout-show-collection-list'); 4101 } 4102 4103 closeCollectinListView() { 4104 this.classList.remove('swym-storefront-layout-show-collection-list'); 4105 setTimeout(()=>{ 4106 this.clearUI(); 4107 },100); 4108 } 4109 4110} 4111 4112 4113/** 4114 * SwymStorefrontLayoutActions - Custom Web Component 4115 * 4116 * This class extends `HTMLElement` to create and manage the UI for wishlist-related actions 4117 * in the Swym Storefront Layout. It handles functionalities such as: 4118 * 4119 * - Displaying a modal-like action panel for managing wishlist items. 4120 * - Supporting different action types (e.g., adding an item to a collection). 4121 * - Rendering the UI dynamically based on the selected list or collection. 4122 * - Managing event listeners to update UI interactions seamlessly. 4123 * 4124 * Key Features: 4125 * - Renders a structured UI layout with an image, title, options, and action buttons. 4126 * - Supports selecting collections and dynamically updates the UI upon selection. 4127 * - Implements event handling for collection management, including adding new collections. 4128 * - Uses `SwymStorefrontLayoutContext` for accessing relevant wishlist and collection data. 4129 * 4130 * Usage: 4131 * - This component is initialized automatically when added to the DOM. 4132 * - Data can be set via `setData({ listId, list, item, selectedVariantId, collections, actionType })`. 4133 * - Internal methods like `renderAddToCollection()` handle specific UI updates. 4134 * 4135 * Example: 4136 * ```html 4137 * <swym-storefront-layout-actions></swym-storefront-layout-actions> 4138 * ``` 4139 * 4140 * Dependencies: 4141 * - Requires `SwymStorefrontLayoutContext` for accessing wishlist and collection data. 4142 * - Uses event listeners for handling user interactions. 4143 * 4144 * Notes: 4145 * - Ensures smooth UI updates with proper event handling and DOM manipulations. 4146 * - Uses query selectors to manage DOM elements efficiently. 4147 */ 4148class SwymStorefrontLayoutActions extends HTMLElement { 4149 constructor() { 4150 super(); 4151 4152 this.actionType = SwymStorefrontLayoutContext?.ActionTypes.ManageListItem; 4153 4154 this.wishlistItem = null; 4155 this.collections = null; 4156 this.listId = null; 4157 this.list = null; 4158 this.selectedCollectionLid = null; 4159 this.selectedList = null; 4160 this.renderUI(); 4161 this.elements = { 4162 container: this.querySelector('.swym-storefront-layout-actions-container'), 4163 backdrop: this.querySelector('.swym-storefront-layout-action-backdrop'), 4164 closeButton: this.querySelector('.swym-storefront-layout-action-close-button'), 4165 title: this.querySelector('.swym-storefront-layout-action-title-container'), 4166 optionsContainer: this.querySelector('.swym-storefront-layout-action-options'), 4167 actionContianer: this.querySelector('.swym-storefront-layout-action-container'), 4168 wishlistItemsContainer: document.querySelector('swym-storefront-layout-wishlist-container #swym-storefront-layout-items-container'), 4169 sflItemsContainer: document.querySelector('swym-storefront-layout-sfl-container #swym-storefront-layout-sfl-items-container'), 4170 imageContainer: this.querySelector('.swym-storefront-layout-action-image-container') 4171 } 4172 this.addEventListenerToView(); 4173 } 4174 4175 renderUI() { 4176 this.innerHTML = ` 4177 <div class="swym-storefront-layout-actions-container"> 4178 <div class="swym-storefront-layout-action-backdrop"></div> 4179 <div class="swym-storefront-layout-action-layout"> 4180 <div class="swym-storefront-layout-action-image-container"></div> 4181 <div class="swym-storefront-layout-action-content"> 4182 <div class="swym-storefront-layout-action-header"> 4183 <div class="swym-storefront-layout-action-title-container"></div> 4184 <button type="button" aria-label="${SwymStorefrontLayoutContext?.Strings?.closeDrawer || 'Close'}" class="swym-storefront-layout-action-close-button"> 4185 <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="currentColor"> 4186 <path d="M0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10Z" fill="white"/> 4187 <path d="M13.9697 15.0303C14.2626 15.3232 14.7374 15.3232 15.0303 15.0303C15.3232 14.7374 15.3232 14.2626 15.0303 13.9697L11.0607 10L15.0303 6.03033C15.3232 5.73744 15.3232 5.26256 15.0303 4.96967C14.7374 4.67678 14.2626 4.67678 13.9697 4.96967L10 8.93934L6.03033 4.96967C5.73744 4.67678 5.26256 4.67678 4.96967 4.96967C4.67678 5.26256 4.67678 5.73744 4.96967 6.03033L8.93934 10L4.96967 13.9697C4.67678 14.2626 4.67678 14.7374 4.96967 15.0303C5.26256 15.3232 5.73744 15.3232 6.03033 15.0303L10 11.0607L13.9697 15.0303Z" fill="currentColor"/> 4188 </svg> 4189 </button> 4190 </div> 4191 <div class="swym-storefront-layout-action-options"></div> 4192 <div class="swym-storefront-layout-action-container"></div> 4193 </div> 4194 </div> 4195 </div> 4196 ` 4197 } 4198 4199 setData({ listId, list, item, selectedVariantId, collections, actionType = SwymStorefrontLayoutContext?.ActionTypes.ManageListItem, canCreateLists = true }) { 4200 this.listId = listId; 4201 this.list = list; 4202 this.wishlistItem = item; 4203 this.collections = SwymStorefrontLayoutContext?.collections; 4204 this.currentSlectedVariantId = selectedVariantId; 4205 this.selectedCollectionLid = this.collections[0]?.lid; 4206 this.selectedList = this.collections[0] || null; 4207 this.canCreateLists = canCreateLists; 4208 this.elements.wishlistItemsContainer = document.querySelector(`.swym-storefront-layout-items-container[data-lid="${listId}"]`); 4209 this.elements.actionContianer.innerHTML = ''; 4210 if (actionType === SwymStorefrontLayoutContext?.ActionTypes.AddToCollection) { 4211 this.renderAddToCollection(); 4212 } 4213 SwymStorefrontLayoutExtensions?.checkContainerScrollbar(this.elements.optionsContainer); 4214 } 4215 4216 async renderAddToCollection() { 4217 this.collections = SwymStorefrontLayoutContext?.collections || []; 4218 if (this.collections && this.elements.optionsContainer) { 4219 4220 if (this.wishlistItem.iu) { 4221 this.elements.imageContainer.innerHTML = `<img src="${this.wishlistItem.iu}" alt="${swymEscAttr(this.wishlistItem.dt) || 'Product image'}">`; 4222 this.elements.imageContainer.style.display = ''; 4223 } else { 4224 this.elements.imageContainer.innerHTML = ''; 4225 this.elements.imageContainer.style.display = 'none'; 4226 } 4227 4228 this.elements.title.innerHTML = ` 4229 <div class="swym-storefront-layout-action-addtocollection-title"> 4230 <div class="swym-storefront-layout-action-product-title">${this.wishlistItem.dt}</div> 4231 <div class="swym-storefront-layout-action-title">${SwymStorefrontLayoutContext?.Strings?.addToCollectionTitle}</div> 4232 </div> 4233 ` 4234 this.renderCollectionOptions(); 4235 this.renderCreteCollectionAction() 4236 } 4237 } 4238 4239 renderCollectionOptions(){ 4240 this.elements.optionsContainer.innerHTML = ` 4241 ${this.collections.map((collection) => { 4242 4243 let { lid, lname, listcontents } = collection; 4244 let item = listcontents[0]; 4245 let count = listcontents.length; 4246 4247 // Check if current product exists in this collection 4248 const productExists = listcontents.some(listItem => 4249 listItem.epi === this.wishlistItem.epi 4250 ); 4251 4252 const listImagesHtml = Array.from({ length: 3 }) 4253 .map((_, i) => { 4254 let item = listcontents[i]; 4255 return ` 4256 <div class="${i === 0 ? 'swym-storefront-layout-action-collection-item-image-primary' : 'swym-storefront-layout-action-collection-item-image-secondary'} ">
4257 ${item?.iu ? `<img src="${item ? item.iu : null}" alt="${swymEscAttr(item?.dt) || ''}" class="swym-storefront-layout-action-collection-item-image" />` : `<div class="swym-storefront-layout-action-collection-item-image" ></div>`} 4258 </div> 4259 `; 4260 }) 4261 .join(""); 4262 4263 return ` 4264 <div class="swym-storefront-layout-collection-action-btn add-item-to-collection swym-storefront-layout-action-collection-item-container" data-lid="${lid}"> 4265 <div class="swym-storefront-layout-action-collection-image-container"> 4266 ${listImagesHtml} 4267 </div> 4268 <div class="swym-storefront-layout-action-collection-content"> 4269 <div class="swym-storefront-layout-action-collection-item-name">${lname}</div> 4270 <div class="swym-storefront-layout-action-collection-item-count">${count} ${count === 1 ? SwymStorefrontLayoutContext?.Strings?.item : SwymStorefrontLayoutContext?.Strings?.items}</div> 4271 </div> 4272 <div class="swym-storefront-layout-checkbox-container"> 4273 <label class="swym-storefront-layout-checkbox"> 4274 <input type="checkbox" ${productExists ? 'checked' : ''} name="swym-storefront-layout-collections"> 4275 <span class="swym-storefront-layout-checkmark"></span> 4276 </label> 4277 </div> 4278 </div> 4279 ` 4280 }).join('')} 4281 `; 4282 4283 this.addMangeCollectionEventListeners(); 4284 } 4285 4286 addMangeCollectionEventListeners() { 4287 // Track initial checkbox states 4288 this.initialCheckStates = new Map(); 4289 this.newlyCheckedLids = []; // Declare at class level 4290 this.newlyUncheckedLids = [] // Array for newly unchecked lids 4291 4292 this.collections.forEach((collection) => { 4293 const collectionItem = this.querySelector(`[data-lid="${collection.lid}"]`); 4294 const checkbox = collectionItem?.querySelector('input[type="checkbox"]'); 4295 4296 // Store initial checked state 4297 if (checkbox) { 4298 this.initialCheckStates.set(collection.lid, checkbox.checked); 4299 } 4300 4301 // Add event listener specifically for the checkbox 4302 collectionItem?.addEventListener("click", async (event) => { 4303 event.preventDefault(); 4304 event.stopPropagation(); 4305 4306 // Toggle checkbox 4307 if (checkbox) { 4308 checkbox.checked = !checkbox.checked; 4309 } 4310 4311 const collectionSaveButton = this.querySelector('.swym-storefront-layout-save-collection-list-button'); 4312 // Clear arrays on each click 4313 this.newlyCheckedLids = []; 4314 this.newlyUncheckedLids = []; 4315 4316 const collectionNameInput = this.querySelector('.swym-storefront-layout-create-collection-name-input'); 4317 4318 this.collections.forEach(col => { 4319 const item = this.querySelector(`[data-lid="${col.lid}"]`); 4320 const cb = item?.querySelector('input[type="checkbox"]'); 4321 if (cb) { 4322 const wasChecked = this.initialCheckStates.get(col.lid); 4323 const isNowChecked = cb.checked; 4324 4325 if (!wasChecked && isNowChecked) { 4326 this.newlyCheckedLids.push(col.lid); 4327 } 4328 if (wasChecked && !isNowChecked) { 4329 this.newlyUncheckedLids.push(col.lid); 4330 } 4331 } 4332 }); 4333 4334 // Enable button if there are any changes 4335 if (collectionSaveButton) { 4336 const hasChanges = this.newlyCheckedLids.length > 0 || this.newlyUncheckedLids.length > 0; 4337 collectionSaveButton.disabled = !hasChanges; 4338 // Update button text based on changes 4339 if (hasChanges) { 4340 if (this.newlyUncheckedLids.length > 0 && this.newlyCheckedLids.length === 0) { 4341 // Only unchecking existing items 4342 if (!collectionNameInput.value.trim()) { 4343 collectionSaveButton.textContent = SwymStorefrontLayoutContext?.Strings?.removeItemCta; 4344 } else { 4345 collectionSaveButton.textContent = SwymStorefrontLayoutContext?.Strings?.updateCollectionCta || "Upd
4345ate List"; 4346 } 4347 } else if (this.newlyCheckedLids.length > 0 && this.newlyUncheckedLids.length === 0) { 4348 // Only adding new items 4349 collectionSaveButton.textContent = SwymStorefrontLayoutContext?.Strings?.addToCollectionCta || "Add to Collection"; 4350 } else { 4351 // Both adding and removing 4352 collectionSaveButton.textContent = SwymStorefrontLayoutContext?.Strings?.updateCollectionCta || "Update List"; 4353 } 4354 } else { 4355 if (collectionNameInput.value.trim() && collectionNameInput.value.trim().length >= 3) { 4356 collectionSaveButton.textContent = SwymStorefrontLayoutContext?.Strings?.addToCollectionCta || "Add to Collection"; 4357 collectionSaveButton.disabled = false; 4358 } 4359 } 4360 } 4361 // Toggle selected class on the parent container 4362 collectionItem.classList.toggle('swym-storefront-layout-checkbox-selected'); 4363 // Your existing logic for handling checkbox state 4364 }); 4365 // Prevent checkbox from triggering twice 4366 checkbox?.addEventListener('click', (event) => { 4367 event.stopPropagation(); 4368 }); 4369 }); 4370 } 4371 4372 renderCreteCollectionAction(){ 4373 // Render save button always, but only show create collection input if user can create new lists 4374 this.elements.actionContianer.innerHTML = ` 4375 <form class="swym-storefront-layout-create-collection-form"> 4376 ${this.canCreateLists ? ` 4377 <div class="swym-storefront-layout-create-collection-name-form-group"> 4378 <input 4379 type="text" 4380 class="swym-storefront-layout-action-input create-collection-name-input swym-storefront-layout-create-collection-name-input" 4381 placeholder="${SwymStorefrontLayoutContext?.Strings?.createCollectionCta}" 4382 /> 4383 <div class="swym-storefront-layout-checkbox-container"> 4384 <label class="swym-storefront-layout-checkbox"> 4385 <input type="checkbox" ${this.selectedCollectionLid === 'fromInput'?'checked':''} name="swym-storefront-layout-collections"> 4386 <span class="swym-storefront-layout-checkmark"></span> 4387 </label> 4388 </div> 4389 <div class="swym-storefront-layout-create-collection-error-message-container error-message" style="display: none;"> 4390 <svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" class="base-icon error-icon" aria-hidden="true"><path d="M7 .7a6.3 6.3 0 1 0 0 12.6A6.3 6.3 0 0 0 7 .7Zm.7 9.8H6.3V9.1h1.4v1.4Zm0-2.1H6.3V3.5h1.4v4.9Z" fill="currentColor"></path></svg> 4391 <span class="swym-storefront-layout-create-collection-error-message">Name is required</span> 4392 </div> 4393 </div> 4394 ` : ''} 4395 <button type="submit" class="swym-storefront-layout-save-collection-list-button" disabled>${SwymStorefrontLayoutContext?.Strings?.addToCollectionCta}</button> 4396 </form> 4397 `; 4398 this.addCreateCollectionEventListeners(); 4399 } 4400 4401 addCreateCollectionEventListeners() { 4402 const createCollectionForm = this.querySelector('.swym-storefront-layout-create-collection-form'); 4403 const collectionNameInputContainer = this.querySelector('.swym-storefront-layout-create-collection-name-form-group'); 4404 const collectionNameInput = this.querySelector('.swym-storefront-layout-create-collection-name-input'); 4405 4406 // Only add input-specific listeners if the input exists (when canCreateLists is true) 4407 if (collectionNameInput) { 4408 collectionNameInput.addEventListener("focus", () => { 4409 this.selectedCollectionLid = 'fromInput'; 4410 this.validateCollectionInputField(false); 4411 }); 4412 4413 collectionNameInput.addEventListener("blur", () => { 4414 if (!collectionNameInput.value.trim()) { 4415 const errorMessageContainer = this.querySelector('.swym-storefront-layout-create-collection-error-message-container'); 4416 const errorMessage = this.querySelector('.swym-storefront-layout-create-collection-error-message'); 4417 4418 // Hide error message 4419 errorMessage.innerHTML = ''; 4420 errorMessageContainer.style.display = 'none'; 4421 collectionNameInput.classList.remove('swym-storefront-layout-input-has-error'); 4422 } 4423 }); 4424 4425 collectionNameInput.addEventListener('input', () => { 4426 this.validateCollectionInputField(); 4427 }); 4428 4429 // auto focus create collection input by default when no collection available 4430 // Small delay ensures the input is focusable after the dialog/DOM finishes rendering. 4431 const COLLECTION_INPUT_FOCUS_DELAY_MS = 50; 4432 if(!this.collections?.length){ 4433 setTimeout(() => { 4434 collectionNameInput?.focus(); 4435 }, COLLECTION_INPUT_FOCUS_DELAY_MS); 4436 } 4437 } 4438 4439 // Always add form submit listener (works whether input exists or not) 4440 createCollectionForm?.addEventListener('submit', async (event) => {
4441 try { 4442 event.preventDefault(); 4443 let listIdToAdd; 4444 if(this.selectedCollectionLid === 'fromInput' && collectionNameInput.value.length >= 3){ 4445 let name = collectionNameInput.value.trim(); 4446 let newList = await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.createList(name); 4447 listIdToAdd = newList.lid; 4448 this.newlyCheckedLids.push(listIdToAdd); 4449 this.selectedList = newList; 4450 } 4451 let { epi, empi, du, iu } = this.wishlistItem; 4452 4453 // Check if item is being moved from default list 4454 // Check if item exists in default list 4455 const isInDefaultList = SwymStorefrontLayoutContext?.DefaultList?.listcontents.some( 4456 item => item.epi === epi && item.empi === empi 4457 ); 4458 // If item is from the SFY list, remove it from there when adding to a collection 4459 const sfyList = SwymStorefrontLayoutContext.getSfyList(); 4460 if (sfyList && this.listId === sfyList.lid) { 4461 this.newlyUncheckedLids.push(sfyList.lid); 4462 } 4463 if(this.newlyCheckedLids.length > 0 && this.newlyUncheckedLids.length > 0) { 4464 SwymStorefrontLayoutContext.CommonAddtoWL = true; 4465 SwymStorefrontLayoutContext.CommonRemovefromWL = true; 4466 } 4467 this.closeDrawer(); 4468 4469 if(this.newlyCheckedLids.length > 0) { 4470 // Add to new lists 4471 SwymStorefrontLayoutContext.CommonAddtoWL = true; 4472 if(isInDefaultList) { 4473 SwymStorefrontLayoutContext.CommonRemovefromWL = true; 4474 this.newlyUncheckedLids.push(SwymStorefrontLayoutContext?.DefaultList?.lid); 4475 } 4476 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.addProductToLists( 4477 { empi, epi, du }, 4478 this.newlyCheckedLids 4479 ); 4480 } 4481 // Handle unchecked lists (except default list) 4482 if(this.newlyUncheckedLids.length > 0) { 4483 SwymStorefrontLayoutContext.CommonRemovefromWL = true; 4484 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.removeProductFromLists( 4485 { empi, epi, du }, 4486 this.newlyUncheckedLids 4487 ); 4488 } 4489 // Show notification 4490 if(this.newlyCheckedLids.length > 0 && this.newlyUncheckedLids.length > 0) { 4491 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 4492 title: SwymStorefrontLayoutContext?.Strings?.notificationMessageCollectionUpdated, 4493 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageCollectionUpdated, 4494 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 4495 actionType: "", 4496 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 4497 product: this.wishlistItem, 4498 image: iu, 4499 action: { 4500 label: SwymStorefrontLayoutContext?.Strings?.notificationActionView, 4501 onClick: () => { 4502 _swat.ui.open(); 4503 }, 4504 } 4505 },{ 4506 showActionButton: true, 4507 showTitle: false, 4508 showProgress: true 4509 }); 4510 SwymStorefrontLayoutContext.CommonAddtoWL = false; 4511 SwymStorefrontLayoutContext.CommonRemovefromWL = false; 4512 } 4513 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutCollectionCreated, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutCollectionInteractions }); 4514 } catch (error) { 4515 window._swat?.utils.error('Error on create and add product to list', error); 4516 } 4517 }); 4518 } 4519 4520 validateCollectionInputField(showError = true){ 4521 4522 const collectionNameInput = this.querySelector('.swym-storefront-layout-create-collection-name-input'); 4523 const collectionSaveButton = this.querySelector('.swym-storefront-layout-save-collection-list-button'); 4524 const errorMessageContainer = this.querySelector('.swym-storefront-layout-create-collection-error-message-container'); 4525 const errorMessage = this.querySelector('.swym-storefront-layout-create-collection-error-message'); 4526 const checkbox = collectionNameInput.closest('.swym-storefront-layout-create-collection-name-form-group')?.querySelector('input[type="checkbox"]'); 4527 4528 let name = collectionNameInput.value.trim(); 4529 let error; 4530 4531 let isNameExist = this.collections.some(collection => collection.lname.toLowerCase() === name.toLowerCase()); 4532 4533 // Check if there are any newly checked or unchecked items 4534 const hasChanges = this.newlyCheckedLids.length > 0 || this.newlyUncheckedLids.length > 0; 4535 4536 if (name === '') { 4537 error = SwymStorefrontLayoutContext?.Strings?.errorMessageListNameRequired; 4538 checkbox.checked = false;
4539 if (this.selectedCollectionLid === 'fromInput') { 4540 this.selectedCollectionLid = null; 4541 } 4542 } else if (name.length < 3) { 4543 error = SwymStorefrontLayoutContext?.Strings?.errorMessageListNameRequire3Char; 4544 checkbox.checked = false; 4545 if (this.selectedCollectionLid === 'fromInput') { 4546 this.selectedCollectionLid = null; 4547 } 4548 }else if(isNameExist){ 4549 error = SwymStorefrontLayoutContext?.Strings?.errorMessageListNameAlreadyExist; 4550 checkbox.checked = false; 4551 if (this.selectedCollectionLid === 'fromInput') { 4552 this.selectedCollectionLid = null; 4553 } 4554 } 4555 4556 if (name.length > 0) { 4557 collectionNameInput.classList.add('swym-storefront-layout-input-has-value'); 4558 } else { 4559 collectionNameInput.classList.remove('swym-storefront-layout-input-has-value'); 4560 } 4561 4562 if (error) { 4563 if(showError){ 4564 errorMessage.innerHTML = error; 4565 errorMessageContainer.style.display = 'flex'; 4566 collectionNameInput.classList.add('swym-storefront-layout-input-has-error'); 4567 } 4568 // Only disable button if there are no other changes 4569 collectionSaveButton.disabled = !hasChanges; 4570 } else { 4571 errorMessage.innerHTML = ''; 4572 errorMessageContainer.style.display = 'none'; 4573 collectionSaveButton.disabled = false; 4574 collectionNameInput.classList.remove('swym-storefront-layout-input-has-error'); 4575 // Auto check the checkbox when input is valid 4576 checkbox.checked = true; 4577 this.selectedCollectionLid = 'fromInput'; 4578 // Update button text 4579 collectionSaveButton.textContent = SwymStorefrontLayoutContext?.Strings?.addToCollectionCta; 4580 } 4581 } 4582 4583 addEventListenerToView() { 4584 this.elements.backdrop.addEventListener('click', () => this.closeDrawer()); 4585 this.elements.closeButton.addEventListener('click', () => this.closeDrawer()); 4586 } 4587 4588 openDrawer() { 4589 this.elements.container.classList.add('swym-storefront-layout-show-action-view'); 4590 } 4591 4592 closeDrawer() { 4593 this.elements.container.classList.remove('swym-storefront-layout-show-action-view'); 4594 } 4595 4596} 4597 4598/** 4599 * SwymStorefrontLayoutActionTooltip 4600 * 4601 * This custom Web Component manages the action tooltip for storefront layout interactions. 4602 * It provides UI options for managing wishlist items, including adding to collections 4603 * and removing from lists. The tooltip dynamically updates based on user actions 4604 * and permissions. 4605 * 4606 * Features: 4607 * - Displays options based on the selected wishlist item. 4608 * - Supports adding/removing items from collections or lists. 4609 * - Listens for user interactions and updates the UI accordingly. 4610 * - Ensures smooth integration with SwymStorefrontLayoutContext?. 4611 * 4612 * Dependencies: 4613 * - SwymStorefrontLayoutContext: Provides necessary context for actions and permissions. 4614 * - SwymStorefrontLayoutAPI: Handles wishlist operations. 4615 * - SwymStorefrontLayoutExtensions: Manages UI interactions like opening drawers. 4616 */ 4617class SwymStorefrontLayoutActionTooltip extends HTMLElement { 4618 constructor() { 4619 super(); 4620 4621 this.isTooltipOpen = false; 4622 this.currentTarget = null; 4623 this._tooltipKeydownController = null; 4624 this.actionType = SwymStorefrontLayoutContext?.ActionTypes.ManageListItem; 4625 4626 this.listItem = null; 4627 this.collections = null; 4628 this.listId = null; 4629 this.list = null; 4630 this.renderUI(); 4631 this.elements = { 4632 viewPortLayout: document.querySelector('.swym-storefront-layout-layout'), 4633 tooltipLayout: this.querySelector('.swym-storefront-layout-action-tooltip-layout'), 4634 optionsContainer: this.querySelector('#swym-storefront-layout-action-tooltip-options'), 4635 wishlistItemsContainer: document.querySelector('swym-storefront-layout-wishlist-container #swym-storefront-layout-items-container'), 4636 sflItemsContainer: document.querySelector('swym-storefront-layout-sfl-container #swym-storefront-layout-sfl-items-container') 4637 } 4638 this.addEventListenerToView(); 4639 } 4640 4641 renderUI() { 4642 this.innerHTML = ` 4643 <div class="swym-storefront-layout-action-tooltip-layout"> 4644 <div id="swym-storefront-layout-action-tooltip-options" tabindex="-1" aria-label="Wishlist options"></div> 4645 </div> 4646 `; 4647 } 4648 4649 setData({ listId, list, item, selectedVariantId, collections, actionType = SwymStorefrontLayoutContext?.ActionTypes.ManageListItem, canCreateLists = true }) { 4650 this.listId = listId; 4651 this.list = list; 4652 this.listItem = item; 4653 this.collections = SwymStorefrontLayoutContext?.collections; 4654 this.currentSlectedVariantId = selectedVariantId; 4655 this.canCreateLists = canCreateLists; 4656 this.elements.wishlistItemsContainer = document.querySelector(`swym-storefront-layout-wishlist-container .swym-storefront-layout-items-container[data-lid="${listId}"]`) 4657 this.elements.sflItemsContainer = document.querySelector(`swym-storefront-layout-sfl-container .swym-storefront-layout-items-container[data-lid="${listId}"]`); 4658 this.elements.collectionItemsContainer = document.querySelector(`swym-storefront-layout-collection-list .swym-storefront-layout-items-container[data-lid="${listId}"]`); 4659 if (actionType === SwymStorefrontLayoutContext?.ActionTypes.ManageListItem) { 4660 this.renderWishlistItemOptions(); 4661 }else if(actionType === SwymStorefrontLayoutContext?.ActionTypes.EditCollection){ 4662 this.renderEditCollection(); 4663 }else if(actionType === SwymStorefrontLayoutContext?.ActionTypes.SaveCollection){ 4664 this.renderSaveCollection(); 4665 }else if(actionType === SwymStorefrontLayoutContext?.ActionTypes.ManageSflListItem){ 4666 this.renderSflListItemOptions(); 4667 }else if(actionType === SwymStorefrontLayoutContext?.ActionTypes.ManageSfyListItem){ 4668 this.renderSfyListItemOptions(); 4669 } else if(actionType === SwymStorefrontLayoutContext?.ActionTypes.ShareSingleWishlist) { 4670 this.renderShareSingleWishlist(); 4671 } 4672 } 4673 4674 async renderWishlistItemOptions() { 4675 if (this.listItem && this.elements.optionsContainer) { 4676 let selectedVariantId = this.currentSlectedVariantId || this.listItem?.epi; 4677 const selectedVariant = SwymStorefrontLayoutContext?.getSelectedVariant(this.listItem, selectedVariantId); 4678 const productImage = selectedVariant?.featured_image?.src || this.listItem.productData?.featured_image || this.listItem.iu; 4679 let canEdit = SwymStorefrontLayoutContext?.checkUserCanEditList(this.list); 4680 4681 // Get multi-wishlist configuration to determine if we should show "Add to list" option 4682 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 4683 4684 this.elements.optionsContainer.innerHTML = ` 4685 ${shouldShowMultiple? `<div class="swym-storefront-layout-action-tooltip-btn add-to-collection-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.addToCollectionCta || 'Add to collection'}"> 4686 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewB
4686ox="0 0 16 16" fill="currentColor" aria-hidden="true"> 4687 <path d="M7.33331 8.66683H3.33331V7.3335H7.33331V3.3335H8.66665V7.3335H12.6666V8.66683H8.66665V12.6668H7.33331V8.66683Z" fill="currentColor"/> 4688 </svg> 4689 ${SwymStorefrontLayoutContext?.Strings?.addToCollectionCta} 4690 </div>` : ''} 4691 ${canEdit?`<div class="swym-storefront-layout-action-tooltip-btn remove-from-list-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.removeItemCta || 'Remove from list'}"> 4692 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" viewBox="0 0 16 17" fill="currentColor" aria-hidden="true"> 4693 <path d="M4.66669 14.5C4.30002 14.5 3.98613 14.3694 3.72502 14.1083C3.46391 13.8472 3.33335 13.5333 3.33335 13.1667V4.5H2.66669V3.16667H6.00002V2.5H10V3.16667H13.3334V4.5H12.6667V13.1667C12.6667 13.5333 12.5361 13.8472 12.275 14.1083C12.0139 14.3694 11.7 14.5 11.3334 14.5H4.66669ZM11.3334 4.5H4.66669V13.1667H11.3334V4.5ZM6.00002 11.8333H7.33335V5.83333H6.00002V11.8333ZM8.66669 11.8333H10V5.83333H8.66669V11.8333Z" fill="currentColor"></path> 4694 </svg> 4695 ${SwymStorefrontLayoutContext?.Strings?.removeItemCta || 'Remove from list'} 4696 </div>`:''} 4697 `; 4698 this.addOptionEventListeners(); 4699 } 4700 } 4701 4702 async renderShareSingleWishlist() { 4703 // Check if user is logged in before showing share option 4704 const isLoggedIn = window._swat?.platform?.isLoggedIn(); 4705 if(!isLoggedIn) { 4706 this.elements.optionsContainer.innerHTML = ''; 4707 return; 4708 } 4709 4710 if(this.elements.optionsContainer) { 4711 this.elements.optionsContainer.innerHTML = `<div class="swym-storefront-layout-action-tooltip-btn share-collection-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.shareCollectionCta || 'Share collection'}"> 4712 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true"> 4713 <path d="M9.91666 12.8337C9.43055 12.8337 9.01735 12.6635 8.67707 12.3232C8.3368 11.983 8.16666 11.5698 8.16666 11.0837C8.16666 11.0253 8.18124 10.8892 8.21041 10.6753L4.11249 8.28366C3.95693 8.42949 3.77707 8.54373 3.57291 8.62637C3.36874 8.70901 3.14999 8.75033 2.91666 8.75033C2.43055 8.75033 2.01735 8.58019 1.67707 8.23991C1.3368 7.89963 1.16666 7.48644 1.16666 7.00033C1.16666 6.51421 1.3368 6.10102 1.67707 5.76074C2.01735 5.42046 2.43055 5.25033 2.91666 5.25033C3.14999 5.25033 3.36874 5.29164 3.57291 5.37428C3.77707 5.45692 3.95693 5.57116 4.11249 5.71699L8.21041 3.32533C8.19096 3.25727 8.17881 3.19164 8.17395 3.12845C8.16909 3.06526 8.16666 2.99477 8.16666 2.91699C8.16666 2.43088 8.3368 2.01769 8.67707 1.67741C9.01735 1.33713 9.43055 1.16699 9.91666 1.16699C10.4028 1.16699 10.816 1.33713 11.1562 1.67741C11.4965 2.01769 11.6667 2.43088 11.6667 2.91699C11.6667 3.4031 11.4965 3.8163 11.1562 4.15658C10.816 4.49685 10.4028 4.66699 9.91666 4.66699C9.68332 4.66699 9.46457 4.62567 9.26041 4.54303C9.05624 4.46039 8.87638 4.34616 8.72082 4.20033L4.62291 6.59199C4.64235 6.66005 4.6545 6.72567 4.65936 6.78887C4.66423 6.85206 4.66666 6.92255 4.66666 7.00033C4.66666 7.0781 4.66423 7.14859 4.65936 7.21178C4.6545 7.27498 4.64235 7.3406 4.62291 7.40866L8.72082 9.80033C8.87638 9.65449 9.05624 9.54026 9.26041 9.45762C9.46457 9.37498 9.68332 9.33366 9.91666 9.33366C10.4028 9.33366 10.816 9.5038 11.1562 9.84408C11.4965 10.1844 11.6667 10.5975 11.6667 11.0837C11.6667 11.5698 11.4965 11.983 11.1562 12.3232C10.816 12.6635 10.4028 12.8337 9.91666 12.8337ZM9.91666 11.667C10.0819 11.667 10.2205 11.6111 10.3323 11.4993C10.4441 11.3875 10.5 11.2489 10.5 11.0837C10.5 10.9184 10.4441 10.7798 10.3323 10.668C10.2205 10.5562 10.0819 10.5003 9.91666 10.5003C9.75138 10.5003 9.61284 10.5562 9.50103 10.668C9.38923 10.7798 9.33332 10.9184 9.33332 11.0837C9.33332 11.2489 9.38923 11.3875 9.50103 11.4993C9.61284 11.6111 9.75138 11.667 9.91666 11.667ZM2.91666 7.58366C3.08193 7.58366 3.22048 7.52776 3.33228 7.41595C3.44409 7.30415 3.49999 7.1656 3.49999 7.00033C3.49999 6.83505 3.44409 6.69651 3.33228 6.5847C3.22048 6.4729 3.08193 6.41699 2.91666 6.41699C2.75138 6.41699 2.61284 6.4729 2.50103 6.5847C2.38923 6.69651 2.33332 6.83505 2.33332 7.00033C2.33332 7.1656 2.38923 7.30415 2.50103 7.41595C2.61284 7.52776 2.75138 7.58366 2.91666 7.58366ZM9.91666 3.50033C10.0819 3.50033 10.2205 3.44442 10.3323 3.33262C10.4441 3.22081 10.5 3.08227 10.5 2.91699C10.5 2.75171 10.4441 2.61317 10.3323 2.50137C10.2205 2.38956 10.0819 2.33366 9.91666 2.33366C9.75138 2.33366 9.61284 2.38956 9.50103 2.50137C9.38923 2.61317 9.33332 2.75171 9.33332 2.91699C9.33332 3.08227 9.38923 3.22081 9.50103 3.33262C9.61284 3.44442 9.75138 3.50033 9.91666 3.50033Z" fill="currentColor" fill-opacity="0.8"/> 4714 </svg> 4715 ${SwymStorefrontLayoutContext?.Strings?.shareCollectionCta} 4716 </div>`; 4717 this.addEditCollectionEventListeners(); 4718 } 4719 } 4720
4721 async renderShareSFLWishlist() { 4722 // Check if user is logged in before showing share option 4723 const isLoggedIn = window._swat?.platform?.isLoggedIn(); 4724 if(!isLoggedIn) { 4725 this.elements.optionsContainer.innerHTML = ''; 4726 return; 4727 } 4728 4729 if(this.elements.optionsContainer) { 4730 this.elements.optionsContainer.innerHTML = `<div class="swym-storefront-layout-action-tooltip-btn share-collection-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.shareCollectionCta || 'Share collection'}"> 4731 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true"> 4732 <path d="M9.91666 12.8337C9.43055 12.8337 9.01735 12.6635 8.67707 12.3232C8.3368 11.983 8.16666 11.5698 8.16666 11.0837C8.16666 11.0253 8.18124 10.8892 8.21041 10.6753L4.11249 8.28366C3.95693 8.42949 3.77707 8.54373 3.57291 8.62637C3.36874 8.70901 3.14999 8.75033 2.91666 8.75033C2.43055 8.75033 2.01735 8.58019 1.67707 8.23991C1.3368 7.89963 1.16666 7.48644 1.16666 7.00033C1.16666 6.51421 1.3368 6.10102 1.67707 5.76074C2.01735 5.42046 2.43055 5.25033 2.91666 5.25033C3.14999 5.25033 3.36874 5.29164 3.57291 5.37428C3.77707 5.45692 3.95693 5.57116 4.11249 5.71699L8.21041 3.32533C8.19096 3.25727 8.17881 3.19164 8.17395 3.12845C8.16909 3.06526 8.16666 2.99477 8.16666 2.91699C8.16666 2.43088 8.3368 2.01769 8.67707 1.67741C9.01735 1.33713 9.43055 1.16699 9.91666 1.16699C10.4028 1.16699 10.816 1.33713 11.1562 1.67741C11.4965 2.01769 11.6667 2.43088 11.6667 2.91699C11.6667 3.4031 11.4965 3.8163 11.1562 4.15658C10.816 4.49685 10.4028 4.66699 9.91666 4.66699C9.68332 4.66699 9.46457 4.62567 9.26041 4.54303C9.05624 4.46039 8.87638 4.34616 8.72082 4.20033L4.62291 6.59199C4.64235 6.66005 4.6545 6.72567 4.65936 6.78887C4.66423 6.85206 4.66666 6.92255 4.66666 7.00033C4.66666 7.0781 4.66423 7.14859 4.65936 7.21178C4.6545 7.27498 4.64235 7.3406 4.62291 7.40866L8.72082 9.80033C8.87638 9.65449 9.05624 9.54026 9.26041 9.45762C9.46457 9.37498 9.68332 9.33366 9.91666 9.33366C10.4028 9.33366 10.816 9.5038 11.1562 9.84408C11.4965 10.1844 11.6667 10.5975 11.6667 11.0837C11.6667 11.5698 11.4965 11.983 11.1562 12.3232C10.816 12.6635 10.4028 12.8337 9.91666 12.8337ZM9.91666 11.667C10.0819 11.667 10.2205 11.6111 10.3323 11.4993C10.4441 11.3875 10.5 11.2489 10.5 11.0837C10.5 10.9184 10.4441 10.7798 10.3323 10.668C10.2205 10.5562 10.0819 10.5003 9.91666 10.5003C9.75138 10.5003 9.61284 10.5562 9.50103 10.668C9.38923 10.7798 9.33332 10.9184 9.33332 11.0837C9.33332 11.2489 9.38923 11.3875 9.50103 11.4993C9.61284 11.6111 9.75138 11.667 9.91666 11.667ZM2.91666 7.58366C3.08193 7.58366 3.22048 7.52776 3.33228 7.41595C3.44409 7.30415 3.49999 7.1656 3.49999 7.00033C3.49999 6.83505 3.44409 6.69651 3.33228 6.5847C3.22048 6.4729 3.08193 6.41699 2.91666 6.41699C2.75138 6.41699 2.61284 6.4729 2.50103 6.5847C2.38923 6.69651 2.33332 6.83505 2.33332 7.00033C2.33332 7.1656 2.38923 7.30415 2.50103 7.41595C2.61284 7.52776 2.75138 7.58366 2.91666 7.58366ZM9.91666 3.50033C10.0819 3.50033 10.2205 3.44442 10.3323 3.33262C10.4441 3.22081 10.5 3.08227 10.5 2.91699C10.5 2.75171 10.4441 2.61317 10.3323 2.50137C10.2205 2.38956 10.0819 2.33366 9.91666 2.33366C9.75138 2.33366 9.61284 2.38956 9.50103 2.50137C9.38923 2.61317 9.33332 2.75171 9.33332 2.91699C9.33332 3.08227 9.38923 3.22081 9.50103 3.33262C9.61284 3.44442 9.75138 3.50033 9.91666 3.50033Z" fill="currentColor" fill-opacity="0.8"/> 4733 </svg> 4734 ${SwymStorefrontLayoutContext?.Strings?.shareCollectionCta} 4735 </div>`; 4736 this.addShareSFLEventListeners(); 4737 } 4738 } 4739 4740 addEditCollectionEventListeners() { 4741 const shareCollectionBtn = this.querySelector('.share-collection-button'); 4742 const deleteCollectionBtn = this.querySelector('.delete-list-button'); 4743 this.listId = SwymStorefrontLayoutContext?.sflList?.lid; 4744 4745 shareCollectionBtn?.addEventListener('click', () => { 4746 this.shareCollection(); 4747 }); 4748 4749 // Keyboard support for share button 4750 shareCollectionBtn?.addEventListener('keydown', (e) => { 4751 if (e.key === 'Enter' || e.key === ' ') { 4752 e.preventDefault(); 4753 this.shareCollection(); 4754 } 4755 }); 4756 4757 deleteCollectionBtn?.addEventListener('click', async () => {
4758 try { 4759 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.deleteLists([this.list]); 4760 this.closeTooltip(); 4761 window.location.hash = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.List; 4762 SwymStorefrontLayoutExtensions?.refreshWishList(); 4763 const collectionListView = document.querySelector('swym-storefront-layout-collection-list'); 4764 collectionListView.closeCollectinListView(); 4765 4766 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 4767 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageCollectionDeleted, 4768 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Toast, 4769 iconType: SwymStorefrontLayoutContext?.NotificationIconType?.SuccessIcon 4770 }); 4771 4772 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutCollectionDeleted, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutCollectionInteractions }); 4773 4774 } catch (error) { 4775 window._swat?.utils.error('Failed to delete collection:', error); 4776 } 4777 }); 4778 4779 // Keyboard support for delete button 4780 deleteCollectionBtn?.addEventListener('keydown', (e) => { 4781 if (e.key === 'Enter' || e.key === ' ') { 4782 e.preventDefault(); 4783 deleteCollectionBtn.click(); 4784 } 4785 }); 4786 } 4787 4788 addShareSFLEventListeners() { 4789 const shareCollectionBtn = this.querySelector('.share-collection-button'); 4790 shareCollectionBtn?.addEventListener('click', () => { 4791 this.shareCollection(SwymStorefrontLayoutContext.sflList); 4792 }); 4793 4794 // Keyboard support for share button 4795 shareCollectionBtn?.addEventListener('keydown', (e) => { 4796 if (e.key === 'Enter' || e.key === ' ') { 4797 e.preventDefault(); 4798 this.shareCollection(SwymStorefrontLayoutContext.sflList); 4799 } 4800 }); 4801 } 4802 4803 addOptionEventListeners() { 4804 const addToCollectionBtn = this.querySelector('.add-to-collection-button'); 4805 const removeFromListBtn = this.querySelector('.remove-from-list-button'); 4806 4807 addToCollectionBtn?.addEventListener('click', () => { 4808 this.closeTooltip(); 4809 4810 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActions?.setData({ 4811 listId: this.listId, 4812 list: this.list, 4813 item: this.listItem, 4814 collections: this.collections, 4815 actionType: SwymStorefrontLayoutContext?.ActionTypes.AddToCollection, 4816 canCreateLists: this.canCreateLists 4817 }); 4818 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActions?.openDrawer(); 4819 4820 }); 4821 4822 removeFromListBtn?.addEventListener('click', async () => { 4823 try { 4824 4825 this.closeTooltip(); 4826 4827 let { epi, empi, du } = this.listItem; 4828 if(this.currentSlectedVariantId){ 4829 epi = this.currentSlectedVariantId; 4830 } 4831 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.removeProductFromList({ epi, empi, du }, this.listId); 4832 4833 // Try to find and remove the element from whichever container it's in 4834 let elementDeleted = this.elements.wishlistItemsContainer?.querySelector(`[data-epi="${epi}"]`) || 4835 this.elements.collectionItemsContainer?.querySelector(`[data-epi="${epi}"]`); 4836 elementDeleted?.remove(); 4837 4838 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutItemRemovedFromList, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi }); 4839 4840 } catch (error) { 4841 window._swat?.utils.error('Failed to remove product from the list:', error); 4842 } 4843 }); 4844 } 4845 4846 async renderEditCollection() { 4847 if (this.list && this.elements.optionsContainer) { 4848 // Check if user is logged in before showing share option 4849 const isLoggedIn = window._swat?.platform?.isLoggedIn(); 4850 4851 const shareButton = isLoggedIn ? ` 4852 <div class="swym-storefront-layout-action-tooltip-btn share-collection-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.shareCollectionCta || 'Share collection'}"> 4853 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true"> 4854 <path d="M9.91666 12.8337C9.43055 12.8337 9.01735 12.6635 8.67707 12.3232C8.3368 11.983 8.16666 11.5698 8.16666 11.0837C8.16666 11.0253 8.18124 10.8892 8.21041 10.6753L4.11249 8.28366C3.95693 8.42949 3.77707 8.54373 3.57291 8.62637C3.36874 8.70901 3.14999 8.75033 2.91666 8.75033C2.43055 8.75033 2.01735 8.58019 1.67707 8.23991C1.3368 7.89963 1.16666 7.48644 1.16666 7.00033C1.16666 6.51421 1.3368 6.10102 1.67707 5.76074C2.01735 5.42046 2.43055 5.25033 2.91666 5.25033C3.14999 5.25033 3.36874 5.29164 3.57291 5.37428C3.77707 5.45692 3.95693 5.57116 4.11249 5.71699L8.21041 3.32533C8.19096 3.25727 8.17881 3.19164 8.17395 3.12845C8.16909 3.06526 8.16666 2.99477 8.16666 2.91699C8.16666 2.43088 8.3368 2.01769 8.67707 1.67741C9.01735 1.33713 9.43055 1.16699 9.91666 1.16699C10.4028 1.16699 10.816 1.33713 11.1562 1.67741C11.4965 2.01769 11.6667 2.43088 11.6667 2.91699C11.6667 3.4031 11.4965 3.8163 11.1562 4.15658C10.816 4.49685 10.4028 4.66699 9.91666 4.66699C9.68332 4.66699 9.46457 4.62567 9.26041 4.54303C9.05624 4.46039 8.87638 4.34616 8.72082 4.20033L4.62291 6.59199C4.64235 6.66005 4.6545 6.72567 4.65936 6.78887C4.66423 6.85206 4.66666 6.92255 4.66666 7.00033C4.66666 7.0781 4.66423 7.14859 4.65936 7.21178C4.6545 7.27498 4.64235 7.3406 4.62291 7.40866L8.72082 9.80033C8.87638 9.65449 9.05624 9.54026 9.26041 9.45762C9.46457 9.37498 9.68332 9.33366 9.91666 9.33366C10.4028 9.33366 10.816 9.5038 11.1562 9.84408C11.4965 10.1844 11.6667 10.5975 11.6667 11.0837C11.6667 11.5698 11.4965 11.983 11.1562 12.3232C10.816 12.6635 10.4028 12.8337 9.91666 12.8337ZM9.91666 11.667C10.0819 11.667 10.2205 11.6111 10.3323 11.4993C10.4441 11.3875 10.5 11.2489 10.5 11.0837C10.5 10.9184 10.4441 10.7798 10.3323 10.668C10.2205 10.5562 10.0819 10.5003 9.91666 10.5003C9.7
48545138 10.5003 9.61284 10.5562 9.50103 10.668C9.38923 10.7798 9.33332 10.9184 9.33332 11.0837C9.33332 11.2489 9.38923 11.3875 9.50103 11.4993C9.61284 11.6111 9.75138 11.667 9.91666 11.667ZM2.91666 7.58366C3.08193 7.58366 3.22048 7.52776 3.33228 7.41595C3.44409 7.30415 3.49999 7.1656 3.49999 7.00033C3.49999 6.83505 3.44409 6.69651 3.33228 6.5847C3.22048 6.4729 3.08193 6.41699 2.91666 6.41699C2.75138 6.41699 2.61284 6.4729 2.50103 6.5847C2.38923 6.69651 2.33332 6.83505 2.33332 7.00033C2.33332 7.1656 2.38923 7.30415 2.50103 7.41595C2.61284 7.52776 2.75138 7.58366 2.91666 7.58366ZM9.91666 3.50033C10.0819 3.50033 10.2205 3.44442 10.3323 3.33262C10.4441 3.22081 10.5 3.08227 10.5 2.91699C10.5 2.75171 10.4441 2.61317 10.3323 2.50137C10.2205 2.38956 10.0819 2.33366 9.91666 2.33366C9.75138 2.33366 9.61284 2.38956 9.50103 2.50137C9.38923 2.61317 9.33332 2.75171 9.33332 2.91699C9.33332 3.08227 9.38923 3.22081 9.50103 3.33262C9.61284 3.44442 9.75138 3.50033 9.91666 3.50033Z" fill="currentColor" fill-opacity="0.8"/> 4855 </svg> 4856 ${SwymStorefrontLayoutContext?.Strings?.shareCollectionCta} 4857 </div>` : ''; 4858 4859 this.elements.optionsContainer.innerHTML = ` 4860 ${shareButton} 4861 <div class="swym-storefront-layout-action-tooltip-btn delete-list-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.deleteCollectionCta || 'Delete collection'}"> 4862 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" viewBox="0 0 16 17" fill="currentColor" aria-hidden="true"> 4863 <path d="M4.66667 14.5C4.3000114.5 3.98612 14.3694 3.72501 14.1083C3.46389 13.8472 3.33334 13.5333 3.33334 13.1667V4.5H2.66667V3.16667H6.00001V2.5H10V3.16667H13.3333V4.5H12.6667V13.1667C12.6667 13.5333 12.5361 13.8472 12.275 14.1083C12.0139 14.3694 11.7 14.5 11.3333 14.5H4.66667ZM11.3333 4.5H4.66667V13.1667H11.3333V4.5ZM6.00001 11.8333H7.33334V5.83333H6.00001V11.8333ZM8.66667 11.8333H10V5.83333H8.66667V11.8333Z" fill="currentColor"/> 4864 </svg> 4865 ${SwymStorefrontLayoutContext?.Strings?.deleteCollectionCta} 4866 </div> 4867 `; 4868 this.addEditCollectionEventListeners(); 4869 } 4870 } 4871 4872 addEditCollectionEventListeners() { 4873 const shareCollectionBtn = this.querySelector('.share-collection-button'); 4874 const deleteListBtn = this.querySelector('.delete-list-button'); 4875 4876 shareCollectionBtn?.addEventListener('click', () => { 4877 this.shareCollection(); 4878 this.closeTooltip(); 4879 }); 4880 4881 // Keyboard support for share button 4882 shareCollectionBtn?.addEventListener('keydown', (e) => { 4883 if (e.key === 'Enter' || e.key === ' ') { 4884 e.preventDefault(); 4885 this.shareCollection(); 4886 this.closeTooltip(); 4887 } 4888 }); 4889 4890 deleteListBtn?.addEventListener('click', async () => { 4891 try { 4892 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.deleteLists([this.list]); 4893 this.closeTooltip(); 4894 window.location.hash = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.List; 4895 SwymStorefrontLayoutExtensions?.refreshWishList(); 4896 const collectionListView = document.querySelector('swym-storefront-layout-collection-list'); 4897 collectionListView.closeCollectinListView(); 4898 4899 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 4900 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageCollectionDeleted, 4901 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Toast, 4902 iconType: SwymStorefrontLayoutContext?.NotificationIconType?.SuccessIcon 4903 }); 4904 4905 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutCollectionDeleted, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutCollectionInteractions }); 4906 4907 } catch (error) { 4908 window._swat?.utils.error('Failed to remove product from the list:', error); 4909 } 4910 }); 4911 4912 // Keyboard support for delete button 4913 deleteListBtn?.addEventListener('keydown', (e) => { 4914 if (e.key === 'Enter' || e.key === ' ') { 4915 e.preventDefault(); 4916 deleteListBtn.click(); 4917 } 4918 }); 4919 } 4920
4921 async renderSaveCollection() { 4922 if (this.list && this.elements.optionsContainer) { 4923 this.elements.optionsContainer.innerHTML = ` 4924 <div class="swym-storefront-layout-action-tooltip-btn save-collection-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.saveCollectionCta || 'Save collection'}"> 4925 <svg xmlns="http://www.w3.org/2000/svg" width="12px" height="12px" viewBox="0 0 48 48" fill="none" aria-hidden="true"> 4926 <path d="M8 28H24" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> 4927 <path d="M8 37H24" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> 4928 <path d="M8 19H40" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> 4929 <path d="M8 10H40" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> 4930 <path d="M30 33H40" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> 4931 <path d="M35 28L35 38" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> 4932 </svg> 4933 ${SwymStorefrontLayoutContext?.Strings?.saveCollectionCta} 4934 </div> 4935 `; 4936 this.addSaveCollectionEventListeners(); 4937 } 4938 } 4939 4940 addSaveCollectionEventListeners() { 4941 const saveCollectionBtn = this.querySelector('.save-collection-button'); 4942 4943 saveCollectionBtn?.addEventListener('click', () => { 4944 this.closeTooltip(); 4945 this.handleSaveCollection(); 4946 }); 4947 } 4948 async shareCollection() { 4949 const activeTab = document.querySelector('.swym-storefront-layout-tab-button.swym-storefront-layout-tab-button-active'); 4950 const activeDataView = activeTab?.getAttribute('data-view'); 4951 window._swat.evtLayer.dispatchEvent(new CustomEvent('sw:renderShareWishlistUI', { 4952 detail: { 4953 lid: this.list?.lid || this.listId, 4954 lname: '', 4955 list: activeDataView === "tabSavedForLater" ? SwymStorefrontLayoutContext?.sflList : this.list, 4956 layoutType: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType, 4957 senderName: '', // optional, 4958 activeDataView 4959 } 4960 })); 4961 } 4962 4963 async handleSaveCollection(){ 4964 let savedList = await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.createList(this.list.lname); 4965 let itemsToAdd = this.list.listcontents.map(({ empi, epi, du }) => ({ empi, epi, du })); 4966 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.addProductsToList(itemsToAdd, savedList.lid); 4967 window.location.hash = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.List; 4968 4969 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 4970 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageCollectionSaved, 4971 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Neutral, 4972 iconType: SwymStorefrontLayoutContext?.NotificationIconType?.SuccessIcon, 4973 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 4974 action: { 4975 label: SwymStorefrontLayoutContext?.Strings?.notificationActionViewCollection, 4976 onClick: () => { 4977 window.location.hash = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.CollectionList + savedList.lid; 4978 } 4979 } 4980 }); 4981 4982 } 4983 4984 addEventListenerToView() { 4985 document.addEventListener("click", (event) => { 4986 if (!this.elements.tooltipLayout.contains(event.target)) { 4987 this.closeTooltip(); 4988 } 4989 }); 4990 } 4991 4992 async renderSflListItemOptions(){ 4993 if (this.listItem && this.elements.optionsContainer) { 4994 let selectedVariantId = this.currentSlectedVariantId || this.listItem?.epi; 4995 const selectedVariant = SwymStorefrontLayoutContext?.getSelectedVariant(this.listItem, selectedVariantId); 4996 let canEdit = SwymStorefrontLayoutContext?.checkUserCanEditList(this.list); 4997 this.elements.optionsContainer.innerHTML = ` 4998 ${canEdit?`<div class="swym-storefront-layout-action-tooltip-btn remove-from-list-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.removeSflItemCta || 'Remove from list'}"> 4999 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" viewBox="0 0 16 17" fill="currentColor" aria-hidden="true"> 5000 <path d="M4.66669 14.5C4.30002 14.5 3.98613 14.3694 3.72502 14.1083C3.46391 13.8472 3.33335 13.5333 3.33335 13.1667V4.5H2.66669V3.16667H6.00002V2.5H10V3.16667H13.3334V4.5H12.6667V13.1667C12.6667 13.5333 12.5361 13.8472 12.275 14.1083C12.0139 14.3694 11.7 14.5 11.3334 14.5H4.66669ZM11.3334 4.5H4.66669V13.1667H11.3334V4.5ZM6.00002 11.8333H7.33335V5.83333H6.00002V11.8333ZM8.66669 11.8333H10V5.83333H8.66669V11.8333Z" fill="currentColor"></path> 5001 </svg> 5002 ${SwymStorefrontLayoutContext?.Strings?.removeSflItemCta || 'Remove from list'} 5003 </div>`:''} 5004 `; 5005 this.addSflOptionEventListeners(); 5006 } 5007 } 5008
5009 async renderSfyListItemOptions(){ 5010 if (this.listItem && this.elements.optionsContainer) { 5011 this.elements.optionsContainer.innerHTML = ` 5012 <div class="swym-storefront-layout-action-tooltip-btn swym-sfy-remove-button" role="button" tabindex="0" aria-label="${SwymStorefrontLayoutContext?.Strings?.removeItemCta || 'Remove'}"> 5013 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" viewBox="0 0 16 17" fill="currentColor" aria-hidden="true"> 5014 <path d="M4.66669 14.5C4.30002 14.5 3.98613 14.3694 3.72502 14.1083C3.46391 13.8472 3.33335 13.5333 3.33335 13.1667V4.5H2.66669V3.16667H6.00002V2.5H10V3.16667H13.3334V4.5H12.6667V13.1667C12.6667 13.5333 12.5361 13.8472 12.275 14.1083C12.0139 14.3694 11.7 14.5 11.3334 14.5H4.66669ZM11.3334 4.5H4.66669V13.1667H11.3334V4.5ZM6.00002 11.8333H7.33335V5.83333H6.00002V11.8333ZM8.66669 11.8333H10V5.83333H8.66669V11.8333Z" fill="currentColor"></path> 5015 </svg> 5016 ${SwymStorefrontLayoutContext?.Strings?.removeItemCta || 'Remove'} 5017 </div> 5018 `; 5019 this.addSfyOptionEventListeners(); 5020 } 5021 } 5022 5023 addSfyOptionEventListeners() { 5024 const removeBtn = this.querySelector('.swym-sfy-remove-button'); 5025 removeBtn?.addEventListener('click', async () => { 5026 try { 5027 this.closeTooltip(); 5028 const { epi, empi, du } = this.listItem; 5029 const lid = this.listId; 5030 5031 // Remove item from ALL context references (String comparison for type safety) 5032 SwymStorefrontLayoutContext.updateAllListContents((listcontents = []) => 5033 listcontents.filter(item => String(item.epi) !== String(epi)) 5034 ); 5035 5036 // Dispatch WishlistFetched to update UI immediately 5037 if (SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched) { 5038 document.dispatchEvent(new CustomEvent(SwymStorefrontLayoutContext.CustomEvents.WishlistFetched, { 5039 detail: { 5040 defaultList: SwymStorefrontLayoutContext.DefaultList, 5041 collections: SwymStorefrontLayoutContext.collections, 5042 lists: SwymStorefrontLayoutContext.lists 5043 } 5044 })); 5045 } 5046 5047 SwymStorefrontLayoutExtensions?.Notification?.setMessage({ 5048 message: SwymStorefrontLayoutContext?.Strings?.notificationMessageItemRemoved, 5049 status: SwymStorefrontLayoutContext?.NotificationStatusTypes?.Toast, 5050 duration: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutNotificationDuration, 5051 image: this.listItem?.iu 5052 }); 5053 5054 // Fire API THEN refresh to reconcile with server 5055 await SwymStorefrontLayoutAPI?.SwymWLAsyncApis?.removeProductFromList({ epi, empi, du }, lid); 5056 SwymStorefrontLayoutExtensions?.refreshWishList(); 5057 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFYItemRemoved, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutItemInteractions, epi, empi }); 5058 } catch (err) { 5059 window._swat?.utils.error('[SFY] Remove failed', err); 5060 } 5061 }); 5062 removeBtn?.addEventListener('keydown', (e) => { 5063 if (e.key === 'Enter' || e.key === ' ') { 5064 e.preventDefault(); 5065 removeBtn.click(); 5066 } 5067 }); 5068 } 5069 5070 addSflOptionEventListeners() { 5071 const removeFromListBtn = this.querySelector('.remove-from-list-button'); 5072 5073 removeFromListBtn?.addEventListener('click', async () => { 5074 try { 5075 5076 this.closeTooltip(); 5077 5078 let { epi, empi, du } = this.listItem; 5079 if(this.currentSlectedVariantId){ 5080 epi = this.currentSlectedVariantId; 5081 } 5082 await SwymStorefrontLayoutAPI?.SwymSFLAsyncApis?.removeProductFromSFL({ epi, empi, du }, this.listId); 5083 5084 let elementDeleted = this.elements.sflItemsContainer?.querySelector(`[data-epi="${epi}"]`); 5085 elementDeleted?.remove(); 5086 5087 window._swat?.triggerSwymEvent(window._swat?.JSEvents?.removedFromSFL, this.listItem); 5088 5089 } catch (error) { 5090 window._swat?.utils.error('Failed to remove product from the list:', error); 5091 } 5092 }); 5093 } 5094 5095 attachEventToHideTooltip(){ 5096 5097 window.removeEventListener("scroll", ()=> this.closeTooltip()); 5098 window.addEventListener("scroll", ()=> this.closeTooltip()); 5099 5100 let scrollableElements = document.querySelectorAll('.swym-storefront-layout-scrollable'); 5101
5102 scrollableElements.forEach((element) => { 5103 element.removeEventListener("scroll", ()=> this.closeTooltip()); 5104 element.addEventListener("scroll", ()=> this.closeTooltip()); 5105 }); 5106 } 5107 5108 openTooltip() { 5109 this.isTooltipOpen = true; 5110 this._closedViaKeyboard = false; 5111 const tl = this.elements.tooltipLayout; 5112 5113 // Abort any previous keydown listener to prevent accumulation 5114 if (this._tooltipKeydownController) { 5115 this._tooltipKeydownController.abort(); 5116 } 5117 this._tooltipKeydownController = new AbortController(); 5118 5119 tl.classList.add("swym-storefront-layout-show-action-tooltip-view"); 5120 tl.setAttribute("role", "dialog"); 5121 tl.setAttribute("aria-label", "Wishlist options"); 5122 tl.setAttribute("aria-hidden", "false"); 5123 tl.setAttribute("tabindex", "-1"); 5124 5125 const firstBtn = this.elements.optionsContainer?.querySelector('.swym-storefront-layout-action-tooltip-btn'); 5126 if (firstBtn) { 5127 firstBtn.focus(); 5128 } else { 5129 tl.focus(); 5130 } 5131 5132 tl.addEventListener( 5133 "keydown", 5134 (e) => { 5135 if (["Enter", " "].includes(e.key)) { 5136 const activeBtn = document.activeElement?.closest('[role="button"]'); 5137 if (activeBtn && tl.contains(activeBtn)) { 5138 e.preventDefault(); 5139 activeBtn.click(); 5140 } 5141 } 5142 if (e.key === "Escape") { 5143 e.preventDefault(); 5144 this._closedViaKeyboard = true; 5145 this.closeTooltip(); 5146 } 5147 }, 5148 { signal: this._tooltipKeydownController.signal } 5149 ); 5150 5151 this.attachEventToHideTooltip(); 5152 } 5153 5154 showOnTarget(buttonTarget){ 5155 this.closeTooltip(); 5156 5157 let viewPort = document.querySelector('.swym-storefront-layout-layout'); 5158 5159 const tooltip = this.elements.tooltipLayout; 5160 let buttonRect = buttonTarget.getBoundingClientRect(); 5161 let viewPortRect = viewPort?.getBoundingClientRect(); 5162 5163 const buttonRight = buttonRect.right; 5164 const buttonLeft = buttonRect.left; 5165 const buttonLeftOnViewPort = buttonRect.left - viewPortRect?.left; 5166 5167 const tooltipWidth = this.elements.tooltipLayout.offsetWidth 5168 const viewportWidth = viewPort.clientWidth 5169 5170 5171 if (buttonLeftOnViewPort > viewportWidth / 2) { 5172 tooltip.style.left = `${(buttonLeft + 2) - tooltipWidth}px` 5173 tooltip.style.top = `${buttonRect.top + 25}px` 5174 } else { 5175 tooltip.style.left = `${buttonLeft + 2 }px` 5176 tooltip.style.top = `${buttonRect.top + 25}px` 5177 } 5178 5179 setTimeout(()=>{ 5180 this.openTooltip(); 5181 this.currentTarget = buttonTarget; 5182 },100) 5183 5184 } 5185 5186 closeTooltip() { 5187 this.isTooltipOpen = false; 5188 const tl = this.elements.tooltipLayout; 5189 tl.classList.remove('swym-storefront-layout-show-action-tooltip-view'); 5190 tl.setAttribute("aria-hidden", "true"); 5191 tl.removeAttribute("role"); 5192 tl.removeAttribute("aria-label"); 5193 if (this._tooltipKeydownController) { 5194 this._tooltipKeydownController.abort(); 5195 this._tooltipKeydownController = null; 5196 } 5197 if (this._closedViaKeyboard && this.currentTarget) { 5198 this.currentTarget.focus(); 5199 } 5200 this._closedViaKeyboard = false; 5201 } 5202 5203} 5204 5205/** 5206 * SwymStorefrontLayoutNotification 5207 * 5208 * A Web Component for displaying notifications in the storefront layout. 5209 * It supports customizable configurations, auto-hide functionality, 5210 * and dynamic content rendering based on user actions. 5211 * 5212 * Features: 5213 * - Supports various notification types (success, info, etc.). 5214 * - Configurable options for displaying images, titles, messages, and action buttons. 5215 * - Auto-hide functionality with configurable duration. 5216 * - Supports icons and images based on notification type. 5217 * - Integrates with `SwymStorefrontLayoutContext?.Settings` to check if notifications are enabled. 5218 * 5219 * Dependencies: 5220 * - `SwymStorefrontLayoutContext`: Provides settings and icon type references. 5221 * 5222 * Usage: 5223 * ```html 5224 * <swym-storefront-layout-notification></swym-storefront-layout-notification> 5225 * ``` 5226 * 5227 * Example JavaScript Usage: 5228 * ```javascript 5229 * const notification = document.querySelector('swym-storefront-layout-notification'); 5230 * notification.setMessage({ 5231 * message: 'Item added to wishlist!', 5232 * status: 'success', 5233 * duration: 4000, 5234 * image: 'https://example.com/image.jpg', 5235 * action: { 5236 * label: 'View Wishlist', 5237 * onClick: () => console.log('Wishlist opened') 5238 * } 5239 * }); 5240 * ``` 5241 * 5242 * Attributes: 5243 * - `status` (String) - Defines the notification status (e.g., `success`, `info`, `error`). 5244 * - `role` (String) - Defaults to `"alert"` for accessibility. 5245 * 5246 * Methods: 5247 * - `setMessage(data, config)`: Sets notification content and displays it. 5248 * - `setNotificationConfig(config)`: Merges user-defined configuration with defaults. 5249 * - `renderUI()`: Updates the UI based on the provided notification data. 5250 * - `autoHide(duration)`: Automatically hides the notification after a specified duration. 5251 * - `showNotification()`: Displays the notification. 5252 * - `hideNotification()`: Hides the notification. 5253 * 5254 * Events: 5255 * - `click` on `.swym-storefront-layout-notification-action` triggers the provided action callback. 5256 */ 5257class SwymStorefrontLayoutNotification extends HTMLElement { 5258 constructor() { 5259 super(); 5260 this.timeout = null; 5261 this.notificationData = null; 5262 this.defaultConfig = { 5263 showProgress: false, 5264 showImage: true,
5265 showActionButton: false, 5266 showMessage: true, 5267 showTitle: false 5268 }; 5269 5270 this.notificationConfig = this.defaultConfig; 5271 } 5272 5273 connectedCallback() { 5274 this.setAttribute('role', this.getAttribute('role') || 'alert'); 5275 this.setAttribute('status', this.getAttribute('status') || 'info'); 5276 } 5277 5278 setMessage(data, config = {}) { 5279 // Login nudge is intentionally persistent — suppress transient toasts while it is visible. 5280 // Pass config.priority = 'high' (e.g. errors) to break through and dismiss the nudge. 5281 if (this.hasAttribute('data-nudge-active')) { 5282 if (config.priority !== 'high') return; 5283 this.removeAttribute('data-nudge-active'); 5284 } 5285 const { 5286 actionType, message, title, status = 'success', duration = 3000, action, image, product, icon, iconType 5287 } = data; 5288 5289 this.notificationData = { 5290 actionType, message, status, duration, action, image, product, icon, iconType, 5291 title: title || product?.dt || null 5292 }; 5293 5294 if (SwymStorefrontLayoutContext?.Settings?.EnableStorefrontLayoutNotification) { 5295 this.setAttribute('status', status); 5296 this.setNotificationConfig(config); 5297 this.renderUI(); 5298 this.autoHide(duration); 5299 } 5300 } 5301 5302 setNotificationConfig(config) { 5303 this.notificationConfig = { ...this.defaultConfig, ...config }; 5304 } 5305 5306 renderUI() { 5307 if (!this.notificationData) return; 5308 5309 const { message, image, iconType, title, action } = this.notificationData; 5310 const { showProgress, showImage, showActionButton, showMessage, showTitle } = this.notificationConfig; 5311 5312 this.innerHTML = ` 5313 ${showProgress ? '<div class="swym-storefront-layout-notification-progress-bar swym-storefront-notification-animated"></div>' : ''} 5314 <div class="swym-storefront-layout-notification-container"> 5315 <div class="swym-storefront-layout-notification-content-container"> 5316 ${showImage && (image || iconType) ?`<div class="swym-storefront-layout-notification-image-container"> 5317 ${ image ? `<img class="swym-storefront-layout-notification-image" src="${image}" alt="Notification Icon">` : ''} 5318 ${iconType ? this.getIconHtml(iconType) : ''} 5319 </div>`:''} 5320 <div class="swym-storefront-layout-notification-content"> 5321 ${showTitle && title ? `<div class="swym-storefront-layout-notification-title">${title}</div>` : ''} 5322 ${showMessage && message ? `<div class="swym-storefront-layout-notification-message">${message}</div>` : ''} 5323 </div> 5324 </div> 5325 ${showActionButton && action ? `<button class="swym-storefront-layout-notification-action">${action.label}</button>` : ''} 5326 </div> 5327 `; 5328 5329 if (action) { 5330 this.querySelector('.swym-storefront-layout-notification-action')?.addEventListener('click', (event) => { 5331 this.hideNotification(); 5332 action.onClick?.(event); 5333 }); 5334 } 5335 } 5336 5337 getIconHtml(iconType) { 5338 if (iconType === SwymStorefrontLayoutContext?.NotificationIconType?.SuccessIcon) { 5339 return `<svg viewBox="0 0 18 18" fill="none" class="swym-storefront-layout-notification-icon" aria-hidden="true"> 5340 <path d="m12.053 5.429-.62.645-3.782 3.98-1.131-1.04-.654-.602-1.152 1.384.661.603 1.745 1.606.613.566 5.553-5.844-1.233-1.298Z" fill="currentColor"></path> 5341 </svg>`; 5342 } 5343 return ''; 5344 } 5345 5346 autoHide(duration) { 5347 clearTimeout(this.timeout); 5348 this.showNotification(); 5349 this.timeout = setTimeout(() => this.hideNotification(), duration); 5350 } 5351 5352 showNotification() { 5353 this.classList.add('swym-storefront-layout-notification-visible'); 5354 } 5355 5356 hideNotification() { 5357 this.classList.remove('swym-storefront-layout-notification-visible'); 5358 } 5359 5360 /** 5361 * setLoginNudge — shows a persistent corner notification prompting guest users to 5362 * log in when auto-wishlist has accumulated >= THRESHOLD items this session. 5363 * 5364 * Design choices: 5365 * - Reuses this element (already bottom-left corner, clear of ATC bars). 5366 * - No autoHide — stays until the user acts (login or dismiss). 5367 * - _swat.wrapLogin respects the merchant's configured LoginPath and restores 5368 * the current page after login via the POSTLOGINRD cookie. 5369 * - Two thresholds: first nudge at 3 items (no +N), second nudge at 5 items (3 thumbs + +N). 5370 * - sessionStorage keys are set immediately on show so each threshold fires at most once per session. 5371 */ 5372 setLoginNudge() { 5373 const THRESHOLD_FIRST = 3; 5374 const THRESHOLD_SECOND = 5; 5375 const SESSION_KEY_FIRST = 'swym-login-nudge-3'; 5376 const SESSION_KEY_SECOND = 'swym-login-nudge-5';
5377 const AUTO_DISMISS_MS = 2 * 60 * 1000; 5378 if (!SwymStorefrontLayoutContext.isLoginNudgeEnabled()) return; 5379 if (SwymStorefrontLayoutContext?.isShopperLoggedIn) return; 5380 if (!SwymStorefrontLayoutContext?.Settings?.EnableStorefrontLayoutNotification) return; 5381 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig?.() || {}; 5382 const SFY_LIST_NAME = 'Saved For Me'; 5383 // B1: separate AW vs explicit counts — whichever hits threshold first fires 5384 let awItems, explicitItems; 5385 if (shouldShowMultiple) { 5386 awItems = SwymStorefrontLayoutContext.getSfyList?.()?.listcontents || []; 5387 explicitItems = (SwymStorefrontLayoutContext?.allLists || []) 5388 .filter(list => list.lname !== SFY_LIST_NAME) 5389 .flatMap(list => list.listcontents || []); 5390 } else { 5391 const defaultContents = SwymStorefrontLayoutContext?.DefaultList?.listcontents || []; 5392 awItems = defaultContents.filter(item => item.source === 'auto-wishlist'); 5393 explicitItems = defaultContents.filter(item => item.source !== 'auto-wishlist'); 5394 } 5395 const awCount = awItems.length; 5396 const explicitCount = explicitItems.length; 5397 // Priority: AW threshold-2 > AW threshold-1 > explicit threshold-2 > explicit threshold-1 5398 // Build priority-ordered candidate list, then walk it skipping session-suppressed entries. 5399 // Shared session keys (swym-login-nudge-3 / -5) are intentional: once a threshold nudge 5400 // fires for either AW or explicit items, that same threshold won't fire again this session 5401 // regardless of item type — preventing double-nudging within a session. 5402 const candidates = [ 5403 ...(awCount >= THRESHOLD_SECOND ? [{ sessionKey: SESSION_KEY_SECOND, showExtras: true, nudgeItems: awItems, isAW: true }] : []), 5404 ...(awCount >= THRESHOLD_FIRST ? [{ sessionKey: SESSION_KEY_FIRST, showExtras: fal
5404se, nudgeItems: awItems, isAW: true }] : []), 5405 ...(explicitCount >= THRESHOLD_SECOND ? [{ sessionKey: SESSION_KEY_SECOND, showExtras: true, nudgeItems: explicitItems, isAW: false }] : []), 5406 ...(explicitCount >= THRESHOLD_FIRST ? [{ sessionKey: SESSION_KEY_FIRST, showExtras: false, nudgeItems: explicitItems, isAW: false }] : []), 5407 ]; 5408 let chosen = null; 5409 for (const c of candidates) { 5410 try { if (sessionStorage.getItem(c.sessionKey)) continue; } catch (e) {} 5411 chosen = c; 5412 break; 5413 } 5414 if (!chosen) return; 5415 const { sessionKey, showExtras, nudgeItems, isAW } = chosen; 5416 const InstrumentActionCodes = SwymStorefrontLayoutExtensions?.InstrumentActionCodes; 5417 const InstrumentUtmTerms = SwymStorefrontLayoutExtensions?.InstrumentUtmTerms; 5418 const nudgeCodes = isAW 5419 ? (sessionKey === SESSION_KEY_FIRST 5420 ? { 5421 shown: InstrumentActionCodes?.StorefrontLayoutAWNudge1Shown, 5422 loginClicked: InstrumentActionCodes?.StorefrontLayoutAWNudge1LoginClicked, 5423 dismissed: InstrumentActionCodes?.StorefrontLayoutAWNudge1Dismissed, 5424 } 5425 : { 5426 shown: InstrumentActionCodes?.StorefrontLayoutAWNudge2Shown, 5427 loginClicked: InstrumentActionCodes?.StorefrontLayoutAWNudge2LoginClicked, 5428 dismissed: InstrumentActionCodes?.StorefrontLayoutAWNudge2Dismissed, 5429 }) 5430 : (sessionKey === SESSION_KEY_FIRST 5431 ? { 5432 shown: InstrumentActionCodes?.StorefrontLayoutExplicitNudge1Shown, 5433 loginClicked: InstrumentActionCodes?.StorefrontLayoutExplicitNudge1LoginClicked, 5434 dismissed: InstrumentActionCodes?.StorefrontLayoutExplicitNudge1Dismissed, 5435 } 5436 : { 5437 shown: InstrumentActionCodes?.StorefrontLayoutExplicitNudge2Shown, 5438 loginClicked: InstrumentActionCodes?.StorefrontLayoutExplicitNudge2LoginClicked, 5439 dismissed: InstrumentActionCodes?.StorefrontLayoutExplicitNudge2Dismissed, 5440 }); 5441 const nudgeUtmTerm = isAW 5442 ? InstrumentUtmTerms?.StorefrontLayoutAWLoginNudgeEngagement 5443 : InstrumentUtmTerms?.StorefrontLayoutExplicitLoginNudgeEngagement; 5444 const count = nudgeItems.length; 5445 const strings = SwymStorefrontLayoutContext?.Strings; 5446 const thumbs = nudgeItems.slice(0, THRESHOLD_FIRST); // always first 3 5447 const extraCount = showExtras ? count - THRESHOLD_FIRST : 0; 5448 // XSS: escape item.iu — alt is already escaped with " but src was not 5449 const escAttr = (s) => (s || '').replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>'); 5450 const thumbsHtml = thumbs.map(item => `<img class="swym-aw-nudge-thumb" src="${escAttr(item.iu)}" alt="${escAttr(item.dt)}" loading="lazy" />`).join(''); 5451 const extraHtml = extraCount >
5451 0 ? `<span class="swym-aw-nudge-extra">+${extraCount}</span>` : ''; 5452 // No hardcoded English fallbacks — strings come from strings/*.js locale files via retailerSettings 5453 const boldMessage = (isAW 5454 ? (strings?.awNudgeMessage || '') 5455 : (strings?.explicitNudgeMessage || '') 5456 ).replace('{count}', count); 5457 const subMessage = strings?.awNudgeMessageSub || ''; 5458 const dismissLabel = strings?.awNudgeDismiss || ''; 5459 const loginLabel = strings?.awNudgeLoginCTA || ''; 5460 clearTimeout(this.timeout); 5461 // Use data-nudge-active instead of status to avoid inheriting the dark info background 5462 this.setAttribute('data-nudge-active', ''); 5463 this.removeAttribute('status'); 5464 // Mark as shown immediately — each threshold fires at most once per session 5465 try { sessionStorage.setItem(sessionKey, '1'); } catch (e) {} 5466 this.innerHTML = ` 5467 <div class="swym-aw-nudge-card"> 5468 <div class="swym-aw-nudge-header"> 5469 <div class="swym-aw-nudge-thumbs-row"> 5470 ${thumbsHtml}${extraHtml} 5471 </div> 5472 <button type="button" class="swym-aw-nudge-dismiss-btn" aria-label="${escAttr(dismissLabel)}"><svg width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true" focusable="false"><path d="M1 1l8 8M9 1L1 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button> 5473 </div> 5474 <p class="swym-aw-nudge-message"><strong>${escAttr(boldMessage)}</strong> ${escAttr(subMessage)}</p> 5475 <div class="swym-aw-nudge-login-container"></div> 5476 </div> 5477 `; 5478 const nudgeLoginContainer = this.querySelector('.swym-aw-nudge-login-container'); 5479 const loginUrl = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.Login; 5480 const renderNudgeClassicBtn = () => { 5481 window._swat?.instrumentV3(InstrumentActionCodes?.StorefrontLayoutLoginNudgeShopLoginFallback, { "utm-term": InstrumentUtmTerms?.StorefrontLayoutShopLoginNudgeFallbackEngagement }); 5482 nudgeLoginContainer.innerHTML = `<button type="button" class="swym-aw-nudge-login-btn" aria-label="${escAttr(loginLabel)}">${escAttr(loginLabel)}</button>`; 5483 nudgeLoginContainer.querySelector('.swym-aw-nudge-login-btn')?.addEventListener('click', () => { 5484 clearTimeout(this._nudgeAutoDismissTimer); 5485 window._swat?.instrumentV3(nudgeCodes.loginClicked, { 'utm-term': nudgeUtmTerm }); 5486 window.location.href = loginUrl || '/account/login'; 5487 }); 5488 }; 5489 window._swat?.initShopLogin(nudgeLoginContainer, { 5490 apiKey: SWYM_SHOP_API_KEY, 5491 showFallbackImmediately: true, 5492 loginUrl: loginUrl, 5493 presentationMode: 'card', 5494 onComplete: (event) => { 5495 if (event.signedIn) { 5496 clearTimeout(this._nudgeAutoDismissTimer); 5497 window._swat?.instrumentV3(InstrumentActionCodes?.StorefrontLayoutLoginNudgeShopLoginSuccess, { "utm-term": InstrumentUtmTerms?.StorefrontLayoutShopLoginNudgeSuccessEngagement }); 5498 window.location.reload(); 5499 } else { 5500 window._swat?.instrumentV3(InstrumentActionCodes?.StorefrontLayoutLoginNudgeShopLoginDeclined, { "utm-term": InstrumentUtmTerms?.StorefrontLayoutShopLoginNudgeDeclinedEngagement }); 5501 } 5502 }, 5503 onFallback: renderNudgeClassicBtn, 5504 onShown: () => { 5505 window._swat?.instrumentV3(InstrumentActionCodes?.StorefrontLayoutLoginNudgeShopLoginShown, { "utm-term": InstrumentUtmTerms?.StorefrontLayoutShopLoginNudgeEngagement }); 5506 }, 5507 // Routed into the existing declined code, not a new one: onError means the SDK 5508 // itself broke before the shopper could make a choice, but at the RAT level this 5509 // and a real decline are both "didn't sign in" outcomes for this surface. 5510 onError: () => { 5511 window._swat?.instrumentV3(InstrumentActionCodes?.StorefrontLayoutLoginNudgeShopLoginDeclined, { "utm-term": InstrumentUtmTerms?.StorefrontLayoutShopLoginNudgeDeclinedEngagement }); 5512 } 5513 }); 5514 this.querySelector('.swym-aw-nudge-dismiss-btn')?.addEventListener('click', () => { 5515 clearTimeout(this._nudgeAutoDismissTimer); 5516 nudgeLoginContainer.innerHTML = ''; 5517 window._swat?.instrumentV3(nudgeCodes.dismissed, { "utm-term": nudgeUtmTerm }); 5518 this.removeAttribute('data-nudge-active'); 5519 this.hideNotification(); 5520 }); 5521 this.showNotification(); 5522 window._swat?.instrumentV3(nudgeCodes.shown, { "utm-term": nudgeUtmTerm }); 5523 // Auto-dismiss after 2 minutes — unblocks setMessage callers after a reasonable window 5524 this._nudgeAutoDismissTimer = setTimeout(() => { 5525 if (this.hasAttribute('data-nudge-active')) { 5526 this.removeAttribute('data-nudge-active'); 5527 this.hideNotification(); 5528 } 5529 }
5529, AUTO_DISMISS_MS); 5530 } 5531} 5532 5533/** 5534 * SwymStorefrontLayoutLoader Web Component 5535 * 5536 * This custom element is responsible for rendering different types of loading placeholders 5537 * for the storefront layout. The type of loader is determined by the `loadertype` attribute. 5538 * 5539 * Supported loader types: 5540 * - WishlistListItem: Displays skeleton loaders for wishlist items. 5541 * - CollectionImages: Displays a placeholder for collection images. 5542 * - CarouselListItem: Displays skeleton loaders for carousel items. 5543 * - SpinnerLoader: Displays a rotating spinner for loading states. 5544 * 5545 * The component listens for attribute changes to control visibility (`loading`) 5546 * and size (`width`, `height`). 5547 * 5548 * Usage: 5549 * <swym-storefront-layout-loader loadertype="WishlistListItem" loading="true"></swym-storefront-layout-loader> 5550 */ 5551class SwymStorefrontLayoutLoader extends HTMLElement { 5552 constructor() { 5553 super(); 5554 5555 this.renderLoader(); 5556 5557 } 5558 5559 renderLoader() { 5560 const loadertype = this.getAttribute('loadertype'); 5561 const buttonCta = this.getAttribute('buttonCta'); 5562 5563 const loaderListItemHtml = Array.from({ length: 5 }) 5564 .map((_, i) => { 5565 return ` 5566 <swym-storefront-layout-item class="swym-storefront-layout-loader"> 5567 <div class="swym-storefront-layout-grid-item"> 5568 <div class="swym-storefront-layout-grid-item-image-container"> 5569 <div class="swym-storefront-layout-grid-item-image"></div> 5570 </div> 5571 <div class="swym-storefront-layout-grid-item-content"> 5572 <div class="swym-storefront-layout-grid-item-title"></div> 5573 <div class="swym-storefront-layout-grid-item-price-variant"> 5574 </div> 5575 <div class="swym-storefront-layout-grid-item-action-container"> 5576 ${buttonCta?`<button class="swym-storefront-layout-grid-item-add-to-cart-button" data-action="add-to-cart">${buttonCta}</button>`:''} 5577 </div> 5578 </div> 5579 5580 <div tabindex="-1" class="swym-storefront-layout-grid-item-option-button"> 5581 <svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13" fill="currentcolor"> 5582 <circle cx="1.5" cy="1.50014" r="1.5" fill="#B1B7C3"></circle> 5583 <circle cx="1.5" cy="6.50014" r="1.5" fill="#B1B7C3"></circle> 5584 <circle cx="1.5" cy="11.5001" r="1.5" fill="#B1B7C3"></circle> 5585 </svg> 5586 </div> 5587 5588 </div> 5589 </swym-storefront-layout-item> 5590 `; 5591 }) 5592 .join(""); 5593 5594 if(loadertype === SwymStorefrontLayoutContext?.LoaderViewType.WishlistListItem){ 5595 this.parentElement.innerHTML = `${loaderListItemHtml}` 5596 }else if(loadertype === SwymStorefrontLayoutContext?.LoaderViewType.CollectionImages){ 5597 this.parentElement.innerHTML = ` 5598 <div class="swym-storefront-layout-loader swym-storefront-layout-collection-list-images"></div> 5599 ` 5600 }else if(loadertype === SwymStorefrontLayoutContext?.LoaderViewType.CarouselListItem){ 5601 const loaderCollectionListHtml = Array.from({ length: 3 }) 5602 .map((_, i) => { 5603 return ` 5604 <div class="swym-storefront-layout-loader swym-storefront-layout-collection-grid-item"> 5605 <div class="swym-storefront-layout-collection-grid-item-image-container"> 5606 5607 <div class="swym-storefront-layout-collection-grid-item-primary-image "> 5608 <div class="swym-storefront-layout-collection-grid-item-image"></div> 5609 </div> 5610 5611 <div class="swym-storefront-layout-collection-grid-item-secondary-image "> 5612 <div class="swym-storefront-layout-collection-grid-item-image"></div> 5613 </div> 5614 5615 <div class="swym-storefront-layout-collection-grid-item-secondary-image "> 5616 <div class="swym-storefront-layout-collection-grid-item-image"></div> 5617 </div> 5618 5619 </div> 5620 <div class="swym-storefront-layout-collection-grid-item-info-container"> 5621 <div> 5622 <div class="swym-storefront-layout-collection-grid-item-name"></div> 5623 <div class="swym-storefront-layout-collection-grid-item-count"></div> 5624 </div> 5625 <div class="swym-storefront-layout-collection-grid-item-count"></div> 5626 </div> 5627 <div class="swym-storefront-layout-collection-grid-item-option-button"> 5628 <svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13" fill="none"> 5629 <circle cx="1.5" cy="1.50014" r="1.5" fill="#B1B7C3"></circle> 5630 <circle cx="1.5" cy="6.50014" r="1.5" fill="#B1B7C3"></circle> 5631 <circle cx="1.5" cy="11.5001" r="1.5" fill="#B1B7C3"></circle> 5632 </svg> 5633 </div> 5634 </div> 5635 `; 5636 }) 5637 .join(""); 5638 5639 this.parentElement.innerHTML = `${loaderCollectionListHtml}` 5640 }else if(loadertype === SwymStorefrontLayoutContext?.LoaderViewType.SpinnerLoader){ 5641 this.innerHTML = ` 5642 <div class="swym-storefront-layout-loader-container"> 5643 <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5644 viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve"> 5645 <path fill="currentColor" d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50"> 5646 <animateTransform 5647 <animateTransform 5648 <animateTransform 5649 attributeName="transform" 5650 attributeName="transform" 5651 attributeName="transform" 5652 attributeType="XML" 5653 attributeType="XML" 5654 attributeType="XML" 5655 type="rotate" 5656 dur="1s" 5657 dur="1s" 5658 dur="1s" 5659 from="0 50 50" 5660 to="360 50 50" 5661 to="360 50 50" 5662 to="360 50 50" 5663 repeatCount="indefinite" /> 5664 </path> 5665 </svg> 5666 </div> 5667 `; 5668 } 5669 } 5670 5671 static get observedAttributes() { 5672 return ['loading', 'width', 'height']; 5673 } 5674 5675 attributeChangedCallback(name, oldValue, newValue) { 5676 if (name === 'loading') { 5677 if (newValue === 'true') { 5678 this.style.display = 'flex'; 5679 } else { 5680 this.style.display = 'none'; 5681 } 5682 } else if (name == 'width') { 5683 this.style.width = newValue; 5684 } else if (name == 'height') { 5685 this.style.height = newValue; 5686 } 5687 } 5688} 5689 5690/** 5691 * SwymStorefrontLayoutTitle Web Component 5692 * 5693 * This custom element is responsible for rendering the title section of the storefront layout, 5694 * including the wishlist title and total item count. 5695 * 5696 * Functionality: 5697 * - Displays the wishlist title using `SwymStorefrontLayoutContext?.Strings?.title`. 5698 * - Shows the total count of wishlist items retrieved from `SwymStorefrontLayoutContext?.lists`. 5699 * - Listens for the `WishlistFetched` event and updates the title dynamically when the wishlist is fetched. 5700 * 5701 * Usage: 5702 * <swym-storefront-layout-title></swym-storefront-layout-title> 5703 */ 5704class SwymStorefrontLayoutTitle extends HTMLElement { 5705 constructor() { 5706 super(); 5707 5708 this.renderTitle(); 5709 5710 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched, () => { 5711 this.renderTitle(); 5712 }) 5713 } 5714 5715 renderTitle() { 5716 // Exclude the Saved For You list and auto-wishlist items — those are shown in the Saved For You tab 5717 const totalCount = (SwymStorefrontLayoutContext?.allLists || []) 5718 .filter(list => list.lname !== SFY_LIST_NAME) 5719 .reduce((total, list) => total + (list.listcontents || []).filter(item => item.source !== 'auto-wishlist').length, 0); 5720 5721 // Drawer UI Polish: personalize title for single wishlist + logged-in users (drawer only) 5722 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 5723 const isDrawer = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer'; 5724 const isLoggedIn = SwymStorefrontLayoutContext?.isShopperLoggedIn; 5725 const customerName = swymEscAttr(SwymStorefrontLayoutContext?.SwymCustomerData?.name?.split(' ')?.[0]); 5726 // Strings start as "" (see swymStorefrontLayout.liquid) and only get overwritten when the 5727 // retailer has configured a title, so fall back to keep this heading from rendering empty -- 5728 // the drawer/modal points aria-labelledby at it, so an empty heading also leaves the dialog unnamed. 5729 let titleText = SwymStorefrontLayoutContext?.Strings?.title || 'Wishlist'; 5730 if (isDrawer && !shouldShowMultiple && isLoggedIn && customerName) { 5731 titleText = (SwymStorefrontLayoutContext?.Strings?.personalizedTitle || "{{name}}'s Wishlist").replace('{{name}}', customerName); 5732 } 5733 5734 const hideCount = isDrawer && !shouldShowMultiple; 5735 const titleIcon = isDrawer ? (SwymStorefrontLayoutExtensions?.getWishlistIconSvg?.('filled') || '') : ''; 5736 this.innerHTML = ` 5737 <div> 5738 <div class="swym-storefront-layout-title" id="swym-wishlist-title" role="heading" aria-level="2"> 5739 ${titleIcon} 5740 ${titleText} 5741 </div> 5742 ${ !hideCount && totalCount?`<div class="swym-storefront-layout-total-list-items-count" aria-live="polite">${totalCount} ${totalCount<=1? SwymStorefrontLayoutContext?.Strings?.item : SwymStorefrontLayoutContext?.Strings?.items }</div>`:''} 5743 </div> 5744 `; 5745 5746 // Apply heading font styles after rendering the HTML using shared utility 5747 const titleElement = this.querySelector('.swym-storefront-layout-title'); 5748 SwymStorefrontLayoutExtensions?.applyHeadingFontStyles(titleElement); 5749 } 5750 5751} 5752 5753/** 5754 * SwymStorefrontLayoutLoggedUser Web Component 5755 * 5756 * This custom element handles user authentication UI within the storefront layout. 5757 * 5758 * Functionality: 5759 * - Checks if a user is logged in using `SwymStorefrontLayoutContext?.isShopperLoggedIn`. 5760 * - Displays a login prompt if the user is not logged in (when `showLogin="true"`). 5761 * - Shows a welcome message with the user's name or email (when `showuser="true"`). 5762 * - Registers a callback in `window.SwymCallbacks` to update UI when Swym state changes. 5763 * - Tracks login button clicks via `SwymStorefrontLayoutExtensions?.InstrumentActionCodes`. 5764 * 5765 * Attributes: 5766 * - `showuser` (boolean): Determines whether to display logged-in user details. 5767 * - `showLogin` (boolean): Determines whether to display the login prompt. 5768 * 5769 * Usage: 5770 * <swym-storefront-layout-logged-user showuser="true" showLogin="true"></swym-storefront-layout-logged-user> 5771 */ 5772class SwymStorefrontLayoutLoggedUser extends HTMLElement { 5773 constructor() { 5774 super(); 5775 if (!window.SwymCallbacks) { 5776 window.SwymCallbacks = []; 5777 } 5778 window.SwymCallbacks.push(this.swymCallbackFn.bind(this)); 5779 // Re-evaluate login prompt visibility after wishlist data is fetched, 5780 // since DefaultList may not be populated when swymCallbackFn first fires 5781 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched, () => { 5782 this.updateLoginPromptVisibility(); 5783 }); 5784 // Re-evaluate on drawer/modal open too: initShopLogin() must not run while the 5785 // panel is still hidden, since the Shop SDK's onShown() only checks userFound, 5786 // not whether the ancestor panel is actually visible on screen. 5787 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.Opened, () => { 5788 this.updateLoginPromptVisibility(); 5789 }); 5790 } 5791 5792 // The drawer/modal panel is rendered into the DOM up front and only ever 5793 // toggles the hide-view class on open/close, so this element can be connected 5794 // (and asked to init Shop Login) long before the shopper opens the panel. 5795 // No drawer/modal ancestor (e.g. as-section) means there's nothing to be 5796 // hidden behind, so treat it as always open. 5797 isAncestorPanelOpen() { 5798 const panel = this.closest('.swym-storefront-layout-drawer'); 5799 return !panel || !panel.classList.contains('swym-storefront-layout-hide-view'); 5800 } 5801 5802 /** 5803 * Destroy any pending Shop Login instance from a previous render before this component 5804 * re-renders (WishlistFetched can fire updateLoginPromptVisibility again after the initial 5805 * swymCallbackFn render). Without this, a stale instance's async detection can still resolve 5806 * and fire onShown/onComplete against a container this.innerHTML already replaced, logging 5807 * RAT 740/741 for a button the shopper isn't actually looking at anymore. 5808 */ 5809 destroyPendingShopLogin() { 5810 if (this._shopLoginHandle) {
5811 this._shopLoginHandle.destroy(); 5812 this._shopLoginHandle = null; 5813 } 5814 } 5815 5816 swymCallbackFn(swat) { 5817 const showUser = this.getAttribute('showuser') === 'true'; 5818 const showLogin = this.getAttribute('showLogin') === 'true'; 5819 let isUserLogged = SwymStorefrontLayoutContext?.isShopperLoggedIn; 5820 5821 if (!isUserLogged && showLogin) { 5822 this.updateLoginPromptVisibility(); 5823 } else if (isUserLogged && showUser) { 5824 this.renderUserDetails(); 5825 } else if (isUserLogged && showLogin) { 5826 // For drawer + single wishlist: render share button in the login-user container 5827 // (same background, no heading/description text) 5828 this.renderLoggedInShareContainer(); 5829 } 5830 5831 } 5832 5833 updateLoginPromptVisibility() { 5834 const showLogin = this.getAttribute('showLogin') === 'true'; 5835 if (!showLogin) return; 5836 let isUserLogged = SwymStorefrontLayoutContext?.isShopperLoggedIn; 5837 if (isUserLogged) return; 5838 5839 // Always show login prompt inside collection list view for guests, 5840 // since the collection list overlays the main empty state 5841 const isInsideCollectionList = this.closest('swym-storefront-layout-collection-list'); 5842 if (isInsideCollectionList) { 5843 this.renderLoginContainer(); 5844 return; 5845 } 5846 5847 // Hide the separate login prompt when wishlist is empty, since 5848 // the guest empty state already includes login CTAs 5849 const wishlistIsEmpty = !SwymStorefrontLayoutContext?.DefaultList?.listcontents?.length; 5850 if (wishlistIsEmpty) { 5851 this.innerHTML = ''; 5852 return; 5853 } 5854 this.renderLoginContainer(); 5855 } 5856 5857 renderUserDetails() { 5858 this.destroyPendingShopLogin(); 5859 let customer = SwymStorefrontLayoutContext?.SwymCustomerData; 5860 this.innerHTML = ` 5861 <div class="swym-storefront-layout-logged-user-container"> 5862 <div class="swym-storefront-layout-logged-user-welcome-message">${SwymStorefrontLayoutContext?.Strings?.loggedUserWelcomeMessage} <span class="swym-storefront-layout-logged-user-name">${customer?.name ? customer?.name : customer?.email}</span></div> 5863 </div> 5864 ` 5865 } 5866 5867 renderLoginContainer() { 5868 this.destroyPendingShopLogin(); 5869 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 5870 const layoutType = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType; 5871 const isDrawer = layoutType === 'as-drawer'; 5872 const showInlineButtons = isDrawer && !shouldShowMultiple; 5873 // Button presentation only for separate-page and modal layouts; drawer (including 5874 // multi-wishlist drawer, which falls through to the shared block below) stays card. 5875 const presentationMode = (layoutType === 'as-section' || layoutType === 'as-modal') ? 'button' : 'card'; 5876 5877 // Drawer + single wishlist: show inline Login to Save + Share buttons 5878 if (showInlineButtons) { 5879 const shareIcon = SwymStorefrontLayoutExtensions.getShareIconSvg(); 5880 5881 const loginToSaveCta = SwymStorefrontLayoutContext?.Strings?.loginToSaveCta || 'Login to Save'; 5882 const inlineButtons = ` 5883 <div class="swym-storefront-layout-login-actions"> 5884 <div class="swym-storefront-layout-shop-login-container"></div> 5885 <button class="swym-storefront-layout-share-footer-share-btn" data-action="share" aria-label="${SwymStorefrontLayoutContext?.Strings?.shareCta || 'Share'}"> 5886 ${shareIcon} ${SwymStorefrontLayoutContext?.Strings?.shareCta || 'Share'} 5887 </button> 5888 </div>`; 5889 5890 this.innerHTML = ` 5891 <div class="swym-storefront-layout-login-user-container"> 5892 <div class="swym-storefront-layout-login-content"> 5893 <div class="swym-storefront-layout-login-heading">${SwymStorefrontLayoutContext?.Strings?.loginHeading}</div> 5894 <div class="swym-storefront-layout-login-description">${SwymStorefrontLayoutContext?.Strings?.loginText}</div> 5895 </div> 5896 ${inlineButtons} 5897 </div> 5898 `; 5899 const shopLoginContainer = this.querySelector('.swym-storefront-layout-shop-login-container'); 5900 const loginUrl = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.Login; 5901 const renderDrawerClassicBtn = () => { 5902 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginFallback, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiFallbackEngagement }); 5903 shopLoginContainer.innerHTML = `<button class="swym-storefront-layout-sh
5903are-footer-login-btn" data-action="login" aria-label="${loginToSaveCta}">${loginToSaveCta} <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true" focusable="false"><path fill-rule="evenodd" clip-rule="evenodd" d="M3.49951 10C3.49951 9.58579 3.8353 9.25 4.24951 9.25L13.9391 9.25L11.2192 6.53036C10.9263 6.23748 10.9263 5.7626 11.2192 5.4697C11.512 5.17679 11.9869 5.17676 12.2798 5.46964L16.2802 9.46964C16.4209 9.6103 16.4999 9.80107 16.4999 10C16.4999 10.1989 16.4209 10.3897 16.2802 10.5304L12.2798 14.5304C11.9869 14.8232 11.512 14.8232 11.2192 14.5303C10.9263 14.2374 10.9263 13.7625 11.2192 13.4696L13.9391 10.75L4.24951 10.75C3.8353 10.75 3.49951 10.4142 3.49951 10Z" fill="currentColor"/></svg></button>`; 5904 shopLoginContainer.querySelector('[data-action="login"]')?.addEventListener('click', () => { 5905 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiClassicLoginClicked, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiClassicLoginClickedEngagement }); 5906 window.location.href = loginUrl || '/account/login'; 5907 }); 5908 }; 5909 // Don't init the Shop SDK button while the panel is still hidden - onShown() 5910 // only checks userFound, not panel visibility, so it would fire "shown" for 5911 // guests who never actually opened the drawer. Re-runs via the Opened listener. 5912 if (this.isAncestorPanelOpen()) { 5913 this._shopLoginHandle = window._swat?.initShopLogin(shopLoginContainer, { 5914 apiKey: SWYM_SHOP_API_KEY, 5915 loginUrl: loginUrl, 5916 presentationMode: presentationMode, 5917 showFallbackImmediately: true, 5918 onComplete: (event) => { 5919 if (event.signedIn) { 5920 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginSuccess, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiSuccessEngagement }); 5921 window.location.reload(); 5922 } else { 5923 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginDeclined, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiDeclinedEngagement }); 5924 } 5925 }, 5926 onFallback: renderDrawerClassicBtn, 5927 onShown: () => { 5928 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginShown, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiEngagement }); 5929 }, 5930 // Routed into the existing declined code, not a new one: onError means the SDK 5931 // itself broke before the shopper could make a choice, but at the RAT level this 5932 // and a real decline are both "didn't sign in" outcomes for this surface. 5933 onError: () => { 5934 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginDeclined, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiDeclinedEngagement }); 5935 } 5936 }); 5937 } 5938 this.attachShareFooterEvents(); 5939 return; 5940 } 5941 5942 // Default: modal/section/multi-wishlist 5943 this.innerHTML = ` 5944 <div class="swym-storefront-layout-login-user-container"> 5945 <div class="swym-storefront-layout-login-content"> 5946 <div class="swym-storefront-layout-login-heading">${SwymStorefrontLayoutContext?.Strings?.loginHeading}</div> 5947 <div class="swym-storefront-layout-login-description">${SwymStorefrontLayoutContext?.Strings?.loginText}</div> 5948 </div> 5949 <div class="swym-storefront-layout-shop-login-container"></div> 5950 </div> 5951 `; 5952 const shopLoginContainer = this.querySelector('.swym-storefront-layout-shop-login-container'); 5953 const loginUrl = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.Login; 5954 const renderModalClassicBtn = () => { 5955 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginFallback, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiFallbackEngagement }); 5956 shopLoginContainer.innerHTML = `<button class="swym-storefront-layout-login-button">${SwymStorefrontLayoutContext?.Strings?.loginButtonText} <svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9" fill="none"><path d="M13.8536 4.85368C14.0488 4.65841 14.0488 4.34183 13.8536 4.14657L10.6716 0.964589C10.4763 0.769327 10.1597 0.769327 9.96447 0.964589C9.7692 1.15985 9.7692 1.47643 9.96447 1.6717L12.7929 4.50012L9.96447 7.32855C9.7692 7.52381 9.7692 7.84039 9.96447 8.03566C10.1597 8.23092 10.4763 8.23092 10.6716 8.03566L13.8536 4.85368ZM0.5 5.00012L13.5 5.00012L13.5 4.00012L0.5 4.00012L0.5 5.00012Z" fill="currentColor"/></svg></button>`; 5957 shopLoginContainer.querySelector('.swym-storefront-layout-login-button')?.addEventListener('click', () => { 5958 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiClassicLoginClicked, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiClassicLoginClickedEngagement }); 5959 window.location.href = loginUrl || '/account/login'; 5960 }); 5961 }; 5962 // Don't init the Shop SDK button while the panel is still hidden - onShown() 5963 // only checks userFound, not panel visibility, so it would fire "shown" for 5964 // guests who never actually opened the modal. Re-runs via the Opened listener. 5965 if (this.isAncestorPanelOpen()) { 5966 this._shopLoginHandle = window._swat?.initShopLogin(shopLoginContainer, { 5967 apiKey: SWYM_SHOP_API_KEY, 5968 loginUrl: loginUrl, 5969 presentationMode: presentationMode, 5970 showFallbackImmediately: true, 5971 onComplete: (event) => { 5972 if (event.signedIn) { 5973 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginSuccess, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiSuccessEngagement }); 5974 window.location.reload(); 5975 } else { 5976 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginDeclined, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiDeclinedEngagement }); 5977 } 5978 }, 5979 onFallback: renderModalClassicBtn, 5980 onShown: () => { 5981 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginShown, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiEngagement }); 5982 }, 5983 // Routed into the existing declined code, not a new one: onError means the SDK 5984 // itself broke before the shopper could make a choice, but at the RAT level this 5985 // and a real decline are both "didn't sign in" outcomes for this surface. 5986 onError: () => { 5987 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutWishlistUiShopLoginDeclined, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutShopLoginWishlistUiDeclinedEngagement }); 5988 } 5989 }); 5990 } 5991 } 5992 5993 attachShareFooterEvents() { 5994 const shareBtn = this.querySelector('[data-action="share"]'); 5995 const loginBtn = this.querySelector('[data-action="login"]'); 5996 5997 shareBtn?.addEventListener('click', (event) => { 5998 event.preventDefault(); 5999 event.stopPropagation(); 6000 const isLoggedIn = window._swat?.platform?.isLoggedIn(); 6001 if (!isLoggedIn) { 6002 const loginUrl = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.Login || '/account/login'; 6003 window.location.href = loginUrl; 6004 return; 6005 } 6006 const activeTab = document.querySelector('.swym-storefront-layout-tab-button.swym-storefront-layout-tab-button-active'); 6007 const activeDataView = activeTab?.getAttribute('data-view'); 6008 const list = SwymStorefrontLayoutContext?.DefaultList; 6009 const isSflTab = activeDataView === 'tabSavedForLater'; 6010 const activeList = isSflTab ? SwymStorefrontLayoutContext?.sflList : list;
6011 window._swat?.evtLayer?.dispatchEvent(new CustomEvent('sw:renderShareWishlistUI', { 6012 detail: { 6013 lid: activeList?.lid || '', 6014 lname: '', 6015 list: activeList, 6016 layoutType: SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType, 6017 senderName: '', 6018 activeDataView 6019 } 6020 })); 6021 }); 6022 6023 loginBtn?.addEventListener('click', () => { 6024 const loginUrl = SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.Login || '/account/login'; 6025 window.location.href = loginUrl; 6026 }); 6027 } 6028 6029 renderLoggedInShareContainer() { 6030 this.destroyPendingShopLogin(); 6031 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6032 const isDrawer = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer'; 6033 if (!isDrawer || shouldShowMultiple) { 6034 this.innerHTML = ''; 6035 return; 6036 } 6037 6038 const selectedView = SwymStorefrontLayoutContext?.SelectedLayoutView; 6039 const isSfyTab = selectedView === SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SavedForYou; 6040 const shareLabel = isSfyTab 6041 ? (SwymStorefrontLayoutContext?.Strings?.shareThisListCta || 'Share This List') 6042 : (SwymStorefrontLayoutContext?.Strings?.shareCta || 'Share'); 6043 6044 const shareIcon = SwymStorefrontLayoutExtensions.getShareIconSvg(); 6045 6046 this.innerHTML = ` 6047 <div class="swym-storefront-layout-login-user-container"> 6048 <div class="swym-storefront-layout-login-actions"> 6049 <button class="swym-storefront-layout-share-footer-share-btn swym-storefront-layout-share-footer-share-btn-full" data-action="share" aria-label="${shareLabel}"> 6050 ${shareIcon} ${shareLabel} 6051 </button> 6052 </div> 6053 </div> 6054 `; 6055 6056 this.attachShareFooterEvents(); 6057 6058 // Listen for tab changes to update share button label. 6059 // Remove any previous listener first to prevent stacking on re-render. 6060 if (this._tabChangeListener) { 6061 document.removeEventListener('click', this._tabChangeListener); 6062 } 6063 this._tabChangeListener = (e) => { 6064 if (e.target?.closest?.('.swym-storefront-layout-tab-button')) { 6065 requestAnimationFrame(() => this.renderLoggedInShareContainer()); 6066 } 6067 }; 6068 document.addEventListener('click', this._tabChangeListener); 6069 } 6070 6071 disconnectedCallback() { 6072 if (this._tabChangeListener) { 6073 document.removeEventListener('click', this._tabChangeListener); 6074 this._tabChangeListener = null; 6075 } 6076 } 6077 6078} 6079 6080class SwymStorefrontLayoutTabs extends HTMLElement { 6081 constructor() { 6082 super(); 6083 this.elements = {}; 6084 SwymStorefrontLayoutContext.SelectedLayoutView = SwymStorefrontLayoutContext?.Tabs?.[0] || null; 6085 } 6086 6087 connectedCallback(){ 6088 this.initUI(); 6089 this._layoutInitHandler = () => { 6090 SwymStorefrontLayoutContext.Tabs = [SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.Wishlist]; 6091 if(window._swat.retailerSettings.SFL?.SFLFeatureEnabled){ 6092 SwymStorefrontLayoutContext.Tabs.push(SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SaveForLater); 6093 } 6094 // SavedForYou tab is added/removed dynamically by the WishlistFetched listener below 6095 this.initUI(); 6096 }; 6097 this._wishlistFetchedHandler = () => { 6098 if (window.SwymEnabledCommonFeatures?.['auto-add-to-wishlist-from-pdp']?.enabled) { 6099 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6100 const hasAutoWishlistItems = shouldShowMultiple 6101 ? (SwymStorefrontLayoutContext.getSfyList()?.listcontents?.length > 0) 6102 : (SwymStorefrontLayoutContext?.DefaultList?.listcontents || []).some(item => item.source === 'auto-wishlist'); 6103 const savedForYouViewType = SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SavedForYou; 6104 const currentIdx = SwymStorefrontLayoutContext.Tabs?.indexOf(savedForYouViewType); 6105 if (hasAutoWishlistItems && currentIdx === -1) { 6106 SwymStorefrontLayoutContext.Tabs.splice(1, 0, savedForYouViewType); 6107 } else if (!hasAutoWishlistItems && currentIdx !== -1) { 6108 SwymStorefrontLayoutContext.Tabs.splice(currentIdx, 1); 6109 if (SwymStorefrontLayoutContext.SelectedLayoutView === savedForYouViewType) { 6110 SwymStorefrontLayoutContext.SelectedLayoutView = SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.Wishlist; 6111 } 6112 } 6113 } 6114 // Always re-render to update tab count badges 6115 this.initUI(); 6116 }; 6117 this._sflFetchedHandler = () => { this.initUI(); }; 6118 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.LayoutInitialized, this._layoutInitHandler); 6119 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched, this._wishlistFetchedHandler); 6120 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.SavedForLaterFetched, this._sflFetchedHandler); 6121 } 6122 6123 disconnectedCallback(){ 6124 if (this._layoutInitHandler) { 6125 document.removeEventListener(SwymStorefrontLayoutContext?.CustomEvents?.LayoutInitialized, this._layoutInitHandler); 6126 } 6127 if (this._wishlistFetchedHandler) { 6128 document.removeEventListener(SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched, this._wishlistFetchedHandler); 6129 } 6130 if (this._sflFetchedHandler) { 6131 document.removeEventListener(SwymStorefrontLayoutContext?.CustomEvents?.SavedForLaterFetched, this._sflFetchedHandler); 6132 } 6133 } 6134 6135 initUI(){ 6136 this.renderUI(); 6137 this.initElement(); 6138 6139 // Always sync content panels visibility with SelectedLayoutView 6140 const selectedView = SwymStorefrontLayoutContext?.SelectedLayoutView || SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.Wishlist; 6141 const tabContent = document.querySelector('swym-storefront-layout-tab-content'); 6142 tabContent?.querySelectorAll('.swym-storefront-layout-tab-container').forEach(container => { 6143 const isActive = container.id === selectedView; 6144 container.classList.toggle('swym-storefront-layout-tab-content-active', isActive); 6145 if (isActive) container.removeAttribute('hidden'); 6146 else container.setAttribute('hidden', ''); 6147 6148 // The panel's tab button only exists when its view is in SwymStorefrontLayoutContext.Tabs 6149 // AND there's more than one tab (renderUI() above renders an empty tablist otherwise). 6150 // Reconcile against the Tabs array itself, not document.getElementById -- this runs 6151 // before SwymStorefrontLayoutTabContent's panels are upgraded into the DOM on first load, 6152 // so a DOM lookup here would always miss. 6153 const tabs = SwymStorefrontLayoutContext?.Tabs; 6154 if (tabs?.length > 1 && tabs.includes(container.id)) { 6155 container.setAttribute('role', 'tabpanel'); 6156 container.setAttribute('aria-labelledby', 'tab-' + container.id); 6157 container.removeAttribute('aria-label'); 6158 container.setAttribute('tabindex', '0'); 6159 } else { 6160 container.removeAttribute('role'); 6161 container.removeAttribute('aria-labelledby'); 6162 container.setAttribute('aria-label', SwymStorefrontLayoutContext?.Strings?.[container.id] || ''); 6163 container.removeAttribute('tabindex'); 6164 } 6165 }); 6166 6167 if (this.elements.currentSelectedTab) { 6168 this.setTabSelection(this.elements.currentSelectedTab); 6169 this.attachEvents(); 6170 } 6171 } 6172 6173 renderUI() { 6174 let tabs = SwymStorefrontLayoutContext?.Tabs; 6175 if (!tabs?.length || tabs?.length<=1){ 6176 this.innerHTML = ''; 6177 return; 6178 } 6179 6180 this.innerHTML = ` 6181 <div class="swym-storefront-layout-tabs-view"> 6182 <div class="swym-storefront-layout-tabs" role="tablist" aria-label="Wishlist views"> 6183 ${tabs.map((tab, index)=>{ 6184 const isActive = tab===SwymStorefrontLayoutContext?.SelectedLayoutView; 6185 const isDrawerLayout = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer'; 6186 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6187 const tabCount = isDrawerLayout ? this.getTabItemCount(tab) : 0; 6188 const hideCount = !isDrawerLayout || shouldShowMultiple || !isActive; 6189 const countBadge = isDrawerLayout ? `<span class="swym-storefront-layout-tab-count${hideCount ? ' swym-storefront-layout-tab-count-hidden' : ''}">${tabCount}</span>` : ''; 6190 return `<button class="swym-storefront-layout-tab-button ${isActive?'swym-storefront-layout-tab-button-active':''}" data-view="${tab}" role="tab" aria-selected="${isActive}" aria-controls="${tab}" tabindex="${isActive ? '0' : '-1'}" id="tab-${tab}">${SwymStorefrontLayoutContext?.Strings?.[tab]}${countBadge}</button>` 6191 }).join('')} 6192 </div> 6193 <div class="swym-storefront-layout-tab-indicator"></div> 6194 </div> 6195 `; 6196 } 6197 6198 getTabItemCount(tab) { 6199 if (tab === SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.Wishlist) { 6200 const allItems = SwymStorefrontLayoutContext?.DefaultList?.listcontents || []; 6201 const hasSfyTab = SwymStorefrontLayoutContext?.Tabs?.includes(SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SavedForYou); 6202 return hasSfyTab ? allItems.filter(item => item.source !== 'auto-wishlist').length : allItems.length; 6203 } 6204 if (tab === SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SavedForYou) { 6205 return SwymStorefrontLayoutContext?.savedForYouItems?.length || 0; 6206 } 6207 if (tab === SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SaveForLater) { 6208 return SwymStorefrontLayoutContext?.sflListItems?.length || 0; 6209 } 6210 return 0; 6211 } 6212 6213 initElement(){ 6214 this.elements = { 6215 tabs: this.querySelectorAll('.swym-storefront-layout-tab-button'), 6216 tabIndicator: this.querySelector('.swym-storefront-layout-tab-indicator'), 6217 currentSelectedTab: this.querySelector('.swym-storefront-layout-tab-button-active'), 6218 tabContent: document.querySelector('swym-storefront-layout-tab-content') 6219 }; 6220 } 6221 6222 setTabSelection(selectedTab){ 6223 if (!selectedTab) return; 6224 6225 const selectedView = selectedTab.getAttribute('data-view'); 6226 if (!selectedView) return; 6227 6228 SwymStorefrontLayoutContext.SelectedLayoutView = selectedView || SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.Wishlist; 6229 6230 this.elements.tabContainers = this.elements.tabContent?.querySelectorAll('.swym-storefront-layout-tab-container'); 6231 6232 // Update ARIA attributes and tabindex for accessibility 6233 this.elements.tabs?.forEach(tab => { 6234 const isSelected = tab === selectedTab; 6235 tab.classList.toggle('swym-storefront-layout-tab-button-active', isSelected); 6236 tab.setAttribute('aria-selected', isSelected); 6237 tab.setAttribute('tabindex', isSelected ? '0' : '-1'); 6238 // Toggle tab count visibility (opacity) for drawer layout — always hidden for multi-wishlist 6239 const countBadge = tab.querySelector('.swym-storefront-layout-tab-count'); 6240 if (countBadge) { 6241 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6242 countBadge.classList.toggle('swym-storefront-layout-tab-count-hidden', shouldShowMultiple || !isSelected); 6243 } 6244 }); 6245
6246 this.elements.tabContainers?.forEach(tabContainer => { 6247 const isActive = tabContainer.id === selectedView; 6248 tabContainer.classList.toggle('swym-storefront-layout-tab-content-active', isActive); 6249 // Manage hidden attribute for better accessibility 6250 if (isActive) { 6251 tabContainer.removeAttribute('hidden'); 6252 } else { 6253 tabContainer.setAttribute('hidden', ''); 6254 } 6255 }); 6256 6257 this.setTabIndicator(); 6258 } 6259 6260 setTabIndicator(){ 6261 let selectedTab = this.elements.currentSelectedTab; 6262 if(selectedTab){ 6263 // Wait for next frame to ensure layout is complete 6264 requestAnimationFrame(() => { 6265 const tabCenter = selectedTab.offsetLeft + selectedTab.offsetWidth / 2; 6266 this.elements.tabIndicator?.style.setProperty( 6267 'left', 6268 `${tabCenter - (this.elements.tabIndicator.offsetWidth ?? 0) / 2}px` 6269 ); 6270 }) 6271 } 6272 } 6273 6274 attachEvents() { 6275 this.elements.tabs?.forEach((tab) => { 6276 // Click event handler 6277 tab.addEventListener('click', (event) => { 6278 const clickedTab = event.target?.closest('[role="tab"]'); 6279 if (!clickedTab) return; 6280 this.elements.currentSelectedTab = clickedTab; 6281 this.setTabSelection(clickedTab); 6282 if (clickedTab.getAttribute('data-view') === SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SavedForYou) { 6283 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFYTabViewed, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 6284 } 6285 }); 6286 6287 // Keyboard navigation for accessibility 6288 tab.addEventListener('keydown', (event) => { 6289 // Validate event target is a tab 6290 const eventTarget = event.target?.closest('[role="tab"]'); 6291 if (!eventTarget) return; 6292 6293 const tabArray = Array.from(this.elements.tabs || []); 6294 if (tabArray.length === 0) return; 6295 6296 const currentIndex = tabArray.indexOf(eventTarget); 6297 if (currentIndex === -1) return; // Target not in tabs array 6298 6299 let targetTab = null; 6300 6301 switch(event.key) { 6302 case 'ArrowRight': 6303 event.preventDefault(); 6304 targetTab = tabArray[(currentIndex + 1) % tabArray.length]; 6305 break; 6306 case 'ArrowLeft': 6307 event.preventDefault(); 6308 targetTab = tabArray[(currentIndex - 1 + tabArray.length) % tabArray.length]; 6309 break; 6310 case 'Home': 6311 event.preventDefault(); 6312 targetTab = tabArray[0]; 6313 break; 6314 case 'End': 6315 event.preventDefault(); 6316 targetTab = tabArray[tabArray.length - 1]; 6317 break; 6318 case 'Enter': 6319 case ' ': 6320 event.preventDefault(); 6321 this.elements.currentSelectedTab = eventTarget; 6322 this.setTabSelection(eventTarget); 6323 if (eventTarget.getAttribute('data-view') === SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SavedForYou) { 6324 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFYTabViewed, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 6325 } 6326 return; 6327 default: 6328 return; 6329 } 6330 6331 if (targetTab) { 6332 this.elements.currentSelectedTab = targetTab; 6333 this.setTabSelection(targetTab); 6334 targetTab.focus(); 6335 } 6336 }); 6337 }); 6338 6339 // Debounced resize handler to reposition indicator 6340 if (window.addEventListener && SwymStorefrontLayoutContext?.swat?.utils?.debounce) { 6341 window.addEventListener('resize', SwymStorefrontLayoutContext.swat.utils.debounce(() => { 6342 this.setTabIndicator(); 6343 }, 200)); 6344 } 6345 } 6346} 6347 6348class SwymStorefrontLayoutTabContent extends HTMLElement { 6349 constructor() { 6350 super(); 6351 this.renderUI(); 6352 } 6353 6354 renderUI() { 6355 // Tabs is always populated by module load (defaults to [Wishlist]), so this reflects the 6356 // real tab-button set even though it runs before SwymStorefrontLayoutTabs has rendered any 6357 // buttons -- unlike a DOM lookup for the button, which would race against that render. 6358 // A tab button only exists when there's more than one tab (SwymStorefrontLayoutTabs.renderUI() 6359 // renders an empty tablist otherwise). 6360 const ariaAttrs = (viewType) => (SwymStorefrontLayoutContext?.Tabs?.length > 1 && SwymStorefrontLayoutContext.Tabs.includes(viewType)) 6361 ? `role="tabpanel" aria-labelledby="tab-${viewType}" tabindex="0"` 6362 : `aria-label="${SwymStorefrontLayoutContext?.Strings?.[viewType] || ''}"`; 6363 this.innerHTML = ` 6364 <swym-storefront-layout-wishlist-container 6365 id="${SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.Wishlist}" 6366 class="swym-storefront-layout-tab-container swym-storefront-layout-tab-content-active" 6367 ${ariaAttrs(SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.Wishlist)} 6368 ></swym-storefront-layout-wishlist-container> 6369 <swym-storefront-layout-sfl-container 6370 id="${SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SaveForLater}" 6371 class="swym-storefront-layout-tab-container" 6372 ${ariaAttrs(SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SaveForLater)} 6373 hidden 6374 ></swym-storefront-layout-sfl-container> 6375 <swym-storefront-layout-saved-for-you-container 6376 id="${SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SavedForYou}" 6377 class="swym-storefront-layout-tab-container" 6378 ${ariaAttrs(SwymStorefrontLayoutContext?.StorefrontLayoutViewType?.SavedForYou)} 6379 hidden 6380 ></swym-storefront-layout-saved-for-you-container> 6381 6382 `; 6383 } 6384} 6385 6386 6387class SwymStorefrontLayoutWishlistContainer extends HTMLElement { 6388 constructor() { 6389 super(); 6390 this.wishlist = null; 6391 this.collections = []; 6392 this.elements = {}; 6393 this.initUI(); 6394 document.addEventListener( 6395 SwymStorefrontLayoutContext?.CustomEvents?.LayoutInitialized, 6396 () => { 6397 this.attachSwymEventListeners(SwymStorefrontLayoutContext?.swat); 6398 } 6399 ); 6400 document.addEventListener( 6401 SwymStorefrontLayoutContext?.CustomEvents?.AsyncApisInitialized, 6402 () => { 6403 SwymStorefrontLayoutExtensions?.refreshWishList(); 6404 } 6405 ); 6406 this.attachEventListner(); 6407 } 6408 6409 initUI() { 6410 this.renderUI(); 6411 this.initElement(); 6412 } 6413 6414 renderUI() { 6415 const isLoggedIn = window._swat?.platform?.isLoggedIn(); 6416 6417 // Always render carousel element - it will show/hide itself based on data 6418 // We can't conditionally render here because allLists data isn't available yet 6419 this.innerHTML = ` 6420 <div class="swym-storefront-layout-header"> 6421 <swym-storefront-layout-title></swym-storefront-layout-title> 6422 ${isLoggedIn ? 6423 `<button class="swym-storefront-layout-collection-option-button swym-single-share-wishlist-btn swym-hidden" tabindex="0" aria-haspopup="dialog" aria-expanded="false" aria-label="Action menu options"> 6424 <svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13" fill="currentcolor"> 6425 <circle cx="1.5" cy="1.50014" r="1.5" fill="currentColor"></circle> 6426 <circle cx="1.5" cy="6.50014" r="1.5" fill="currentColor"></circle> 6427 <circle cx="1.5" cy="11.5001" r="1.5" fill="currentColor"></circle> 6428 </svg> 6429 </button>` : ""} 6430 </div> 6431 <div class="swym-storefront-layout-body" role="region" aria-label="Wishlist Content Section"> 6432 <swym-storefront-layout-collection-carousel></swym-storefront-layout-collection-carousel> 6433 <swym-storefront-layout-default-wishlist></swym-storefront-layout-default-wishlist> 6434 <swym-storefront-layout-collection-list id="swym-storefront-layout-collection-list" role="list" aria-label="Your Wishlist Collections"></swym-storefront-layout-collection-list>
6435 </div> 6436 `; 6437 } 6438 6439 initElement() { 6440 this.elements = { 6441 wishlistListComponent: this.querySelector("swym-storefront-layout-default-wishlist"), 6442 collectionsCarouselComponent: this.querySelector("swym-storefront-layout-collection-carousel"), 6443 collectionsListComponent: this.querySelector("swym-storefront-layout-collection-list"), 6444 swymListActionButton: this.querySelector(".swym-storefront-layout-collection-option-button"), 6445 }; 6446 } 6447 6448 attachEventListner(){ 6449 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched, () => { 6450 this.wishlist = SwymStorefrontLayoutContext?.DefaultList || null; 6451 // Use allLists as the source for collections, excluding default list and the SFY list 6452 // (SFY list is shown in its own dedicated tab, not in the collections carousel) 6453 const allLists = SwymStorefrontLayoutContext?.allLists || SwymStorefrontLayoutContext?.collections || []; 6454 const defaultListId = SwymStorefrontLayoutContext?.DefaultList?.lid; 6455 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6456 const sfyListId = shouldShowMultiple ? SwymStorefrontLayoutContext.getSfyList()?.lid : null; 6457 this.collections = allLists.filter(list => list.lid !== defaultListId && list.lid !== sfyListId); 6458 this.renderWishlistAndCollections(); 6459 }) 6460 this.elements.swymListActionButton?.addEventListener('click', (event) => { 6461 event.preventDefault(); 6462 event.stopPropagation(); 6463 if(SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.isTooltipOpen && SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.currentTarget === event.target){ 6464 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.closeTooltip(); 6465 event.target.setAttribute('aria-expanded', 'false'); 6466 }else{ 6467 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.setData({ 6468 listId: "", 6469 list: this.wishlist, 6470 collections: this.collections, 6471 actionType: SwymStorefrontLayoutContext?.ActionTypes.ShareSingleWishlist 6472 }); 6473 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.showOnTarget(event.target); 6474 event.target.setAttribute('aria-expanded', 'true'); 6475 } 6476 }); 6477 6478 } 6479 attachSwymEventListeners(swat) { 6480 swat?.evtLayer?.addEventListener( 6481 swat?.JSEvents?.storeAddedToCart, 6482 (data) => { 6483 swat.utils.debounce(() => { 6484 this.renderWishlistAndCollections(); 6485 }, 1000)(); 6486 } 6487 ); 6488 6489 swat?.evtLayer?.addEventListener( 6490 swat?.JSEvents?.storeRemovedFromCart, 6491 (data) => { 6492 swat.utils.debounce(() => { 6493 this.renderWishlistAndCollections(); 6494 }, 1000)(); 6495 } 6496 ); 6497 } 6498 6499 6500 renderWishlistAndCollections() { 6501 // Use centralized helper to determine multi-wishlist behavior 6502 const { shouldShowMultiple, canCreateLists } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6503 // Check for auto-wishlist items directly — don't rely on Tabs array state since 6504 // the Tabs listener and this listener fire in undefined order on the same event. 6505 const autoWishlistEnabled = window.SwymEnabledCommonFeatures?.['auto-add-to-wishlist-from-pdp']?.enabled; 6506 // Multi-list: SFY items live in the dedicated "Saved For Me" list, not in DefaultList 6507 const hasSfyItems = autoWishlistEnabled && ( 6508 shouldShowMultiple 6509 ? (SwymStorefrontLayoutContext.getSfyList()?.listcontents?.length > 0) 6510 : (SwymStorefrontLayoutContext?.DefaultList?.listcontents || []).some(item => item.source === 'auto-wishlist') 6511 ); 6512 6513 const isDrawer = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer'; 6514 if(this.collections?.length > 0 || (this.wishlist && this.wishlist?.listcontents?.length > 0)) { 6515 // Hide the 3-dot menu in drawer + single wishlist — share is handled by the footer button 6516 if (!(isDrawer && !shouldShowMultiple)) { 6517 this.querySelector(".swym-storefront-layout-collection-option-button")?.classList.remove('swym-hidden'); 6518 } 6519 } 6520 6521 if ( this.wishlist || this.collections) { 6522 if(window.location.hash.startsWith(SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.CollectionList)){ 6523 const collectionId = window.location.hash.slice(SwymStorefrontLayoutContext?.StorefrontLayoutUrls?.CollectionList.length); 6524 if(this.elements.collectionsListComponent && this.collections){ 6525 let selectedCollection = this.collections?.find((collection)=> collection.lid === collectionId); 6526 this.elements.collectionsListComponent.setData({ 6527 list: selectedCollection, 6528 collections: this.collections, 6529 canCreateLists: canCreateLists, 6530 shouldShowMultiple: shouldShowMultiple 6531 }) 6532 } 6533 } 6534 6535 // Filter auto-wishlist items from the Wishlist tab — they belong in the SFY tab only. 6536 // In single-list mode, both share DefaultList. In multi-list mode, this is a safety net 6537 // in case DefaultList somehow contains auto-wishlist items (e.g. SFY list was lists[0]). 6538 const wishlistForDisplay = hasSfyItems 6539 ? { ...this.wishlist, listcontents: (this.wishlist?.listcontents || []).filter(item => item.source !== 'auto-wishlist') } 6540 : this.wishlist; 6541 6542 this.elements.wishlistListComponent?.setData({ 6543 wishlist: wishlistForDisplay, 6544 collections: this.collections, 6545 canCreateLists: canCreateLists, 6546 shouldShowMultiple: shouldShowMultiple 6547 }); 6548 6549 // Always call setData on carousel so it can manage its own visibility 6550 if (this.elements.collectionsCarouselComponent) { 6551 this.elements.collectionsCarouselComponent?.setData({ 6552 collections: shouldShowMultiple ? this.collections : [], 6553 canCreateLists: canCreateLists, 6554 shouldShowMultiple: shouldShowMultiple 6555 }); 6556 } 6557 } 6558 } 6559} 6560 6561class SwymStorefrontLayoutSaveForLaterContainer extends HTMLElement{ 6562 constructor(){ 6563 super(); 6564 this.sflData = null; 6565 this.collections = []; 6566 this.elements = {}; 6567 6568 this.initUI(); 6569 6570 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.AsyncApisInitialized, (
6570) => { 6571 if(window._swat?.retailerSettings.SFL?.SFLFeatureEnabled){ 6572 this.initSfl(); 6573 } 6574 }) 6575 6576 this.attachEventListner(); 6577 } 6578 6579 initUI(){ 6580 this.renderUI(); 6581 this.initElement(); 6582 } 6583 6584 renderUI(){ 6585 const isLoggedIn = window?._swat?.platform?.isLoggedIn(); 6586 const isDrawer = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer'; 6587 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6588 const hideOptionButton = isDrawer && !shouldShowMultiple; 6589 this.innerHTML = ` 6590 <div class="swym-storefront-layout-header"> 6591 <swym-storefront-layout-sfl-title></swym-storefront-layout-sfl-title> 6592 ${isLoggedIn && !hideOptionButton ? 6593 `<div class="swym-storefront-layout-collection-option-button swym-hidden"> 6594 <svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13" fill="currentcolor"> 6595 <circle cx="1.5" cy="1.50014" r="1.5" fill="currentColor"></circle> 6596 <circle cx="1.5" cy="6.50014" r="1.5" fill="currentColor"></circle> 6597 <circle cx="1.5" cy="11.5001" r="1.5" fill="currentColor"></circle> 6598 </svg> 6599 </div>` : "" 6600 } 6601 </div> 6602 <div class="swym-storefront-layout-body"> 6603 <swym-storefront-layout-sfl-list></swym-storefront-layout-sfl-list> 6604 </div> 6605 `; 6606 } 6607 6608 initElement(){ 6609 this.elements = { 6610 sflList: this.querySelector('swym-storefront-layout-sfl-list'), 6611 swymListActionButton: this.querySelector('.swym-storefront-layout-collection-option-button') 6612 } 6613 } 6614 6615 async initSfl(){ 6616 if(!SwymStorefrontLayoutContext?.sflListId){ 6617 await SwymStorefrontLayoutAPI?.SwymSFLAsyncApis?.updateSflList(); 6618 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFLInitialized, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 6619 } 6620 6621 if(!SwymStorefrontLayoutContext?.sflList){ 6622 SwymStorefrontLayoutExtensions?.refreshSFLList(); 6623 } 6624 if(SwymStorefrontLayoutContext?.sflList) { 6625 const isDrawer = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer'; 6626 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6627 // Hide the 3-dot menu in drawer + single wishlist — share is handled by the footer button 6628 if (!(isDrawer && !shouldShowMultiple)) { 6629 this.querySelector(".swym-storefront-layout-collection-option-button")?.classList.remove("swym-hidden"); 6630 } 6631 } 6632 } 6633 attachEventListner(){ 6634 this.elements.swymListActionButton?.addEventListener('click', (event) => { 6635 event.preventDefault(); 6636 event.stopPropagation(); 6637 if(SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.isTooltipOpen && SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.currentTarget === event.target){ 6638 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.closeTooltip(); 6639 }else{ 6640 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.setData({ 6641 listId: SwymStorefrontLayoutContext?.sflListId, 6642 list: "", 6643 collections: "", 6644 actionType: SwymStorefrontLayoutContext?.ActionTypes.ShareSingleWishlist 6645 }); 6646 6647 SwymStorefrontLayoutExtensions?.SwymStorefrontLayoutActionTooltip?.showOnTarget(event.target); 6648 } 6649 }); 6650 6651 } 6652} 6653 6654class SwymStorefrontLayoutSaveForLaterTitle extends HTMLElement { 6655 constructor() { 6656 super(); 6657 6658 this.renderTitle(); 6659 6660 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.SavedForLaterFetched, () => { 6661 this.renderTitle(); 6662 }) 6663 } 6664 6665 renderTitle() { 6666 const totalCount = SwymStorefrontLayoutContext?.sflListItems?.length || 0; 6667 this.innerHTML = ` 6668 <div> 6669 <div class="swym-storefront-layout-title" id="swym-sfl-title" role="heading" aria-level="2"> 6670 ${SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer' ? (SwymStorefrontLayoutExtensions?.getWishlistIconSvg?.('filled') || '') : ''} 6671 ${SwymStorefrontLayoutContext?.Strings?.savedForLaterTitle} 6672 </div> 6673 ${ totalCount?`<div class="swym-storefront-layout-total-list-items-count" aria-live="polite">${totalCount} ${totalCount<=1? SwymStorefrontLayoutContext?.Strings?.item : SwymStorefrontLayoutContext?.Strings?.items }</div>`:''} 6674 </div> 6675 `; 6676 6677 // Apply heading font styles after rendering the HTML using shared utility 6678 const titleElement = this.querySelector('.swym-storefront-layout-title'); 6679 SwymStorefrontLayoutExtensions?.applyHeadingFontStyles(titleElement); 6680 } 6681 6682} 6683class SwymStorefrontLayoutSaveForLaterList extends HTMLElement { 6684 constructor() { 6685 super(); 6686 this.renderUI(); 6687 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.SavedForLaterFetched, () => { 6688 this.renderSFL(); 6689 }) 6690 } 6691 6692 renderUI() { 6693 let sflData = SwymStorefrontLayoutContext?.sflData || null; 6694 this.innerHTML = ` 6695 <div id="swym-storefront-layout-sfl-items-container" class="swym-storefront-layout-items-container swym-storefront-layout-sfl-list-container"> 6696 ${sflData===null?`<swym-storefront-layout-loader loading="true" loadertype="${SwymStorefrontLayoutContext?.LoaderViewType?.WishlistListItem}" buttonCta="${SwymStorefrontLayoutContext?.Strings?.moveToCartCta}" height="100px"></swym-storefront-layout-loader>`:''} 6697 </div> 6698 ` 6699 this.initElements(); 6700 } 6701 6702 initElements(){ 6703 this.elements = { 6704 title: this.querySelector('.swym-storefront-layout-sfl-list-title'), 6705 itemsContainer: this.querySelector('#swym-storefront-layout-sfl-items-container') 6706 } 6707 } 6708 6709
6710 async renderSFL() { 6711 const slfListItems = SwymStorefrontLayoutContext?.sflListItems; 6712 if (slfListItems?.length && this.elements.itemsContainer) { 6713 6714 const fragment = document.createDocumentFragment(); 6715 6716 slfListItems.forEach((item) => { 6717 const sflItem = document.createElement('template'); 6718 sflItem.innerHTML = `<swym-storefront-layout-item id="swym-storefront-layout-sfl-item-${item.empi}-${item.epi}" class="swym-storefront-layout-sfl-item"></swym-storefront-layout-item>`; 6719 fragment.appendChild(sflItem.content); 6720 }); 6721 6722 this.elements.itemsContainer.innerHTML = ''; 6723 this.elements.itemsContainer.appendChild(fragment); 6724 6725 slfListItems.forEach(async (item) => { 6726 let sflItemElement = document.querySelector(`#swym-storefront-layout-sfl-item-${item.empi}-${item.epi}`); 6727 sflItemElement?.setData({ 6728 item, 6729 listItemType: SwymStorefrontLayoutContext?.ListItemType?.SaveForLaterItem, 6730 sflList: SwymStorefrontLayoutContext?.sflData?.list 6731 }) 6732 }); 6733 this.elements.itemsContainer.classList.remove('swym-storefront-layout-items-has-empty'); 6734 window._swat?.instrumentV3(SwymStorefrontLayoutExtensions?.InstrumentActionCodes?.StorefrontLayoutSFLItemsRendered, { "utm-term": SwymStorefrontLayoutExtensions?.InstrumentUtmTerms?.StorefrontLayoutUiEngagement }); 6735 }else{ 6736 const isLoggedIn = SwymStorefrontLayoutContext?.isShopperLoggedIn; 6737 const { shouldShowMultiple: sflShouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6738 const cartUrl = (window.Shopify?.routes?.root || '/').replace(/\/$/, '') + '/cart'; 6739 const accountUrl = (window.Shopify?.routes?.root || '/').replace(/\/$/, '') + '/account'; 6740 6741 if (sflShouldShowMultiple) { 6742 // Multi wishlist – old simple SFL empty state 6743 this.elements.itemsContainer.innerHTML = ` 6744 <div class="swym-storefront-layout-empty-container"> 6745 <div class="swym-storefront-layout-empty-sfl-container"> 6746 <div class="swym-storefront-layout-empty-sfl-content"> 6747 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="75" height="75" viewBox="0 0 75 75" fill="none"> 6748 <rect width="75" height="75" fill="url(#pattern0_1065_979)" fill-opacity="0.8"/> 6749 <defs> 6750 <pattern id="pattern0_1065_979" patternContentUnits="objectBoundingBox" width="1" height="1"> 6751 <use xlink:href="#image0_1065_979" transform="scale(0.00195312)"/> 6752 </pattern> 6753 <image id="image0_1065_979" width="512" height="512" preserveAspectRatio="none" xlink:href="data:image/png;base64,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"/> 6754 </defs> 6755 </svg> 6756 <div class="swym-storefront-layout-empty-sfl-title">${SwymStorefrontLayoutContext?.Strings?.emptySavedForLaterTitle}</div> 6757 <div class="swym-storefront-layout-empty-sfl-description">${SwymStorefrontLayoutContext?.Strings?.emptySavedForLaterDescription}</div> 6758 <a class="swym-storefront-layout-empty-sfl-view-cart-button" href="${(window.Shopify?.routes?.root || '/').replace(/\/$/, '') + '/cart'}">${SwymStorefrontLayoutContext?.Strings?.viewCartCta}</a> 6759 </div> 6760 </div> 6761 </div> 6762 `; 6763 this.elements.itemsContainer.classList.add('swym-storefront-layout-items-has-empty'); 6764 return; 6765 } 6766 6767 let sflNoticeText = ''; 6768 6769 if (isLoggedIn) { 6770 sflNoticeText = SwymStorefrontLayoutContext?.Strings?.emptySflLoggedInNotice || ''; 6771 this.elements.itemsContainer.innerHTML = ` 6772 <div class="swym-storefront-layout-empty-container swym-storefront-layout-empty-state-sfl swym-storefront-layout-empty-state-sfl-logged"> 6773 <div class="swym-storefront-layout-empty-wishlist-container"> 6774 <div class="swym-storefront-layout-empty-wishlist-content"> 6775 <div class="swym-storefront-layout-empty-state-icon"> 6776 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none"> 6777 <circle cx="24" cy="24" r="24" fill="#F5F5F5"/> 6778 <path d="M20 18H18C17.4696 18 16.9609 18.2107 16.5858 18.5858C16.2107 18.9609 16 19.4696 16 20V32C16 32.5304 16.2107 33.0391 16.5858 33.4142C16.9609 33.7893 17.4696 34 18 34H30C30.5304 34 31.0391 33.7893 31.4142 33.4142C31.7893 33.0391 32 32.5304 32 32V20C32 19.4696 31.7893 18.9609 31.4142 18.5858C31.0391 18.2107 30.5304 18 30 18H28M20 18V16C20 15.4696 20.2107 14.9609 20.5858 14.5858C20.9609 14.2107 21.4696 14 22 14H26C26.5304 14 27.0391 14.2107 27.4142 14.5858C27.7893 14.9609 28 15.4696 28 16V18M20 18H28" stroke="#9B9B9B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 6779 <path d="M20 24H28M20 28H25" stroke="#9B9B9B" stroke-width="1.5" stroke-linecap="round"/> 6780 </svg>
6781 <span class="swym-storefront-layout-empty-state-badge"> 6782 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none"> 6783 <circle cx="7" cy="7" r="7" fill="#4CAF77"/> 6784 <path d="M4 7L6 9L10 5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 6785 </svg> 6786 </span> 6787 </div> 6788 <div class="swym-storefront-layout-empty-wishlist-title">${SwymStorefrontLayoutContext?.Strings?.emptySflTitle || ''}</div> 6789 <div class="swym-storefront-layout-empty-wishlist-description">${SwymStorefrontLayoutContext?.Strings?.emptySflDescription || ''}</div> 6790 <div class="swym-storefront-layout-empty-sfl-steps-card"> 6791 <div class="swym-storefront-layout-empty-sfl-step"> 6792 <div class="swym-storefront-layout-empty-sfl-step-icon swym-storefront-layout-empty-sfl-step-icon--green"> 6793 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"> 6794 <path d="M1.5 1.5H3.5L4.8 8.8C4.9 9.3 5.3 9.6 5.8 9.6H13.2C13.7 9.6 14.1 9.2 14.2 8.7L15.5 4H4" stroke="#4CAF77" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/> 6795 <circle cx="6.5" cy="12.5" r="1.3" stroke="#4CAF77" stroke-width="1.3"/> 6796 <circle cx="13" cy="12.5" r="1.3" stroke="#4CAF77" stroke-width="1.3"/> 6797 </svg> 6798 </div> 6799 <div class="swym-storefront-layout-empty-sfl-step-content"> 6800 <div class="swym-storefront-layout-empty-sfl-step-title">${SwymStorefrontLayoutContext?.Strings?.emptySflStep1Title || ''}</div> 6801 <div class="swym-storefront-layout-empty-sfl-step-desc">${SwymStorefrontLayoutContext?.Strings?.emptySflStep1Description || ''}</div> 6802 </div> 6803 </div> 6804 <div class="swym-storefront-layout-empty-sfl-step-divider"></div> 6805 <div class="swym-storefront-layout-empty-sfl-step"> 6806 <div class="swym-storefront-layout-empty-sfl-step-icon swym-storefront-layout-empty-sfl-step-icon--red"> 6807 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"> 6808 <path d="M3 5H15" stroke="#E53E3E" stroke-width="1.3" stroke-linecap="round"/> 6809 <path d="M6 5V3H12V5" stroke="#E53E3E" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/> 6810 <path d="M4.5 5L5.5 15H12.5L13.5 5" stroke="#E53E3E" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/> 6811 <path d="M7.5 8V12M10.5 8V12" stroke="#E53E3E" stroke-width="1.3" stroke-linecap="round"/> 6812 </svg> 6813 </div> 6814 <div class="swym-storefront-layout-empty-sfl-step-content"> 6815 <div class="swym-storefront-layout-empty-sfl-step-title">${SwymStorefrontLayoutContext?.Strings?.emptySflStep2Title || ''}</div> 6816 <div class="swym-storefront-layout-empty-sfl-step-desc">${SwymStorefrontLayoutContext?.Strings?.emptySflStep2Description || ''}</div> 6817 </div> 6818 </div> 6819 </div> 6820 <div class="swym-storefront-layout-empty-sfl-notice swym-storefront-layout-empty-sfl-notice--grey"> 6821 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"> 6822 <circle cx="9" cy="9" r="8" stroke="#9B9B9B" stroke-width="1.2"/> 6823 <path d="M6 9L8 11L12 7" stroke="#9B9B9B" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> 6824 </svg> 6825 <span class="swym-storefront-layout-empty-sfl-notice-text"></span> 6826 </div> 6827 <hr class="swym-storefront-layout-empty-state-divider" /> 6828 <a href="${cartUrl}" class="swym-storefront-layout-empty-state-cta-button swym-storefront-layout-empty-sfl-go-to-cart"> 6829 ${SwymStorefrontLayoutContext?.Strings?.emptySflGoToCartCTA || ''} 6830 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9" fill="none"><path d="M13.8536 4.85368C14.0488 4.65841 14.0488 4.34183 13.8536 4.14657L10.6716 0.964589C10.4763 0.769327 10.1597 0.769327 9.96447 0.964589C9.7692 1.15985 9.7692 1.47643 9.96447 1.6717L12.7929 4.50012L9.96447 7.32855C9.7692 7.52381 9.7692 7.84039 9.96447 8.03566C10.1597 8.23092 10.4763 8.23092 10.6716 8.03566L13.8536 4.85368ZM0.5 5.00012L13.5 5.00012L13.5 4.00012L0.5 4.00012L0.5 5.00012Z" fill="currentColor"/></svg> 6831 </a> 6832 </div> 6833 </div> 6834 </div> 6835 `; 6836 } else { 6837 sflNoticeText = SwymStorefrontLayoutContext?.Strings?.emptySflGuestNotice || ''; 6838 this.elements.itemsContainer.innerHTML = ` 6839 <div class="swym-storefront-layout-empty-container swym-storefront-layout-empty-state-sfl swym-storefront-layout-empty-state-sfl-guest"> 6840 <div class="swym-storefront-layout-empty-wishlist-container"> 6841 <div class="swym-storefront-layout-empty-wishlist-content"> 6842 <div class="swym-storefront-layout-empty-state-icon"> 6843 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none"> 6844 <circle cx="24" cy="24" r="24" fill="#F5F5F5"/> 6845 <path d="M20 18H18C17.4696 18 16.9609 18.2107 16.5858 18.5858C16.2107 18.9609 16 19.4696 16 20V32C16 32.5304 16.2107 33.0391 16.5858 33.4142C16.9609 33.7893 17.4696 34 18 34H30C30.5304 34 31.0391 33.7893 31.4142 33.4142C31.7893 33.0391 32 32.5304 32 32V20C32 19.4696 31.7893 18.9609 31.4142 18.5858C31.0391 18.2107 30.5304 18 30 18H28M20 18V16C20 15.4696 20.2107 14.9609 20.5858 14.5858C20.9609 14.2107 21.4696 14 22 14H26C26.5304 14 27.0391 14.2107 27.4142 14.5858C27.7893 14.9609 28 15.4696 28 16V18M20 18H28" stroke="#9B9B9B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 6846 <path d="M20 24H28M20 28H25" stroke="#9B9B9B" stroke-width="1.5" stroke-linecap="round"/> 6847 </svg>
6848 <span class="swym-storefront-layout-empty-state-badge"> 6849 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none"> 6850 <circle cx="7" cy="7" r="7" fill="#4CAF77"/> 6851 <path d="M4 7L6 9L10 5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 6852 </svg> 6853 </span> 6854 </div> 6855 <div class="swym-storefront-layout-empty-wishlist-title">${SwymStorefrontLayoutContext?.Strings?.emptySflTitle || ''}</div> 6856 <div class="swym-storefront-layout-empty-wishlist-description">${SwymStorefrontLayoutContext?.Strings?.emptySflDescription || ''}</div> 6857 <div class="swym-storefront-layout-empty-sfl-steps-card"> 6858 <div class="swym-storefront-layout-empty-sfl-step"> 6859 <div class="swym-storefront-layout-empty-sfl-step-icon swym-storefront-layout-empty-sfl-step-icon--green"> 6860 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"> 6861 <path d="M1.5 1.5H3.5L4.8 8.8C4.9 9.3 5.3 9.6 5.8 9.6H13.2C13.7 9.6 14.1 9.2 14.2 8.7L15.5 4H4" stroke="#4CAF77" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/> 6862 <circle cx="6.5" cy="12.5" r="1.3" stroke="#4CAF77" stroke-width="1.3"/> 6863 <circle cx="13" cy="12.5" r="1.3" stroke="#4CAF77" stroke-width="1.3"/> 6864 </svg> 6865 </div> 6866 <div class="swym-storefront-layout-empty-sfl-step-content"> 6867 <div class="swym-storefront-layout-empty-sfl-step-title">${SwymStorefrontLayoutContext?.Strings?.emptySflStep1Title || ''}</div> 6868 <div class="swym-storefront-layout-empty-sfl-step-desc">${SwymStorefrontLayoutContext?.Strings?.emptySflStep1Description || ''}</div> 6869 </div> 6870 </div> 6871 <div class="swym-storefront-layout-empty-sfl-step-divider"></div> 6872 <div class="swym-storefront-layout-empty-sfl-step"> 6873 <div class="swym-storefront-layout-empty-sfl-step-icon swym-storefront-layout-empty-sfl-step-icon--red"> 6874 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"> 6875 <path d="M3 5H15" stroke="#E53E3E" stroke-width="1.3" stroke-linecap="round"/> 6876 <path d="M6 5V3H12V5" stroke="#E53E3E" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/> 6877 <path d="M4.5 5L5.5 15H12.5L13.5 5" stroke="#E53E3E" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/> 6878 <path d="M7.5 8V12M10.5 8V12" stroke="#E53E3E" stroke-width="1.3" stroke-linecap="round"/> 6879 </svg> 6880 </div> 6881 <div class="swym-storefront-layout-empty-sfl-step-content"> 6882 <div class="swym-storefront-layout-empty-sfl-step-title">${SwymStorefrontLayoutContext?.Strings?.emptySflStep2Title || ''}</div> 6883 <div class="swym-storefront-layout-empty-sfl-step-desc">${SwymStorefrontLayoutContext?.Strings?.emptySflStep2Description || ''}</div> 6884 </div> 6885 </div> 6886 </div> 6887 <div class="swym-storefront-layout-empty-sfl-notice swym-storefront-layout-empty-sfl-notice--amber"> 6888 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"> 6889 <circle cx="9" cy="9" r="8" stroke="#B45309" stroke-width="1.2"/> 6890 <path d="M9 5V9L11 11" stroke="#B45309" stroke-width="1.2" stroke-linecap="round"/> 6891 </svg> 6892 <span class="swym-storefront-layout-empty-sfl-notice-text"></span> 6893 </div> 6894 <hr class="swym-storefront-layout-empty-state-divider" /> 6895 <a href="${cartUrl}" class="swym-storefront-layout-empty-state-cta-button swym-storefront-layout-empty-sfl-go-to-cart"> 6896 ${SwymStorefrontLayoutContext?.Strings?.emptySflGoToCartCTA || ''} 6897 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9" fill="none"><path d="M13.8536 4.85368C14.0488 4.65841 14.0488 4.34183 13.8536 4.14657L10.6716 0.964589C10.4763 0.769327 10.1597 0.769327 9.96447 0.964589C9.7692 1.15985 9.7692 1.47643 9.96447 1.6717L12.7929 4.50012L9.96447 7.32855C9.7692 7.52381 9.7692 7.84039 9.96447 8.03566C10.1597 8.23092 10.4763 8.23092 10.6716 8.03566L13.8536 4.85368ZM0.5 5.00012L13.5 5.00012L13.5 4.00012L0.5 4.00012L0.5 5.00012Z" fill="currentColor"/></svg> 6898 </a> 6899 <a href="${accountUrl}" class="swym-storefront-layout-empty-state-cta-button swym-storefront-layout-empty-sfl-login-cta"> 6900 ${SwymStorefrontLayoutContext?.Strings?.emptySflLoginCTA || ''} 6901 </a> 6902 <div class="swym-storefront-layout-empty-state-signup-text"> 6903 ${SwymStorefrontLayoutContext?.Strings?.emptySflSignupText || ''} <a href="${accountUrl}" class="swym-storefront-layout-empty-state-signup-link">${SwymStorefrontLayoutContext?.Strings?.emptySflSignupCTA || ''}</a> 6904 </div> 6905 </div> 6906 </div> 6907 </div> 6908 `; 6909 } 6910 6911 this.elements.itemsContainer.classList.add('swym-storefront-layout-items-has-empty'); 6912 6913 const sflNoticeEl = this.elements.itemsContainer.querySelector('.swym-storefront-layout-empty-sfl-notice-text'); 6914 if (sflNoticeEl) { 6915 sflNoticeEl.innerHTML = sflNoticeText || ''; 6916 } 6917 } 6918 6919 6920 } 6921} 6922 6923 6924class SwymStorefrontLayoutSavedForYouContainer extends HTMLElement { 6925 constructor() { 6926 super(); 6927 this.elements = {}; 6928 this.initUI(); 6929 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched, () => { 6930 this.loadSavedForYouItems(); 6931 }); 6932 } 6933 6934 initUI() { 6935 this.renderUI(); 6936 this.initElement(); 6937 } 6938 6939 renderUI() { 6940 this.innerHTML = ` 6941 <div class="swym-storefront-layout-header"> 6942 <swym-storefront-layout-saved-for-you-title></swym-storefront-layout-saved-for-you-title> 6943 </div> 6944 <div class="swym-storefront-layout-body" role="region" aria-labelledby="swym-saved-for-you-title"> 6945 <swym-storefront-layout-saved-for-you-list></swym-storefront-layout-saved-for-you-list> 6946 </div> 6947 `; 6948 } 6949 6950 initElement() { 6951 this.elements = { 6952 savedForYouList: this.querySelector('swym-storefront-layout-saved-for-you-list') 6953 }; 6954 } 6955 6956 loadSavedForYouItems() { 6957 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6958 if (shouldShowMultiple) {
6959 // Multi-list: items live in the dedicated "Saved For Me" list 6960 const sfyList = SwymStorefrontLayoutContext.getSfyList(); 6961 SwymStorefrontLayoutContext.savedForYouItems = sfyList?.listcontents || []; 6962 SwymStorefrontLayoutContext.savedForYouList = sfyList || SwymStorefrontLayoutContext?.DefaultList; 6963 } else { 6964 // Single-list: items are source-tagged inside DefaultList 6965 const allItems = SwymStorefrontLayoutContext?.DefaultList?.listcontents || []; 6966 SwymStorefrontLayoutContext.savedForYouItems = allItems.filter(item => item.source === 'auto-wishlist'); 6967 SwymStorefrontLayoutContext.savedForYouList = SwymStorefrontLayoutContext?.DefaultList; 6968 } 6969 document.dispatchEvent(new CustomEvent(SwymStorefrontLayoutContext?.CustomEvents?.SavedForYouFetched)); 6970 } 6971} 6972 6973class SwymStorefrontLayoutSavedForYouTitle extends HTMLElement { 6974 constructor() { 6975 super(); 6976 this.renderTitle(); 6977 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.SavedForYouFetched, () => { 6978 this.renderTitle(); 6979 }); 6980 } 6981 6982 renderTitle() { 6983 const totalCount = SwymStorefrontLayoutContext?.savedForYouItems?.length || 0; 6984 6985 // Drawer UI Polish: personalize SFY title for single wishlist + logged-in users (drawer only) 6986 const { shouldShowMultiple } = SwymStorefrontLayoutContext.getMultiWishlistConfig(); 6987 const isDrawer = SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-drawer'; 6988 const isLoggedIn = SwymStorefrontLayoutContext?.isShopperLoggedIn; 6989 const customerName = swymEscAttr(SwymStorefrontLayoutContext?.SwymCustomerData?.name?.split(' ')?.[0]); 6990 let sfyTitleText = SwymStorefrontLayoutContext?.Strings?.tabSavedForYou || 'Saved For You'; 6991 if (isDrawer && !shouldShowMultiple && isLoggedIn && customerName) { 6992 sfyTitleText = (SwymStorefrontLayoutContext?.Strings?.personalizedSfyTitle || 'Saved For {{name}}').replace('{{name}}', customerName); 6993 } 6994 6995 const hideCount = isDrawer && !shouldShowMultiple; 6996 const sfyTitleIcon = isDrawer ? (SwymStorefrontLayoutExtensions?.getWishlistIconSvg?.('filled') || '') : ''; 6997 6998 this.innerHTML = ` 6999 <div> 7000 <div class="swym-storefront-layout-title" id="swym-saved-for-you-title" role="heading" aria-level="2"> 7001 ${sfyTitleIcon} 7002 ${sfyTitleText} 7003 </div> 7004 ${ !hideCount && totalCount ? `<div class="swym-storefront-layout-total-list-items-count" id="swym-saved-for-you-items-count" aria-live="polite">${totalCount} ${totalCount <= 1 ? SwymStorefrontLayoutContext?.Strings?.item : SwymStorefrontLayoutContext?.Strings?.items}</div>` : ''} 7005 </div> 7006 `; 7007 const titleElement = this.querySelector('.swym-storefront-layout-title'); 7008 SwymStorefrontLayoutExtensions?.applyHeadingFontStyles(titleElement); 7009 } 7010} 7011 7012class SwymStorefrontLayoutSavedForYouList extends HTMLElement { 7013 constructor() { 7014 super(); 7015 this.elements = {}; 7016 this.renderUI(); 7017 document.addEventListener(SwymStorefrontLayoutContext?.CustomEvents?.SavedForYouFetched, () => { 7018 this.renderItems(); 7019 }); 7020 } 7021 7022 renderUI() { 7023 this.innerHTML = ` 7024 <div id="swym-storefront-layout-sfy-items-container" class="swym-storefront-layout-items-container swym-storefront-layout-saved-for-you-list-container" role="list" aria-label="${SwymStorefrontLayoutContext?.Strings?.tabSavedForYou || 'Saved For You'} items" aria-busy="true" aria-live="polite"> 7025 <swym-storefront-layout-loader loading="true" loadertype="${SwymStorefrontLayoutContext?.LoaderViewType?.WishlistListItem}" buttonCta="${SwymStorefrontLayoutContext?.Strings?.addToCart}" height="100px"></swym-storefront-layout-loader> 7026 </div> 7027 `; 7028 this.elements = { 7029 itemsContainer: this.querySelector('#swym-storefront-layout-sfy-items-container') 7030 }; 7031 } 7032 7033 async renderItems() { 7034 const items = SwymStorefrontLayoutContext?.savedForYouItems || []; 7035 // In multi-list mode this is the "Saved For Me" list; in single-list it's DefaultList. 7036 // The correct lid is needed so remove/move operations target the right list. 7037 const list = SwymStorefrontLayoutContext?.savedForYouList || SwymStorefrontLayoutContext?.DefaultList; 7038 7039 if (!this.elements.itemsContainer) return; 7040 7041 if (items.length === 0) { 7042 this.elements.itemsContainer.innerHTML = ` 7043 <div class="swym-storefront-layout-empty-container swym-storefront-layout-empty-sfy"></div> 7044 `; 7045 this.elements.itemsContainer.classList.add('swym-storefront-layout-items-has-empty'); 7046 this.elements.itemsContainer.setAttribute('aria-busy', 'false'); 7047 return; 7048 } 7049 7050 // Reverse-chronological: most recently auto-wishlisted appears first 7051 const sortedItems = [...items].sort((a, b) => (b.cdate || b.ts || 0) - (a.cdate || a.ts || 0)); 7052 7053 const fragment = document.createDocumentFragment();
7054 sortedItems.forEach((item) => { 7055 const el = document.createElement('template'); 7056 el.innerHTML = `<swym-storefront-layout-item id="swym-storefront-layout-sfy-item-${item.empi}-${item.epi}" class="swym-storefront-layout-sfy-item"></swym-storefront-layout-item>`; 7057 fragment.appendChild(el.content); 7058 }); 7059 this.elements.itemsContainer.innerHTML = ''; 7060 this.elements.itemsContainer.appendChild(fragment); 7061 7062 sortedItems.forEach((item) => { 7063 const el = document.querySelector(`#swym-storefront-layout-sfy-item-${item.empi}-${item.epi}`); 7064 el?.setData({ 7065 item, 7066 listItemType: SwymStorefrontLayoutContext?.ListItemType?.SavedForYouItem, 7067 list 7068 }); 7069 }); 7070 this.elements.itemsContainer.classList.remove('swym-storefront-layout-items-has-empty'); 7071 this.elements.itemsContainer.setAttribute('aria-busy', 'false'); 7072 } 7073} 7074 7075// Show the login nudge when wishlist data is available. 7076document.addEventListener( 7077 SwymStorefrontLayoutContext?.CustomEvents?.WishlistFetched, 7078 () => { SwymStorefrontLayoutExtensions?.Notification?.setLoginNudge?.(); } 7079); 7080 7081// Warm the Shop Login SDK ahead of time when Shopify's own native signal says this 7082// visitor is worth it, so the nudge/drawer don't pay script-load + init latency later. 7083// Fires as soon as swat itself is ready (SwymCallbacks), not on WishlistFetched -- 7084// the signal we check here doesn't depend on wishlist data, only swat.preloadShopLogin 7085// being callable, so waiting for the wishlist fetch to finish would be unnecessary delay. 7086if (typeof window !== 'undefined') { 7087 if (!window.SwymCallbacks) { 7088 window.SwymCallbacks = []; 7089 } 7090 window.SwymCallbacks.push((swat) => { 7091 const signInWithShop = window.Shopify?.SignInWithShop; 7092 if (signInWithShop?.eligible && signInWithShop?.User?.recognized) { 7093 swat?.preloadShopLogin?.(SWYM_SHOP_API_KEY); 7094 } 7095 }); 7096} 7097 7098customElements.define('swym-storefront-layout-as-modal', SwymStorefrontLayoutAsModal); 7099if(SwymStorefrontLayoutContext?.Settings?.StorefrontLayoutType === 'as-section'){ 7100 customElements.define('swym-storefront-layout-as-section', SwymStorefrontLayoutAsSection); 7101} 7102customElements.define('swym-storefront-layout-as-drawer', SwymStorefrontLayoutAsDrawer); 7103customElements.define('swym-storefront-layout-collection-carousel', SwymStorefrontLayoutCollectionCarousel); 7104customElements.define('swym-storefront-layout-item', SwymStorefrontLayoutWishlistItem); 7105customElements.define('swym-storefront-layout-default-wishlist', SwymStorefrontLayoutDefaultWishList); 7106customElements.define('swym-storefront-layout-collection-list', SwymStorefrontLayoutCollectionList); 7107customElements.define('swym-storefront-layout-actions', SwymStorefrontLayoutActions); 7108customElements.define('swym-storefront-layout-action-tooltip', SwymStorefrontLayoutActionTooltip); 7109customElements.define('swym-storefront-layout-notification', SwymStorefrontLayoutNotification); 7110customElements.define('swym-storefront-layout-loader', SwymStorefrontLayoutLoader); 7111customElements.define('swym-storefront-layout-login-user', SwymStorefrontLayoutLoggedUser); 7112customElements.define('swym-storefront-layout-title', SwymStorefrontLayoutTitle); 7113customElements.define('swym-storefront-layout-tabs', SwymStorefrontLayoutTabs); 7114customElements.define('swym-storefront-layout-tab-content', SwymStorefrontLayoutTabContent); 7115customElements.define('swym-storefront-layout-wishlist-container', SwymStorefrontLayoutWishlistContainer); 7116customElements.define('swym-storefront-layout-sfl-container', SwymStorefrontLayoutSaveForLaterContainer); 7117customElements.define('swym-storefront-layout-sfl-title', SwymStorefrontLayoutSaveForLaterTitle); 7118customElements.define('swym-storefront-layout-sfl-list', SwymStorefrontLayoutSaveForLaterList); 7119customElements.define('swym-storefront-layout-saved-for-you-container', SwymStorefrontLayoutSavedForYouContainer); 7120customElements.define('swym-storefront-layout-saved-for-you-title', SwymStorefrontLayoutSavedForYouTitle); 7121customElements.define('swym-storefront-layout-saved-for-you-list', SwymStorefrontLayoutSavedForYouList); 7122})();
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.