Extend the standard wishlist page with multi-list navigation, quantity controls, item notes, and a running total for home and furniture stores.
Wishlist Page — Home & Furniture Extensions
Home and furniture shoppers typically curate separate wishlists by room — Living Room, Bedroom, Kitchen, and so on. This guide extends the base Wishlist Page implementation with features suited to that browsing pattern:
- Visual lists landing view — a grid of the shopper's wishlists, each tile showing product image previews and an item count, so they can jump directly to a room-specific list
- Back navigation — a button that returns the shopper to the lists grid from any open list
- Running total — a live price total for the current list, displayed alongside the back button and updated whenever quantities change
- Quantity controls (
+/−buttons) on each product card - Item notes (a textarea per card) for room dimensions, colour notes, or special delivery instructions
- Add All to Cart button that adds every in-stock item at its current quantity in a single request
- Dual-view layout — a structured table (default) and a card grid the shopper can toggle between; both views stay in sync when qty or notes change
Quantity and note changes are persisted back to the Swym list via swat.updateListItem().

Visual Lists Landing View

Wishlist UI adapted for Home & Furniture with Running Total, Add All To Cart, Per-item Quantities and Notes (Table/Row View)

Wishlist UI adapted for Home & Furniture with Running Total, Add All To Cart, Per-item Quantities and Notes (Grid/Column View)
What Changes
| Base guide | Home & Furniture extension | |
|---|---|---|
| Liquid | Standard single-list structure | Full multi-list page: .swym-page-header / .swym-header-controls wrapper in the header; #swym-view-toggle, #swym-add-all-btn, and #swym-share-btn grouped in the controls area; #swym-lists-grid (landing view); #swym-wishlist-bar (back button + running total); table structure (#swym-wishlist-table-wrapper / #swym-wishlist-tbody); card grid (#swym-wishlist-grid); both grid and table start hidden |
| Module-level vars | productJSONCache, shareButtonHandler | Adds currentView (persists table/grid choice across renders) and SS_ACTIVE_LID_KEY (sessionStorage key for the last-selected list ID) |
renderProducts | Async; fetches Shopify product JSON; renders cards into a grid | Also accepts lid; caches product JSON by variant ID (productJSONCache); sets data-epi and data-price on each card; renders qty controls and a note textarea per card; simultaneously builds a structured table row in #swym-wishlist-tbody; calls setupViewToggle and recalcTotal when done |
initWishlistPage | Fetches lists; renders the first (or previously-active) list; calls setupListSwitcher for multi-list support (a <select> dropdown) | On load, reads SS_ACTIVE_LID_KEY and SS_PRODUCTS_KEY from sessionStorage; if a matching cached list is found, renders immediately before swat.fetchLists resolves. swat.fetchLists then always re-renders from the authoritative API response. For multi-list users with no remembered selection, calls renderListsGrid (a visual tile grid, replacing the dropdown approach); for a remembered selection, renders the list directly and calls setupBackToLists. |
(new) renderListsGrid | — | Renders the lists landing view: a grid of clickable tiles, one per wishlist, each showing up to three product image thumbnails and an item count; selecting a tile stores the chosen lid in SS_ACTIVE_LID_KEY and calls renderProducts |
(new) setupBackToLists | — | Shows a "Back to all Lists" button; clicking it clears SS_ACTIVE_LID_KEY and SS_PRODUCTS_KEY, then calls renderListsGrid |
(new) recalcTotal | — | Reads data-price and the qty display from every card in #swym-wishlist-grid and writes a formatted total to #swym-wishlist-total; called after every render, qty change, and remove |
(new) setupViewToggle | — | Shows or hides the Table / Grid toggle; wires button clicks to flip currentView and swap visibility between table and grid |
(new) syncQtyAcrossViews | — | Keeps qty displays in sync when the shopper changes qty in one view, preventing table and grid from diverging |
(new) syncNoteAcrossViews | — | Same for note textareas |
(new) scheduleItemUpdate | — | Debounced call to swat.updateListItem() to persist qty + note changes (600 ms) |
(new) setupAddAllButton | — | Shows/hides the Add All to Cart button; hidden when all items are out of stock |
(new) addAllToCart | — | Reads data-epi + .swym-wishlist-qty-value from every card and POSTs all in-stock items to /cart/add.js in one request |
The share modal, save nudge, login nudge, and openShareModal are unchanged from the base guide.
Cache is a UX optimisation, not a data source.
initWishlistPagerenders immediately fromsessionStorageif a matching cached list is found, thenswat.fetchListsalways re-renders from the authoritative API response. The cached render does not callsetWishlistTitleorsetupBackToLists-- both appear once the live render completes.
Wiring setupAddAllButton into initWishlistPage
setupAddAllButton into initWishlistPageIf you are copying the Complete Implementation directly, setupAddAllButton is already wired at every call site — this section is a reference for integrating piecemeal into an existing custom build.
Call setupAddAllButton immediately after renderProducts in each place renderProducts is called — the initial fetchLists callback, the sw:removedfromwishlist listener, and the list tile click handler:
// In the fetchLists callbackFn, after renderProducts:
renderProducts(swat, lid, products, grid, countEl, empty);
setupShareButton(swat, lid, products.length);
setupAddAllButton(products); // ← add this line
// In the sw:removedfromwishlist handler, after renderProducts:
renderProducts(swat, lid, updated, grid, countEl, empty);
setupShareButton(swat, lid, count);
setupAddAllButton(updated); // ← add this lineModified renderProducts
renderProductsReplace renderProducts in assets/swym-wishlist-page.js with the version in the Complete Implementation below. The Home & Furniture version differs from the base guide in four ways:
-
Product JSON cache — fetches are keyed by variant ID (
product.epi) and stored inproductJSONCache. Switching between wishlists re-uses cached data without hitting Shopify's product endpoint again. -
Dual-view output — each product renders into two places simultaneously: a card appended to
#swym-wishlist-grid(same structure as the base guide, plus qty controls and a note textarea; carriesdata-epianddata-price), and a structured table row appended to#swym-wishlist-tbody(image, product name, variant title, qty stepper, live line price, note textarea, Add to Cart, Remove). Both elements carrydata-episo the sync helpers can find them by variant ID. -
Cross-view sync — qty and note change handlers call
syncQtyAcrossViewsandsyncNoteAcrossViewsso both the card and the table row always reflect the same value. The table row also updates the displayed line price (unit price × qty) as the shopper changes qty. Every qty change also callsrecalcTotalto keep the running total current. -
View toggle and total —
setupViewToggle(true)is called after all rows are built to show the Table / Grid buttons and apply the currentcurrentViewstate.recalcTotal()is called immediately after to initialize the running total in#swym-wishlist-total. When the wishlist is empty,setupViewToggle(false)hides the toggle andrecalcTotal()clears the total.
Note on quantity: each per-card Add to Cart call reads the current qty display value rather than a hardcoded
1, so the quantity the shopper set carries through to the cart.addAllToCartdoes the same — reading from the DOM at click time.
New scheduleItemUpdate
scheduleItemUpdateAdd this function anywhere in the IIFE, alongside the existing helpers. It debounces writes so rapid +/− taps or keystroke-by-keystroke note edits do not fire one API call per interaction. The complete function is in the Complete Implementation below.
Debounce interval: 600 ms works well for both quantity buttons and textarea input. Lower values increase API call volume; higher values make saves feel sluggish.
New setupViewToggle
setupViewToggleAdd this function alongside the existing helpers. renderProducts calls it at the end of every render pass — with true when products exist and false when the wishlist is empty.
When hasProducts is false, the toggle wrapper is hidden. When true, it reads currentView and applies visibility to either #swym-wishlist-table-wrapper or #swym-wishlist-grid, then re-wires the Table and Grid buttons using cloneNode to clear any listener attached in a previous render pass.
currentViewis module-level — it persists across list switches and re-renders within the same page session, so the shopper's view choice is preserved when they navigate between lists.
New syncQtyAcrossViews and syncNoteAcrossViews
syncQtyAcrossViews and syncNoteAcrossViewsAdd both functions alongside the existing helpers. They are called from the qty stepper and note input handlers in both the grid card and the table row.
Both functions query [data-epi] elements so they work regardless of which view is currently visible. The sourceEl guard prevents the triggering input from being overwritten with its own value, which would reset the cursor position in a textarea.
New renderListsGrid
renderListsGridAdd this function alongside the existing helpers. initWishlistPage calls it when a multi-list user arrives with no remembered list selection.
The function hides the table wrapper, product grid, view toggle, Add All button, Share button, back button, and total display — everything that belongs to a single-list view — then builds a grid of <button> tiles in #swym-lists-grid. Each tile shows up to three product thumbnails (using the iu field from listcontents) and an item count. The first tile's first image loads with fetchpriority="high"; all others use loading="lazy".
Clicking a tile stores the selected lid in sessionStorage under SS_ACTIVE_LID_KEY, hides the lists grid, sets the page title to the list name, and calls renderProducts to load that list's items.
New setupBackToLists
setupBackToListsAdd this function alongside the existing helpers. initWishlistPage calls it (via renderListsGrid's tile click handler and directly after rendering a single list) for any multi-list user who is viewing a specific list.
The function shows #swym-back-to-lists and replaces the button node with cloneNode(true) to clear any listener from a previous render pass. Clicking the button removes SS_ACTIVE_LID_KEY and SS_PRODUCTS_KEY from sessionStorage and calls renderListsGrid, taking the shopper back to the landing view.
New recalcTotal
recalcTotalAdd this function alongside the existing helpers. It is called at the end of every renderProducts pass, after every qty change (in both the card and the table row), and inside each card's remove handler.
The function queries every .swym-wishlist-card[data-price] inside #swym-wishlist-grid, multiplies each card's data-price by its current qty display value, and sums the results. If the total is greater than zero it writes a formatted string to #swym-wishlist-total and shows the element; otherwise it hides the element. Prices are stored in cents on data-price, matching Shopify's variant price format, so the same formatPrice helper used elsewhere formats them correctly.
Liquid
The Home & Furniture Liquid is a complete page template, not a small addition to the base guide's template. Copy sections/swym-wishlist-page.liquid from the Complete Implementation directly. Key structural differences from the base guide:
- Header — a
.swym-page-headerflex wrapper separates.swym-header-title(theh1and item count) from.swym-header-controls(the view toggle, Add All button, and Share button grouped together) - Lists landing view —
#swym-lists-gridrenders the grid of list tiles; it starts hidden and is shown byrenderListsGrid - Wishlist bar —
#swym-wishlist-barsits below the header and holds#swym-back-to-lists(hidden until a list is selected) and#swym-wishlist-total(the running total, hidden until products are present) - Table and grid — both
#swym-wishlist-table-wrapperand#swym-wishlist-gridstart withdisplay:none;setupViewTogglereveals whichever view matchescurrentView
New setupAddAllButton and addAllToCart
setupAddAllButton and addAllToCartAdd both functions to assets/swym-wishlist-page.js alongside the existing helpers. The complete functions are in the Complete Implementation below.
setupAddAllButton shows or hides #swym-add-all-btn based on whether at least one in-stock product exists. It uses cloneNode to replace the button and clear any listener from a previous render pass before attaching a fresh click handler.
addAllToCart reads data-epi and the qty display from every .swym-wishlist-card[data-epi] inside #swym-wishlist-grid at click time (not from the original products array), so any qty changes the shopper made are respected. It checks whether each card's cart button is disabled before including the item, skipping out-of-stock products silently.
Out-of-stock items are skipped.
addAllToCartchecks the per-card cart button's disabled state before including an item — so a wishlist with a mix of in-stock and out-of-stock products adds only the in-stock ones, without erroring on the rest.
Styling additions
The Complete Implementation includes a full CSS file. The selectors below cover the additions specific to this guide on top of the base guide's card and modal styles.
| Element | Purpose |
|---|---|
.swym-page-header | Flex wrapper for the header row — title on the left, controls on the right |
.swym-header-title | Left side of the header, holds the h1 and count |
.swym-header-controls | Right side of the header, holds view toggle, Add All button, and Share button |
#swym-wishlist-bar | Bar below the header holding the back button (left) and running total (right) |
#swym-back-to-lists | "Back to all Lists" button; hidden when the shopper is on the landing view |
#swym-wishlist-total | Running price total for the current list; hidden when no priced items are present |
#swym-lists-grid | Grid of list tiles shown on the landing view |
.swym-list-tile | Individual list tile button — card-like, with hover lift |
.swym-list-tile-images | Image strip inside the tile; shows up to 3 product thumbnails side-by-side |
.swym-list-tile-images--1/2/3 | Modifier classes that control how many images fill the strip |
.swym-list-tile-placeholder | Gradient fill shown when the list has no product images; flex container so the empty label centres |
.swym-list-tile-empty-label | "This list is empty!" label centred inside the placeholder |
.swym-list-tile-info | Name and item count below the image strip |
.swym-list-tile-name | List name label |
.swym-list-tile-count | Item count label |
#swym-add-all-btn | Add All to Cart button — hidden until 1+ in-stock items exist |
#swym-view-toggle | Wrapper for the Table / Grid toggle buttons; hidden when wishlist is empty |
.swym-view-btn | Table and Grid toggle buttons |
.swym-view-btn.is-active | Active view button — indicates the selected view |
#swym-wishlist-table-wrapper | Outer wrapper for the table view; hidden when grid view is active |
.swym-table-header | Column header row (Product, Qty, Price, Note, actions) |
.swym-table-row | One row per product in the table |
.swym-table-col-* | Individual column cells: img, product, qty, price, note, actions |
.swym-table-variant-title | Variant name shown below the product title in the table |
.swym-table-line-price | Per-row price that updates live as qty changes (unit price × qty) |
.swym-table-actions-inner | Flex wrapper for the cart and remove buttons inside .swym-table-col-actions |
.swym-table-note | Modifier on .swym-wishlist-note for compact table-row sizing |
.swym-wishlist-qty | Quantity stepper wrapper (− / count / +) — shared between grid cards and table rows |
.swym-wishlist-qty-value | Quantity number display — set a fixed min-width to prevent layout shift |
.swym-wishlist-note-label | "Note" label above the card textarea (grid view only) |
.swym-wishlist-note | Note textarea — resize:vertical; shared class between grid and table |
.swym-wishlist-cart-btn | Add to Cart button; [disabled] = out of stock |
.swym-wishlist-remove-btn | Remove button |
As with all card styles, pull border colour, border radius, and font values from your theme's CSS variables rather than hardcoding them.
Product Fields Used (additions)
These fields are added on top of the ones documented in the base guide.
| Field | Usage |
|---|---|
iu | Item image URL — used as the thumbnail source inside each list tile on the landing view (up to three images per tile, sourced from listcontents) |
qty | Quantity stored on the wishlist item (defaults to 1 if not set). Read on render, written back via updateListItem |
note | Free-text note stored on the wishlist item. Read on render, written back via updateListItem |
Complete Implementation
Copy the files for your setup into the theme. The Liquid goes in sections/, the JS in assets/. Choose the variant that matches your Swym dashboard configuration.
sections/swym-wishlist-page.liquid
sections/swym-wishlist-page.liquid{% comment %} Swym Wishlist Page — Home n Furniture, Multiple Wishlists {% endcomment %}
<div id="swym-wishlist-page" class="page-width">
<div class="swym-page-header">
<div class="swym-header-title">
<h1><span id="swym-wishlist-title">My Wishlists</span> <span id="swym-wishlist-count"></span></h1>
</div>
<div class="swym-header-controls">
<div id="swym-view-toggle" style="display:none;">
<button id="swym-view-table-btn" class="swym-view-btn is-active" aria-pressed="true" aria-label="Switch to table view">Table</button>
<button id="swym-view-grid-btn" class="swym-view-btn" aria-pressed="false" aria-label="Switch to grid view">Grid</button>
</div>
<button id="swym-add-all-btn" style="display:none;" aria-label="Add all wishlist items to cart">Add All to Cart</button>
<button id="swym-share-btn" style="display:none;" aria-label="Share wishlist">Share Wishlist</button>
</div>
</div>
{%- comment -%} Share modal — rendered hidden; JS controls visibility {%- endcomment -%}
<div id="swym-share-modal" role="dialog" aria-modal="true" aria-labelledby="swym-share-modal-title" style="display:none; position:fixed; inset:0; z-index:9999;">
<div id="swym-share-modal-overlay" style="display:block; position:absolute; inset:0; background:rgba(0,0,0,0.5);"></div>
<div id="swym-share-modal-content" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); background:#fff; padding:2rem; max-width:480px; width:90%; border-radius:8px; max-height:90vh; overflow-y:auto;">
<div id="swym-share-modal-header">
<h2 id="swym-share-modal-title">Share Your Wishlist</h2>
<button id="swym-share-modal-close" aria-label="Close share dialog">×</button>
</div>
{%- comment -%} Email share form {%- endcomment -%}
<div class="swym-share-section">
<label for="swym-share-name-input">Name</label>
<input id="swym-share-name-input" type="text" placeholder="Your name" aria-label="Your name">
<label for="swym-share-email-input">Email</label>
<input id="swym-share-email-input" type="email" placeholder="[email protected]" aria-label="Recipient email address">
<label for="swym-share-note-input">Message</label>
<textarea id="swym-share-note-input" placeholder="Check out my wishlist!" aria-label="Message"></textarea>
<button id="swym-share-email-btn" aria-label="Send wishlist by email">Share Via Email</button>
<span id="swym-share-email-feedback" style="display:none;" aria-live="polite">Email sent!</span>
</div>
{%- comment -%} Social / copy {%- endcomment -%}
<div class="swym-share-section swym-share-social-section">
<p class="swym-share-divider-label">or Share via</p>
<div class="swym-share-social-row">
<button class="swym-share-social-btn" data-platform="facebook" aria-label="Share on Facebook">Facebook</button>
<button class="swym-share-social-btn" data-platform="twitter" aria-label="Share on Twitter / X">X</button>
<button id="swym-share-copy-btn" aria-label="Copy link">Copy Link</button>
</div>
<span id="swym-share-copy-feedback" style="display:none;" aria-live="polite">Copied!</span>
</div>
</div>
</div>
{%- comment -%} Login nudge for anonymous users {%- endcomment -%}
<div id="swym-login-nudge" style="display:none;">
<p>Sign in to save your wishlist across devices</p>
<a href="/account/login?return_url=/pages/wishlist">Sign in</a>
</div>
{%- comment -%} Save nudge — shown after 3+ items for anonymous users {%- endcomment -%}
<div id="swym-save-nudge" style="display:none;" aria-live="polite" aria-atomic="true">
<p><strong>Don't lose your list!</strong> You've saved <span id="swym-save-nudge-count"></span> items. <a href="/account/login?return_url=/pages/wishlist">Sign in to keep them.</a></p>
<button id="swym-save-nudge-dismiss" aria-label="Dismiss">×</button>
</div>
<div id="swym-wishlist-loading" aria-live="polite" aria-atomic="true">Loading your wishlist...</div>
{%- comment -%} Lists selection grid — landing view when multiple wishlists exist {%- endcomment -%}
<div id="swym-lists-grid" style="display:none;" aria-label="Your wishlists"></div>
{%- comment -%} Bar: back button (left) + wishlist total (right) — shown when viewing a specific list {%- endcomment -%}
<div id="swym-wishlist-bar">
<button id="swym-back-to-lists" type="button" style="display:none;" aria-label="Back to all wishlists">< Back to all Lists</button>
<div id="swym-wishlist-total" style="display:none;" aria-live="polite"></div>
</div>
<div id="swym-wishlist-table-wrapper" style="display:none;">
<div id="swym-wishlist-table" role="region" aria-label="Wishlist products">
<div class="swym-table-header">
<div class="swym-table-col-img"> </div>
<div class="swym-table-col-product">Product</div>
<div class="swym-table-col-qty">Qty</div>
<div class="swym-table-col-price">Price</div>
<div class="swym-table-col-note">Note</div>
<div class="swym-table-col-actions"> </div>
</div>
<div id="swym-wishlist-tbody"></div>
</div>
</div>
<div id="swym-wishlist-grid" aria-busy="true" aria-label="Wishlist products" style="display:none;"></div>
<div id="swym-wishlist-empty" style="display:none;">
<p>Your wishlist is empty</p>
<p>Start adding items you love</p>
<a href="/collections">Browse products</a>
</div>
</div>
<script src="{{ 'swym-wishlist-page.js' | asset_url }}" defer></script>assets/swym-wishlist-page.js
assets/swym-wishlist-page.js(function() {
'use strict';
var SAVE_NUDGE_THRESHOLD = 3;
var SS_PRODUCTS_KEY = 'swym-wishlist-products';
var SS_SHARE_URL_KEY = 'swym-share-url';
var SS_ACTIVE_LID_KEY = 'swym-active-lid';
var productJSONCache = {};
var shareButtonHandler = null;
var currentView = 'table';
function initWishlistPage(swat) {
var isLoggedIn = swat.platform.isLoggedIn();
var grid = document.getElementById('swym-wishlist-grid');
var loading = document.getElementById('swym-wishlist-loading');
var empty = document.getElementById('swym-wishlist-empty');
var countEl = document.getElementById('swym-wishlist-count');
if (!grid) return;
if (!isLoggedIn) {
var loginNudge = document.getElementById('swym-login-nudge');
if (loginNudge) loginNudge.style.display = '';
}
// Only use cache when the user has explicitly selected a list in this session
var activeLidCached = sessionStorage.getItem(SS_ACTIVE_LID_KEY);
var cachedRaw = activeLidCached ? sessionStorage.getItem(SS_PRODUCTS_KEY) : null;
var cachedData = null;
if (cachedRaw) {
try {
cachedData = JSON.parse(cachedRaw);
if (cachedData && cachedData.lid === activeLidCached) {
if (loading) loading.style.display = 'none';
renderProducts(swat, cachedData.lid, cachedData.products, grid, countEl, empty);
if (countEl) countEl.textContent = '(' + cachedData.products.length + ' items)';
setupShareButton(swat, cachedData.lid, cachedData.products.length);
setupAddAllButton(cachedData.products);
} else {
cachedData = null;
}
} catch (e) {
sessionStorage.removeItem(SS_PRODUCTS_KEY);
cachedData = null;
}
}
swat.fetchLists({
callbackFn: function(lists) {
if (loading) loading.style.display = 'none';
if (!lists || lists.length === 0) {
if (empty) empty.style.display = 'block';
if (countEl) countEl.textContent = '(0 items)';
return;
}
var multiList = swat.isCollectionsEnabled() && lists.length > 1;
var rememberedLid = sessionStorage.getItem(SS_ACTIVE_LID_KEY);
var activeList = rememberedLid
? lists.find(function(l) { return l.lid === rememberedLid; })
: null;
// Multi-list with no remembered selection: show the lists selection grid
if (multiList && !activeList) {
renderListsGrid(swat, lists, grid, countEl, empty);
return;
}
// Single list or remembered selection: render list directly
if (!activeList) activeList = lists[0];
var lid = activeList.lid;
var products = activeList.listcontents || [];
sessionStorage.setItem(SS_PRODUCTS_KEY, JSON.stringify({ lid: lid, products: products }));
if (products.length === 0) {
if (empty) empty.style.display = 'block';
if (countEl) countEl.textContent = '(0 items)';
} else {
if (countEl) countEl.textContent = '(' + products.length + ' items)';
showSaveNudge(swat, products.length);
renderProducts(swat, lid, products, grid, countEl, empty);
setupShareButton(swat, lid, products.length);
setupAddAllButton(products);
sessionStorage.removeItem(SS_SHARE_URL_KEY);
}
setWishlistTitle(activeList.lname);
if (multiList) setupBackToLists(swat, lists, lid, grid, countEl, empty);
if (swat.evtLayer) {
swat.evtLayer.addEventListener('sw:removedfromwishlist', function() {
swat.fetchLists({
callbackFn: function(freshLists) {
var activeLid = sessionStorage.getItem(SS_ACTIVE_LID_KEY) || lid;
var freshActive = freshLists && freshLists.length
? (freshLists.find(function(l) { return l.lid === activeLid; }) || freshLists[0])
: null;
var currentLid = freshActive ? freshActive.lid : null;
var updated = freshActive ? (freshActive.listcontents || []) : [];
var count = updated.length;
sessionStorage.setItem(SS_PRODUCTS_KEY, JSON.stringify({ lid: currentLid, products: updated }));
sessionStorage.removeItem(SS_SHARE_URL_KEY);
renderProducts(swat, currentLid, updated, grid, countEl, empty);
if (countEl) countEl.textContent = '(' + count + ' items)';
setupShareButton(swat, currentLid, count);
setupAddAllButton(updated);
if (multiList) setupBackToLists(swat, freshLists || [], currentLid, grid, countEl, empty);
}
});
});
}
},
errorFn: function() {
if (loading) loading.style.display = 'none';
}
});
}
// Renders the lists selection grid (landing view for multi-list users)
function renderListsGrid(swat, lists, grid, countEl, emptyEl) {
var listsGridEl = document.getElementById('swym-lists-grid');
if (!listsGridEl) return;
var tableWrapper = document.getElementById('swym-wishlist-table-wrapper');
var viewToggle = document.getElementById('swym-view-toggle');
var addAllBtn = document.getElementById('swym-add-all-btn');
var shareBtn = document.getElementById('swym-share-btn');
var backBtn = document.getElementById('swym-back-to-lists');
var totalEl = document.getElementById('swym-wishlist-total');
if (tableWrapper) tableWrapper.style.display = 'none';
grid.style.display = 'none';
if (viewToggle) viewToggle.style.display = 'none';
if (addAllBtn) addAllBtn.style.display = 'none';
if (shareBtn) shareBtn.style.display = 'none';
if (backBtn) backBtn.style.display = 'none';
if (totalEl) totalEl.style.display = 'none';
if (emptyEl) emptyEl.style.display = 'none';
if (countEl) countEl.textContent = '';
setWishlistTitle('My Wishlists');
listsGridEl.innerHTML = '';
listsGridEl.style.display = '';
lists.forEach(function(list, listIndex) {
var images = (list.listcontents || [])
.slice(0, 3)
.map(function(item) { return item.iu || ''; })
.filter(Boolean);
var imgCount = images.length;
var imagesHtml = imgCount === 0
? '<div class="swym-list-tile-placeholder"><span class="swym-list-tile-empty-label">This list is empty!</span></div>'
: images.map(function(src, imgIdx) {
var tileLoadAttr = (listIndex === 0 && imgIdx === 0) ? 'fetchpriority="high"' : 'loading="lazy"';
return '<img src="' + esc(src) + '" alt="" ' + tileLoadAttr + '>';
}).join('');
var itemCount = (list.listcontents || []).length;
var tile = document.createElement('button');
tile.type = 'button';
tile.className = 'swym-list-tile';
tile.setAttribute('aria-label', 'Open wishlist: ' + list.lname);
tile.dataset.lid = list.lid;
tile.innerHTML =
'<div class="swym-list-tile-images swym-list-tile-images--' + Math.max(1, imgCount) + '">' +
imagesHtml +
'</div>' +
'<div class="swym-list-tile-info">' +
'<span class="swym-list-tile-name">' + esc(list.lname) + '</span>' +
'<span class="swym-list-tile-count">' + itemCount + ' ' + (itemCount === 1 ? 'item' : 'items') + '</span>' +
'</div>';
tile.addEventListener('click', function() {
var products = list.listcontents || [];
sessionStorage.setItem(SS_ACTIVE_LID_KEY, list.lid);
sessionStorage.setItem(SS_PRODUCTS_KEY, JSON.stringify({ lid: list.lid, products: products }));
sessionStorage.removeItem(SS_SHARE_URL_KEY);
listsGridEl.style.display = 'none';
setWishlistTitle(list.lname);
if (countEl) countEl.textContent = '(' + products.length + ' items)';
setupBackToLists(swat, lists, list.lid, grid, countEl, emptyEl);
setupShareButton(swat, list.lid, products.length);
setupAddAllButton(products);
if (totalEl) totalEl.style.display = '';
renderProducts(swat, list.lid, products, grid, countEl, emptyEl);
});
listsGridEl.appendChild(tile);
});
}
// Wires the back-to-lists button for multi-list users viewing a specific list
function setupBackToLists(swat, lists, activeLid, grid, countEl, emptyEl) {
var backBtn = document.getElementById('swym-back-to-lists');
if (!backBtn) return;
var fresh = backBtn.cloneNode(true);
backBtn.parentNode.replaceChild(fresh, backBtn);
fresh.style.display = '';
fresh.addEventListener('click', function() {
sessionStorage.removeItem(SS_ACTIVE_LID_KEY);
sessionStorage.removeItem(SS_PRODUCTS_KEY);
renderListsGrid(swat, lists, grid, countEl, emptyEl);
});
}
// Recalculates and updates the total from current grid card prices + quantities
function recalcTotal() {
var totalEl = document.getElementById('swym-wishlist-total');
if (!totalEl) return;
var total = 0;
var hasAny = false;
document.querySelectorAll('#swym-wishlist-grid .swym-wishlist-card[data-price]').forEach(function(card) {
var price = parseFloat(card.dataset.price) || 0;
if (price <= 0) return;
var qtyEl = card.querySelector('.swym-wishlist-qty-value');
var qty = qtyEl ? (parseInt(qtyEl.textContent, 10) || 1) : 1;
total += price * qty;
hasAny = true;
});
if (hasAny) {
totalEl.textContent = 'Total: ' + formatPrice(total);
totalEl.style.display = '';
} else {
totalEl.style.display = 'none';
}
}
function showSaveNudge(swat, count) {
if (swat.platform.isLoggedIn()) return;
if (count < SAVE_NUDGE_THRESHOLD) return;
if (sessionStorage.getItem('swym-save-nudge-dismissed')) return;
var nudge = document.getElementById('swym-save-nudge');
if (!nudge) return;
var countSpan = document.getElementById('swym-save-nudge-count');
if (countSpan) countSpan.textContent = count;
nudge.style.display = '';
var dismiss = document.getElementById('swym-save-nudge-dismiss');
if (dismiss) {
dismiss.addEventListener('click', function() {
nudge.style.display = 'none';
sessionStorage.setItem('swym-save-nudge-dismissed', 'true');
});
}
}
async function renderProducts(swat, lid, products, container, countEl, emptyEl) {
container.setAttribute('aria-busy', 'true');
container.innerHTML = '';
var tableBody = document.getElementById('swym-wishlist-tbody');
if (tableBody) tableBody.innerHTML = '';
if (!products || products.length === 0) {
if (emptyEl) emptyEl.style.display = 'block';
container.setAttribute('aria-busy', 'false');
setupViewToggle(false);
recalcTotal();
return;
}
if (emptyEl) emptyEl.style.display = 'none';
var shopifyData = await Promise.all(products.map(function(product) {
var key = String(product.epi);
if (productJSONCache[key]) return Promise.resolve(productJSONCache[key]);
return fetchProductJSON(product.du)
.then(function(data) { productJSONCache[key] = data; return data; })
.catch(function() { return null; });
}));
products.forEach(function(product, index) {
var shopifyProduct = shopifyData[index];
var allVariants = shopifyProduct ? shopifyProduct.variants : null;
var variant = allVariants
? allVariants.find(function(v) { return String(v.id) === String(product.epi); })
: null;
var inStock = variant ? variant.available : product.stk !== 0;
var displayPrice = variant ? variant.price : product.pr;
var title = esc(shopifyProduct && shopifyProduct.title || product.dt || 'Product');
var href = esc(shopifyProduct && shopifyProduct.url || product.du || '#');
var imgSrc = esc(shopifyProduct && (shopifyProduct.featured_image || (shopifyProduct.images && shopifyProduct.images[0])) || product.iu || '');
var noteId = 'swym-note-' + esc(String(product.epi));
var imgLoadAttr = index === 0 ? 'fetchpriority="high"' : 'loading="lazy"';
var card = document.createElement('div');
card.className = 'swym-wishlist-card';
card.dataset.epi = product.epi;
if (displayPrice) card.dataset.price = displayPrice;
card.innerHTML =
'<a href="' + href + '">' +
'<img src="' + imgSrc + '" alt="' + title + '" ' + imgLoadAttr + '>' +
'</a>' +
'<a href="' + href + '">' + title + '</a>' +
(displayPrice ? '<span>' + esc(formatPrice(displayPrice)) + '</span>' : '') +
'<div class="swym-wishlist-card-actions">' +
'<div class="swym-wishlist-qty">' +
'<button type="button" aria-label="Decrease quantity of ' + title + '">−</button>' +
'<span class="swym-wishlist-qty-value" aria-live="polite">' + (product.qty || 1) + '</span>' +
'<button type="button" aria-label="Increase quantity of ' + title + '">+</button>' +
'</div>' +
'<label class="swym-wishlist-note-label" for="' + noteId + '">Note</label>' +
'<textarea id="' + noteId + '" class="swym-wishlist-note" rows="2"' +
' placeholder="Add a note (size, colour, special instructions…)"' +
' aria-label="Note for ' + title + '">' + esc(product.note || '') + '</textarea>' +
'<button type="button" class="swym-wishlist-cart-btn"' + (!inStock ? ' disabled' : '') +
' aria-label="' + (inStock ? 'Add ' + title + ' to cart' : title + ' is out of stock') + '">' +
(inStock ? 'Add to Cart' : 'Out of Stock') +
'</button>' +
'<button type="button" class="swym-wishlist-remove-btn" aria-label="Remove ' + title + ' from wishlist">×</button>' +
'</div>';
var decrementBtn = card.querySelector('.swym-wishlist-qty > button:first-of-type');
var qtyDisplay = card.querySelector('.swym-wishlist-qty-value');
var incrementBtn = card.querySelector('.swym-wishlist-qty > button:last-of-type');
var noteInput = card.querySelector('.swym-wishlist-note');
var cartBtn = card.querySelector('.swym-wishlist-cart-btn');
var removeBtn = card.querySelector('.swym-wishlist-remove-btn');
decrementBtn.addEventListener('click', function() {
var qty = parseInt(qtyDisplay.textContent, 10);
if (qty <= 1) return;
qty -= 1;
qtyDisplay.textContent = qty;
product.qty = qty;
syncQtyAcrossViews(product.epi, qty, qtyDisplay);
scheduleItemUpdate(swat, lid, product, qty, noteInput.value.trim());
recalcTotal();
});
incrementBtn.addEventListener('click', function() {
var qty = parseInt(qtyDisplay.textContent, 10) + 1;
qtyDisplay.textContent = qty;
product.qty = qty;
syncQtyAcrossViews(product.epi, qty, qtyDisplay);
scheduleItemUpdate(swat, lid, product, qty, noteInput.value.trim());
recalcTotal();
});
noteInput.addEventListener('input', function() {
var qty = parseInt(qtyDisplay.textContent, 10);
product.note = noteInput.value.trim();
syncNoteAcrossViews(product.epi, noteInput.value, noteInput);
scheduleItemUpdate(swat, lid, product, qty, noteInput.value.trim());
});
cartBtn.addEventListener('click', function() {
if (cartBtn.disabled) return;
var qty = parseInt(qtyDisplay.textContent, 10);
cartBtn.textContent = 'Adding...';
cartBtn.setAttribute('aria-label', 'Adding ' + title + ' to cart');
cartBtn.disabled = true;
fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: [{ id: Number(product.epi), quantity: qty }] })
})
.then(function(response) {
if (response.ok) {
cartBtn.textContent = 'Added!';
cartBtn.setAttribute('aria-label', title + ' added to cart');
setTimeout(function() {
cartBtn.textContent = 'Add to Cart';
cartBtn.setAttribute('aria-label', 'Add ' + title + ' to cart');
cartBtn.disabled = false;
}, 2000);
} else {
cartBtn.textContent = 'Error';
cartBtn.setAttribute('aria-label', 'Failed to add ' + title + ' to cart');
cartBtn.disabled = false;
}
})
.catch(function() {
cartBtn.textContent = 'Error';
cartBtn.setAttribute('aria-label', 'Failed to add ' + title + ' to cart');
cartBtn.disabled = false;
});
});
removeBtn.addEventListener('click', function() {
swat.deleteFromList(
lid,
{ epi: product.epi, empi: product.empi, du: product.du },
function() {
card.remove();
var tableRow = document.querySelector('#swym-wishlist-table [data-epi="' + String(product.epi) + '"]');
if (tableRow) tableRow.remove();
var remaining = container.querySelectorAll('.swym-wishlist-card');
if (countEl) countEl.textContent = '(' + remaining.length + ' items)';
if (remaining.length === 0 && emptyEl) emptyEl.style.display = 'block';
recalcTotal();
},
function() {}
);
});
container.appendChild(card);
if (tableBody) {
var tableNoteId = 'swym-tnote-' + esc(String(product.epi));
var variantTitle = variant ? esc(variant.title) : '';
var initialQty = product.qty || 1;
var initialLinePrice = displayPrice ? formatPrice(displayPrice * initialQty) : '';
var row = document.createElement('div');
row.className = 'swym-table-row';
row.dataset.epi = product.epi;
row.innerHTML =
'<div class="swym-table-col-img">' +
'<a href="' + href + '"><img src="' + imgSrc + '" alt="' + title + '" width="150" height="150" ' + imgLoadAttr + '></a>' +
'</div>' +
'<div class="swym-table-col-product">' +
'<a href="' + href + '">' + title + '</a>' +
(variantTitle ? '<span class="swym-table-variant-title">' + variantTitle + '</span>' : '') +
'</div>' +
'<div class="swym-table-col-qty">' +
'<div class="swym-wishlist-qty">' +
'<button type="button" aria-label="Decrease quantity of ' + title + '">−</button>' +
'<span class="swym-wishlist-qty-value" aria-live="polite">' + initialQty + '</span>' +
'<button type="button" aria-label="Increase quantity of ' + title + '">+</button>' +
'</div>' +
'</div>' +
'<div class="swym-table-col-price">' +
(displayPrice ? '<span class="swym-table-line-price">' + esc(initialLinePrice) + '</span>' : '') +
'</div>' +
'<div class="swym-table-col-note">' +
'<textarea id="' + tableNoteId + '" class="swym-wishlist-note swym-table-note" rows="1"' +
' placeholder="Note…"' +
' aria-label="Note for ' + title + '">' + esc(product.note || '') + '</textarea>' +
'</div>' +
'<div class="swym-table-col-actions">' +
'<div class="swym-table-actions-inner">' +
'<button type="button" class="swym-wishlist-cart-btn"' + (!inStock ? ' disabled' : '') +
' aria-label="' + (inStock ? 'Add ' + title + ' to cart' : title + ' is out of stock') + '">' +
(inStock ? 'Add to Cart' : 'Out of Stock') +
'</button>' +
'<button type="button" class="swym-wishlist-remove-btn" aria-label="Remove ' + title + ' from wishlist">\xd7</button>' +
'</div>' +
'</div>';
var tDecrBtn = row.querySelector('.swym-table-col-qty .swym-wishlist-qty > button:first-of-type');
var tQtyDisplay = row.querySelector('.swym-wishlist-qty-value');
var tIncrBtn = row.querySelector('.swym-table-col-qty .swym-wishlist-qty > button:last-of-type');
var tNoteInput = row.querySelector('.swym-wishlist-note');
var tCartBtn = row.querySelector('.swym-wishlist-cart-btn');
var tRemoveBtn = row.querySelector('.swym-wishlist-remove-btn');
var tPriceEl = row.querySelector('.swym-table-line-price');
tDecrBtn.addEventListener('click', function() {
var qty = parseInt(tQtyDisplay.textContent, 10);
if (qty <= 1) return;
qty -= 1;
tQtyDisplay.textContent = qty;
product.qty = qty;
syncQtyAcrossViews(product.epi, qty, tQtyDisplay);
if (tPriceEl && displayPrice) tPriceEl.textContent = formatPrice(displayPrice * qty);
scheduleItemUpdate(swat, lid, product, qty, tNoteInput.value.trim());
recalcTotal();
});
tIncrBtn.addEventListener('click', function() {
var qty = parseInt(tQtyDisplay.textContent, 10) + 1;
tQtyDisplay.textContent = qty;
product.qty = qty;
syncQtyAcrossViews(product.epi, qty, tQtyDisplay);
if (tPriceEl && displayPrice) tPriceEl.textContent = formatPrice(displayPrice * qty);
scheduleItemUpdate(swat, lid, product, qty, tNoteInput.value.trim());
recalcTotal();
});
tNoteInput.addEventListener('input', function() {
var qty = parseInt(tQtyDisplay.textContent, 10);
product.note = tNoteInput.value.trim();
syncNoteAcrossViews(product.epi, tNoteInput.value, tNoteInput);
scheduleItemUpdate(swat, lid, product, qty, tNoteInput.value.trim());
});
tCartBtn.addEventListener('click', function() {
if (tCartBtn.disabled) return;
var qty = parseInt(tQtyDisplay.textContent, 10);
tCartBtn.textContent = 'Adding...';
tCartBtn.disabled = true;
fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: [{ id: Number(product.epi), quantity: qty }] })
})
.then(function(response) {
if (response.ok) {
tCartBtn.textContent = 'Added!';
setTimeout(function() {
tCartBtn.textContent = 'Add to Cart';
tCartBtn.disabled = false;
}, 2000);
} else {
tCartBtn.textContent = 'Error';
tCartBtn.disabled = false;
}
})
.catch(function() {
tCartBtn.textContent = 'Error';
tCartBtn.disabled = false;
});
});
tRemoveBtn.addEventListener('click', function() {
swat.deleteFromList(
lid,
{ epi: product.epi, empi: product.empi, du: product.du },
function() {
row.remove();
var cardEl = container.querySelector('[data-epi="' + String(product.epi) + '"]');
if (cardEl) cardEl.remove();
var remaining = container.querySelectorAll('.swym-wishlist-card');
if (countEl) countEl.textContent = '(' + remaining.length + ' items)';
if (remaining.length === 0 && emptyEl) emptyEl.style.display = 'block';
recalcTotal();
},
function() {}
);
});
tableBody.appendChild(row);
}
});
container.setAttribute('aria-busy', 'false');
setupViewToggle(true);
recalcTotal();
}
var _updateTimers = {};
function scheduleItemUpdate(swat, lid, product, qty, note) {
var key = String(product.epi);
clearTimeout(_updateTimers[key]);
_updateTimers[key] = setTimeout(function() {
swat.updateListItem(
lid,
{
epi: product.epi,
empi: product.empi,
du: product.du,
qty: qty,
note: note
},
function() {},
function() {}
);
}, 600);
}
function syncQtyAcrossViews(epi, qty, sourceEl) {
document.querySelectorAll('[data-epi="' + String(epi) + '"] .swym-wishlist-qty-value').forEach(function(el) {
if (el !== sourceEl) el.textContent = qty;
});
}
function syncNoteAcrossViews(epi, value, sourceEl) {
document.querySelectorAll('[data-epi="' + String(epi) + '"] .swym-wishlist-note').forEach(function(el) {
if (el !== sourceEl) el.value = value;
});
}
function setupViewToggle(hasProducts) {
var toggle = document.getElementById('swym-view-toggle');
var tableWrapper = document.getElementById('swym-wishlist-table-wrapper');
var gridEl = document.getElementById('swym-wishlist-grid');
if (!toggle) return;
toggle.style.display = hasProducts ? '' : 'none';
if (!hasProducts) {
if (tableWrapper) tableWrapper.style.display = 'none';
return;
}
var tableBtn = document.getElementById('swym-view-table-btn');
var gridBtn = document.getElementById('swym-view-grid-btn');
if (!tableBtn || !gridBtn || !tableWrapper || !gridEl) return;
if (currentView === 'table') {
tableWrapper.style.display = '';
gridEl.style.display = 'none';
} else {
tableWrapper.style.display = 'none';
gridEl.style.display = '';
}
var freshTable = tableBtn.cloneNode(true);
tableBtn.parentNode.replaceChild(freshTable, tableBtn);
var freshGrid = gridBtn.cloneNode(true);
gridBtn.parentNode.replaceChild(freshGrid, gridBtn);
if (currentView === 'table') {
freshTable.classList.add('is-active');
freshTable.setAttribute('aria-pressed', 'true');
freshGrid.classList.remove('is-active');
freshGrid.setAttribute('aria-pressed', 'false');
} else {
freshGrid.classList.add('is-active');
freshGrid.setAttribute('aria-pressed', 'true');
freshTable.classList.remove('is-active');
freshTable.setAttribute('aria-pressed', 'false');
}
freshTable.addEventListener('click', function() {
currentView = 'table';
tableWrapper.style.display = '';
gridEl.style.display = 'none';
freshTable.classList.add('is-active');
freshTable.setAttribute('aria-pressed', 'true');
freshGrid.classList.remove('is-active');
freshGrid.setAttribute('aria-pressed', 'false');
});
freshGrid.addEventListener('click', function() {
currentView = 'grid';
gridEl.style.display = '';
tableWrapper.style.display = 'none';
freshGrid.classList.add('is-active');
freshGrid.setAttribute('aria-pressed', 'true');
freshTable.classList.remove('is-active');
freshTable.setAttribute('aria-pressed', 'false');
});
}
function setupAddAllButton(products) {
var btn = document.getElementById('swym-add-all-btn');
if (!btn) return;
var hasInStock = products && products.some(function(p) { return p.stk !== 0; });
if (!hasInStock) {
btn.style.display = 'none';
return;
}
btn.style.display = '';
var fresh = btn.cloneNode(true);
btn.parentNode.replaceChild(fresh, btn);
fresh.addEventListener('click', function() { addAllToCart(fresh); });
}
function addAllToCart(btn) {
var cards = document.querySelectorAll('#swym-wishlist-grid .swym-wishlist-card[data-epi]');
var items = [];
cards.forEach(function(card) {
var epi = card.dataset.epi;
var qtyEl = card.querySelector('.swym-wishlist-qty-value');
var qty = qtyEl ? parseInt(qtyEl.textContent, 10) : 1;
var cartBtn = card.querySelector('.swym-wishlist-cart-btn');
if (epi && qty > 0 && cartBtn && !cartBtn.disabled) {
items.push({ id: Number(epi), quantity: qty });
}
});
if (!items.length) return;
btn.disabled = true;
btn.textContent = 'Adding...';
btn.setAttribute('aria-label', 'Adding all items to cart');
fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: items })
})
.then(function(response) {
if (response.ok) {
btn.textContent = 'Added!';
btn.setAttribute('aria-label', 'All items added to cart');
setTimeout(function() {
btn.textContent = 'Add All to Cart';
btn.setAttribute('aria-label', 'Add all wishlist items to cart');
btn.disabled = false;
}, 2000);
} else {
btn.textContent = 'Error';
btn.setAttribute('aria-label', 'Failed to add items to cart');
btn.disabled = false;
}
})
.catch(function() {
btn.textContent = 'Error';
btn.setAttribute('aria-label', 'Failed to add items to cart');
btn.disabled = false;
});
}
function bindShareUrlButtons(modal, copyBtn, url, swat, lid) {
if (copyBtn) {
copyBtn.disabled = false;
var freshCopy = copyBtn.cloneNode(true);
copyBtn.parentNode.replaceChild(freshCopy, copyBtn);
freshCopy.addEventListener('click', function() { handleCopyLink(url); });
}
modal.querySelectorAll('.swym-share-social-btn').forEach(function(socialBtn) {
var freshSocial = socialBtn.cloneNode(true);
socialBtn.parentNode.replaceChild(freshSocial, socialBtn);
freshSocial.addEventListener('click', function() {
handleSocialShare(swat, lid, freshSocial.dataset.platform, url);
});
});
}
function setupShareButton(swat, lid, productCount) {
var btn = document.getElementById('swym-share-btn');
if (!btn || !lid) return;
if (productCount === 0) {
btn.style.display = 'none';
return;
}
btn.style.display = '';
if (shareButtonHandler) btn.removeEventListener('click', shareButtonHandler);
shareButtonHandler = function() { openShareModal(swat, lid); };
btn.addEventListener('click', shareButtonHandler);
}
function openShareModal(swat, lid) {
var modal = document.getElementById('swym-share-modal');
if (!modal) return;
var nameInput = document.getElementById('swym-share-name-input');
var emailInput = document.getElementById('swym-share-email-input');
var noteInput = document.getElementById('swym-share-note-input');
var copyBtn = document.getElementById('swym-share-copy-btn');
if (nameInput) nameInput.value = '';
if (emailInput) emailInput.value = '';
if (noteInput) noteInput.value = '';
if (copyBtn) copyBtn.disabled = true;
modal.querySelectorAll('[id$="-feedback"]').forEach(function(el) { el.style.display = 'none'; });
modal.style.display = '';
document.body.style.overflow = 'hidden';
if (nameInput) nameInput.focus();
var cachedShareUrl = sessionStorage.getItem(SS_SHARE_URL_KEY);
if (cachedShareUrl) {
bindShareUrlButtons(modal, copyBtn, cachedShareUrl, swat, lid);
} else {
swat.markListPublic(lid, function() {
swat.generateSharedListURL(lid, function(url) {
sessionStorage.setItem(SS_SHARE_URL_KEY, url);
bindShareUrlButtons(modal, copyBtn, url, swat, lid);
});
}, function() {
if (copyBtn) copyBtn.disabled = true;
});
}
var emailBtn = document.getElementById('swym-share-email-btn');
if (emailBtn) {
var freshEmail = emailBtn.cloneNode(true);
emailBtn.parentNode.replaceChild(freshEmail, emailBtn);
freshEmail.addEventListener('click', function() { handleEmailShare(swat, lid); });
}
var closeBtn = document.getElementById('swym-share-modal-close');
var overlay = document.getElementById('swym-share-modal-overlay');
if (closeBtn) {
var freshClose = closeBtn.cloneNode(true);
closeBtn.parentNode.replaceChild(freshClose, closeBtn);
freshClose.addEventListener('click', closeShareModal);
}
if (overlay) {
var freshOverlay = overlay.cloneNode(true);
overlay.parentNode.replaceChild(freshOverlay, overlay);
freshOverlay.addEventListener('click', closeShareModal);
}
document.addEventListener('keydown', handleShareModalKeydown);
}
function handleShareModalKeydown(e) {
if (e.key === 'Escape') {
closeShareModal();
return;
}
if (e.key !== 'Tab') return;
var modal = document.getElementById('swym-share-modal-content');
if (!modal) return;
var focusable = Array.prototype.slice.call(
modal.querySelectorAll('a[href],button:not([disabled]),input,textarea,select,[tabindex]:not([tabindex="-1"])')
).filter(function(el) { return !el.disabled && el.offsetParent !== null; });
if (!focusable.length) return;
var first = focusable[0];
var last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first) { e.preventDefault(); last.focus(); }
} else {
if (document.activeElement === last) { e.preventDefault(); first.focus(); }
}
}
function closeShareModal() {
var modal = document.getElementById('swym-share-modal');
if (modal) modal.style.display = 'none';
document.body.style.overflow = '';
document.removeEventListener('keydown', handleShareModalKeydown);
var trigger = document.getElementById('swym-share-btn');
if (trigger) trigger.focus();
}
function handleCopyLink(url) {
var feedback = document.getElementById('swym-share-copy-feedback');
var done = function() {
showModalFeedback(feedback);
setTimeout(closeShareModal, 800);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(done).catch(function() {
fallbackCopy(url, feedback);
setTimeout(closeShareModal, 800);
});
} else {
fallbackCopy(url, feedback);
setTimeout(closeShareModal, 800);
}
}
function handleEmailShare(swat, lid) {
var nameInput = document.getElementById('swym-share-name-input');
var emailInput = document.getElementById('swym-share-email-input');
var noteInput = document.getElementById('swym-share-note-input');
var emailBtn = document.getElementById('swym-share-email-btn');
var feedback = document.getElementById('swym-share-email-feedback');
if (!emailInput) return;
var email = emailInput.value.trim();
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
emailInput.focus();
return;
}
if (emailBtn) emailBtn.disabled = true;
swat.sendListViaEmail({
toEmailId: email,
note: noteInput ? noteInput.value.trim() : '',
fromName: nameInput ? nameInput.value.trim() : '',
lid: lid
}, function() {
if (emailBtn) emailBtn.disabled = false;
if (nameInput) nameInput.value = '';
emailInput.value = '';
if (noteInput) noteInput.value = '';
showModalFeedback(feedback);
setTimeout(closeShareModal, 1000);
}, function() {
if (emailBtn) emailBtn.disabled = false;
});
}
function handleSocialShare(swat, lid, platform, shareUrl) {
var platformUrlTemplate = '';
if (platform === 'facebook') platformUrlTemplate = 'https://www.facebook.com/sharer.php?u=' + encodeURIComponent(shareUrl) + '&t=';
else if (platform === 'twitter') platformUrlTemplate = 'https://twitter.com/share?text=&url=' + encodeURIComponent(shareUrl);
swat.shareListSocial(lid, '', platformUrlTemplate, platform, '', function() {});
if (platformUrlTemplate) window.open(platformUrlTemplate, '_blank', 'noopener,noreferrer');
closeShareModal();
}
function showModalFeedback(el) {
if (!el) return;
el.style.display = '';
setTimeout(function() { el.style.display = 'none'; }, 2500);
}
function fallbackCopy(url, feedbackEl) {
var ta = document.createElement('textarea');
ta.value = url;
ta.style.cssText = 'position:fixed;opacity:0;pointer-events:none;';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); showModalFeedback(feedbackEl); } catch (e) {}
document.body.removeChild(ta);
}
function formatPrice(priceInCents) {
return '$' + (priceInCents / 100).toFixed(2);
}
function esc(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function fetchProductJSON(du) {
var parts = du.split('/products/');
if (parts.length < 2) return Promise.reject(new Error('invalid product URL'));
var url = window.location.origin + window.Shopify.routes.root + 'products/' + parts[1].split('?')[0] + '.js';
return fetch(url)
.then(function(response) {
if (!response.ok) throw new Error('product fetch failed');
return response.json();
});
}
function setWishlistTitle(name) {
var titleEl = document.getElementById('swym-wishlist-title');
if (titleEl) titleEl.textContent = name || 'My Wishlists';
}
window.SwymCallbacks = window.SwymCallbacks || [];
window.SwymCallbacks.push(initWishlistPage);
})();
assets/swym-wishlist-page.css
assets/swym-wishlist-page.css/* ============================================================
Swym Wishlist Page
============================================================ */
/* --- Page layout ------------------------------------------ */
#swym-wishlist-page {
padding: 2rem 1rem 4rem;
}
/* --- Page header ------------------------------------------ */
.swym-page-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 1.5rem;
}
.swym-header-title {
display: flex;
align-items: center;
}
.swym-header-title h1 {
margin: 0;
font-size: 2rem;
font-weight: 700;
line-height: 1.2;
}
.swym-header-controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
@media (max-width: 640px) {
.swym-header-title h1 {
font-size: 1.375rem;
}
}
/* --- Wishlist bar (back button + total) ------------------- */
#swym-wishlist-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin: 0.75rem 0 0.25rem;
}
/* --- Back to lists button --------------------------------- */
#swym-back-to-lists {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 9px 18px;
background: #fff;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 1.125rem;
font-weight: 500;
color: #374151;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
#swym-back-to-lists:hover {
background: #f3f4f6;
border-color: #9ca3af;
}
#swym-wishlist-count {
font-size: 1.25rem;
font-weight: 400;
color: #6b7280;
}
/* --- View toggle ------------------------------------------ */
#swym-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
}
.swym-view-btn {
padding: 7px 16px;
background: #fff;
border: none;
border-left: 1px solid #d1d5db;
font-size: 1.0625rem;
font-weight: 500;
cursor: pointer;
color: #6b7280;
transition: background 0.15s, color 0.15s;
}
.swym-view-btn:first-child {
border-left: none;
}
.swym-view-btn.is-active {
background: #111827;
color: #fff;
}
.swym-view-btn:hover:not(.is-active) {
background: #f3f4f6;
color: #374151;
}
/* --- Share button ----------------------------------------- */
#swym-share-btn {
padding: 9px 18px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: #fff;
font-size: 1.125rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
#swym-share-btn:hover {
background: #f3f4f6;
border-color: #9ca3af;
}
/* --- Lists selection grid --------------------------------- */
#swym-lists-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
margin-top: 0.5rem;
}
@media (max-width: 640px) {
#swym-lists-grid {
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
}
/* --- List tile -------------------------------------------- */
.swym-list-tile {
display: flex;
flex-direction: column;
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
background: #fff;
cursor: pointer;
text-align: left;
padding: 0;
transition: box-shadow 0.2s, transform 0.2s;
}
.swym-list-tile:hover {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
transform: translateY(-3px);
}
.swym-list-tile-images {
display: flex;
height: 200px;
overflow: hidden;
background: #f3f4f6;
}
.swym-list-tile-images img {
flex: 1;
width: 0; /* flex sizing trick: all images share space equally */
height: 100%;
object-fit: cover;
display: block;
}
.swym-list-tile-images img + img {
border-left: 2px solid #fff;
}
.swym-list-tile-placeholder {
flex: 1;
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
display: flex;
align-items: center;
justify-content: center;
}
.swym-list-tile-empty-label {
font-size: 0.8125rem;
color: #9ca3af;
text-align: center;
padding: 0 12px;
}
.swym-list-tile-info {
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 4px;
}
.swym-list-tile-name {
font-size: 1.125rem;
font-weight: 600;
color: #111827;
display: block;
}
.swym-list-tile-count {
font-size: 1rem;
color: #6b7280;
display: block;
}
@media (max-width: 640px) {
.swym-list-tile-images {
height: 140px;
}
}
/* --- Wishlist total --------------------------------------- */
#swym-wishlist-total {
font-size: 1.125rem;
font-weight: 600;
color: #111827;
letter-spacing: 0.01em;
text-align: right;
margin-left: auto;
}
/* --- Loading / empty states ------------------------------- */
#swym-wishlist-loading {
padding: 3rem 0;
text-align: center;
color: #6b7280;
font-size: 1.1875rem;
}
#swym-wishlist-empty {
padding: 4rem 0;
text-align: center;
}
#swym-wishlist-empty p {
margin: 0 0 0.5rem;
color: #374151;
}
#swym-wishlist-empty p:first-child {
font-size: 1.375rem;
font-weight: 600;
}
#swym-wishlist-empty p:last-of-type {
color: #6b7280;
font-size: 1.125rem;
margin-bottom: 1.5rem;
}
#swym-wishlist-empty a {
display: inline-block;
padding: 11px 22px;
background: #111827;
color: #fff;
border-radius: 6px;
font-size: 1.125rem;
font-weight: 500;
text-decoration: none;
transition: background 0.15s;
}
#swym-wishlist-empty a:hover {
background: #374151;
}
/* --- Nudges ----------------------------------------------- */
#swym-login-nudge {
display: flex;
align-items: center;
gap: 12px;
padding: 13px 18px;
margin-bottom: 1.5rem;
background: #f0f9ff;
border: 1px solid #bae6fd;
border-radius: 8px;
font-size: 1.125rem;
color: #0369a1;
}
#swym-login-nudge p {
margin: 0;
flex: 1;
}
#swym-login-nudge a {
font-weight: 600;
color: #0369a1;
text-decoration: underline;
}
#swym-save-nudge {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 13px 18px;
margin-bottom: 1.5rem;
background: #fffbeb;
border: 1px solid #fde68a;
border-radius: 8px;
font-size: 1.125rem;
color: #92400e;
}
#swym-save-nudge p {
margin: 0;
flex: 1;
line-height: 1.5;
}
#swym-save-nudge a {
color: #92400e;
font-weight: 600;
}
#swym-save-nudge-dismiss {
flex-shrink: 0;
padding: 0 4px;
background: none;
border: none;
font-size: 1.375rem;
line-height: 1;
cursor: pointer;
color: #92400e;
opacity: 0.7;
}
#swym-save-nudge-dismiss:hover {
opacity: 1;
}
/* --- Product grid ----------------------------------------- */
#swym-wishlist-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
align-items: start;
gap: 1.5rem;
margin-top: 2rem;
}
#swym-wishlist-grid[aria-busy="true"] {
min-height: 600px;
}
@media (max-width: 640px) {
#swym-wishlist-grid {
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
}
/* --- Product card ----------------------------------------- */
.swym-wishlist-card {
display: flex;
flex-direction: column;
border: 1px solid #e5e7eb;
border-radius: 10px;
overflow: hidden;
background: #fff;
transition: box-shadow 0.15s;
}
.swym-wishlist-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.swym-wishlist-card > a:first-child {
display: block;
aspect-ratio: 1 / 1;
overflow: hidden;
background: #f9fafb;
}
.swym-wishlist-card > a:first-child img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.2s;
}
.swym-wishlist-card:hover > a:first-child img {
transform: scale(1.03);
}
.swym-wishlist-card > a:nth-child(2) {
display: block;
padding: 14px 14px 4px;
font-size: 1.125rem;
font-weight: 500;
color: #111827;
text-decoration: none;
line-height: 1.4;
}
.swym-wishlist-card > a:nth-child(2):hover {
text-decoration: underline;
}
.swym-wishlist-card > span {
padding: 4px 14px 0;
font-size: 1.1875rem;
font-weight: 600;
color: #111827;
}
/* --- Card actions ----------------------------------------- */
.swym-wishlist-card-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px 14px 14px;
margin-top: auto;
}
.swym-wishlist-cart-btn {
flex: 1;
padding: 9px 12px;
background: #111827;
color: #fff;
border: none;
border-radius: 6px;
font-size: 1.0625rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s;
white-space: nowrap;
}
.swym-wishlist-cart-btn:hover:not(:disabled) {
background: #374151;
}
.swym-wishlist-cart-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
.swym-wishlist-remove-btn {
flex-shrink: 0;
width: 38px;
height: 38px;
padding: 0;
background: none;
border: 1px solid #e5e7eb;
border-radius: 6px;
font-size: 1.375rem;
line-height: 1;
cursor: pointer;
color: #6b7280;
transition: color 0.15s, border-color 0.15s;
}
.swym-wishlist-remove-btn:hover {
color: #ef4444;
border-color: #fca5a5;
}
/* --- Add All to Cart button -------------------------------- */
#swym-add-all-btn {
padding: 9px 18px;
background: #111827;
color: #fff;
border: none;
border-radius: 6px;
font-size: 1.125rem;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s;
}
#swym-add-all-btn:hover:not(:disabled) {
background: #374151;
}
#swym-add-all-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
/* --- Quantity stepper ------------------------------------- */
.swym-wishlist-qty {
display: flex;
align-items: center;
flex-basis: 100%;
gap: 0;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
height: 36px;
}
.swym-wishlist-qty button {
flex-shrink: 0;
width: 36px;
height: 100%;
padding: 0;
background: #f9fafb;
border: none;
font-size: 1.25rem;
line-height: 1;
cursor: pointer;
color: #374151;
transition: background 0.15s;
}
.swym-wishlist-qty button:hover {
background: #f3f4f6;
}
.swym-wishlist-qty-value {
flex: 1;
text-align: center;
font-size: 1.0625rem;
font-weight: 500;
color: #111827;
border-left: 1px solid #d1d5db;
border-right: 1px solid #d1d5db;
line-height: 36px;
min-width: 32px;
user-select: none;
}
/* --- Item note -------------------------------------------- */
.swym-wishlist-note-label {
flex-basis: 100%;
font-size: 1rem;
font-weight: 500;
color: #374151;
margin-bottom: -2px;
}
.swym-wishlist-note {
flex: 1 0 100%;
padding: 8px 10px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 1.0625rem;
color: #111827;
background: #fff;
resize: vertical;
min-height: 60px;
font-family: inherit;
line-height: 1.4;
}
.swym-wishlist-note:focus {
outline: 2px solid #6366f1;
outline-offset: 2px;
}
.swym-wishlist-note::placeholder {
color: #9ca3af;
}
/* --- Wishlist table --------------------------------------- */
#swym-wishlist-table-wrapper {
margin-top: 2rem;
}
/* Columns: img | product | qty | price | note | actions */
.swym-table-header,
.swym-table-row {
display: grid;
grid-template-columns: 160px 2fr 120px 90px 1fr 160px;
align-items: center;
}
.swym-table-header {
border-bottom: 2px solid #e5e7eb;
}
.swym-table-header > div {
display: block; /* prevent theme :empty rules from collapsing empty cells */
padding: 8px 12px;
font-size: 0.9375rem;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.04em;
white-space: nowrap;
}
.swym-table-row {
border-bottom: 1px solid #f3f4f6;
transition: background 0.1s;
}
.swym-table-row:hover {
background: #f9fafb;
}
.swym-table-col-img,
.swym-table-col-product,
.swym-table-col-qty,
.swym-table-col-price,
.swym-table-col-note,
.swym-table-col-actions {
padding: 12px;
}
.swym-table-col-img img {
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 6px;
display: block;
}
.swym-table-col-product a {
font-weight: 500;
color: #111827;
text-decoration: none;
display: block;
}
.swym-table-col-product a:hover {
text-decoration: underline;
}
.swym-table-variant-title {
display: block;
font-size: 0.9375rem;
color: #6b7280;
margin-top: 3px;
}
.swym-table-line-price {
font-size: 1.0625rem;
font-weight: 600;
color: #111827;
white-space: nowrap;
}
.swym-table-note {
min-height: 38px;
}
.swym-table-actions-inner {
display: flex;
gap: 6px;
align-items: center;
}
.swym-table-actions-inner .swym-wishlist-cart-btn {
flex: 1;
padding: 12px;
font-size: 1.0625rem;
font-weight: 600;
letter-spacing: 0.02em;
}
@media (max-width: 768px) {
.swym-table-header {
display: none;
}
/* Layout per row:
[img 33%] Title [×]
[img ] [-] qty [+] $price
[img ] [Add to Cart ──────] */
.swym-table-row {
grid-template-columns: 33% 1fr auto;
grid-template-rows: auto auto auto;
gap: 8px 12px;
padding: 12px 0;
align-items: start;
}
.swym-table-col-img {
grid-column: 1;
grid-row: 1 / 4;
align-self: stretch;
padding: 0;
}
.swym-table-col-img img {
width: 100%;
height: auto;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 6px;
}
.swym-table-col-product {
grid-column: 2;
grid-row: 1;
padding: 0;
align-self: center;
}
.swym-table-col-qty {
grid-column: 2;
grid-row: 2;
padding: 0;
}
.swym-table-col-qty .swym-wishlist-qty {
height: 30px;
}
.swym-table-col-qty .swym-wishlist-qty-value {
line-height: 30px;
}
.swym-table-col-qty .swym-wishlist-qty button {
width: 30px;
}
.swym-table-col-price {
grid-column: 3;
grid-row: 2;
padding: 0;
text-align: right;
align-self: center;
}
.swym-table-col-note {
display: none;
}
/* Dissolve the actions wrapper so each button becomes a direct grid item */
.swym-table-col-actions,
.swym-table-col-actions .swym-table-actions-inner {
display: contents;
}
.swym-table-col-actions .swym-wishlist-remove-btn {
grid-column: 3;
grid-row: 1;
align-self: start;
padding: 2px 6px;
font-size: 1.125rem;
line-height: 1;
}
.swym-table-col-actions .swym-wishlist-cart-btn {
grid-column: 2 / 4;
grid-row: 3;
width: 100%;
padding: 12px;
font-size: 1.0625rem;
font-weight: 600;
letter-spacing: 0.02em;
}
}
/* --- Share modal ------------------------------------------ */
#swym-share-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.25rem;
}
#swym-share-modal-title {
margin: 0;
font-size: 1.375rem;
font-weight: 600;
color: #111827;
}
#swym-share-modal-close {
padding: 4px 8px;
background: none;
border: none;
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
color: #6b7280;
border-radius: 4px;
transition: background 0.15s, color 0.15s;
}
#swym-share-modal-close:hover {
background: #f3f4f6;
color: #111827;
}
.swym-share-section {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 1.25rem;
}
.swym-share-section label {
font-size: 1.0625rem;
font-weight: 500;
color: #374151;
}
.swym-share-section input,
.swym-share-section textarea {
padding: 9px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 1.125rem;
color: #111827;
background: #fff;
resize: vertical;
}
.swym-share-section input:focus,
.swym-share-section textarea:focus {
outline: 2px solid #6366f1;
outline-offset: 2px;
}
.swym-share-section textarea {
min-height: 72px;
}
#swym-share-email-btn {
align-self: flex-start;
padding: 10px 20px;
background: #111827;
color: #fff;
border: none;
border-radius: 6px;
font-size: 1.125rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s;
}
#swym-share-email-btn:hover:not(:disabled) {
background: #374151;
}
#swym-share-email-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
#swym-share-email-feedback,
#swym-share-copy-feedback {
font-size: 1.0625rem;
color: #16a34a;
font-weight: 500;
}
.swym-share-divider-label {
margin: 0 0 8px;
font-size: 1.0625rem;
color: #9ca3af;
text-align: center;
position: relative;
}
.swym-share-divider-label::before,
.swym-share-divider-label::after {
content: '';
position: absolute;
top: 50%;
width: 38%;
height: 1px;
background: #e5e7eb;
}
.swym-share-divider-label::before { left: 0; }
.swym-share-divider-label::after { right: 0; }
.swym-share-social-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.swym-share-social-btn,
#swym-share-copy-btn {
flex: 1;
padding: 9px 14px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: #fff;
font-size: 1.0625rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
white-space: nowrap;
}
.swym-share-social-btn:hover,
#swym-share-copy-btn:hover:not(:disabled) {
background: #f3f4f6;
border-color: #9ca3af;
}
#swym-share-copy-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.swym-wishlist-collections-v2-container {
display: none !important;
}
The selectors used in this implementation are:
| Selector | Purpose |
|---|---|
.swym-page-header | Flex wrapper for the page header |
.swym-header-title | Title and count side of the header |
.swym-header-controls | Controls side of the header (view toggle, Add All, Share) |
#swym-lists-grid | Grid of list tiles on the landing view |
.swym-list-tile | Individual list tile button |
.swym-list-tile-images | Image strip inside a tile |
.swym-list-tile-info | Name and count inside a tile |
#swym-wishlist-bar | Bar holding the back button and running total |
#swym-back-to-lists | "Back to all Lists" button |
#swym-wishlist-total | Running total display |
#swym-add-all-btn | Add All to Cart button in the header controls |
#swym-view-toggle | Wrapper for the Table / Grid toggle buttons |
.swym-view-btn | Table and Grid toggle buttons |
.swym-wishlist-card | Individual product card |
.swym-wishlist-card-actions | Actions container inside each card |
.swym-wishlist-qty | Wrapper for decrease button, quantity display, increase button |
.swym-wishlist-qty-value | Quantity number display |
.swym-wishlist-note-label | "Note" label above the textarea |
.swym-wishlist-note | Note textarea |
.swym-wishlist-cart-btn | Add to Cart button; [disabled] state = out of stock |
.swym-wishlist-remove-btn | Remove from wishlist button |
#swym-share-btn | Share Wishlist button in the header controls |
#swym-share-modal | Share modal overlay wrapper |
#swym-share-modal-content | Share modal panel |
#swym-share-modal-header | Share modal title row |
.swym-share-section | Share modal content section (email form, social row) |
.swym-share-social-row | Row of social share buttons |
.swym-share-social-btn | Individual social share button |
.swym-share-divider-label | "or Share via" label between sections |
#swym-login-nudge | Sign-in prompt for anonymous users |
#swym-save-nudge | "Don't lose your list" banner for anonymous users with 3+ items |