PageSourceSearch

https://tvbedstore.co.uk/cdn/shop/t/33/assets/product.min.js?v=179640788655781523061694526134

js tvbedstore.co.uk collected 2026-10-01 03:48:23 UTC 23,241 bytes, 686 lines download raw bytes

1if (!customElements.get('variant-selects')) {
2
3  /**
4   *  @class
5   *  @function VariantSelects
6   */
7  class VariantSelects extends HTMLElement {
8    constructor() {
9      super();
10      this.isDisabledFeature = this.dataset.isDisabled;
11      this.addEventListener('change', this.onVariantChange);
12    }
13
14    connectedCallback() {
15      this.updateOptions();
16      this.updateMasterId();
17      this.setDisabled();
18    }
19
20    onVariantChange() {
21      this.updateOptions();
22      this.updateMasterId();
23      this.toggleAddButton(true, '', false);
24      this.updatePickupAvailability();
25      this.removeErrorMessage();
26      this.updateVariantText();
27      this.setDisabled();
28      if (!this.currentVariant) {
29        this.toggleAddButton(true, '', true);
30        this.setUnavailable();
31      } else {
32        this.updateMedia();
33        this.updateURL();
34        this.updateVariantInput();
35
36        this.renderProductInfo();
37        //this.updateShareUrl();
38      }
39      dispatchCustomEvent('product:variant-change', {
40        variant: this.currentVariant,
41        sectionId: this.dataset.section
42      });
43    }
44
45    updateOptions() {
46      this.fieldsets = Array.from(this.querySelectorAll('fieldset'));
47      this.options = [];
48      this.fieldsets.forEach((fieldset, i) => {
49        if (fieldset.querySelector('select')) {
50          this.options.push(fieldset.querySelector('select').value);
51        } else if (fieldset.querySelectorAll('input').length) {
52          this.options.push(fieldset.querySelector('input:checked').value);
53        }
54      });
55    }
56    updateVariantText() {
57      const fieldsets = Array.from(this.querySelectorAll('fieldset'));
58      fieldsets.forEach((item, i) => {
59        let label = item.querySelector('.form__label__value');
60        if (label) {
61          label.innerHTML = this.options[i];
62        }
63      });
64    }
65    updateMasterId() {
66      this.currentVariant = this.getVariantData().find((variant) => {
67        return !variant.options.map((option, index) => {
68          return this.options[index] === option;
69        }).includes(false);
70      });
71    }
72
73    updateMedia() {
74      if (!this.currentVariant) return;
75      if (!this.currentVariant.featured_media) return;
76
77      let productSlider = this.closest('.thb-product-detail').querySelector('.product-images'),
78        thumbnails = this.closest('.thb-product-detail').querySelector('#Product-Thumbnails');
79
80      if (!productSlider) {
81        productSlider = document.getElementById('Product-Quick-Slider');
82      }
83
84      this.setActiveMedia(`#Slide-${this.dataset.section}-${this.currentVariant.featured_media.id}`, `#Thumb-${this.dataset.section}-${this.currentVariant.featured_media.id}`, productSlider, thumbnails);
85    }
86    setActiveMedia(mediaId, thumbId, productSlider, thumbnails) {
87      var hide_variants = productSlider.dataset.hideVariants == 'true',
88        flkty = Flickity.data(productSlider),
89        images = productSlider.querySelectorAll('.product-images__slide');
90
91      if (flkty && hide_variants) {
92        [].forEach.call(images, function(el) {
93          el.classList.remove('is-selected');
94        });
95        productSlider.querySelector(mediaId).classList.add('is-selected');
96        productSlider.reInit();
97        productSlider.selectCell(mediaId);
98
99        if (thumbnails) {
100          let activeThumb = thumbnails.querySelector(thumbId);
101
102          thumbnails.prepend(activeThumb);
103        }
104      } else if (flkty) {
105        productSlider.selectCell(mediaId);
106      }
107
108    }
109
110    updateURL() {
111      if (!this.currentVariant || this.dataset.updateUrl === 'false') return;
112      window.history.replaceState({}, '', `${this.dataset.url}?variant=${this.currentVariant.id}`);
113    }
114
115    updateShareUrl() {
116      const shareButton = document.getElementById(`Share-${this.dataset.section}`);
117      if (!shareButton) return;
118      shareButton.updateUrl(`${window.shopUrl}${this.dataset.url}?variant=${this.currentVariant.id}`);
119    }
120
121    updateVariantInput() {
122      const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment`);
123      productForms.forEach((productForm) => {
124        const input = productForm.querySelector('input[name="id"]');
125        input.value = this.currentVariant.id;
126        input.dispatchEvent(new Event('change', {
127          bubbles: true
128        }));
129      });
130    }
131
132    updatePickupAvailability() {
133      const pickUpAvailability = document.querySelector('.pickup-availability-wrapper');
134
135      if (!pickUpAvailability) return;
136
137      if (this.currentVariant && this.currentVariant.available) {
138        pickUpAvailability.fetchAvailability(this.currentVariant.id);
139      } else {
140        pickUpAvailability.removeAttribute('available');
141        pickUpAvailability.innerHTML = '';
142      }
143    }
144
145    removeErrorMessage() {
146      const section = this.closest('section');
147      if (!section) return;
148
149      const productForm = section.querySelector('product-form');
150      if (productForm) productForm.handleErrorMessage();
151    }
152
153    getSectionsToRender() {
154      return [`price-${this.dataset.section}`, `inventory-${this.dataset.section}`, `sku-${this.dataset.section}`];
155    }
156
157    renderProductInfo() {
158      let sections = this.getSectionsToRender();
159      fetch(`${this.dataset.url}?variant=${this.currentVariant.id}&section_id=${this.dataset.section}`)
160        .then((response) => response.text())
161        .then((responseText) => {
162          const html = new DOMParser().parseFromString(responseText, 'text/html');
163          sections.forEach((id) => {
164            const destination = document.getElementById(id);
165            const source = html.getElementById(id);
166
167            if (source && destination) destination.innerHTML = source.innerHTML;
168
169            const price = document.getElementById(id);
170
171            if (price) price.classList.remove('visibility-hidden');
172          });
173          this.toggleAddButton(!this.currentVariant.available, window.theme.variantStrings.soldOut);
174        });
175    }
176
177    toggleAddButton(disable = true, text = false, modifyClass = true) {
178      const productForm = document.getElementById(`product-form-${this.dataset.section}`);
179      if (!productForm) return;
180      const addButton = productForm.querySelector('[name="add"]');
181      const addButtonText = productForm.querySelector('[name="add"] > span');
182
183      if (!addButton) return;
184
185      if (disable) {
186        addButton.setAttribute('disabled', 'disabled');
187        if (text) addButtonText.textContent = text;
188      } else {
189        addButton.removeAttribute('disabled');
190        addButton.classList.remove('loading');
191        addButtonText.textContent = window.theme.variantStrings.addToCart;
192      }
193
194      if (!modifyClass) return;
195    }
196
197    setUnavailable() {
198      const button = document.getElementById(`product-form-${this.dataset.section}`);
199      const addButton = button.querySelector('[name="add"]');
200      const addButtonText = button.querySelector('[name="add"] > span');
201      const price = document.getElementById(`price-${this.dataset.section}`);
202      if (!addButton) return;
203      addButtonText.textContent = window.theme.variantStrings.unavailable;
204      addButton.classList.add('sold-out');
205      if (price) price.classList.add('visibility-hidden');
206    }
207
208    setDisabled() {
209      if (this.isDisabledFeature != 'true') {
210        return;
211      }
212      const variant_data = this.getVariantData();
213
214      if (variant_data) {
215        const selected_options = this.currentVariant.options.map((value, index) => {
216          return {
217            value,
218            index: `option${index+1}`
219          };
220        });
221        const available_options = this.createAvailableOptionsTree(variant_data, selected_options);
222
223        this.fieldsets.forEach((fieldset, i) => {
224          const fieldset_options = Object.values(available_options)[i];
225
226          if (fieldset_options) {
227            if (fieldset.querySelector('select')) {
228              fieldset_options.forEach((option, option_i) => {
229                if (option.isUnavailable) {
230                  fieldset.querySelector('option[value=' + JSON.stringify(option.value) + ']').disabled = true;
231                } else {
232                  fieldset.querySelector('option[value=' + JSON.stringify(option.value) + ']').disabled = false;
233                }
234              });
235            } else if (fieldset.querySelectorAll('input').length) {
236              fieldset.querySelectorAll('input').forEach((input, input_i) => {
237                input.classList.toggle('is-disabled', fieldset_options[input_i].isUnavailable);
238              });
239            }
240          }
241        });
242
243      }
244    }
245    createAvailableOptionsTree(variant_data, selected_options) {
246      // Reduce variant array into option availability tree
247      return variant_data.reduce((options, variant) => {
248
249        // Check each option group (e.g. option1, option2, option3) of the variant
250        Object.keys(options).forEach(index => {
251
252          if (variant[index] === null) return;
253
254          let entry = options[index].find(option => option.value === variant[index]);
255
256          if (typeof entry === 'undefined') {
257            // If option has yet to be added to the options tree, add it
258            entry = {
259              value: variant[index],
260              isUnavailable: true
261            };
262            options[index].push(entry);
263          }
264
265          // Check how many selected option values match a variant
266          const countVariantOptionsThatMatchCurrent = selected_options.reduce((count, {
267            value,
268            index
269          }) => {
270            return variant[index] === value ? count + 1 : count;
271          }, 0);
272
273          // Only enable an option if an available variant matches all but one current selected value
274          if (countVariantOptionsThatMatchCurrent >= selected_options.length - 1) {
275            entry.isUnavailable = entry.isUnavailable && variant.available ? false : entry.isUnavailable;
276          }
277
278          // Make sure if a variant is unavailable, disable currently selected option
279          if ((!this.currentVariant || !this.currentVariant.available) && selected_options.find((option) => option.value === entry.value && index === option.index)) {
280            entry.isUnavailable = true;
281          }
282
283          // First option is always enabled
284          if (index === 'option1') {
285            entry.isUnavailable = entry.isUnavailable && variant.available ? false : entry.isUnavailable;
286          }
287        });
288
289        return options;
290      }, {
291        option1: [],
292        option2: [],
293        option3: []
294      });
295    }
296
297    getVariantData() {
298      this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent);
299      return this.variantData;
300    }
301  }
302  customElements.define('variant-selects', VariantSelects);
303
304  /**
305   *  @class
306   *  @function VariantRadios
307   */
308  class VariantRadios extends VariantSelects {
309    constructor() {
310      super();
311    }
312
313    updateOptions() {
314      const fieldsets = Array.from(this.querySelectorAll('fieldset'));
315      this.options = fieldsets.map((fieldset) => {
316        return Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value;
317      });
318    }
319    updateVariantText() {
320
321
322    }
323  }
324
325  customElements.define('variant-radios', VariantRadios);
326}
327if (!customElements.get('product-slider')) {
328  /**
329   *  @class
330   *  @function ProductSlider
331   */
332  class ProductSlider extends HTMLElement {
333    constructor() {
334      super();
335
336      this.addEventListener('change', this.setupProductGallery);
337    }
338    connectedCallback() {
339      const product_container = this.closest('.thb-product-detail');
340      const thumbnail_container = product_container.querySelector('.product-thumbnail-container');
341
342      this.hide_variants = this.dataset.hideVariants == 'true';
343      this.selectedIndex = 0;
344      this.thumbnails = thumbnail_container.querySelectorAll('.product-thumbnail');
345      this.prev_button = this.querySelector('.flickity-prev');
346      this.next_button = this.querySelector('.flickity-next');
347      this.options = {
348        wrapAround: true,
349        pageDots: true,
350        contain: true,
351        adaptiveHeight: true,
352        initialIndex: '.is-initial-selected',
353        prevNextButtons: false,
354        fade: false,
355        cellSelector: '.product-images__slide'
356      };
357
358      if (this.hide_variants) {
359        this.options.cellSelector = '.product-images__slide-item, .product-images__slide-item--variant.is-selected';
360      }
361
362      // Start Slider
363      this.init();
364    }
365    init() {
366      this.flkty = new Flickity(this, this.options);
367
368      this.selectedIndex = this.flkty.selectedIndex;
369
370      // Setup Events
371      this.setupEvents();
372
373      // Start Gallery
374      this.setupProductGallery();
375    }
376    reInit() {
377      this.flkty.destroy();
378
379      this.flkty = new Flickity(this, this.options);
380
381      // Setup Events
382      this.setupEvents();
383
384      this.selectedIndex = this.flkty.selectedIndex;
385    }
386    setupEvents() {
387      const _this = this;
388      if (this.prev_button) {
389        this.prev_button.addEventListener('click', (event) => {
390          this.flkty.previous();
391        });
392        this.prev_button.addEventListener('keyup', (event) => {
393          this.flkty.previous();
394          event.preventDefault();
395        });
396      }
397      if (this.next_button) {
398        this.next_button.addEventListener('click', (event) => {
399          this.flkty.next();
400        });
401        this.next_button.addEventListener('keyup', (event) => {
402          this.flkty.next();
403          event.preventDefault();
404        });
405      }
406      this.flkty.on('settle', function(index) {
407        this.selectedIndex = index;
408      });
409      this.flkty.on('change', (index) => {
410        let previous_slide = this.flkty.cells[this.selectedIndex].element,
411          previous_media = previous_slide.querySelector('.product-single__media'),
412          slide = this.flkty.cells[index].element,
413          media = slide.querySelector('.product-single__media'),
414          active_thumb = this.thumbnails[index];
415
416        this.thumbnails.forEach((item, i) => {
417          item.classList.remove('is-initial-selected');
418        });
419        active_thumb.classList.add('is-initial-selected');
420        requestAnimationFrame(() => {
421          if (active_thumb.offsetParent === null) {
422            return;
423          }
424          const windowHalfHeight = active_thumb.offsetParent.clientHeight / 2,
425            windowHalfWidth = active_thumb.offsetParent.clientWidth / 2;
426          active_thumb.parentElement.scrollTo({
427            left: 0,
428            top: active_thumb.offsetTop - windowHalfHeight + active_thumb.clientHeight / 2,
429            behavior: 'smooth'
430          });
431        });
432        // Stop previous video
433        if (previous_media.classList.contains('product-single__media-external-video')) {
434          if (previous_media.dataset.provider === 'youtube') {
435            previous_media.querySelector('iframe').contentWindow.postMessage(JSON.stringify({
436              event: "command",
437              func: "pauseVideo",
438              args: ""
439            }), "*");
440          } else if (previous_media.dataset.provider === 'vimeo') {
441            previous_media.querySelector('iframe').contentWindow.postMessage(JSON.stringify({
442              method: "pause"
443            }), "*");
444          }
445        } else if (previous_media.classList.contains('product-single__media-native-video')) {
446          previous_media.querySelector("video").pause();
447        }
448
449        // Draggable.
450        if (media.classList.contains('product-single__media-model')) {
451          this.setDraggable(false);
452        } else {
453          this.setDraggable(true);
454        }
455      });
456
457      this.thumbnails.forEach((thumbnail, index) => {
458        thumbnail.addEventListener('click', () => {
459          this.thumbnailClick(thumbnail, index);
460        });
461      });
462    }
463    thumbnailClick(thumbnail, index) {
464      [].forEach.call(this.thumbnails, function(el) {
465        el.classList.remove('is-initial-selected');
466      });
467      thumbnail.classList.add('is-initial-selected');
468      this.flkty.select(index);
469    }
470    setDraggable(draggable) {
471      this.flkty.options.draggable = draggable;
472      this.flkty.updateDraggable();
473    }
474    selectCell(mediaId) {
475      this.flkty.selectCell(mediaId);
476    }
477    setupProductGallery() {
478      if (!this.querySelectorAll('.product-single__media-zoom').length) {
479        return;
480      }
481      this.setEventListeners();
482    }
483    buildItems() {
484      this.activeImages = Array.from(this.querySelectorAll('.product-images__slide-item .product-single__media-image, .product-images__slide-item--variant.is-selected .product-single__media-image'));
485      return this.activeImages.map((item) => {
486        let index = [].indexOf.call(item.parentNode.parentNode.children, item.parentNode);
487
488        let activelink = item.querySelector('.product-single__media-zoom');
489
490        activelink.dataset.index = index;
491        return {
492          src: activelink.getAttribute('href'),
493          msrc: activelink.dataset.msrc,
494          w: activelink.dataset.w,
495          h: activelink.dataset.h
496        };
497      });
498    }
499    setEventListeners() {
500      this.links = this.querySelectorAll('.product-single__media-zoom');
501      this.pswpElement = document.querySelectorAll('.pswp')[0];
502      this.pswpOptions = {
503        maxSpreadZoom: 2,
504        loop: false,
505        allowPanToNext: false,
506        closeOnScroll: false,
507        showHideOpacity: false,
508        arrowKeys: true,
509        history: false,
510        captionEl: false,
511        fullscreenEl: false,
512        zoomEl: false,
513        shareEl: false,
514        counterEl: false,
515        arrowEl: true,
516        preloaderEl: true,
517        getThumbBoundsFn: () => {
518          const thumbnail = this.querySelector('.product-images__slide.is-selected'),
519            pageYScroll = window.pageYOffset || document.documentElement.scrollTop,
520            rect = thumbnail.getBoundingClientRect();
521          return {
522            x: rect.left,
523            y: rect.top + pageYScroll,
524            w: rect.width
525          };
526        }
527      };
528
529
530      this.links.forEach((link => {
531        link.addEventListener('click', (e) => this.zoomClick(e, link));
532      }));
533    }
534    zoomClick(e, link) {
535      this.items = this.buildItems();
536      this.pswpOptions.index = parseInt(link.dataset.index, 10);
537      if (typeof PhotoSwipe !== 'undefined') {
538        let pswp = new PhotoSwipe(this.pswpElement, PhotoSwipeUI_Default, this.items, this.pswpOptions);
539        pswp.listen('firstUpdate', function() {
540          pswp.listen('parseVerticalMargin', function(item) {
541            item.vGap = {
542              top: 50,
543              bottom: 50
544            };
545          });
546        });
547        pswp.init();
548      }
549      e.preventDefault();
550    }
551  }
552  customElements.define('product-slider', ProductSlider);
553}
554
555/**
556 *  @class
557 *  @function ProductForm
558 */
559if (!customElements.get('product-form')) {
560  customElements.define('product-form', class ProductForm extends HTMLElement {
561    constructor() {
562      super();
563
564      this.form = this.querySelector('form');
565      this.form.querySelector('[name=id]').disabled = false;
566      this.form.addEventListener('submit', this.onSubmitHandler.bind(this));
567      this.cartNotification = document.querySelector('cart-notification');
568      this.body = document.body;
569
570      this.hideErrors = this.dataset.hideErrors === 'true';
571    }
572
573    onSubmitHandler(evt) {
574      evt.preventDefault();
575      const submitButton = this.querySelector('[type="submit"]');
576      if (submitButton.classList.contains('loading')) return;
577
578      this.handleErrorMessage();
579
580      submitButton.setAttribute('aria-disabled', true);
581      submitButton.classList.add('loading');
582
583      const config = {
584        method: 'POST',
585        headers: {
586          'X-Requested-With': 'XMLHttpRequest',
587          'Accept': 'application/javascript'
588        }
589      };
590      const formData = new FormData(this.form);
591      formData.append('sections', this.getSectionsToRender().map((section) => section.section));
592      formData.append('sections_url', window.location.pathname);
593      config.body = formData;
594
595      fetch(`${theme.routes.cart_add_url}`, config)
596        .then((response) => response.json())
597        .then((response) => {
598          if (response.status) {
599            dispatchCustomEvent('product:variant-error', {
600              source: 'product-form',
601              productVariantId: formData.get('id'),
602              errors: response.description,
603              message: response.message
604            });
605            this.handleErrorMessage(response.description);
606            return;
607          }
608
609          this.renderContents(response);
610        })
611        .catch((e) => {
612          console.error(e);
613        })
614        .finally(() => {
615          submitButton.classList.remove('loading');
616          submitButton.removeAttribute('aria-disabled');
617        });
618    }
619
620    getSectionsToRender() {
621      return [{
622        id: 'Cart',
623        section: 'main-cart',
624        selector: '.thb-cart-form'
625      },
626      {
627        id: 'Cart-Drawer',
628        section: 'cart-drawer',
629        selector: '.cart-drawer'
630      },
631      {
632        id: 'cart-drawer-toggle',
633        section: 'cart-bubble',
634        selector: '.thb-item-count'
635      }];
636    }
637    renderContents(parsedState) {
638      this.getSectionsToRender().forEach((section => {
639        if (!document.getElementById(section.id)) {
640          return;
641        }
642        const elementToReplace = document.getElementById(section.id).querySelector(section.selector) || document.getElementById(section.id);
643        elementToReplace.innerHTML = this.getSectionInnerHTML(parsedState.sections[section.section], section.selector);
644        if (typeof CartDrawer !== 'undefined') {
645          new CartDrawer();
646        }
647        if (typeof Cart !== 'undefined') {
648          new Cart().renderContents(parsedState);
649        }
650      }));
651
652
653      if (document.getElementById('Cart-Drawer')) {
654        this.body.classList.add('open-cc');
655        document.getElementById('Cart-Drawer').classList.add('active');
656
657        dispatchCustomEvent('cart-drawer:open');
658      }
659
660      let product_drawer = document.getElementById('Product-Drawer');
661      if (product_drawer && product_drawer.contains(this)) {
662        product_drawer.classList.remove('active');
663
664        if (!document.getElementById('Cart-Drawer')) {
665          this.body.classList.remove('open-cc');
666        }
667      }
668    }
669    getSectionInnerHTML(html, selector = '.shopify-section') {
670      return new DOMParser()
671        .parseFromString(html, 'text/html')
672        .querySelector(selector).innerHTML;
673    }
674    handleErrorMessage(errorMessage = false) {
675      if (this.hideErrors) return;
676      this.errorMessageWrapper = this.errorMessageWrapper || this.querySelector('.product-form__error-message-wrapper');
677      this.errorMessage = this.errorMessage || this.errorMessageWrapper.querySelector('.product-form__error-message');
678
679      this.errorMessageWrapper.toggleAttribute('hidden', !errorMessage);
680
681      if (errorMessage) {
682        this.errorMessage.textContent = errorMessage;
683      }
684    }
685  });
686}

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.