· 4 years ago · May 26, 2021, 05:00 AM
1window.theme = window.theme || {};
2
3/* ================ SLATE ================ */
4window.theme = window.theme || {};
5
6theme.Sections = function Sections() {
7 this.constructors = {};
8 this.instances = [];
9
10 document.addEventListener(
11 'shopify:section:load',
12 this._onSectionLoad.bind(this)
13 );
14 document.addEventListener(
15 'shopify:section:unload',
16 this._onSectionUnload.bind(this)
17 );
18 document.addEventListener(
19 'shopify:section:select',
20 this._onSelect.bind(this)
21 );
22 document.addEventListener(
23 'shopify:section:deselect',
24 this._onDeselect.bind(this)
25 );
26 document.addEventListener(
27 'shopify:block:select',
28 this._onBlockSelect.bind(this)
29 );
30 document.addEventListener(
31 'shopify:block:deselect',
32 this._onBlockDeselect.bind(this)
33 );
34};
35
36theme.Sections.prototype = Object.assign({}, theme.Sections.prototype, {
37 _createInstance: function(container, constructor) {
38 var id = container.getAttribute('data-section-id');
39 var type = container.getAttribute('data-section-type');
40
41 constructor = constructor || this.constructors[type];
42
43 if (typeof constructor === 'undefined') {
44 return;
45 }
46
47 var instance = Object.assign(new constructor(container), {
48 id: id,
49 type: type,
50 container: container
51 });
52
53 this.instances.push(instance);
54 },
55
56 _onSectionLoad: function(evt) {
57 var container = document.querySelector(
58 '[data-section-id="' + evt.detail.sectionId + '"]'
59 );
60
61 if (container) {
62 this._createInstance(container);
63 }
64 },
65
66 _onSectionUnload: function(evt) {
67 this.instances = this.instances.filter(function(instance) {
68 var isEventInstance = instance.id === evt.detail.sectionId;
69
70 if (isEventInstance) {
71 if (typeof instance.onUnload === 'function') {
72 instance.onUnload(evt);
73 }
74 }
75
76 return !isEventInstance;
77 });
78 },
79
80 _onSelect: function(evt) {
81 // eslint-disable-next-line no-shadow
82 var instance = this.instances.find(function(instance) {
83 return instance.id === evt.detail.sectionId;
84 });
85
86 if (
87 typeof instance !== 'undefined' &&
88 typeof instance.onSelect === 'function'
89 ) {
90 instance.onSelect(evt);
91 }
92 },
93
94 _onDeselect: function(evt) {
95 // eslint-disable-next-line no-shadow
96 var instance = this.instances.find(function(instance) {
97 return instance.id === evt.detail.sectionId;
98 });
99
100 if (
101 typeof instance !== 'undefined' &&
102 typeof instance.onDeselect === 'function'
103 ) {
104 instance.onDeselect(evt);
105 }
106 },
107
108 _onBlockSelect: function(evt) {
109 // eslint-disable-next-line no-shadow
110 var instance = this.instances.find(function(instance) {
111 return instance.id === evt.detail.sectionId;
112 });
113
114 if (
115 typeof instance !== 'undefined' &&
116 typeof instance.onBlockSelect === 'function'
117 ) {
118 instance.onBlockSelect(evt);
119 }
120 },
121
122 _onBlockDeselect: function(evt) {
123 // eslint-disable-next-line no-shadow
124 var instance = this.instances.find(function(instance) {
125 return instance.id === evt.detail.sectionId;
126 });
127
128 if (
129 typeof instance !== 'undefined' &&
130 typeof instance.onBlockDeselect === 'function'
131 ) {
132 instance.onBlockDeselect(evt);
133 }
134 },
135
136 register: function(type, constructor) {
137 this.constructors[type] = constructor;
138
139 document.querySelectorAll('[data-section-type="' + type + '"]').forEach(
140 function(container) {
141 this._createInstance(container, constructor);
142 }.bind(this)
143 );
144 }
145});
146
147window.slate = window.slate || {};
148
149/**
150 * Slate utilities
151 * -----------------------------------------------------------------------------
152 * A collection of useful utilities to help build your theme
153 *
154 *
155 * @namespace utils
156 */
157
158slate.utils = {
159 /**
160 * Get the query params in a Url
161 * Ex
162 * https://mysite.com/search?q=noodles&b
163 * getParameterByName('q') = "noodles"
164 * getParameterByName('b') = "" (empty value)
165 * getParameterByName('test') = null (absent)
166 */
167 getParameterByName: function(name, url) {
168 if (!url) url = window.location.href;
169 name = name.replace(/[[\]]/g, '\\$&');
170 var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
171 results = regex.exec(url);
172 if (!results) return null;
173 if (!results[2]) return '';
174 return decodeURIComponent(results[2].replace(/\+/g, ' '));
175 },
176
177 resizeSelects: function(selects) {
178 selects.forEach(function(select) {
179 var arrowWidth = 55;
180
181 var test = document.createElement('span');
182 test.innerHTML = select.selectedOptions[0].label;
183
184 document.querySelector('.site-footer').appendChild(test);
185
186 var width = test.offsetWidth + arrowWidth;
187 test.remove();
188
189 select.style.width = width + 'px';
190 });
191 },
192
193 keyboardKeys: {
194 TAB: 9,
195 ENTER: 13,
196 ESCAPE: 27,
197 LEFTARROW: 37,
198 RIGHTARROW: 39
199 }
200};
201
202window.slate = window.slate || {};
203
204/**
205 * iFrames
206 * -----------------------------------------------------------------------------
207 * Wrap videos in div to force responsive layout.
208 *
209 * @namespace iframes
210 */
211
212slate.rte = {
213 /**
214 * Wrap tables in a container div to make them scrollable when needed
215 *
216 * @param {object} options - Options to be used
217 * @param {NodeList} options.tables - Elements of the table(s) to wrap
218 * @param {string} options.tableWrapperClass - table wrapper class name
219 */
220 wrapTable: function(options) {
221 options.tables.forEach(function(table) {
222 var wrapper = document.createElement('div');
223 wrapper.classList.add(options.tableWrapperClass);
224
225 table.parentNode.insertBefore(wrapper, table);
226 wrapper.appendChild(table);
227 });
228 },
229
230 /**
231 * Wrap iframes in a container div to make them responsive
232 *
233 * @param {object} options - Options to be used
234 * @param {NodeList} options.iframes - Elements of the iframe(s) to wrap
235 * @param {string} options.iframeWrapperClass - class name used on the wrapping div
236 */
237 wrapIframe: function(options) {
238 options.iframes.forEach(function(iframe) {
239 var wrapper = document.createElement('div');
240 wrapper.classList.add(options.iframeWrapperClass);
241
242 iframe.parentNode.insertBefore(wrapper, iframe);
243 wrapper.appendChild(iframe);
244
245 iframe.src = iframe.src;
246 });
247 }
248};
249
250window.slate = window.slate || {};
251
252/**
253 * A11y Helpers
254 * -----------------------------------------------------------------------------
255 * A collection of useful functions that help make your theme more accessible
256 * to users with visual impairments.
257 *
258 *
259 * @namespace a11y
260 */
261
262slate.a11y = {
263 state: {
264 firstFocusable: null,
265 lastFocusable: null
266 },
267 /**
268 * For use when focus shifts to a container rather than a link
269 * eg for In-page links, after scroll, focus shifts to content area so that
270 * next `tab` is where user expects
271 *
272 * @param {HTMLElement} element - The element to be acted upon
273 */
274 pageLinkFocus: function(element) {
275 if (!element) return;
276 var focusClass = 'js-focus-hidden';
277
278 element.setAttribute('tabIndex', '-1');
279 element.focus();
280 element.classList.add(focusClass);
281 element.addEventListener('blur', callback, { once: true });
282
283 function callback() {
284 element.classList.remove(focusClass);
285 element.removeAttribute('tabindex');
286 }
287 },
288
289 /**
290 * Traps the focus in a particular container
291 *
292 * @param {object} options - Options to be used
293 * @param {HTMLElement} options.container - Container to trap focus within
294 * @param {HTMLElement} options.elementToFocus - Element to be focused when focus leaves container
295 */
296 trapFocus: function(options) {
297 var focusableElements = Array.from(
298 options.container.querySelectorAll(
299 'button, [href], input, select, textarea, [tabindex]:not([tabindex^="-"])'
300 )
301 ).filter(function(element) {
302 var width = element.offsetWidth;
303 var height = element.offsetHeight;
304
305 return (
306 width !== 0 &&
307 height !== 0 &&
308 getComputedStyle(element).getPropertyValue('display') !== 'none'
309 );
310 });
311
312 this.state.firstFocusable = focusableElements[0];
313 this.state.lastFocusable = focusableElements[focusableElements.length - 1];
314
315 if (!options.elementToFocus) {
316 options.elementToFocus = options.container;
317 }
318
319 options.container.setAttribute('tabindex', '-1');
320 options.elementToFocus.focus();
321
322 this._setupHandlers();
323
324 document.addEventListener('focusin', this._onFocusInHandler);
325 document.addEventListener('focusout', this._onFocusOutHandler);
326 },
327
328 _setupHandlers: function() {
329 if (!this._onFocusInHandler) {
330 this._onFocusInHandler = this._onFocusIn.bind(this);
331 }
332
333 if (!this._onFocusOutHandler) {
334 this._onFocusOutHandler = this._onFocusIn.bind(this);
335 }
336
337 if (!this._manageFocusHandler) {
338 this._manageFocusHandler = this._manageFocus.bind(this);
339 }
340 },
341
342 _onFocusOut: function() {
343 document.removeEventListener('keydown', this._manageFocusHandler);
344 },
345
346 _onFocusIn: function(evt) {
347 if (
348 evt.target !== this.state.lastFocusable &&
349 evt.target !== this.state.firstFocusable
350 )
351 return;
352
353 document.addEventListener('keydown', this._manageFocusHandler);
354 },
355
356 _manageFocus: function(evt) {
357 if (evt.keyCode !== slate.utils.keyboardKeys.TAB) return;
358
359 /**
360 * On the last focusable element and tab forward,
361 * focus the first element.
362 */
363 if (evt.target === this.state.lastFocusable && !evt.shiftKey) {
364 evt.preventDefault();
365 this.state.firstFocusable.focus();
366 }
367 /**
368 * On the first focusable element and tab backward,
369 * focus the last element.
370 */
371 if (evt.target === this.state.firstFocusable && evt.shiftKey) {
372 evt.preventDefault();
373 this.state.lastFocusable.focus();
374 }
375 },
376
377 /**
378 * Removes the trap of focus in a particular container
379 *
380 * @param {object} options - Options to be used
381 * @param {HTMLElement} options.container - Container to trap focus within
382 */
383 removeTrapFocus: function(options) {
384 if (options.container) {
385 options.container.removeAttribute('tabindex');
386 }
387 document.removeEventListener('focusin', this._onFocusInHandler);
388 },
389
390 /**
391 * Add aria-describedby attribute to external and new window links
392 *
393 * @param {object} options - Options to be used
394 * @param {object} options.messages - Custom messages to be used
395 * @param {HTMLElement} options.links - Specific links to be targeted
396 */
397 accessibleLinks: function(options) {
398 var body = document.querySelector('body');
399
400 var idSelectors = {
401 newWindow: 'a11y-new-window-message',
402 external: 'a11y-external-message',
403 newWindowExternal: 'a11y-new-window-external-message'
404 };
405
406 if (options.links === undefined || !options.links.length) {
407 options.links = document.querySelectorAll(
408 'a[href]:not([aria-describedby])'
409 );
410 }
411
412 function generateHTML(customMessages) {
413 if (typeof customMessages !== 'object') {
414 customMessages = {};
415 }
416
417 var messages = Object.assign(
418 {
419 newWindow: 'Opens in a new window.',
420 external: 'Opens external website.',
421 newWindowExternal: 'Opens external website in a new window.'
422 },
423 customMessages
424 );
425
426 var container = document.createElement('ul');
427 var htmlMessages = '';
428
429 for (var message in messages) {
430 htmlMessages +=
431 '<li id=' + idSelectors[message] + '>' + messages[message] + '</li>';
432 }
433
434 container.setAttribute('hidden', true);
435 container.innerHTML = htmlMessages;
436
437 body.appendChild(container);
438 }
439
440 function _externalSite(link) {
441 var hostname = window.location.hostname;
442
443 return link.hostname !== hostname;
444 }
445
446 options.links.forEach(function(link) {
447 var target = link.getAttribute('target');
448 var rel = link.getAttribute('rel');
449 var isExternal = _externalSite(link);
450 var isTargetBlank = target === '_blank';
451
452 if (isExternal) {
453 link.setAttribute('aria-describedby', idSelectors.external);
454 }
455
456 if (isTargetBlank) {
457 if (!rel || rel.indexOf('noopener') === -1) {
458 var relValue = rel === undefined ? '' : rel + ' ';
459 relValue = relValue + 'noopener';
460 link.setAttribute('rel', relValue);
461 }
462
463 link.setAttribute('aria-describedby', idSelectors.newWindow);
464 }
465
466 if (isExternal && isTargetBlank) {
467 link.setAttribute('aria-describedby', idSelectors.newWindowExternal);
468 }
469 });
470
471 generateHTML(options.messages);
472 }
473};
474
475/**
476 * Image Helper Functions
477 * -----------------------------------------------------------------------------
478 * A collection of functions that help with basic image operations.
479 *
480 */
481
482theme.Images = (function() {
483 /**
484 * Preloads an image in memory and uses the browsers cache to store it until needed.
485 *
486 * @param {Array} images - A list of image urls
487 * @param {String} size - A shopify image size attribute
488 */
489
490 function preload(images, size) {
491 if (typeof images === 'string') {
492 images = [images];
493 }
494
495 for (var i = 0; i < images.length; i++) {
496 var image = images[i];
497 this.loadImage(this.getSizedImageUrl(image, size));
498 }
499 }
500
501 /**
502 * Loads and caches an image in the browsers cache.
503 * @param {string} path - An image url
504 */
505 function loadImage(path) {
506 new Image().src = path;
507 }
508
509 /**
510 * Swaps the src of an image for another OR returns the imageURL to the callback function
511 * @param image
512 * @param element
513 * @param callback
514 */
515 function switchImage(image, element, callback) {
516 var size = this.imageSize(element.src);
517 var imageUrl = this.getSizedImageUrl(image.src, size);
518
519 if (callback) {
520 callback(imageUrl, image, element); // eslint-disable-line callback-return
521 } else {
522 element.src = imageUrl;
523 }
524 }
525
526 /**
527 * +++ Useful
528 * Find the Shopify image attribute size
529 *
530 * @param {string} src
531 * @returns {null}
532 */
533 function imageSize(src) {
534 var match = src.match(
535 /.+_((?:pico|icon|thumb|small|compact|medium|large|grande)|\d{1,4}x\d{0,4}|x\d{1,4})[_\\.@]/
536 );
537
538 if (match !== null) {
539 if (match[2] !== undefined) {
540 return match[1] + match[2];
541 } else {
542 return match[1];
543 }
544 } else {
545 return null;
546 }
547 }
548
549 /**
550 * +++ Useful
551 * Adds a Shopify size attribute to a URL
552 *
553 * @param src
554 * @param size
555 * @returns {*}
556 */
557 function getSizedImageUrl(src, size) {
558 if (size === null) {
559 return src;
560 }
561
562 if (size === 'master') {
563 return this.removeProtocol(src);
564 }
565
566 var match = src.match(
567 /\.(jpg|jpeg|gif|png|bmp|bitmap|tiff|tif)(\?v=\d+)?$/i
568 );
569
570 if (match !== null) {
571 var prefix = src.split(match[0]);
572 var suffix = match[0];
573
574 return this.removeProtocol(prefix[0] + '_' + size + suffix);
575 }
576
577 return null;
578 }
579
580 function removeProtocol(path) {
581 return path.replace(/http(s)?:/, '');
582 }
583
584 return {
585 preload: preload,
586 loadImage: loadImage,
587 switchImage: switchImage,
588 imageSize: imageSize,
589 getSizedImageUrl: getSizedImageUrl,
590 removeProtocol: removeProtocol
591 };
592})();
593
594/**
595 * Currency Helpers
596 * -----------------------------------------------------------------------------
597 * A collection of useful functions that help with currency formatting
598 *
599 * Current contents
600 * - formatMoney - Takes an amount in cents and returns it as a formatted dollar value.
601 *
602 * Alternatives
603 * - Accounting.js - http://openexchangerates.github.io/accounting.js/
604 *
605 */
606
607theme.Currency = (function() {
608 var moneyFormat = '${{amount}}'; // eslint-disable-line camelcase
609
610 function formatMoney(cents, format) {
611 if (typeof cents === 'string') {
612 cents = cents.replace('.', '');
613 }
614 var value = '';
615 var placeholderRegex = /\{\{\s*(\w+)\s*\}\}/;
616 var formatString = format || moneyFormat;
617
618 function formatWithDelimiters(number, precision, thousands, decimal) {
619 thousands = thousands || ',';
620 decimal = decimal || '.';
621
622 if (isNaN(number) || number === null) {
623 return 0;
624 }
625
626 number = (number / 100.0).toFixed(precision);
627
628 var parts = number.split('.');
629 var dollarsAmount = parts[0].replace(
630 /(\d)(?=(\d\d\d)+(?!\d))/g,
631 '$1' + thousands
632 );
633 var centsAmount = parts[1] ? decimal + parts[1] : '';
634
635 return dollarsAmount + centsAmount;
636 }
637
638 switch (formatString.match(placeholderRegex)[1]) {
639 case 'amount':
640 value = formatWithDelimiters(cents, 2);
641 break;
642 case 'amount_no_decimals':
643 value = formatWithDelimiters(cents, 0);
644 break;
645 case 'amount_with_comma_separator':
646 value = formatWithDelimiters(cents, 2, '.', ',');
647 break;
648 case 'amount_no_decimals_with_comma_separator':
649 value = formatWithDelimiters(cents, 0, '.', ',');
650 break;
651 case 'amount_no_decimals_with_space_separator':
652 value = formatWithDelimiters(cents, 0, ' ');
653 break;
654 case 'amount_with_apostrophe_separator':
655 value = formatWithDelimiters(cents, 2, "'");
656 break;
657 }
658
659 return formatString.replace(placeholderRegex, value);
660 }
661
662 return {
663 formatMoney: formatMoney
664 };
665})();
666
667/**
668 * Variant Selection scripts
669 * ------------------------------------------------------------------------------
670 *
671 * Handles change events from the variant inputs in any `cart/add` forms that may
672 * exist. Also updates the master select and triggers updates when the variants
673 * price or image changes.
674 *
675 * @namespace variants
676 */
677
678slate.Variants = (function() {
679 /**
680 * Variant constructor
681 *
682 * @param {object} options - Settings from `product.js`
683 */
684 function Variants(options) {
685 this.container = options.container;
686 this.product = options.product;
687 this.originalSelectorId = options.originalSelectorId;
688 this.enableHistoryState = options.enableHistoryState;
689 this.singleOptions = this.container.querySelectorAll(
690 options.singleOptionSelector
691 );
692 this.currentVariant = this._getVariantFromOptions();
693
694 this.singleOptions.forEach(
695 function(option) {
696 option.addEventListener('change', this._onSelectChange.bind(this));
697 }.bind(this)
698 );
699 }
700
701 Variants.prototype = Object.assign({}, Variants.prototype, {
702 /**
703 * Get the currently selected options from add-to-cart form. Works with all
704 * form input elements.
705 *
706 * @return {array} options - Values of currently selected variants
707 */
708 _getCurrentOptions: function() {
709 var result = [];
710
711 this.singleOptions.forEach(function(option) {
712 var type = option.getAttribute('type');
713 var isRadioOrCheckbox = type === 'radio' || type === 'checkbox';
714
715 if (!isRadioOrCheckbox || option.checked) {
716 result.push({
717 value: option.value,
718 index: option.getAttribute('data-index')
719 });
720 }
721 });
722
723 return result;
724 },
725
726 /**
727 * Find variant based on selected values.
728 *
729 * @param {array} selectedValues - Values of variant inputs
730 * @return {object || undefined} found - Variant object from product.variants
731 */
732 _getVariantFromOptions: function() {
733 var selectedValues = this._getCurrentOptions();
734 var variants = this.product.variants;
735
736 var found = variants.find(function(variant) {
737 return selectedValues.every(function(values) {
738 return variant[values.index] === values.value;
739 });
740 });
741
742 return found;
743 },
744
745 /**
746 * Event handler for when a variant input changes.
747 */
748 _onSelectChange: function() {
749 var variant = this._getVariantFromOptions();
750
751 this.container.dispatchEvent(
752 new CustomEvent('variantChange', {
753 detail: {
754 variant: variant
755 },
756 bubbles: true,
757 cancelable: true
758 })
759 );
760
761 if (!variant) {
762 return;
763 }
764
765 this._updateMasterSelect(variant);
766 this._updateImages(variant);
767 this._updatePrice(variant);
768 this._updateSKU(variant);
769 this.currentVariant = variant;
770
771 if (this.enableHistoryState) {
772 this._updateHistoryState(variant);
773 }
774 },
775
776 /**
777 * Trigger event when variant image changes
778 *
779 * @param {object} variant - Currently selected variant
780 * @return {event} variantImageChange
781 */
782 _updateImages: function(variant) {
783 var variantImage = variant.featured_image || {};
784 var currentVariantImage = this.currentVariant.featured_image || {};
785
786 if (
787 !variant.featured_image ||
788 variantImage.src === currentVariantImage.src
789 ) {
790 return;
791 }
792
793 this.container.dispatchEvent(
794 new CustomEvent('variantImageChange', {
795 detail: {
796 variant: variant
797 },
798 bubbles: true,
799 cancelable: true
800 })
801 );
802 },
803
804 /**
805 * Trigger event when variant price changes.
806 *
807 * @param {object} variant - Currently selected variant
808 * @return {event} variantPriceChange
809 */
810 _updatePrice: function(variant) {
811 if (
812 variant.price === this.currentVariant.price &&
813 variant.compare_at_price === this.currentVariant.compare_at_price &&
814 variant.unit_price === this.currentVariant.unit_price
815 ) {
816 return;
817 }
818
819 this.container.dispatchEvent(
820 new CustomEvent('variantPriceChange', {
821 detail: {
822 variant: variant
823 },
824 bubbles: true,
825 cancelable: true
826 })
827 );
828 },
829
830 /**
831 * Trigger event when variant sku changes.
832 *
833 * @param {object} variant - Currently selected variant
834 * @return {event} variantSKUChange
835 */
836 _updateSKU: function(variant) {
837 if (variant.sku === this.currentVariant.sku) {
838 return;
839 }
840
841 this.container.dispatchEvent(
842 new CustomEvent('variantSKUChange', {
843 detail: {
844 variant: variant
845 },
846 bubbles: true,
847 cancelable: true
848 })
849 );
850 },
851
852 /**
853 * Update history state for product deeplinking
854 *
855 * @param {variant} variant - Currently selected variant
856 * @return {k} [description]
857 */
858 _updateHistoryState: function(variant) {
859 if (!history.replaceState || !variant) {
860 return;
861 }
862
863 var newurl =
864 window.location.protocol +
865 '//' +
866 window.location.host +
867 window.location.pathname +
868 '?variant=' +
869 variant.id;
870 window.history.replaceState({ path: newurl }, '', newurl);
871 },
872
873 /**
874 * Update hidden master select of variant change
875 *
876 * @param {variant} variant - Currently selected variant
877 */
878 _updateMasterSelect: function(variant) {
879 var masterSelect = this.container.querySelector(this.originalSelectorId);
880
881 if (!masterSelect) return;
882 masterSelect.value = variant.id;
883 }
884 });
885
886 return Variants;
887})();
888
889this.Shopify = this.Shopify || {};
890this.Shopify.theme = this.Shopify.theme || {};
891this.Shopify.theme.PredictiveSearch = (function() {
892 'use strict';
893
894 function validateQuery(query) {
895 var error;
896
897 if (query === null || query === undefined) {
898 error = new TypeError("'query' is missing");
899 error.type = 'argument';
900 throw error;
901 }
902
903 if (typeof query !== 'string') {
904 error = new TypeError("'query' is not a string");
905 error.type = 'argument';
906 throw error;
907 }
908 }
909
910 function GenericError() {
911 var error = Error.call(this);
912
913 error.name = 'Server error';
914 error.message = 'Something went wrong on the server';
915 error.status = 500;
916
917 return error;
918 }
919
920 function NotFoundError(status) {
921 var error = Error.call(this);
922
923 error.name = 'Not found';
924 error.message = 'Not found';
925 error.status = status;
926
927 return error;
928 }
929
930 function ServerError() {
931 var error = Error.call(this);
932
933 error.name = 'Server error';
934 error.message = 'Something went wrong on the server';
935 error.status = 500;
936
937 return error;
938 }
939
940 function ContentTypeError(status) {
941 var error = Error.call(this);
942
943 error.name = 'Content-Type error';
944 error.message = 'Content-Type was not provided or is of wrong type';
945 error.status = status;
946
947 return error;
948 }
949
950 function JsonParseError(status) {
951 var error = Error.call(this);
952
953 error.name = 'JSON parse error';
954 error.message = 'JSON syntax error';
955 error.status = status;
956
957 return error;
958 }
959
960 function ThrottledError(status, name, message, retryAfter) {
961 var error = Error.call(this);
962
963 error.name = name;
964 error.message = message;
965 error.status = status;
966 error.retryAfter = retryAfter;
967
968 return error;
969 }
970
971 function InvalidParameterError(status, name, message) {
972 var error = Error.call(this);
973
974 error.name = name;
975 error.message = message;
976 error.status = status;
977
978 return error;
979 }
980
981 function ExpectationFailedError(status, name, message) {
982 var error = Error.call(this);
983
984 error.name = name;
985 error.message = message;
986 error.status = status;
987
988 return error;
989 }
990
991 function request(searchUrl, queryParams, query, onSuccess, onError) {
992 var xhr = new XMLHttpRequest();
993 var route = searchUrl + '/suggest.json';
994
995 xhr.onreadystatechange = function() {
996 if (xhr.readyState !== XMLHttpRequest.DONE) {
997 return;
998 }
999
1000 var contentType = xhr.getResponseHeader('Content-Type');
1001
1002 if (xhr.status >= 500) {
1003 onError(new ServerError());
1004
1005 return;
1006 }
1007
1008 if (xhr.status === 404) {
1009 onError(new NotFoundError(xhr.status));
1010
1011 return;
1012 }
1013
1014 if (
1015 typeof contentType !== 'string' ||
1016 contentType.toLowerCase().match('application/json') === null
1017 ) {
1018 onError(new ContentTypeError(xhr.status));
1019
1020 return;
1021 }
1022
1023 if (xhr.status === 417) {
1024 try {
1025 var invalidParameterJson = JSON.parse(xhr.responseText);
1026
1027 onError(
1028 new InvalidParameterError(
1029 xhr.status,
1030 invalidParameterJson.message,
1031 invalidParameterJson.description
1032 )
1033 );
1034 } catch (error) {
1035 onError(new JsonParseError(xhr.status));
1036 }
1037
1038 return;
1039 }
1040
1041 if (xhr.status === 422) {
1042 try {
1043 var expectationFailedJson = JSON.parse(xhr.responseText);
1044
1045 onError(
1046 new ExpectationFailedError(
1047 xhr.status,
1048 expectationFailedJson.message,
1049 expectationFailedJson.description
1050 )
1051 );
1052 } catch (error) {
1053 onError(new JsonParseError(xhr.status));
1054 }
1055
1056 return;
1057 }
1058
1059 if (xhr.status === 429) {
1060 try {
1061 var throttledJson = JSON.parse(xhr.responseText);
1062
1063 onError(
1064 new ThrottledError(
1065 xhr.status,
1066 throttledJson.message,
1067 throttledJson.description,
1068 xhr.getResponseHeader('Retry-After')
1069 )
1070 );
1071 } catch (error) {
1072 onError(new JsonParseError(xhr.status));
1073 }
1074
1075 return;
1076 }
1077
1078 if (xhr.status === 200) {
1079 try {
1080 var res = JSON.parse(xhr.responseText);
1081 res.query = query;
1082 onSuccess(res);
1083 } catch (error) {
1084 onError(new JsonParseError(xhr.status));
1085 }
1086
1087 return;
1088 }
1089
1090 try {
1091 var genericErrorJson = JSON.parse(xhr.responseText);
1092 onError(
1093 new GenericError(
1094 xhr.status,
1095 genericErrorJson.message,
1096 genericErrorJson.description
1097 )
1098 );
1099 } catch (error) {
1100 onError(new JsonParseError(xhr.status));
1101 }
1102
1103 return;
1104 };
1105
1106 xhr.open(
1107 'get',
1108 route + '?q=' + encodeURIComponent(query) + '&' + queryParams
1109 );
1110
1111 xhr.setRequestHeader('Content-Type', 'application/json');
1112
1113 xhr.send();
1114 }
1115
1116 function Cache(config) {
1117 this._store = {};
1118 this._keys = [];
1119 if (config && config.bucketSize) {
1120 this.bucketSize = config.bucketSize;
1121 } else {
1122 this.bucketSize = 20;
1123 }
1124 }
1125
1126 Cache.prototype.set = function(key, value) {
1127 if (this.count() >= this.bucketSize) {
1128 var deleteKey = this._keys.splice(0, 1);
1129 this.delete(deleteKey);
1130 }
1131
1132 this._keys.push(key);
1133 this._store[key] = value;
1134
1135 return this._store;
1136 };
1137
1138 Cache.prototype.get = function(key) {
1139 return this._store[key];
1140 };
1141
1142 Cache.prototype.has = function(key) {
1143 return Boolean(this._store[key]);
1144 };
1145
1146 Cache.prototype.count = function() {
1147 return Object.keys(this._store).length;
1148 };
1149
1150 Cache.prototype.delete = function(key) {
1151 var exists = Boolean(this._store[key]);
1152 delete this._store[key];
1153 return exists && !this._store[key];
1154 };
1155
1156 function Dispatcher() {
1157 this.events = {};
1158 }
1159
1160 Dispatcher.prototype.on = function(eventName, callback) {
1161 var event = this.events[eventName];
1162 if (!event) {
1163 event = new DispatcherEvent(eventName);
1164 this.events[eventName] = event;
1165 }
1166 event.registerCallback(callback);
1167 };
1168
1169 Dispatcher.prototype.off = function(eventName, callback) {
1170 var event = this.events[eventName];
1171 if (event && event.callbacks.indexOf(callback) > -1) {
1172 event.unregisterCallback(callback);
1173 if (event.callbacks.length === 0) {
1174 delete this.events[eventName];
1175 }
1176 }
1177 };
1178
1179 Dispatcher.prototype.dispatch = function(eventName, payload) {
1180 var event = this.events[eventName];
1181 if (event) {
1182 event.fire(payload);
1183 }
1184 };
1185
1186 function DispatcherEvent(eventName) {
1187 this.eventName = eventName;
1188 this.callbacks = [];
1189 }
1190
1191 DispatcherEvent.prototype.registerCallback = function(callback) {
1192 this.callbacks.push(callback);
1193 };
1194
1195 DispatcherEvent.prototype.unregisterCallback = function(callback) {
1196 var index = this.callbacks.indexOf(callback);
1197 if (index > -1) {
1198 this.callbacks.splice(index, 1);
1199 }
1200 };
1201
1202 DispatcherEvent.prototype.fire = function(payload) {
1203 var callbacks = this.callbacks.slice(0);
1204 callbacks.forEach(function(callback) {
1205 callback(payload);
1206 });
1207 };
1208
1209 function debounce(func, wait) {
1210 var timeout = null;
1211 return function() {
1212 var context = this;
1213 var args = arguments;
1214 clearTimeout(timeout);
1215 timeout = setTimeout(function() {
1216 timeout = null;
1217 func.apply(context, args);
1218 }, wait || 0);
1219 };
1220 }
1221
1222 function objectToQueryParams(obj, parentKey) {
1223 var output = '';
1224 parentKey = parentKey || null;
1225
1226 Object.keys(obj).forEach(function(key) {
1227 var outputKey = key + '=';
1228 if (parentKey) {
1229 outputKey = parentKey + '[' + key + ']';
1230 }
1231
1232 switch (trueTypeOf(obj[key])) {
1233 case 'object':
1234 output += objectToQueryParams(obj[key], parentKey ? outputKey : key);
1235 break;
1236 case 'array':
1237 output += outputKey + '=' + obj[key].join(',') + '&';
1238 break;
1239 default:
1240 if (parentKey) {
1241 outputKey += '=';
1242 }
1243 output += outputKey + encodeURIComponent(obj[key]) + '&';
1244 break;
1245 }
1246 });
1247
1248 return output;
1249 }
1250
1251 function trueTypeOf(obj) {
1252 return Object.prototype.toString
1253 .call(obj)
1254 .slice(8, -1)
1255 .toLowerCase();
1256 }
1257
1258 var DEBOUNCE_RATE = 10;
1259 var requestDebounced = debounce(request, DEBOUNCE_RATE);
1260
1261 function PredictiveSearch(params, searchUrl) {
1262 if (!params) {
1263 throw new TypeError('No params object was specified');
1264 }
1265
1266 this.searchUrl = searchUrl;
1267
1268 this._retryAfter = null;
1269 this._currentQuery = null;
1270
1271 this.dispatcher = new Dispatcher();
1272 this.cache = new Cache({ bucketSize: 40 });
1273
1274 this.queryParams = objectToQueryParams(params);
1275 }
1276
1277 PredictiveSearch.TYPES = {
1278 PRODUCT: 'product',
1279 PAGE: 'page',
1280 ARTICLE: 'article'
1281 };
1282
1283 PredictiveSearch.FIELDS = {
1284 AUTHOR: 'author',
1285 BODY: 'body',
1286 PRODUCT_TYPE: 'product_type',
1287 TAG: 'tag',
1288 TITLE: 'title',
1289 VARIANTS_BARCODE: 'variants.barcode',
1290 VARIANTS_SKU: 'variants.sku',
1291 VARIANTS_TITLE: 'variants.title',
1292 VENDOR: 'vendor'
1293 };
1294
1295 PredictiveSearch.UNAVAILABLE_PRODUCTS = {
1296 SHOW: 'show',
1297 HIDE: 'hide',
1298 LAST: 'last'
1299 };
1300
1301 PredictiveSearch.prototype.query = function query(query) {
1302 try {
1303 validateQuery(query);
1304 } catch (error) {
1305 this.dispatcher.dispatch('error', error);
1306 return;
1307 }
1308
1309 if (query === '') {
1310 return this;
1311 }
1312
1313 this._currentQuery = normalizeQuery(query);
1314 var cacheResult = this.cache.get(this._currentQuery);
1315 if (cacheResult) {
1316 this.dispatcher.dispatch('success', cacheResult);
1317 return this;
1318 }
1319
1320 requestDebounced(
1321 this.searchUrl,
1322 this.queryParams,
1323 query,
1324 function(result) {
1325 this.cache.set(normalizeQuery(result.query), result);
1326 if (normalizeQuery(result.query) === this._currentQuery) {
1327 this._retryAfter = null;
1328 this.dispatcher.dispatch('success', result);
1329 }
1330 }.bind(this),
1331 function(error) {
1332 if (error.retryAfter) {
1333 this._retryAfter = error.retryAfter;
1334 }
1335 this.dispatcher.dispatch('error', error);
1336 }.bind(this)
1337 );
1338
1339 return this;
1340 };
1341
1342 PredictiveSearch.prototype.on = function on(eventName, callback) {
1343 this.dispatcher.on(eventName, callback);
1344
1345 return this;
1346 };
1347
1348 PredictiveSearch.prototype.off = function on(eventName, callback) {
1349 this.dispatcher.off(eventName, callback);
1350
1351 return this;
1352 };
1353
1354 function normalizeQuery(query) {
1355 if (typeof query !== 'string') {
1356 return null;
1357 }
1358
1359 return query
1360 .trim()
1361 .replace(' ', '-')
1362 .toLowerCase();
1363 }
1364
1365 return PredictiveSearch;
1366})();
1367
1368this.Shopify = this.Shopify || {};
1369this.Shopify.theme = this.Shopify.theme || {};
1370this.Shopify.theme.PredictiveSearchComponent = (function(PredictiveSearch) {
1371 'use strict';
1372
1373 PredictiveSearch =
1374 PredictiveSearch && PredictiveSearch.hasOwnProperty('default')
1375 ? PredictiveSearch['default']
1376 : PredictiveSearch;
1377
1378 var DEFAULT_PREDICTIVE_SEARCH_API_CONFIG = {
1379 resources: {
1380 type: [PredictiveSearch.TYPES.PRODUCT],
1381 options: {
1382 unavailable_products: PredictiveSearch.UNAVAILABLE_PRODUCTS.LAST,
1383 fields: [
1384 PredictiveSearch.FIELDS.TITLE,
1385 PredictiveSearch.FIELDS.VENDOR,
1386 PredictiveSearch.FIELDS.PRODUCT_TYPE,
1387 PredictiveSearch.FIELDS.VARIANTS_TITLE
1388 ]
1389 }
1390 }
1391 };
1392
1393 function PredictiveSearchComponent(config) {
1394 // validate config
1395 if (
1396 !config ||
1397 !config.selectors ||
1398 !config.selectors.input ||
1399 !isString(config.selectors.input) ||
1400 !config.selectors.result ||
1401 !isString(config.selectors.result) ||
1402 !config.resultTemplateFct ||
1403 !isFunction(config.resultTemplateFct) ||
1404 !config.numberOfResultsTemplateFct ||
1405 !isFunction(config.numberOfResultsTemplateFct) ||
1406 !config.loadingResultsMessageTemplateFct ||
1407 !isFunction(config.loadingResultsMessageTemplateFct)
1408 ) {
1409 var error = new TypeError(
1410 'PredictiveSearchComponent config is not valid'
1411 );
1412 error.type = 'argument';
1413 throw error;
1414 }
1415
1416 // Find nodes
1417 this.nodes = findNodes(config.selectors);
1418
1419 // Validate nodes
1420 if (!isValidNodes(this.nodes)) {
1421 // eslint-disable-next-line no-console
1422 console.warn('Could not find valid nodes');
1423 return;
1424 }
1425
1426 this.searchUrl = config.searchUrl || '/search';
1427
1428 // Store the keyword that was used for the search
1429 this._searchKeyword = '';
1430
1431 // Assign result template
1432 this.resultTemplateFct = config.resultTemplateFct;
1433
1434 // Assign number of results template
1435 this.numberOfResultsTemplateFct = config.numberOfResultsTemplateFct;
1436
1437 // Assign loading state template function
1438 this.loadingResultsMessageTemplateFct =
1439 config.loadingResultsMessageTemplateFct;
1440
1441 // Assign number of search results
1442 this.numberOfResults = config.numberOfResults || 4;
1443
1444 // Set classes
1445 this.classes = {
1446 visibleVariant: config.visibleVariant
1447 ? config.visibleVariant
1448 : 'predictive-search-wrapper--visible',
1449 itemSelected: config.itemSelectedClass
1450 ? config.itemSelectedClass
1451 : 'predictive-search-item--selected',
1452 clearButtonVisible: config.clearButtonVisibleClass
1453 ? config.clearButtonVisibleClass
1454 : 'predictive-search__clear-button--visible'
1455 };
1456
1457 this.selectors = {
1458 searchResult: config.searchResult
1459 ? config.searchResult
1460 : '[data-search-result]'
1461 };
1462
1463 // Assign callbacks
1464 this.callbacks = assignCallbacks(config);
1465
1466 // Add input attributes
1467 addInputAttributes(this.nodes.input);
1468
1469 // Add input event listeners
1470 this._addInputEventListeners();
1471
1472 // Add body listener
1473 this._addBodyEventListener();
1474
1475 // Add accessibility announcer
1476 this._addAccessibilityAnnouncer();
1477
1478 // Display the reset button if the input is not empty
1479 this._toggleClearButtonVisibility();
1480
1481 // Instantiate Predictive Search API
1482 this.predictiveSearch = new PredictiveSearch(
1483 config.PredictiveSearchAPIConfig
1484 ? config.PredictiveSearchAPIConfig
1485 : DEFAULT_PREDICTIVE_SEARCH_API_CONFIG,
1486 this.searchUrl
1487 );
1488
1489 // Add predictive search success event listener
1490 this.predictiveSearch.on(
1491 'success',
1492 this._handlePredictiveSearchSuccess.bind(this)
1493 );
1494
1495 // Add predictive search error event listener
1496 this.predictiveSearch.on(
1497 'error',
1498 this._handlePredictiveSearchError.bind(this)
1499 );
1500 }
1501
1502 /**
1503 * Private methods
1504 */
1505 function findNodes(selectors) {
1506 return {
1507 input: document.querySelector(selectors.input),
1508 reset: document.querySelector(selectors.reset),
1509 result: document.querySelector(selectors.result)
1510 };
1511 }
1512
1513 function isValidNodes(nodes) {
1514 if (
1515 !nodes ||
1516 !nodes.input ||
1517 !nodes.result ||
1518 nodes.input.tagName !== 'INPUT'
1519 ) {
1520 return false;
1521 }
1522
1523 return true;
1524 }
1525
1526 function assignCallbacks(config) {
1527 return {
1528 onBodyMousedown: config.onBodyMousedown,
1529 onBeforeOpen: config.onBeforeOpen,
1530 onOpen: config.onOpen,
1531 onBeforeClose: config.onBeforeClose,
1532 onClose: config.onClose,
1533 onInputFocus: config.onInputFocus,
1534 onInputKeyup: config.onInputKeyup,
1535 onInputBlur: config.onInputBlur,
1536 onInputReset: config.onInputReset,
1537 onBeforeDestroy: config.onBeforeDestroy,
1538 onDestroy: config.onDestroy
1539 };
1540 }
1541
1542 function addInputAttributes(input) {
1543 input.setAttribute('autocorrect', 'off');
1544 input.setAttribute('autocomplete', 'off');
1545 input.setAttribute('autocapitalize', 'off');
1546 input.setAttribute('spellcheck', 'false');
1547 }
1548
1549 function removeInputAttributes(input) {
1550 input.removeAttribute('autocorrect', 'off');
1551 input.removeAttribute('autocomplete', 'off');
1552 input.removeAttribute('autocapitalize', 'off');
1553 input.removeAttribute('spellcheck', 'false');
1554 }
1555
1556 /**
1557 * Public variables
1558 */
1559 PredictiveSearchComponent.prototype.isResultVisible = false;
1560 PredictiveSearchComponent.prototype.results = {};
1561
1562 /**
1563 * "Private" variables
1564 */
1565 PredictiveSearchComponent.prototype._latencyTimer = null;
1566 PredictiveSearchComponent.prototype._resultNodeClicked = false;
1567
1568 /**
1569 * "Private" instance methods
1570 */
1571 PredictiveSearchComponent.prototype._addInputEventListeners = function() {
1572 var input = this.nodes.input;
1573 var reset = this.nodes.reset;
1574
1575 if (!input) {
1576 return;
1577 }
1578
1579 this._handleInputFocus = this._handleInputFocus.bind(this);
1580 this._handleInputBlur = this._handleInputBlur.bind(this);
1581 this._handleInputKeyup = this._handleInputKeyup.bind(this);
1582 this._handleInputKeydown = this._handleInputKeydown.bind(this);
1583
1584 input.addEventListener('focus', this._handleInputFocus);
1585 input.addEventListener('blur', this._handleInputBlur);
1586 input.addEventListener('keyup', this._handleInputKeyup);
1587 input.addEventListener('keydown', this._handleInputKeydown);
1588
1589 if (reset) {
1590 this._handleInputReset = this._handleInputReset.bind(this);
1591 reset.addEventListener('click', this._handleInputReset);
1592 }
1593 };
1594
1595 PredictiveSearchComponent.prototype._removeInputEventListeners = function() {
1596 var input = this.nodes.input;
1597
1598 input.removeEventListener('focus', this._handleInputFocus);
1599 input.removeEventListener('blur', this._handleInputBlur);
1600 input.removeEventListener('keyup', this._handleInputKeyup);
1601 input.removeEventListener('keydown', this._handleInputKeydown);
1602 };
1603
1604 PredictiveSearchComponent.prototype._addBodyEventListener = function() {
1605 this._handleBodyMousedown = this._handleBodyMousedown.bind(this);
1606
1607 document
1608 .querySelector('body')
1609 .addEventListener('mousedown', this._handleBodyMousedown);
1610 };
1611
1612 PredictiveSearchComponent.prototype._removeBodyEventListener = function() {
1613 document
1614 .querySelector('body')
1615 .removeEventListener('mousedown', this._handleBodyMousedown);
1616 };
1617
1618 PredictiveSearchComponent.prototype._removeClearButtonEventListener = function() {
1619 var reset = this.nodes.reset;
1620
1621 if (!reset) {
1622 return;
1623 }
1624
1625 reset.removeEventListener('click', this._handleInputReset);
1626 };
1627
1628 /**
1629 * Event handlers
1630 */
1631 PredictiveSearchComponent.prototype._handleBodyMousedown = function(evt) {
1632 if (this.isResultVisible && this.nodes !== null) {
1633 if (
1634 evt.target.isEqualNode(this.nodes.input) ||
1635 this.nodes.input.contains(evt.target) ||
1636 evt.target.isEqualNode(this.nodes.result) ||
1637 this.nodes.result.contains(evt.target)
1638 ) {
1639 this._resultNodeClicked = true;
1640 } else {
1641 if (isFunction(this.callbacks.onBodyMousedown)) {
1642 var returnedValue = this.callbacks.onBodyMousedown(this.nodes);
1643 if (isBoolean(returnedValue) && returnedValue) {
1644 this.close();
1645 }
1646 } else {
1647 this.close();
1648 }
1649 }
1650 }
1651 };
1652
1653 PredictiveSearchComponent.prototype._handleInputFocus = function(evt) {
1654 if (isFunction(this.callbacks.onInputFocus)) {
1655 var returnedValue = this.callbacks.onInputFocus(this.nodes);
1656 if (isBoolean(returnedValue) && !returnedValue) {
1657 return false;
1658 }
1659 }
1660
1661 if (evt.target.value.length > 0) {
1662 this._search();
1663 }
1664
1665 return true;
1666 };
1667
1668 PredictiveSearchComponent.prototype._handleInputBlur = function() {
1669 // This has to be done async, to wait for the focus to be on the next
1670 // element and avoid closing the results.
1671 // Example: Going from the input to the reset button.
1672 setTimeout(
1673 function() {
1674 if (isFunction(this.callbacks.onInputBlur)) {
1675 var returnedValue = this.callbacks.onInputBlur(this.nodes);
1676 if (isBoolean(returnedValue) && !returnedValue) {
1677 return false;
1678 }
1679 }
1680
1681 if (document.activeElement.isEqualNode(this.nodes.reset)) {
1682 return false;
1683 }
1684
1685 if (this._resultNodeClicked) {
1686 this._resultNodeClicked = false;
1687 return false;
1688 }
1689
1690 this.close();
1691 }.bind(this)
1692 );
1693
1694 return true;
1695 };
1696
1697 PredictiveSearchComponent.prototype._addAccessibilityAnnouncer = function() {
1698 this._accessibilityAnnouncerDiv = window.document.createElement('div');
1699
1700 this._accessibilityAnnouncerDiv.setAttribute(
1701 'style',
1702 'position: absolute !important; overflow: hidden; clip: rect(0 0 0 0); height: 1px; width: 1px; margin: -1px; padding: 0; border: 0;'
1703 );
1704
1705 this._accessibilityAnnouncerDiv.setAttribute('data-search-announcer', '');
1706 this._accessibilityAnnouncerDiv.setAttribute('aria-live', 'polite');
1707 this._accessibilityAnnouncerDiv.setAttribute('aria-atomic', 'true');
1708
1709 this.nodes.result.parentElement.appendChild(
1710 this._accessibilityAnnouncerDiv
1711 );
1712 };
1713
1714 PredictiveSearchComponent.prototype._removeAccessibilityAnnouncer = function() {
1715 this.nodes.result.parentElement.removeChild(
1716 this._accessibilityAnnouncerDiv
1717 );
1718 };
1719
1720 PredictiveSearchComponent.prototype._updateAccessibilityAttributesAfterSelectingElement = function(
1721 previousSelectedElement,
1722 currentSelectedElement
1723 ) {
1724 // Update the active descendant on the search input
1725 this.nodes.input.setAttribute(
1726 'aria-activedescendant',
1727 currentSelectedElement.id
1728 );
1729
1730 // Unmark the previousSelected elemented as selected
1731 if (previousSelectedElement) {
1732 previousSelectedElement.removeAttribute('aria-selected');
1733 }
1734
1735 // Mark the element as selected
1736 currentSelectedElement.setAttribute('aria-selected', true);
1737 };
1738
1739 PredictiveSearchComponent.prototype._clearAriaActiveDescendant = function() {
1740 this.nodes.input.setAttribute('aria-activedescendant', '');
1741 };
1742
1743 PredictiveSearchComponent.prototype._announceNumberOfResultsFound = function(
1744 results
1745 ) {
1746 var currentAnnouncedMessage = this._accessibilityAnnouncerDiv.innerHTML;
1747 var newMessage = this.numberOfResultsTemplateFct(results);
1748
1749 // If the messages are the same, they won't get announced
1750 // add white space so it gets announced
1751 if (currentAnnouncedMessage === newMessage) {
1752 newMessage = newMessage + ' ';
1753 }
1754
1755 this._accessibilityAnnouncerDiv.innerHTML = newMessage;
1756 };
1757
1758 PredictiveSearchComponent.prototype._announceLoadingState = function() {
1759 this._accessibilityAnnouncerDiv.innerHTML = this.loadingResultsMessageTemplateFct();
1760 };
1761
1762 PredictiveSearchComponent.prototype._handleInputKeyup = function(evt) {
1763 var UP_ARROW_KEY_CODE = 38;
1764 var DOWN_ARROW_KEY_CODE = 40;
1765 var RETURN_KEY_CODE = 13;
1766 var ESCAPE_KEY_CODE = 27;
1767
1768 if (isFunction(this.callbacks.onInputKeyup)) {
1769 var returnedValue = this.callbacks.onInputKeyup(this.nodes);
1770 if (isBoolean(returnedValue) && !returnedValue) {
1771 return false;
1772 }
1773 }
1774
1775 this._toggleClearButtonVisibility();
1776
1777 if (this.isResultVisible && this.nodes !== null) {
1778 if (evt.keyCode === UP_ARROW_KEY_CODE) {
1779 this._navigateOption(evt, 'UP');
1780 return true;
1781 }
1782
1783 if (evt.keyCode === DOWN_ARROW_KEY_CODE) {
1784 this._navigateOption(evt, 'DOWN');
1785 return true;
1786 }
1787
1788 if (evt.keyCode === RETURN_KEY_CODE) {
1789 this._selectOption();
1790 return true;
1791 }
1792
1793 if (evt.keyCode === ESCAPE_KEY_CODE) {
1794 this.close();
1795 }
1796 }
1797
1798 if (evt.target.value.length <= 0) {
1799 this.close();
1800 this._setKeyword('');
1801 } else if (evt.target.value.length > 0) {
1802 this._search();
1803 }
1804
1805 return true;
1806 };
1807
1808 PredictiveSearchComponent.prototype._handleInputKeydown = function(evt) {
1809 var RETURN_KEY_CODE = 13;
1810 var UP_ARROW_KEY_CODE = 38;
1811 var DOWN_ARROW_KEY_CODE = 40;
1812
1813 // Prevent the form default submission if there is a selected option
1814 if (evt.keyCode === RETURN_KEY_CODE && this._getSelectedOption() !== null) {
1815 evt.preventDefault();
1816 }
1817
1818 // Prevent the cursor from moving in the input when using the up and down arrow keys
1819 if (
1820 evt.keyCode === UP_ARROW_KEY_CODE ||
1821 evt.keyCode === DOWN_ARROW_KEY_CODE
1822 ) {
1823 evt.preventDefault();
1824 }
1825 };
1826
1827 PredictiveSearchComponent.prototype._handleInputReset = function(evt) {
1828 evt.preventDefault();
1829
1830 if (isFunction(this.callbacks.onInputReset)) {
1831 var returnedValue = this.callbacks.onInputReset(this.nodes);
1832 if (isBoolean(returnedValue) && !returnedValue) {
1833 return false;
1834 }
1835 }
1836
1837 this.nodes.input.value = '';
1838 this.nodes.input.focus();
1839 this._toggleClearButtonVisibility();
1840 this.close();
1841
1842 return true;
1843 };
1844
1845 PredictiveSearchComponent.prototype._navigateOption = function(
1846 evt,
1847 direction
1848 ) {
1849 var currentOption = this._getSelectedOption();
1850
1851 if (!currentOption) {
1852 var firstOption = this.nodes.result.querySelector(
1853 this.selectors.searchResult
1854 );
1855 firstOption.classList.add(this.classes.itemSelected);
1856 this._updateAccessibilityAttributesAfterSelectingElement(
1857 null,
1858 firstOption
1859 );
1860 } else {
1861 if (direction === 'DOWN') {
1862 var nextOption = currentOption.nextElementSibling;
1863 if (nextOption) {
1864 currentOption.classList.remove(this.classes.itemSelected);
1865 nextOption.classList.add(this.classes.itemSelected);
1866 this._updateAccessibilityAttributesAfterSelectingElement(
1867 currentOption,
1868 nextOption
1869 );
1870 }
1871 } else {
1872 var previousOption = currentOption.previousElementSibling;
1873 if (previousOption) {
1874 currentOption.classList.remove(this.classes.itemSelected);
1875 previousOption.classList.add(this.classes.itemSelected);
1876 this._updateAccessibilityAttributesAfterSelectingElement(
1877 currentOption,
1878 previousOption
1879 );
1880 }
1881 }
1882 }
1883 };
1884
1885 PredictiveSearchComponent.prototype._getSelectedOption = function() {
1886 return this.nodes.result.querySelector('.' + this.classes.itemSelected);
1887 };
1888
1889 PredictiveSearchComponent.prototype._selectOption = function() {
1890 var selectedOption = this._getSelectedOption();
1891
1892 if (selectedOption) {
1893 selectedOption.querySelector('a, button').click();
1894 }
1895 };
1896
1897 PredictiveSearchComponent.prototype._search = function() {
1898 var newSearchKeyword = this.nodes.input.value;
1899
1900 if (this._searchKeyword === newSearchKeyword) {
1901 return;
1902 }
1903
1904 clearTimeout(this._latencyTimer);
1905 this._latencyTimer = setTimeout(
1906 function() {
1907 this.results.isLoading = true;
1908
1909 // Annonuce that we're loading the results
1910 this._announceLoadingState();
1911
1912 this.nodes.result.classList.add(this.classes.visibleVariant);
1913 // NOTE: We could benifit in using DOMPurify.
1914 // https://github.com/cure53/DOMPurify
1915 this.nodes.result.innerHTML = this.resultTemplateFct(this.results);
1916 }.bind(this),
1917 500
1918 );
1919
1920 this.predictiveSearch.query(newSearchKeyword);
1921 this._setKeyword(newSearchKeyword);
1922 };
1923
1924 PredictiveSearchComponent.prototype._handlePredictiveSearchSuccess = function(
1925 json
1926 ) {
1927 clearTimeout(this._latencyTimer);
1928 this.results = json.resources.results;
1929
1930 this.results.isLoading = false;
1931 this.results.products = this.results.products.slice(
1932 0,
1933 this.numberOfResults
1934 );
1935 this.results.canLoadMore =
1936 this.numberOfResults <= this.results.products.length;
1937 this.results.searchQuery = this.nodes.input.value;
1938
1939 if (this.results.products.length > 0 || this.results.searchQuery) {
1940 this.nodes.result.innerHTML = this.resultTemplateFct(this.results);
1941 this._announceNumberOfResultsFound(this.results);
1942 this.open();
1943 } else {
1944 this.nodes.result.innerHTML = '';
1945
1946 this._closeOnNoResults();
1947 }
1948 };
1949
1950 PredictiveSearchComponent.prototype._handlePredictiveSearchError = function() {
1951 clearTimeout(this._latencyTimer);
1952 this.nodes.result.innerHTML = '';
1953
1954 this._closeOnNoResults();
1955 };
1956
1957 PredictiveSearchComponent.prototype._closeOnNoResults = function() {
1958 if (this.nodes) {
1959 this.nodes.result.classList.remove(this.classes.visibleVariant);
1960 }
1961
1962 this.isResultVisible = false;
1963 };
1964
1965 PredictiveSearchComponent.prototype._setKeyword = function(keyword) {
1966 this._searchKeyword = keyword;
1967 };
1968
1969 PredictiveSearchComponent.prototype._toggleClearButtonVisibility = function() {
1970 if (!this.nodes.reset) {
1971 return;
1972 }
1973
1974 if (this.nodes.input.value.length > 0) {
1975 this.nodes.reset.classList.add(this.classes.clearButtonVisible);
1976 } else {
1977 this.nodes.reset.classList.remove(this.classes.clearButtonVisible);
1978 }
1979 };
1980
1981 /**
1982 * Public methods
1983 */
1984 PredictiveSearchComponent.prototype.open = function() {
1985 if (this.isResultVisible) {
1986 return;
1987 }
1988
1989 if (isFunction(this.callbacks.onBeforeOpen)) {
1990 var returnedValue = this.callbacks.onBeforeOpen(this.nodes);
1991 if (isBoolean(returnedValue) && !returnedValue) {
1992 return false;
1993 }
1994 }
1995
1996 this.nodes.result.classList.add(this.classes.visibleVariant);
1997 this.nodes.input.setAttribute('aria-expanded', true);
1998 this.isResultVisible = true;
1999
2000 if (isFunction(this.callbacks.onOpen)) {
2001 return this.callbacks.onOpen(this.nodes) || true;
2002 }
2003
2004 return true;
2005 };
2006
2007 PredictiveSearchComponent.prototype.close = function() {
2008 if (!this.isResultVisible) {
2009 return true;
2010 }
2011
2012 if (isFunction(this.callbacks.onBeforeClose)) {
2013 var returnedValue = this.callbacks.onBeforeClose(this.nodes);
2014 if (isBoolean(returnedValue) && !returnedValue) {
2015 return false;
2016 }
2017 }
2018
2019 if (this.nodes) {
2020 this.nodes.result.classList.remove(this.classes.visibleVariant);
2021 }
2022
2023 this.nodes.input.setAttribute('aria-expanded', false);
2024 this._clearAriaActiveDescendant();
2025 this._setKeyword('');
2026
2027 if (isFunction(this.callbacks.onClose)) {
2028 this.callbacks.onClose(this.nodes);
2029 }
2030
2031 this.isResultVisible = false;
2032 this.results = {};
2033
2034 return true;
2035 };
2036
2037 PredictiveSearchComponent.prototype.destroy = function() {
2038 this.close();
2039
2040 if (isFunction(this.callbacks.onBeforeDestroy)) {
2041 var returnedValue = this.callbacks.onBeforeDestroy(this.nodes);
2042 if (isBoolean(returnedValue) && !returnedValue) {
2043 return false;
2044 }
2045 }
2046
2047 this.nodes.result.classList.remove(this.classes.visibleVariant);
2048 removeInputAttributes(this.nodes.input);
2049 this._removeInputEventListeners();
2050 this._removeBodyEventListener();
2051 this._removeAccessibilityAnnouncer();
2052 this._removeClearButtonEventListener();
2053
2054 if (isFunction(this.callbacks.onDestroy)) {
2055 this.callbacks.onDestroy(this.nodes);
2056 }
2057
2058 return true;
2059 };
2060
2061 PredictiveSearchComponent.prototype.clearAndClose = function() {
2062 this.nodes.input.value = '';
2063 this.close();
2064 };
2065
2066 /**
2067 * Utilities
2068 */
2069 function getTypeOf(value) {
2070 return Object.prototype.toString.call(value);
2071 }
2072
2073 function isString(value) {
2074 return getTypeOf(value) === '[object String]';
2075 }
2076
2077 function isBoolean(value) {
2078 return getTypeOf(value) === '[object Boolean]';
2079 }
2080
2081 function isFunction(value) {
2082 return getTypeOf(value) === '[object Function]';
2083 }
2084
2085 return PredictiveSearchComponent;
2086})(Shopify.theme.PredictiveSearch);
2087
2088window.theme = window.theme || {};
2089
2090theme.TouchEvents = function TouchEvents(element, options) {
2091 this.axis;
2092 this.checkEvents = [];
2093 this.eventHandlers = {};
2094 this.eventModel = {};
2095 this.events = [
2096 ['touchstart', 'touchmove', 'touchend', 'touchcancel'],
2097 ['pointerdown', 'pointermove', 'pointerup', 'pointercancel'],
2098 ['mousedown', 'mousemove', 'mouseup']
2099 ];
2100 this.eventType;
2101 this.difference = {};
2102 this.direction;
2103 this.start = {};
2104
2105 this.element = element;
2106 this.options = Object.assign(
2107 {},
2108 {
2109 dragThreshold: 10,
2110 start: function() {}, // eslint-disable-line
2111 move: function() {}, // eslint-disable-line
2112 end: function() {} // eslint-disable-line
2113 },
2114 options
2115 );
2116
2117 this.checkEvents = this._getCheckEvents();
2118 this.eventModel = this._getEventModel();
2119
2120 this._setupEventHandlers();
2121};
2122
2123theme.TouchEvents.prototype = Object.assign({}, theme.TouchEvents.prototype, {
2124 destroy: function() {
2125 this.element.removeEventListener(
2126 'dragstart',
2127 this.eventHandlers.preventDefault
2128 );
2129
2130 this.element.removeEventListener(
2131 this.events[this.eventModel][0],
2132 this.eventHandlers.touchStart
2133 );
2134
2135 if (!this.eventModel) {
2136 this.element.removeEventListener(
2137 this.events[2][0],
2138 this.eventHandlers.touchStart
2139 );
2140 }
2141
2142 this.element.removeEventListener('click', this.eventHandlers.preventClick);
2143 },
2144
2145 _setupEventHandlers: function() {
2146 this.eventHandlers.preventDefault = this._preventDefault.bind(this);
2147 this.eventHandlers.preventClick = this._preventClick.bind(this);
2148 this.eventHandlers.touchStart = this._touchStart.bind(this);
2149 this.eventHandlers.touchMove = this._touchMove.bind(this);
2150 this.eventHandlers.touchEnd = this._touchEnd.bind(this);
2151
2152 // Prevent element from dragging when using mouse
2153 this.element.addEventListener(
2154 'dragstart',
2155 this.eventHandlers.preventDefault
2156 );
2157
2158 // Bind the touchstart/pointerdown event
2159 this.element.addEventListener(
2160 this.events[this.eventModel][0],
2161 this.eventHandlers.touchStart
2162 );
2163
2164 // Bind mousedown if necessary
2165 if (!this.eventModel) {
2166 this.element.addEventListener(
2167 this.events[2][0],
2168 this.eventHandlers.touchStart
2169 );
2170 }
2171
2172 // No clicking during touch
2173 this.element.addEventListener('click', this.eventHandlers.preventClick);
2174 },
2175
2176 _touchStart: function(event) {
2177 this.eventType = this.eventModel;
2178
2179 if (event.type === 'mousedown' && !this.eventModel) {
2180 this.eventType = 2;
2181 }
2182
2183 if (this.checkEvents[this.eventType](event)) return;
2184 if (this.eventType) this._preventDefault(event);
2185
2186 document.addEventListener(
2187 this.events[this.eventType][1],
2188 this.eventHandlers.touchMove
2189 );
2190
2191 document.addEventListener(
2192 this.events[this.eventType][2],
2193 this.eventHandlers.touchEnd
2194 );
2195
2196 if (this.eventType < 2) {
2197 document.addEventListener(
2198 this.events[this.eventType][3],
2199 this.eventHandlers.touchEnd
2200 );
2201 }
2202
2203 this.start = {
2204 xPosition: this.eventType ? event.clientX : event.touches[0].clientX,
2205 yPosition: this.eventType ? event.clientY : event.touches[0].clientY,
2206 time: new Date().getTime()
2207 };
2208
2209 // Ensure we empty out the this.difference object
2210 Object.keys(this.difference).forEach(
2211 function(key) {
2212 delete this.difference[key];
2213 }.bind(this)
2214 );
2215
2216 this.options.start(event);
2217 },
2218
2219 _touchMove: function(event) {
2220 this.difference = this._getDifference(event);
2221
2222 // Prevent document from scrolling during swipe gesture
2223 document['on' + this.events[this.eventType][1]] = function(event) {
2224 this._preventDefault(event);
2225 }.bind(this);
2226
2227 // Get the direction user is dragging
2228 if (!this.axis) {
2229 if (this.options.dragThreshold < Math.abs(this.difference.xPosition)) {
2230 this.axis = 'xPosition';
2231 } else if (
2232 this.options.dragThreshold < Math.abs(this.difference.yPosition)
2233 ) {
2234 this.axis = 'yPosition';
2235 } else {
2236 this.axis = false;
2237 }
2238 } else if (this.axis === 'xPosition') {
2239 this.direction = this.difference.xPosition < 0 ? 'left' : 'right';
2240 } else if (this.axis === 'yPosition') {
2241 this.direction = this.difference.yPosition < 0 ? 'up' : 'down';
2242 }
2243
2244 this.options.move(event, this.direction, this.difference);
2245 },
2246
2247 _touchEnd: function(event) {
2248 document.removeEventListener(
2249 this.events[this.eventType][1],
2250 this.eventHandlers.touchMove
2251 );
2252
2253 document.removeEventListener(
2254 this.events[this.eventType][2],
2255 this.eventHandlers.touchEnd
2256 );
2257
2258 if (this.eventType < 2) {
2259 document.removeEventListener(
2260 this.events[this.eventType][3],
2261 this.eventHandlers.touchEnd
2262 );
2263 }
2264
2265 // Re-enable document scrolling
2266 document['on' + this.events[this.eventType][1]] = function() {
2267 return true;
2268 };
2269
2270 this.options.end(event, this.direction, this.difference);
2271 this.axis = false;
2272 },
2273
2274 _getDifference: function(event) {
2275 return {
2276 xPosition:
2277 (this.eventType ? event.clientX : event.touches[0].clientX) -
2278 this.start.xPosition,
2279 yPosition:
2280 (this.eventType ? event.clientY : event.touches[0].clientY) -
2281 this.start.yPosition,
2282 time: new Date().getTime() - this.start.time
2283 };
2284 },
2285
2286 _getCheckEvents: function() {
2287 return [
2288 // Touch events
2289 function(event) {
2290 // Skip the event if it's a multi-touch or pinch move
2291 return (
2292 (event.touches && event.touches.length > 1) ||
2293 (event.scale && event.scale !== 1)
2294 );
2295 },
2296 // Pointer events
2297 function(event) {
2298 // Skip it, if:
2299 // 1. The event is not primary (other pointers during multi-touch),
2300 // 2. Left mouse button is not pressed,
2301 // 3. Event is not a touch event
2302 return (
2303 !event.isPrimary ||
2304 (event.buttons && event.buttons !== 1) ||
2305 (event.pointerType !== 'touch' && event.pointerType !== 'pen')
2306 );
2307 },
2308 // Mouse events
2309 function(event) {
2310 // Skip the event if left mouse button is not pressed
2311 return event.buttons && event.buttons !== 1;
2312 }
2313 ];
2314 },
2315
2316 _getEventModel: function() {
2317 return window.navigator.pointerEnabled ? 1 : 0;
2318 },
2319
2320 _preventDefault: function(event) {
2321 event.preventDefault ? event.preventDefault() : (event.returnValue = false);
2322 },
2323
2324 _preventClick: function(event) {
2325 if (Math.abs(this.difference.xPosition) > this.options.dragThreshold) {
2326 this._preventDefault(event);
2327 }
2328 }
2329});
2330
2331
2332/* ================ GLOBAL ================ */
2333/*============================================================================
2334 Drawer modules
2335==============================================================================*/
2336theme.Drawers = (function() {
2337 function Drawer(id, position, options) {
2338 var DEFAULT_OPEN_CLASS = 'js-drawer-open';
2339 var DEFAULT_CLOSE_CLASS = 'js-drawer-close';
2340
2341 var defaults = {
2342 selectors: {
2343 openVariant: '.' + DEFAULT_OPEN_CLASS + '-' + position,
2344 close: '.' + DEFAULT_CLOSE_CLASS
2345 },
2346 classes: {
2347 open: DEFAULT_OPEN_CLASS,
2348 openVariant: DEFAULT_OPEN_CLASS + '-' + position
2349 },
2350 withPredictiveSearch: false
2351 };
2352
2353 this.nodes = {
2354 parents: [document.documentElement, document.body],
2355 page: document.getElementById('PageContainer')
2356 };
2357
2358 this.eventHandlers = {};
2359
2360 this.config = Object.assign({}, defaults, options);
2361 this.position = position;
2362 this.drawer = document.getElementById(id);
2363
2364 if (!this.drawer) {
2365 return false;
2366 }
2367
2368 this.drawerIsOpen = false;
2369 this.init();
2370 }
2371
2372 Drawer.prototype.init = function() {
2373 document
2374 .querySelector(this.config.selectors.openVariant)
2375 .addEventListener('click', this.open.bind(this));
2376 this.drawer
2377 .querySelector(this.config.selectors.close)
2378 .addEventListener('click', this.close.bind(this));
2379 };
2380
2381 Drawer.prototype.open = function(evt) {
2382 // Keep track if drawer was opened from a click, or called by another function
2383 var externalCall = false;
2384
2385 // Prevent following href if link is clicked
2386 if (evt) {
2387 evt.preventDefault();
2388 } else {
2389 externalCall = true;
2390 }
2391
2392 // Without this, the drawer opens, the click event bubbles up to nodes.page
2393 // which closes the drawer.
2394 if (evt && evt.stopPropagation) {
2395 evt.stopPropagation();
2396 // save the source of the click, we'll focus to this on close
2397 this.activeSource = evt.currentTarget;
2398 }
2399
2400 if (this.drawerIsOpen && !externalCall) {
2401 return this.close();
2402 }
2403
2404 // Add is-transitioning class to moved elements on open so drawer can have
2405 // transition for close animation
2406 if (!this.config.withPredictiveSearch) {
2407 theme.Helpers.prepareTransition(this.drawer);
2408 }
2409
2410 this.nodes.parents.forEach(
2411 function(parent) {
2412 parent.classList.add(
2413 this.config.classes.open,
2414 this.config.classes.openVariant
2415 );
2416 }.bind(this)
2417 );
2418
2419 this.drawerIsOpen = true;
2420
2421 // Run function when draw opens if set
2422 if (
2423 this.config.onDrawerOpen &&
2424 typeof this.config.onDrawerOpen === 'function'
2425 ) {
2426 if (!externalCall) {
2427 this.config.onDrawerOpen();
2428 }
2429 }
2430
2431 if (this.activeSource && this.activeSource.hasAttribute('aria-expanded')) {
2432 this.activeSource.setAttribute('aria-expanded', 'true');
2433 }
2434
2435 // Set focus on drawer
2436 var trapFocusConfig = {
2437 container: this.drawer
2438 };
2439
2440 if (this.config.elementToFocusOnOpen) {
2441 trapFocusConfig.elementToFocus = this.config.elementToFocusOnOpen;
2442 }
2443
2444 slate.a11y.trapFocus(trapFocusConfig);
2445
2446 this.bindEvents();
2447
2448 return this;
2449 };
2450
2451 Drawer.prototype.close = function() {
2452 if (!this.drawerIsOpen) {
2453 // don't close a closed drawer
2454 return;
2455 }
2456
2457 // deselect any focused form elements
2458 document.activeElement.dispatchEvent(
2459 new CustomEvent('blur', { bubbles: true, cancelable: true })
2460 );
2461
2462 // Ensure closing transition is applied to moved elements, like the nav
2463 if (!this.config.withPredictiveSearch) {
2464 theme.Helpers.prepareTransition(this.drawer);
2465 }
2466
2467 this.nodes.parents.forEach(
2468 function(parent) {
2469 parent.classList.remove(
2470 this.config.classes.open,
2471 this.config.classes.openVariant
2472 );
2473 }.bind(this)
2474 );
2475
2476 if (this.activeSource && this.activeSource.hasAttribute('aria-expanded')) {
2477 this.activeSource.setAttribute('aria-expanded', 'false');
2478 }
2479
2480 this.drawerIsOpen = false;
2481
2482 // Remove focus on drawer
2483 slate.a11y.removeTrapFocus({
2484 container: this.drawer
2485 });
2486
2487 this.unbindEvents();
2488
2489 // Run function when draw closes if set
2490 if (
2491 this.config.onDrawerClose &&
2492 typeof this.config.onDrawerClose === 'function'
2493 ) {
2494 this.config.onDrawerClose();
2495 }
2496 };
2497
2498 Drawer.prototype.bindEvents = function() {
2499 this.eventHandlers.drawerKeyupHandler = function(evt) {
2500 // close on 'esc' keypress
2501 if (evt.keyCode === 27) {
2502 this.close();
2503 return false;
2504 } else {
2505 return true;
2506 }
2507 }.bind(this);
2508
2509 this.eventHandlers.drawerTouchmoveHandler = function() {
2510 return false;
2511 };
2512
2513 this.eventHandlers.drawerClickHandler = function() {
2514 this.close();
2515 return false;
2516 }.bind(this);
2517
2518 // Add event listener to document body
2519 document.body.addEventListener(
2520 'keyup',
2521 this.eventHandlers.drawerKeyupHandler
2522 );
2523
2524 // Lock scrolling on mobile
2525 this.nodes.page.addEventListener(
2526 'touchmove',
2527 this.eventHandlers.drawerTouchmoveHandler
2528 );
2529
2530 this.nodes.page.addEventListener(
2531 'click',
2532 this.eventHandlers.drawerClickHandler
2533 );
2534 };
2535
2536 Drawer.prototype.unbindEvents = function() {
2537 this.nodes.page.removeEventListener(
2538 'touchmove',
2539 this.eventHandlers.drawerTouchmoveHandler
2540 );
2541 this.nodes.page.removeEventListener(
2542 'click',
2543 this.eventHandlers.drawerClickHandler
2544 );
2545 document.body.removeEventListener(
2546 'keyup',
2547 this.eventHandlers.drawerKeyupHandler
2548 );
2549 };
2550
2551 return Drawer;
2552})();
2553
2554theme.Helpers = (function() {
2555 var touchDevice = false;
2556
2557 var classes = {
2558 preventScrolling: 'prevent-scrolling'
2559 };
2560
2561 var scrollPosition = window.pageYOffset;
2562
2563 function setTouch() {
2564 touchDevice = true;
2565 }
2566
2567 function isTouch() {
2568 return touchDevice;
2569 }
2570
2571 function enableScrollLock() {
2572 scrollPosition = window.pageYOffset;
2573 document.body.style.top = '-' + scrollPosition + 'px';
2574 document.body.classList.add(classes.preventScrolling);
2575 }
2576
2577 function disableScrollLock() {
2578 document.body.classList.remove(classes.preventScrolling);
2579 document.body.style.removeProperty('top');
2580 window.scrollTo(0, scrollPosition);
2581 }
2582
2583 function debounce(func, wait, immediate) {
2584 var timeout;
2585
2586 return function() {
2587 var context = this,
2588 args = arguments;
2589
2590 var later = function() {
2591 timeout = null;
2592 if (!immediate) func.apply(context, args);
2593 };
2594
2595 var callNow = immediate && !timeout;
2596 clearTimeout(timeout);
2597 timeout = setTimeout(later, wait);
2598 if (callNow) func.apply(context, args);
2599 };
2600 }
2601
2602 function getScript(source, beforeEl) {
2603 return new Promise(function(resolve, reject) {
2604 var script = document.createElement('script');
2605 var prior = beforeEl || document.getElementsByTagName('script')[0];
2606
2607 script.async = true;
2608 script.defer = true;
2609
2610 // eslint-disable-next-line shopify/prefer-early-return
2611 function onloadHander(_, isAbort) {
2612 if (
2613 isAbort ||
2614 !script.readyState ||
2615 /loaded|complete/.test(script.readyState)
2616 ) {
2617 script.onload = null;
2618 script.onreadystatechange = null;
2619 script = undefined;
2620
2621 if (isAbort) {
2622 reject();
2623 } else {
2624 resolve();
2625 }
2626 }
2627 }
2628
2629 script.onload = onloadHander;
2630 script.onreadystatechange = onloadHander;
2631
2632 script.src = source;
2633 prior.parentNode.insertBefore(script, prior);
2634 });
2635 }
2636
2637 /* Based on the prepareTransition by Jonathan Snook */
2638 /* Jonathan Snook - MIT License - https://github.com/snookca/prepareTransition */
2639 function prepareTransition(element) {
2640 element.addEventListener(
2641 'transitionend',
2642 function(event) {
2643 event.currentTarget.classList.remove('is-transitioning');
2644 },
2645 { once: true }
2646 );
2647
2648 var properties = [
2649 'transition-duration',
2650 '-moz-transition-duration',
2651 '-webkit-transition-duration',
2652 '-o-transition-duration'
2653 ];
2654
2655 var duration = 0;
2656
2657 properties.forEach(function(property) {
2658 var computedValue = getComputedStyle(element)[property];
2659
2660 if (computedValue) {
2661 computedValue.replace(/\D/g, '');
2662 duration || (duration = parseFloat(computedValue));
2663 }
2664 });
2665
2666 if (duration !== 0) {
2667 element.classList.add('is-transitioning');
2668 element.offsetWidth;
2669 }
2670 }
2671
2672 /*!
2673 * Serialize all form data into a SearchParams string
2674 * (c) 2020 Chris Ferdinandi, MIT License, https://gomakethings.com
2675 * @param {Node} form The form to serialize
2676 * @return {String} The serialized form data
2677 */
2678 function serialize(form) {
2679 var arr = [];
2680 Array.prototype.slice.call(form.elements).forEach(function(field) {
2681 if (
2682 !field.name ||
2683 field.disabled ||
2684 ['file', 'reset', 'submit', 'button'].indexOf(field.type) > -1
2685 )
2686 return;
2687 if (field.type === 'select-multiple') {
2688 Array.prototype.slice.call(field.options).forEach(function(option) {
2689 if (!option.selected) return;
2690 arr.push(
2691 encodeURIComponent(field.name) +
2692 '=' +
2693 encodeURIComponent(option.value)
2694 );
2695 });
2696 return;
2697 }
2698 if (['checkbox', 'radio'].indexOf(field.type) > -1 && !field.checked)
2699 return;
2700 arr.push(
2701 encodeURIComponent(field.name) + '=' + encodeURIComponent(field.value)
2702 );
2703 });
2704 return arr.join('&');
2705 }
2706 function cookiesEnabled() {
2707 var cookieEnabled = navigator.cookieEnabled;
2708
2709 if (!cookieEnabled) {
2710 document.cookie = 'testcookie';
2711 cookieEnabled = document.cookie.indexOf('testcookie') !== -1;
2712 }
2713
2714 return cookieEnabled;
2715 }
2716
2717 function promiseStylesheet(stylesheet) {
2718 var stylesheetUrl = stylesheet || theme.stylesheet;
2719
2720 if (typeof this.stylesheetPromise === 'undefined') {
2721 this.stylesheetPromise = new Promise(function(resolve) {
2722 var link = document.querySelector('link[href="' + stylesheetUrl + '"]');
2723
2724 if (link.loaded) resolve();
2725
2726 link.addEventListener('load', function() {
2727 setTimeout(resolve, 0);
2728 });
2729 });
2730 }
2731
2732 return this.stylesheetPromise;
2733 }
2734
2735 return {
2736 setTouch: setTouch,
2737 isTouch: isTouch,
2738 enableScrollLock: enableScrollLock,
2739 disableScrollLock: disableScrollLock,
2740 debounce: debounce,
2741 getScript: getScript,
2742 prepareTransition: prepareTransition,
2743 serialize: serialize,
2744 cookiesEnabled: cookiesEnabled,
2745 promiseStylesheet: promiseStylesheet
2746 };
2747})();
2748
2749theme.LibraryLoader = (function() {
2750 var types = {
2751 link: 'link',
2752 script: 'script'
2753 };
2754
2755 var status = {
2756 requested: 'requested',
2757 loaded: 'loaded'
2758 };
2759
2760 var cloudCdn = 'https://cdn.shopify.com/shopifycloud/';
2761
2762 var libraries = {
2763 plyrShopifyStyles: {
2764 tagId: 'plyr-shopify-styles',
2765 src: cloudCdn + 'plyr/v2.0/shopify-plyr.css',
2766 type: types.link
2767 },
2768 modelViewerUiStyles: {
2769 tagId: 'shopify-model-viewer-ui-styles',
2770 src: cloudCdn + 'model-viewer-ui/assets/v1.0/model-viewer-ui.css',
2771 type: types.link
2772 }
2773 };
2774
2775 function load(libraryName, callback) {
2776 var library = libraries[libraryName];
2777
2778 if (!library) return;
2779 if (library.status === status.requested) return;
2780
2781 callback = callback || function() {};
2782 if (library.status === status.loaded) {
2783 callback();
2784 return;
2785 }
2786
2787 library.status = status.requested;
2788
2789 var tag;
2790
2791 switch (library.type) {
2792 case types.script:
2793 tag = createScriptTag(library, callback);
2794 break;
2795 case types.link:
2796 tag = createLinkTag(library, callback);
2797 break;
2798 }
2799
2800 tag.id = library.tagId;
2801 library.element = tag;
2802
2803 var firstScriptTag = document.getElementsByTagName(library.type)[0];
2804 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
2805 }
2806
2807 function createScriptTag(library, callback) {
2808 var tag = document.createElement('script');
2809 tag.src = library.src;
2810 tag.addEventListener('load', function() {
2811 library.status = status.loaded;
2812 callback();
2813 });
2814 return tag;
2815 }
2816
2817 function createLinkTag(library, callback) {
2818 var tag = document.createElement('link');
2819 tag.href = library.src;
2820 tag.rel = 'stylesheet';
2821 tag.type = 'text/css';
2822 tag.addEventListener('load', function() {
2823 library.status = status.loaded;
2824 callback();
2825 });
2826 return tag;
2827 }
2828
2829 return {
2830 load: load
2831 };
2832})();
2833
2834
2835/* ================ MODULES ================ */
2836window.theme = window.theme || {};
2837
2838theme.Header = (function() {
2839 var selectors = {
2840 body: 'body',
2841 navigation: '#AccessibleNav',
2842 siteNavHasDropdown: '[data-has-dropdowns]',
2843 siteNavChildLinks: '.site-nav__child-link',
2844 siteNavActiveDropdown: '.site-nav--active-dropdown',
2845 siteNavHasCenteredDropdown: '.site-nav--has-centered-dropdown',
2846 siteNavCenteredDropdown: '.site-nav__dropdown--centered',
2847 siteNavLinkMain: '.site-nav__link--main',
2848 siteNavChildLink: '.site-nav__link--last',
2849 siteNavDropdown: '.site-nav__dropdown',
2850 siteHeader: '.site-header'
2851 };
2852
2853 var config = {
2854 activeClass: 'site-nav--active-dropdown',
2855 childLinkClass: 'site-nav__child-link',
2856 rightDropdownClass: 'site-nav__dropdown--right',
2857 leftDropdownClass: 'site-nav__dropdown--left'
2858 };
2859
2860 var cache = {};
2861
2862 function init() {
2863 cacheSelectors();
2864 styleDropdowns(document.querySelectorAll(selectors.siteNavHasDropdown));
2865 positionFullWidthDropdowns();
2866
2867 cache.parents.forEach(function(element) {
2868 element.addEventListener('click', submenuParentClickHandler);
2869 });
2870
2871 // check when we're leaving a dropdown and close the active dropdown
2872 cache.siteNavChildLink.forEach(function(element) {
2873 element.addEventListener('focusout', submenuFocusoutHandler);
2874 });
2875
2876 cache.topLevel.forEach(function(element) {
2877 element.addEventListener('focus', hideDropdown);
2878 });
2879
2880 cache.subMenuLinks.forEach(function(element) {
2881 element.addEventListener('click', stopImmediatePropagation);
2882 });
2883
2884 window.addEventListener('resize', resizeHandler);
2885 }
2886
2887 function stopImmediatePropagation(event) {
2888 event.stopImmediatePropagation();
2889 }
2890
2891 function cacheSelectors() {
2892 var navigation = document.querySelector(selectors.navigation);
2893
2894 cache = {
2895 nav: navigation,
2896 topLevel: document.querySelectorAll(selectors.siteNavLinkMain),
2897 parents: navigation.querySelectorAll(selectors.siteNavHasDropdown),
2898 subMenuLinks: document.querySelectorAll(selectors.siteNavChildLinks),
2899 activeDropdown: document.querySelector(selectors.siteNavActiveDropdown),
2900 siteHeader: document.querySelector(selectors.siteHeader),
2901 siteNavChildLink: document.querySelectorAll(selectors.siteNavChildLink)
2902 };
2903 }
2904
2905 function showDropdown(element) {
2906 element.classList.add(config.activeClass);
2907
2908 if (cache.activeDropdown) hideDropdown();
2909
2910 cache.activeDropdown = element;
2911
2912 element
2913 .querySelector(selectors.siteNavLinkMain)
2914 .setAttribute('aria-expanded', 'true');
2915
2916 setTimeout(function() {
2917 window.addEventListener('keyup', keyUpHandler);
2918 document.body.addEventListener('click', hideDropdown);
2919 }, 250);
2920 }
2921
2922 function hideDropdown() {
2923 if (!cache.activeDropdown) return;
2924
2925 cache.activeDropdown
2926 .querySelector(selectors.siteNavLinkMain)
2927 .setAttribute('aria-expanded', 'false');
2928 cache.activeDropdown.classList.remove(config.activeClass);
2929
2930 cache.activeDropdown = document.querySelector(
2931 selectors.siteNavActiveDropdown
2932 );
2933
2934 window.removeEventListener('keyup', keyUpHandler);
2935 document.body.removeEventListener('click', hideDropdown);
2936 }
2937
2938 function styleDropdowns(dropdownListItems) {
2939 dropdownListItems.forEach(function(item) {
2940 var dropdownLi = item.querySelector(selectors.siteNavDropdown);
2941
2942 if (!dropdownLi) return;
2943
2944 if (isRightOfLogo(item)) {
2945 dropdownLi.classList.remove(config.leftDropdownClass);
2946 dropdownLi.classList.add(config.rightDropdownClass);
2947 } else {
2948 dropdownLi.classList.remove(config.rightDropdownClass);
2949 dropdownLi.classList.add(config.leftDropdownClass);
2950 }
2951 });
2952 }
2953
2954 function isRightOfLogo(item) {
2955 var rect = item.getBoundingClientRect();
2956 var win = item.ownerDocument.defaultView;
2957 var leftOffset = rect.left + win.pageXOffset;
2958
2959 var headerWidth = Math.floor(cache.siteHeader.offsetWidth) / 2;
2960 return leftOffset > headerWidth;
2961 }
2962
2963 function positionFullWidthDropdowns() {
2964 document
2965 .querySelectorAll(selectors.siteNavHasCenteredDropdown)
2966 .forEach(function(el) {
2967 var fullWidthDropdown = el.querySelector(
2968 selectors.siteNavCenteredDropdown
2969 );
2970
2971 var fullWidthDropdownOffset = el.offsetTop + 41;
2972 fullWidthDropdown.style.top = fullWidthDropdownOffset + 'px';
2973 });
2974 }
2975
2976 function keyUpHandler(event) {
2977 if (event.keyCode === 27) hideDropdown();
2978 }
2979
2980 function resizeHandler() {
2981 adjustStyleAndPosition();
2982 }
2983
2984 function submenuParentClickHandler(event) {
2985 var element = event.currentTarget;
2986
2987 element.classList.contains(config.activeClass)
2988 ? hideDropdown()
2989 : showDropdown(element);
2990 }
2991
2992 function submenuFocusoutHandler() {
2993 setTimeout(function() {
2994 if (
2995 document.activeElement.classList.contains(config.childLinkClass) ||
2996 !cache.activeDropdown
2997 ) {
2998 return;
2999 }
3000
3001 hideDropdown();
3002 });
3003 }
3004
3005 var adjustStyleAndPosition = theme.Helpers.debounce(function() {
3006 styleDropdowns(document.querySelectorAll(selectors.siteNavHasDropdown));
3007 positionFullWidthDropdowns();
3008 }, 50);
3009
3010 function unload() {
3011 cache.topLevel.forEach(function(element) {
3012 element.removeEventListener('focus', hideDropdown);
3013 });
3014
3015 cache.subMenuLinks.forEach(function(element) {
3016 element.removeEventListener('click', stopImmediatePropagation);
3017 });
3018
3019 cache.parents.forEach(function(element) {
3020 element.removeEventListener('click', submenuParentClickHandler);
3021 });
3022
3023 cache.siteNavChildLink.forEach(function(element) {
3024 element.removeEventListener('focusout', submenuFocusoutHandler);
3025 });
3026
3027 window.removeEventListener('resize', resizeHandler);
3028 window.removeEventListener('keyup', keyUpHandler);
3029 document.body.removeEventListener('click', hideDropdown);
3030 }
3031
3032 return {
3033 init: init,
3034 unload: unload
3035 };
3036})();
3037
3038window.theme = window.theme || {};
3039
3040theme.MobileNav = (function() {
3041 var classes = {
3042 mobileNavOpenIcon: 'mobile-nav--open',
3043 mobileNavCloseIcon: 'mobile-nav--close',
3044 navLinkWrapper: 'mobile-nav__item',
3045 navLink: 'mobile-nav__link',
3046 subNavLink: 'mobile-nav__sublist-link',
3047 return: 'mobile-nav__return-btn',
3048 subNavActive: 'is-active',
3049 subNavClosing: 'is-closing',
3050 navOpen: 'js-menu--is-open',
3051 subNavShowing: 'sub-nav--is-open',
3052 thirdNavShowing: 'third-nav--is-open',
3053 subNavToggleBtn: 'js-toggle-submenu'
3054 };
3055
3056 var cache = {};
3057 var isTransitioning;
3058 var activeSubNav;
3059 var activeTrigger;
3060 var menuLevel = 1;
3061 var mediumUpQuery = '(min-width: ' + theme.breakpoints.medium + 'px)';
3062 var mql = window.matchMedia(mediumUpQuery);
3063
3064 function init() {
3065 cacheSelectors();
3066
3067 if (cache.mobileNavToggle) {
3068 cache.mobileNavToggle.addEventListener('click', toggleMobileNav);
3069 }
3070
3071 cache.subNavToggleBtns.forEach(function(element) {
3072 element.addEventListener('click', toggleSubNav);
3073 });
3074
3075 mql.addListener(initBreakpoints);
3076 }
3077
3078 function initBreakpoints() {
3079 if (
3080 mql.matches &&
3081 cache.mobileNavContainer.classList.contains(classes.navOpen)
3082 ) {
3083 closeMobileNav();
3084 }
3085 }
3086
3087 function toggleMobileNav() {
3088 var mobileNavIsOpen = cache.mobileNavToggle.classList.contains(
3089 classes.mobileNavCloseIcon
3090 );
3091
3092 if (mobileNavIsOpen) {
3093 closeMobileNav();
3094 } else {
3095 openMobileNav();
3096 }
3097 }
3098
3099 function cacheSelectors() {
3100 cache = {
3101 pageContainer: document.querySelector('#PageContainer'),
3102 siteHeader: document.querySelector('.site-header'),
3103 mobileNavToggle: document.querySelector('.js-mobile-nav-toggle'),
3104 mobileNavContainer: document.querySelector('.mobile-nav-wrapper'),
3105 mobileNav: document.querySelector('#MobileNav'),
3106 sectionHeader: document.querySelector('#shopify-section-header'),
3107 subNavToggleBtns: document.querySelectorAll('.' + classes.subNavToggleBtn)
3108 };
3109 }
3110
3111 function openMobileNav() {
3112 var translateHeaderHeight = cache.siteHeader.offsetHeight;
3113
3114 theme.Helpers.prepareTransition(cache.mobileNavContainer);
3115 cache.mobileNavContainer.classList.add(classes.navOpen);
3116
3117 cache.mobileNavContainer.style.transform =
3118 'translateY(' + translateHeaderHeight + 'px)';
3119
3120 cache.pageContainer.style.transform =
3121 'translate3d(0, ' + cache.mobileNavContainer.scrollHeight + 'px, 0)';
3122
3123 slate.a11y.trapFocus({
3124 container: cache.sectionHeader,
3125 elementToFocus: cache.mobileNavToggle
3126 });
3127
3128 cache.mobileNavToggle.classList.add(classes.mobileNavCloseIcon);
3129 cache.mobileNavToggle.classList.remove(classes.mobileNavOpenIcon);
3130 cache.mobileNavToggle.setAttribute('aria-expanded', true);
3131
3132 window.addEventListener('keyup', keyUpHandler);
3133 }
3134
3135 function keyUpHandler(event) {
3136 if (event.which === 27) {
3137 closeMobileNav();
3138 }
3139 }
3140
3141 function closeMobileNav() {
3142 theme.Helpers.prepareTransition(cache.mobileNavContainer);
3143 cache.mobileNavContainer.classList.remove(classes.navOpen);
3144 cache.mobileNavContainer.style.transform = 'translateY(-100%)';
3145 cache.pageContainer.setAttribute('style', '');
3146
3147 slate.a11y.trapFocus({
3148 container: document.querySelector('html'),
3149 elementToFocus: document.body
3150 });
3151
3152 cache.mobileNavContainer.addEventListener(
3153 'transitionend',
3154 mobileNavRemoveTrapFocus,
3155 { once: true }
3156 );
3157
3158 cache.mobileNavToggle.classList.add(classes.mobileNavOpenIcon);
3159 cache.mobileNavToggle.classList.remove(classes.mobileNavCloseIcon);
3160 cache.mobileNavToggle.setAttribute('aria-expanded', false);
3161 cache.mobileNavToggle.focus();
3162
3163 window.removeEventListener('keyup', keyUpHandler);
3164 window.scrollTo(0, 0);
3165 }
3166
3167 function mobileNavRemoveTrapFocus() {
3168 slate.a11y.removeTrapFocus({
3169 container: cache.mobileNav
3170 });
3171 }
3172
3173 function toggleSubNav(event) {
3174 if (isTransitioning) return;
3175
3176 var toggleBtn = event.currentTarget;
3177 var isReturn = toggleBtn.classList.contains(classes.return);
3178
3179 isTransitioning = true;
3180
3181 if (isReturn) {
3182 var subNavToggleBtn = document.querySelectorAll(
3183 '.' + classes.subNavToggleBtn + "[data-level='" + (menuLevel - 1) + "']"
3184 );
3185
3186 subNavToggleBtn.forEach(function(element) {
3187 element.classList.remove(classes.subNavActive);
3188 });
3189
3190 if (activeTrigger) {
3191 activeTrigger.classList.remove(classes.subNavActive);
3192 }
3193 } else {
3194 toggleBtn.classList.add(classes.subNavActive);
3195 }
3196
3197 activeTrigger = toggleBtn;
3198
3199 goToSubnav(toggleBtn.getAttribute('data-target'));
3200 }
3201
3202 function goToSubnav(target) {
3203 var targetMenu = target
3204 ? document.querySelector(
3205 '.mobile-nav__dropdown[data-parent="' + target + '"]'
3206 )
3207 : cache.mobileNav;
3208
3209 menuLevel = targetMenu.dataset.level ? Number(targetMenu.dataset.level) : 1;
3210
3211 if (activeSubNav) {
3212 theme.Helpers.prepareTransition(activeSubNav);
3213 activeSubNav.classList.add(classes.subNavClosing);
3214 }
3215
3216 activeSubNav = targetMenu;
3217
3218 var translateMenuHeight = targetMenu.offsetHeight;
3219
3220 var openNavClass =
3221 menuLevel > 2 ? classes.thirdNavShowing : classes.subNavShowing;
3222
3223 cache.mobileNavContainer.style.height = translateMenuHeight + 'px';
3224 cache.mobileNavContainer.classList.remove(classes.thirdNavShowing);
3225 cache.mobileNavContainer.classList.add(openNavClass);
3226
3227 if (!target) {
3228 cache.mobileNavContainer.classList.remove(
3229 classes.thirdNavShowing,
3230 classes.subNavShowing
3231 );
3232 }
3233
3234 /* if going back to first subnav, focus is on whole header */
3235 var container = menuLevel === 1 ? cache.sectionHeader : targetMenu;
3236
3237 cache.mobileNavContainer.addEventListener(
3238 'transitionend',
3239 trapMobileNavFocus,
3240 { once: true }
3241 );
3242
3243 function trapMobileNavFocus() {
3244 slate.a11y.trapFocus({
3245 container: container
3246 });
3247
3248 cache.mobileNavContainer.removeEventListener(
3249 'transitionend',
3250 trapMobileNavFocus
3251 );
3252
3253 isTransitioning = false;
3254 }
3255
3256 // Match height of subnav
3257 cache.pageContainer.style.transform =
3258 'translateY(' + translateMenuHeight + 'px)';
3259
3260 activeSubNav.classList.remove(classes.subNavClosing);
3261 }
3262
3263 function unload() {
3264 mql.removeListener(initBreakpoints);
3265 }
3266
3267 return {
3268 init: init,
3269 unload: unload,
3270 closeMobileNav: closeMobileNav
3271 };
3272})();
3273
3274window.Modals = (function() {
3275 function Modal(id, name, options) {
3276 var defaults = {
3277 close: '.js-modal-close',
3278 open: '.js-modal-open-' + name,
3279 openClass: 'modal--is-active',
3280 closeModalOnClick: false
3281 };
3282
3283 this.modal = document.getElementById(id);
3284
3285 if (!this.modal) return false;
3286
3287 this.nodes = {
3288 parents: [document.querySelector('html'), document.body]
3289 };
3290
3291 this.config = Object.assign(defaults, options);
3292
3293 this.modalIsOpen = false;
3294
3295 this.focusOnOpen = this.config.focusOnOpen
3296 ? document.getElementById(this.config.focusOnOpen)
3297 : this.modal;
3298
3299 this.openElement = document.querySelector(this.config.open);
3300 this.init();
3301 }
3302
3303 Modal.prototype.init = function() {
3304 this.openElement.addEventListener('click', this.open.bind(this));
3305
3306 this.modal
3307 .querySelector(this.config.close)
3308 .addEventListener('click', this.closeModal.bind(this));
3309 };
3310
3311 Modal.prototype.open = function(evt) {
3312 var self = this;
3313 // Keep track if modal was opened from a click, or called by another function
3314 var externalCall = false;
3315
3316 if (this.modalIsOpen) return;
3317
3318 // Prevent following href if link is clicked
3319 if (evt) {
3320 evt.preventDefault();
3321 } else {
3322 externalCall = true;
3323 }
3324
3325 // Without this, the modal opens, the click event bubbles up
3326 // which closes the modal.
3327 if (evt && evt.stopPropagation) {
3328 evt.stopPropagation();
3329 }
3330
3331 if (this.modalIsOpen && !externalCall) {
3332 this.closeModal();
3333 }
3334
3335 this.modal.classList.add(this.config.openClass);
3336
3337 this.nodes.parents.forEach(function(node) {
3338 node.classList.add(self.config.openClass);
3339 });
3340
3341 this.modalIsOpen = true;
3342
3343 slate.a11y.trapFocus({
3344 container: this.modal,
3345 elementToFocus: this.focusOnOpen
3346 });
3347
3348 this.bindEvents();
3349 };
3350
3351 Modal.prototype.closeModal = function() {
3352 if (!this.modalIsOpen) return;
3353
3354 document.activeElement.blur();
3355
3356 this.modal.classList.remove(this.config.openClass);
3357
3358 var self = this;
3359
3360 this.nodes.parents.forEach(function(node) {
3361 node.classList.remove(self.config.openClass);
3362 });
3363
3364 this.modalIsOpen = false;
3365
3366 slate.a11y.removeTrapFocus({
3367 container: this.modal
3368 });
3369
3370 this.openElement.focus();
3371
3372 this.unbindEvents();
3373 };
3374
3375 Modal.prototype.bindEvents = function() {
3376 this.keyupHandler = this.keyupHandler.bind(this);
3377 this.clickHandler = this.clickHandler.bind(this);
3378 document.body.addEventListener('keyup', this.keyupHandler);
3379 document.body.addEventListener('click', this.clickHandler);
3380 };
3381
3382 Modal.prototype.unbindEvents = function() {
3383 document.body.removeEventListener('keyup', this.keyupHandler);
3384 document.body.removeEventListener('click', this.clickHandler);
3385 };
3386
3387 Modal.prototype.keyupHandler = function(event) {
3388 if (event.keyCode === 27) {
3389 this.closeModal();
3390 }
3391 };
3392
3393 Modal.prototype.clickHandler = function(event) {
3394 if (this.config.closeModalOnClick && !this.modal.contains(event.target)) {
3395 this.closeModal();
3396 }
3397 };
3398
3399 return Modal;
3400})();
3401
3402(function() {
3403 var selectors = {
3404 backButton: '.return-link'
3405 };
3406
3407 var backButton = document.querySelector(selectors.backButton);
3408
3409 if (!document.referrer || !backButton || !window.history.length) {
3410 return;
3411 }
3412
3413 backButton.addEventListener(
3414 'click',
3415 function(evt) {
3416 evt.preventDefault();
3417
3418 var referrerDomain = urlDomain(document.referrer);
3419 var shopDomain = urlDomain(window.location.href);
3420
3421 if (shopDomain === referrerDomain) {
3422 history.back();
3423 }
3424
3425 return false;
3426 },
3427 { once: true }
3428 );
3429
3430 function urlDomain(url) {
3431 var anchor = document.createElement('a');
3432 anchor.ref = url;
3433
3434 return anchor.hostname;
3435 }
3436})();
3437
3438theme.Slideshow = (function() {
3439 var selectors = {
3440 button: '[data-slider-button]',
3441 indicator: '[data-slider-indicator]',
3442 indicators: '[data-slider-indicators]',
3443 pause: '[data-slider-pause]',
3444 slider: '[data-slider]',
3445 sliderItem: '[data-slider-item]',
3446 sliderItemLink: '[data-slider-item-link]',
3447 sliderTrack: '[data-slider-track]',
3448 sliderContainer: '[data-slider-container]'
3449 };
3450
3451 var classes = {
3452 isPaused: 'slideshow__pause--is-paused',
3453 indicator: 'slider-indicators__item',
3454 indicatorActive: 'slick-active',
3455 sliderInitialized: 'slick-initialized',
3456 slideActive: 'slideshow__slide--active',
3457 slideClone: 'slick-cloned'
3458 };
3459
3460 var attributes = {
3461 buttonNext: 'data-slider-button-next'
3462 };
3463
3464 function Slideshow(container, options) {
3465 this.container = container;
3466 this.slider = this.container.querySelector(selectors.slider);
3467
3468 if (!this.slider) return;
3469
3470 this.eventHandlers = {};
3471 this.lastSlide = 0;
3472 this.slideIndex = 0;
3473 this.sliderContainer = null;
3474 this.slides = [];
3475 this.options = Object.assign(
3476 {},
3477 {
3478 autoplay: false,
3479 canUseKeyboardArrows: true,
3480 canUseTouchEvents: false,
3481 slideActiveClass: classes.slideActive,
3482 slideInterval: 0,
3483 slidesToShow: 0,
3484 slidesToScroll: 1,
3485 type: 'fade'
3486 },
3487 options
3488 );
3489
3490 this.sliderContainer = this.slider.querySelector(selectors.sliderContainer);
3491 this.adaptHeight =
3492 this.sliderContainer.getAttribute('data-adapt-height') === 'true';
3493 this.slides = Array.from(
3494 this.sliderContainer.querySelectorAll(selectors.sliderItem)
3495 );
3496 // adding -1 to accomodate Array order
3497 this.lastSlide = this.slides.length - 1;
3498 this.buttons = this.container.querySelectorAll(selectors.button);
3499 this.pause = this.container.querySelector(selectors.pause);
3500 this.indicators = this.container.querySelectorAll(selectors.indicators);
3501
3502 if (this.slides.length <= 1) return;
3503
3504 this.timeout = 250;
3505
3506 if (this.options.autoplay) {
3507 this.startAutoplay();
3508 }
3509
3510 if (this.adaptHeight) {
3511 this.setSlideshowHeight();
3512 }
3513
3514 if (this.options.type === 'slide') {
3515 this.isFirstSlide = false;
3516 this.isLastSlide = false;
3517 this.sliderItemWidthTotal = 0;
3518 this.sliderTrack = this.slider.querySelector(selectors.sliderTrack);
3519 // added setTimeout due to matchMedia calling too early
3520 // which result wrong value when getting dimension from an element
3521 this.sliderItemWidthTotal = 0;
3522 theme.Helpers.promiseStylesheet().then(
3523 function() {
3524 this._setupSlideType();
3525 }.bind(this)
3526 );
3527 } else {
3528 this.setupSlider(0);
3529 }
3530
3531 this._setupEventHandlers();
3532 }
3533
3534 Slideshow.prototype = Object.assign({}, Slideshow.prototype, {
3535 /**
3536 * Moves to the previous slide
3537 */
3538 previousSlide: function() {
3539 this._move();
3540 },
3541
3542 /**
3543 * Moves to the next slide
3544 */
3545 nextSlide: function() {
3546 this._move('next');
3547 },
3548
3549 /**
3550 * Moves to the specified slide
3551 * @param {Number} index - The index of the slide to move to
3552 */
3553 setSlide: function(index) {
3554 this._setPosition(Number(index));
3555 },
3556
3557 /**
3558 * Starts autoplaying the slider if autoplay is enabled
3559 */
3560 startAutoplay: function() {
3561 this.isAutoPlaying = true;
3562
3563 window.clearTimeout(this.autoTimeOut);
3564
3565 this.autoTimeOut = window.setTimeout(
3566 function() {
3567 var nextSlideIndex = this._getNextSlideIndex('next');
3568 this._setPosition(nextSlideIndex);
3569 }.bind(this),
3570 this.options.slideInterval
3571 );
3572 },
3573
3574 /**
3575 * Stops autoplaying the slider if autoplay is enabled
3576 */
3577 stopAutoplay: function() {
3578 this.isAutoPlaying = false;
3579
3580 window.clearTimeout(this.autoTimeOut);
3581 },
3582
3583 /**
3584 * Set active states for sliders and indicators
3585 * @param {index} integer - Slide index to set up slider from
3586 */
3587 setupSlider: function(index) {
3588 this.slideIndex = index;
3589
3590 if (this.indicators.length) {
3591 this._setActiveIndicator(index);
3592 }
3593
3594 this._setupActiveSlide(index);
3595 },
3596
3597 /**
3598 * Removes event listeners, among other things when wanting to destroy the
3599 * slider instance. This method needs to be called manually and will most
3600 * likely be included in a section's onUnload() method.
3601 */
3602 destroy: function() {
3603 if (this.adaptHeight) {
3604 window.removeEventListener('resize', this.eventHandlers.debounceResize);
3605 }
3606
3607 this.container.removeEventListener(
3608 'focus',
3609 this.eventHandlers.focus,
3610 true
3611 );
3612 this.slider.removeEventListener(
3613 'focusin',
3614 this.eventHandlers.focusIn,
3615 true
3616 );
3617 this.slider.removeEventListener(
3618 'focusout',
3619 this.eventHandlers.focusOut,
3620 true
3621 );
3622 this.container.removeEventListener('blur', this.eventHandlers.blur, true);
3623
3624 if (this.buttons) {
3625 this.buttons.forEach(
3626 function(button) {
3627 button.removeEventListener('click', this.eventHandlers.clickButton);
3628 }.bind(this)
3629 );
3630 }
3631
3632 this.indicators.forEach(function(indicatorWrapper) {
3633 indicatorWrapper.childNodes.forEach(function(indicator) {
3634 indicator.firstElementChild.removeEventListener(
3635 'click',
3636 this.eventHandlers.onClickIndicator
3637 );
3638
3639 indicator.firstElementChild.removeEventListener(
3640 'keydown',
3641 this.eventHandlers.onKeydownIndicator
3642 );
3643 }, this);
3644 }, this);
3645
3646 if (this.options.type === 'slide') {
3647 window.removeEventListener(
3648 'resize',
3649 this.eventHandlers.debounceResizeSlideIn
3650 );
3651
3652 if (this.touchEvents && this.options.canUseTouchEvents) {
3653 this.touchEvents.destroy();
3654 this.touchEvents = null;
3655 }
3656 }
3657 },
3658
3659 _setupEventHandlers: function() {
3660 this.eventHandlers.focus = this._onFocus.bind(this);
3661 this.eventHandlers.focusIn = this._onFocusIn.bind(this);
3662 this.eventHandlers.focusOut = this._onFocusOut.bind(this);
3663 this.eventHandlers.blur = this._onBlur.bind(this);
3664 this.eventHandlers.keyUp = this._onKeyUp.bind(this);
3665 this.eventHandlers.clickButton = this._onClickButton.bind(this);
3666 this.eventHandlers.onClickIndicator = this._onClickIndicator.bind(this);
3667 this.eventHandlers.onKeydownIndicator = this._onKeydownIndicator.bind(
3668 this
3669 );
3670 this.eventHandlers.onClickPause = this._onClickPause.bind(this);
3671
3672 if (this.adaptHeight) {
3673 this.eventHandlers.debounceResize = theme.Helpers.debounce(
3674 function() {
3675 this.setSlideshowHeight();
3676 }.bind(this),
3677 50
3678 );
3679
3680 window.addEventListener('resize', this.eventHandlers.debounceResize);
3681 }
3682
3683 this.container.addEventListener('focus', this.eventHandlers.focus, true);
3684 this.slider.addEventListener('focusin', this.eventHandlers.focusIn, true);
3685 this.slider.addEventListener(
3686 'focusout',
3687 this.eventHandlers.focusOut,
3688 true
3689 );
3690 this.container.addEventListener('blur', this.eventHandlers.blur, true);
3691
3692 if (this.buttons) {
3693 this.buttons.forEach(
3694 function(button) {
3695 button.addEventListener('click', this.eventHandlers.clickButton);
3696 }.bind(this)
3697 );
3698 }
3699
3700 if (this.pause) {
3701 this.pause.addEventListener('click', this.eventHandlers.onClickPause);
3702 }
3703
3704 this.indicators.forEach(function(indicatorWrapper) {
3705 indicatorWrapper.childNodes.forEach(function(indicator) {
3706 indicator.firstElementChild.addEventListener(
3707 'click',
3708 this.eventHandlers.onClickIndicator
3709 );
3710
3711 indicator.firstElementChild.addEventListener(
3712 'keydown',
3713 this.eventHandlers.onKeydownIndicator
3714 );
3715 }, this);
3716 }, this);
3717
3718 if (this.options.type === 'slide') {
3719 this.eventHandlers.debounceResizeSlideIn = theme.Helpers.debounce(
3720 function() {
3721 this.sliderItemWidthTotal = 0;
3722 this._setupSlideType(true);
3723 }.bind(this),
3724 50
3725 );
3726
3727 window.addEventListener(
3728 'resize',
3729 this.eventHandlers.debounceResizeSlideIn
3730 );
3731
3732 if (
3733 this.options.canUseTouchEvents &&
3734 this.options.slidesToScroll < this.slides.length
3735 ) {
3736 this._setupTouchEvents();
3737 }
3738 }
3739 },
3740
3741 _setupTouchEvents: function() {
3742 this.touchEvents = new theme.TouchEvents(this.sliderTrack, {
3743 start: function() {
3744 this._onTouchStart();
3745 }.bind(this),
3746 move: function(event, direction, difference) {
3747 this._onTouchMove(event, direction, difference);
3748 }.bind(this),
3749 end: function(event, direction, difference) {
3750 this._onTouchEnd(event, direction, difference);
3751 }.bind(this)
3752 });
3753 },
3754
3755 /**
3756 * Set slideshop for "slide-in" effect
3757 * @param {Boolean} onResize if function call came from resize event
3758 */
3759 _setupSlideType: function(onResize) {
3760 this.sliderItemWidth = Math.floor(
3761 this.sliderContainer.offsetWidth / this.options.slidesToShow
3762 );
3763 this.sliderTranslateXMove =
3764 this.sliderItemWidth * this.options.slidesToScroll;
3765
3766 if (!onResize) {
3767 this.sliderContainer.classList.add(classes.sliderInitialized);
3768 }
3769
3770 // Loop through all slider items
3771 // Set width according to the number of items to show in 1 slide
3772 // Set container width to accomodate all items
3773 this.slides.forEach(function(sliderItem, index) {
3774 var sliderItemLink = sliderItem.querySelector(selectors.sliderItemLink);
3775 sliderItem.style.width = this.sliderItemWidth + 'px';
3776 sliderItem.setAttribute('aria-hidden', true);
3777 sliderItem.setAttribute('tabindex', -1);
3778 this.sliderItemWidthTotal =
3779 this.sliderItemWidthTotal + sliderItem.offsetWidth;
3780
3781 if (sliderItemLink) {
3782 sliderItemLink.setAttribute('tabindex', -1);
3783 }
3784
3785 if (index < this.options.slidesToShow) {
3786 sliderItem.setAttribute('aria-hidden', false);
3787 sliderItem.classList.add(this.options.slideActiveClass);
3788
3789 if (sliderItemLink) {
3790 sliderItemLink.setAttribute('tabindex', 0);
3791 }
3792 }
3793 }, this);
3794
3795 this.sliderTrack.style.width =
3796 Math.floor(this.sliderItemWidthTotal) + 'px';
3797 this.sliderTrack.style.transform = 'translateX(-0px)';
3798
3799 // set disabled attribute on Previous button
3800 if (this.buttons.length) {
3801 this.buttons[0].setAttribute('aria-disabled', true);
3802 this.buttons[1].removeAttribute('aria-disabled');
3803 }
3804
3805 if (this.indicators.length) {
3806 this._setActiveIndicator(0);
3807 }
3808 },
3809
3810 _onTouchStart: function() {
3811 this.touchStartPosition = this._getTranslateXPosition();
3812 },
3813
3814 _onTouchMove: function(event, direction, difference) {
3815 // Fix touch events cause unexpected behaviour
3816 // when the dragging motion goes beyond the theme editor preview.
3817 var threshold = 80;
3818 if (
3819 Shopify.designMode &&
3820 (event.clientX <= threshold ||
3821 event.clientX >= window.innerWidth - threshold)
3822 ) {
3823 event.target.dispatchEvent(
3824 new MouseEvent('mouseup', {
3825 bubbles: true,
3826 cancelable: true
3827 })
3828 );
3829 return;
3830 }
3831
3832 if (direction !== 'left' && direction !== 'right') return;
3833
3834 this.touchMovePosition = this.touchStartPosition + difference.xPosition;
3835
3836 this.sliderTrack.style.transform =
3837 'translateX(' + this.touchMovePosition + 'px';
3838 },
3839
3840 _onTouchEnd: function(event, direction, difference) {
3841 var nextTranslateXPosition = 0;
3842
3843 if (Object.keys(difference).length === 0) return;
3844
3845 var slideDirection = direction === 'left' ? 'next' : '';
3846
3847 if (direction === 'left') {
3848 if (this._isNextTranslateXLast(this.touchStartPosition)) {
3849 nextTranslateXPosition = this.touchStartPosition;
3850 } else {
3851 nextTranslateXPosition =
3852 this.touchStartPosition - this.sliderTranslateXMove;
3853 }
3854 } else {
3855 nextTranslateXPosition =
3856 this.touchStartPosition + this.sliderTranslateXMove;
3857 if (this._isNextTranslateXFirst(this.touchStartPosition)) {
3858 nextTranslateXPosition = 0;
3859 }
3860 }
3861
3862 this.slideIndex = this._getNextSlideIndex(slideDirection);
3863
3864 this.sliderTrack.style.transition = 'transform 500ms ease 0s';
3865 this.sliderTrack.style.transform =
3866 'translateX(' + nextTranslateXPosition + 'px';
3867
3868 window.setTimeout(
3869 function() {
3870 this.sliderTrack.style.transition = '';
3871 }.bind(this),
3872 500
3873 );
3874
3875 this._verifyFirstLastSlideTranslateX(nextTranslateXPosition);
3876
3877 this._postTransitionEnd();
3878 },
3879
3880 /**
3881 * Events handlers for next and previous button
3882 * @param {Object} event event handler
3883 */
3884 _onClickButton: function(event) {
3885 // prevent multiple clicks
3886 if (event.detail > 1) return;
3887
3888 var button = event.currentTarget;
3889 var nextButton = button.hasAttribute(attributes.buttonNext);
3890
3891 if (
3892 this.options.type === 'slide' &&
3893 button.getAttribute('aria-disabled') === 'true'
3894 ) {
3895 return;
3896 }
3897
3898 if (this.options.autoplay && this.isAutoPlaying) {
3899 this.stopAutoplay();
3900 }
3901
3902 if (nextButton) {
3903 this.nextSlide();
3904 } else {
3905 this.previousSlide();
3906 }
3907 },
3908
3909 _onClickIndicator: function(event) {
3910 event.preventDefault();
3911
3912 if (event.target.classList.contains(classes.indicatorActive)) return;
3913
3914 if (this.options.autoplay && this.isAutoPlaying) {
3915 this.stopAutoplay();
3916 }
3917
3918 this.slideIndex = Number(event.target.dataset.slideNumber);
3919 this.goToSlideByIndex(this.slideIndex);
3920 },
3921
3922 goToSlideByIndex: function(index) {
3923 this._setPosition(index);
3924
3925 if (this.options.type === 'slide' && this.sliderTrack) {
3926 this.sliderTrack.style.transition = 'transform 500ms ease 0s';
3927 var newPosition = index * this.slides[0].offsetWidth;
3928
3929 this.sliderTrack.style.transform = 'translateX(-' + newPosition + 'px)';
3930
3931 if (this.options.slidesToShow > 1) {
3932 this._verifyFirstLastSlideTranslateX(newPosition);
3933
3934 if (this.buttons.length) {
3935 this._disableArrows();
3936 }
3937
3938 this._setupMultipleActiveSlide(
3939 index,
3940 index + (this.options.slidesToShow - 1)
3941 );
3942 }
3943 }
3944 },
3945
3946 _onKeydownIndicator: function(event) {
3947 if (event.keyCode !== slate.utils.keyboardKeys.ENTER) return;
3948
3949 this._onClickIndicator(event);
3950
3951 this.slider.focus();
3952 },
3953
3954 _onClickPause: function(event) {
3955 if (!event.currentTarget.classList.contains(classes.isPaused)) {
3956 event.currentTarget.classList.add(classes.isPaused);
3957 this.stopAutoplay();
3958 } else {
3959 event.currentTarget.classList.remove(classes.isPaused);
3960 this.startAutoplay();
3961 }
3962 },
3963
3964 _onFocus: function() {
3965 this.container.addEventListener('keyup', this.eventHandlers.keyUp);
3966 },
3967
3968 _onFocusIn: function() {
3969 if (this.slider.hasAttribute('aria-live')) return;
3970
3971 if (this.options.autoplay && this.isAutoPlaying) {
3972 this.stopAutoplay();
3973 }
3974
3975 this.slider.setAttribute('aria-live', 'polite');
3976 },
3977
3978 _onBlur: function() {
3979 this.container.removeEventListener('keyup', this.eventHandlers.keyUp);
3980 },
3981
3982 _onFocusOut: function() {
3983 this.slider.removeAttribute('aria-live');
3984
3985 // Adding a setTimeout because everytime we focus out
3986 // It automatically goes to <body>
3987 // We want to resume autoplay when focus is outside of the slideshow container
3988 setTimeout(
3989 function() {
3990 if (
3991 !document.activeElement.closest(
3992 '#' + this.slider.getAttribute('id')
3993 )
3994 ) {
3995 if (
3996 this.options.autoplay &&
3997 !this.isAutoPlaying &&
3998 !this.pause.classList.contains(classes.isPaused)
3999 ) {
4000 this.startAutoplay();
4001 }
4002 }
4003 }.bind(this),
4004 this.timeout
4005 );
4006 },
4007
4008 _onKeyUp: function(event) {
4009 switch (event.keyCode) {
4010 case slate.utils.keyboardKeys.LEFTARROW:
4011 if (!this.options.canUseKeyboardArrows) return;
4012
4013 if (this.options.type === 'slide' && this.isFirstSlide) {
4014 return;
4015 }
4016
4017 this.previousSlide();
4018
4019 break;
4020 case slate.utils.keyboardKeys.RIGHTARROW:
4021 if (!this.options.canUseKeyboardArrows) return;
4022
4023 if (this.options.type === 'slide' && this.isLastSlide) {
4024 return;
4025 }
4026
4027 this.nextSlide();
4028
4029 break;
4030 case slate.utils.keyboardKeys.ESCAPE:
4031 this.slider.blur();
4032 break;
4033 }
4034 },
4035
4036 _move: function(direction) {
4037 if (this.options.type === 'slide') {
4038 this.slideIndex = this._getNextSlideIndex(direction);
4039 this._moveSlideshow(direction);
4040 } else {
4041 var nextSlideIndex = this._getNextSlideIndex(direction);
4042 this._setPosition(nextSlideIndex);
4043 }
4044 },
4045
4046 _moveSlideshow: function(direction) {
4047 this.direction = direction;
4048 var valueXToMove = 0;
4049
4050 // Get current position of translateX
4051 var currentTranslateXPosition = this._getTranslateXPosition();
4052 var currentActiveSlidesIndex = this._getActiveSlidesIndex();
4053
4054 // In the future, we'll use ES6 deconstructure
4055 // Math.min(...currentActiveSlidesIndex);
4056 var currentActiveSlidesMinIndex = Math.min.apply(
4057 Math,
4058 currentActiveSlidesIndex
4059 );
4060 var currentActiveSlidesMaxIndex = Math.max.apply(
4061 Math,
4062 currentActiveSlidesIndex
4063 );
4064
4065 // Set the next active state depending on the direction
4066 // We bump up the index depending on the "slidesToShow" option
4067 this.nextMinIndex =
4068 direction === 'next'
4069 ? currentActiveSlidesMinIndex + this.options.slidesToShow
4070 : currentActiveSlidesMinIndex - this.options.slidesToShow;
4071 this.nextMaxIndex =
4072 direction === 'next'
4073 ? currentActiveSlidesMaxIndex + this.options.slidesToShow
4074 : currentActiveSlidesMinIndex - 1;
4075
4076 this.sliderTrack.style.transition = 'transform 500ms ease 0s';
4077
4078 if (direction === 'next') {
4079 valueXToMove = currentTranslateXPosition - this.sliderTranslateXMove;
4080 this.sliderTrack.style.transform = 'translateX(' + valueXToMove + 'px)';
4081 } else {
4082 valueXToMove = currentTranslateXPosition + this.sliderTranslateXMove;
4083 this.sliderTrack.style.transform = 'translateX(' + valueXToMove + 'px)';
4084 }
4085
4086 this._verifyFirstLastSlideTranslateX(valueXToMove);
4087
4088 this._postTransitionEnd();
4089
4090 this._setupMultipleActiveSlide(this.nextMinIndex, this.nextMaxIndex);
4091 },
4092
4093 _setPosition: function(nextSlideIndex) {
4094 this.slideIndex = nextSlideIndex;
4095
4096 if (this.indicators.length) {
4097 this._setActiveIndicator(nextSlideIndex);
4098 }
4099
4100 this._setupActiveSlide(nextSlideIndex);
4101
4102 if (this.options.autoplay && this.isAutoPlaying) {
4103 this.startAutoplay();
4104 }
4105
4106 this.container.dispatchEvent(
4107 new CustomEvent('slider_slide_changed', {
4108 detail: nextSlideIndex
4109 })
4110 );
4111 },
4112
4113 _setupActiveSlide: function(index) {
4114 this.slides.forEach(function(slide) {
4115 slide.setAttribute('aria-hidden', true);
4116 slide.classList.remove(this.options.slideActiveClass);
4117 }, this);
4118
4119 this.slides[index].setAttribute('aria-hidden', false);
4120 this.slides[index].classList.add(this.options.slideActiveClass);
4121 },
4122
4123 /**
4124 * Loops through all slide items
4125 * Set the active state depending the direction and slide indexes
4126 * Because slide-in effect can have multiple items in 1 slide, we need to target multiple active elements
4127 * @param {String} direction "next" for next slides or empty string for previous
4128 * @param {*} minIndex the current active minimum index
4129 * @param {*} maxIndex the current active maximum index
4130 */
4131 _setupMultipleActiveSlide: function(minIndex, maxIndex) {
4132 this.slides.forEach(function(slide) {
4133 var sliderIndex = Number(slide.getAttribute('data-slider-slide-index'));
4134 var sliderItemLink = slide.querySelector(selectors.sliderItemLink);
4135
4136 slide.setAttribute('aria-hidden', true);
4137 slide.classList.remove(this.options.slideActiveClass);
4138 if (sliderItemLink) {
4139 sliderItemLink.setAttribute('tabindex', -1);
4140 }
4141
4142 if (sliderIndex >= minIndex && sliderIndex <= maxIndex) {
4143 slide.setAttribute('aria-hidden', false);
4144 slide.classList.add(this.options.slideActiveClass);
4145
4146 if (sliderItemLink) {
4147 sliderItemLink.setAttribute('tabindex', 0);
4148 }
4149 }
4150 }, this);
4151 },
4152
4153 _setActiveIndicator: function(index) {
4154 this.indicators.forEach(function(indicatorWrapper) {
4155 var activeIndicator = indicatorWrapper.querySelector(
4156 '.' + classes.indicatorActive
4157 );
4158
4159 var nextIndicator = indicatorWrapper.childNodes[index];
4160
4161 if (activeIndicator) {
4162 activeIndicator.setAttribute('aria-selected', false);
4163 activeIndicator.classList.remove(classes.indicatorActive);
4164 activeIndicator.firstElementChild.removeAttribute('aria-current');
4165 }
4166
4167 nextIndicator.classList.add(classes.indicatorActive);
4168 nextIndicator.setAttribute('aria-selected', true);
4169 nextIndicator.firstElementChild.setAttribute('aria-current', true);
4170 }, this);
4171 },
4172
4173 setSlideshowHeight: function() {
4174 var minAspectRatio = this.sliderContainer.getAttribute(
4175 'data-min-aspect-ratio'
4176 );
4177 this.sliderContainer.style.height =
4178 document.documentElement.offsetWidth / minAspectRatio + 'px';
4179 },
4180
4181 /**
4182 * Increase or decrease index position of the slideshow
4183 * Automatically auto-rotate
4184 * - Last slide goes to first slide when clicking "next"
4185 * - First slide goes to last slide when clicking "previous"
4186 * @param {String} direction "next" as a String, other empty string is previous slide
4187 */
4188 _getNextSlideIndex: function(direction) {
4189 var counter = direction === 'next' ? 1 : -1;
4190
4191 if (direction === 'next') {
4192 if (this.slideIndex === this.lastSlide) {
4193 return this.options.type === 'slide' ? this.lastSlide : 0;
4194 }
4195 } else if (!this.slideIndex) {
4196 return this.options.type === 'slide' ? 0 : this.lastSlide;
4197 }
4198
4199 return this.slideIndex + counter;
4200 },
4201
4202 /**
4203 * In "slide-in" type, multiple items are active in 1 slide
4204 * This will return an array containing their indexes
4205 */
4206 _getActiveSlidesIndex: function() {
4207 var currentActiveSlides = this.slides.filter(function(sliderItem) {
4208 if (sliderItem.classList.contains(this.options.slideActiveClass)) {
4209 return sliderItem;
4210 }
4211 }, this);
4212 var currentActiveSlidesIndex = currentActiveSlides.map(function(
4213 sliderItem
4214 ) {
4215 return Number(sliderItem.getAttribute('data-slider-slide-index'));
4216 });
4217
4218 return currentActiveSlidesIndex;
4219 },
4220
4221 /**
4222 * This checks the next "translateX" value and verifies
4223 * If it's at the last slide or beginning of the slide
4224 * So we can disable the arrow buttons
4225 */
4226 _disableArrows: function() {
4227 if (this.buttons.length === 0) return;
4228
4229 var previousButton = this.buttons[0];
4230 var nextButton = this.buttons[1];
4231
4232 // first slide
4233 if (this.isFirstSlide) {
4234 previousButton.setAttribute('aria-disabled', true);
4235 } else {
4236 previousButton.removeAttribute('aria-disabled');
4237 }
4238
4239 // last slide
4240 if (this.isLastSlide) {
4241 nextButton.setAttribute('aria-disabled', true);
4242 } else {
4243 nextButton.removeAttribute('aria-disabled');
4244 }
4245 },
4246
4247 /**
4248 * Verify if translateX reaches at first or last slide
4249 * @param {Number} translateXValue
4250 */
4251 _verifyFirstLastSlideTranslateX: function(translateXValue) {
4252 // first slide
4253 if (this._isNextTranslateXFirst(translateXValue)) {
4254 this.isFirstSlide = true;
4255 } else {
4256 this.isFirstSlide = false;
4257 }
4258
4259 // last slide
4260 if (this._isNextTranslateXLast(translateXValue)) {
4261 this.isLastSlide = true;
4262 } else {
4263 this.isLastSlide = false;
4264 }
4265 },
4266
4267 _getTranslateXPosition: function() {
4268 return Number(this.sliderTrack.style.transform.match(/(-?[0-9]+)/g)[0]);
4269 },
4270
4271 _isNextTranslateXFirst: function(translateXValue) {
4272 return translateXValue === 0;
4273 },
4274
4275 _isNextTranslateXLast: function(translateXValue) {
4276 // because translateX values are using negative, I'm converting into positive value
4277 var translateXValueAbsolute = Math.abs(translateXValue);
4278 var nextTranslateXValue =
4279 translateXValueAbsolute + this.sliderTranslateXMove;
4280
4281 return nextTranslateXValue >= this.sliderItemWidthTotal;
4282 },
4283
4284 _postTransitionEnd: function() {
4285 if (this.buttons.length) {
4286 this._disableArrows();
4287 }
4288
4289 if (this.indicators.length) {
4290 this._setActiveIndicator(this.slideIndex);
4291 }
4292 }
4293 });
4294
4295 return Slideshow;
4296})();
4297
4298theme.Video = (function() {
4299 var autoplayCheckComplete = false;
4300 var playOnClickChecked = false;
4301 var playOnClick = false;
4302 var youtubeLoaded = false;
4303 var videos = {};
4304 var videoPlayers = [];
4305 var videoOptions = {
4306 ratio: 16 / 9,
4307 scrollAnimationDuration: 400,
4308 playerVars: {
4309 // eslint-disable-next-line camelcase
4310 iv_load_policy: 3,
4311 modestbranding: 1,
4312 autoplay: 0,
4313 controls: 0,
4314 wmode: 'opaque',
4315 branding: 0,
4316 autohide: 0,
4317 rel: 0
4318 },
4319 events: {
4320 onReady: onPlayerReady,
4321 onStateChange: onPlayerChange
4322 }
4323 };
4324 var classes = {
4325 playing: 'video-is-playing',
4326 paused: 'video-is-paused',
4327 loading: 'video-is-loading',
4328 loaded: 'video-is-loaded',
4329 backgroundVideoWrapper: 'video-background-wrapper',
4330 videoWithImage: 'video--image_with_play',
4331 backgroundVideo: 'video--background',
4332 userPaused: 'is-paused',
4333 supportsAutoplay: 'autoplay',
4334 supportsNoAutoplay: 'no-autoplay',
4335 wrapperMinHeight: 'video-section-wrapper--min-height'
4336 };
4337
4338 var selectors = {
4339 section: '.video-section',
4340 videoWrapper: '.video-section-wrapper',
4341 playVideoBtn: '.video-control__play',
4342 closeVideoBtn: '.video-control__close-wrapper',
4343 pauseVideoBtn: '.video__pause',
4344 pauseVideoStop: '.video__pause-stop',
4345 pauseVideoResume: '.video__pause-resume',
4346 fallbackText: '.icon__fallback-text'
4347 };
4348
4349 /**
4350 * Public functions
4351 */
4352 function init(video) {
4353 if (!video) return;
4354
4355 videos[video.id] = {
4356 id: video.id,
4357 videoId: video.dataset.id,
4358 type: video.dataset.type,
4359 status:
4360 video.dataset.type === 'image_with_play' ? 'closed' : 'background', // closed, open, background
4361 video: video,
4362 videoWrapper: video.closest(selectors.videoWrapper),
4363 section: video.closest(selectors.section),
4364 controls: video.dataset.type === 'background' ? 0 : 1
4365 };
4366
4367 if (!youtubeLoaded) {
4368 // This code loads the IFrame Player API code asynchronously.
4369 var tag = document.createElement('script');
4370 tag.src = 'https://www.youtube.com/iframe_api';
4371 var firstScriptTag = document.getElementsByTagName('script')[0];
4372 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
4373 }
4374
4375 playOnClickCheck();
4376 }
4377
4378 function customPlayVideo(playerId) {
4379 // Make sure we have carried out the playOnClick check first
4380 if (!playOnClickChecked && !playOnClick) {
4381 return;
4382 }
4383
4384 if (playerId && typeof videoPlayers[playerId].playVideo === 'function') {
4385 privatePlayVideo(playerId);
4386 }
4387 }
4388
4389 function pauseVideo(playerId) {
4390 if (
4391 videoPlayers[playerId] &&
4392 typeof videoPlayers[playerId].pauseVideo === 'function'
4393 ) {
4394 videoPlayers[playerId].pauseVideo();
4395 }
4396 }
4397
4398 function loadVideos() {
4399 for (var key in videos) {
4400 if (videos.hasOwnProperty(key)) {
4401 createPlayer(key);
4402 }
4403 }
4404
4405 initEvents();
4406 youtubeLoaded = true;
4407 }
4408
4409 function editorLoadVideo(key) {
4410 if (!youtubeLoaded) {
4411 return;
4412 }
4413 createPlayer(key);
4414
4415 initEvents();
4416 }
4417
4418 /**
4419 * Private functions
4420 */
4421
4422 function privatePlayVideo(id, clicked) {
4423 var videoData = videos[id];
4424 var player = videoPlayers[id];
4425 var videoWrapper = videoData.videoWrapper;
4426
4427 if (playOnClick) {
4428 // playOnClick means we are probably on mobile (no autoplay).
4429 // setAsPlaying will show the iframe, requiring another click
4430 // to play the video.
4431 setAsPlaying(videoData);
4432 } else if (clicked || autoplayCheckComplete) {
4433 // Play if autoplay is available or clicked to play
4434 videoWrapper.classList.remove(classes.loading);
4435 setAsPlaying(videoData);
4436 player.playVideo();
4437 return;
4438 } else {
4439 player.playVideo();
4440 }
4441 }
4442
4443 function setAutoplaySupport(supported) {
4444 var supportClass = supported
4445 ? classes.supportsAutoplay
4446 : classes.supportsNoAutoplay;
4447 document.documentElement.classList.remove(
4448 classes.supportsAutoplay,
4449 classes.supportsNoAutoplay
4450 );
4451 document.documentElement.classList.add(supportClass);
4452
4453 if (!supported) {
4454 playOnClick = true;
4455 }
4456
4457 autoplayCheckComplete = true;
4458 }
4459
4460 function playOnClickCheck() {
4461 if (playOnClickChecked) {
4462 return;
4463 }
4464
4465 if (isMobile()) {
4466 playOnClick = true;
4467 }
4468
4469 if (playOnClick) {
4470 // No need to also do the autoplay check
4471 setAutoplaySupport(false);
4472 }
4473
4474 playOnClickChecked = true;
4475 }
4476
4477 // The API will call this function when each video player is ready
4478 function onPlayerReady(evt) {
4479 evt.target.setPlaybackQuality('hd1080');
4480 var videoData = getVideoOptions(evt);
4481 var videoTitle = evt.target.getVideoData().title;
4482 playOnClickCheck();
4483
4484 // Prevent tabbing through YouTube player controls until visible
4485 document.getElementById(videoData.id).setAttribute('tabindex', '-1');
4486
4487 sizeBackgroundVideos();
4488 setButtonLabels(videoData.videoWrapper, videoTitle);
4489
4490 // Customize based on options from the video ID
4491 if (videoData.type === 'background') {
4492 evt.target.mute();
4493 privatePlayVideo(videoData.id);
4494 }
4495
4496 videoData.videoWrapper.classList.add(classes.loaded);
4497 }
4498
4499 function onPlayerChange(evt) {
4500 var videoData = getVideoOptions(evt);
4501 if (
4502 videoData.status === 'background' &&
4503 !isMobile() &&
4504 !autoplayCheckComplete &&
4505 (evt.data === YT.PlayerState.PLAYING ||
4506 evt.data === YT.PlayerState.BUFFERING)
4507 ) {
4508 setAutoplaySupport(true);
4509 autoplayCheckComplete = true;
4510 videoData.videoWrapper.classList.remove(classes.loading);
4511 }
4512 switch (evt.data) {
4513 case YT.PlayerState.ENDED:
4514 setAsFinished(videoData);
4515 break;
4516 case YT.PlayerState.PAUSED:
4517 // Seeking on a YouTube video also fires a PAUSED state change,
4518 // checking the state after a delay prevents us pausing the video when
4519 // the user is seeking instead of pausing
4520 setTimeout(function() {
4521 if (evt.target.getPlayerState() === YT.PlayerState.PAUSED) {
4522 setAsPaused(videoData);
4523 }
4524 }, 200);
4525 break;
4526 }
4527 }
4528
4529 function setAsFinished(videoData) {
4530 switch (videoData.type) {
4531 case 'background':
4532 videoPlayers[videoData.id].seekTo(0);
4533 break;
4534 case 'image_with_play':
4535 closeVideo(videoData.id);
4536 toggleExpandVideo(videoData.id, false);
4537 break;
4538 }
4539 }
4540
4541 function setAsPlaying(videoData) {
4542 var videoWrapper = videoData.videoWrapper;
4543 var pauseButton = videoWrapper.querySelector(selectors.pauseVideoBtn);
4544
4545 videoWrapper.classList.remove(classes.loading);
4546
4547 if (pauseButton.classList.contains(classes.userPaused)) {
4548 pauseButton.classList.remove(classes.userPaused);
4549 }
4550
4551 // Do not change element visibility if it is a background video
4552 if (videoData.status === 'background') {
4553 return;
4554 }
4555
4556 document.getElementById(videoData.id).setAttribute('tabindex', '0');
4557
4558 if (videoData.type === 'image_with_play') {
4559 videoWrapper.classList.remove(classes.paused);
4560 videoWrapper.classList.add(classes.playing);
4561 }
4562
4563 // Update focus to the close button so we stay within the video wrapper,
4564 // allowing time for the scroll animation
4565 setTimeout(function() {
4566 videoWrapper.querySelector(selectors.closeVideoBtn).focus();
4567 }, videoOptions.scrollAnimationDuration);
4568 }
4569
4570 function setAsPaused(videoData) {
4571 var videoWrapper = videoData.videoWrapper;
4572
4573 // YT's events fire after our click event. This status flag ensures
4574 // we don't interact with a closed or background video.
4575 if (videoData.type === 'image_with_play') {
4576 if (videoData.status === 'closed') {
4577 videoWrapper.classList.remove(classes.paused);
4578 } else {
4579 videoWrapper.classList.add(classes.paused);
4580 }
4581 }
4582
4583 videoWrapper.classList.remove(classes.playing);
4584 }
4585
4586 function closeVideo(playerId) {
4587 var videoData = videos[playerId];
4588 var videoWrapper = videoData.videoWrapper;
4589
4590 document.getElementById(videoData.id).setAttribute('tabindex', '-1');
4591
4592 videoData.status = 'closed';
4593
4594 switch (videoData.type) {
4595 case 'image_with_play':
4596 videoPlayers[playerId].stopVideo();
4597 setAsPaused(videoData); // in case the video is already paused
4598 break;
4599 case 'background':
4600 videoPlayers[playerId].mute();
4601 setBackgroundVideo(playerId);
4602 break;
4603 }
4604
4605 videoWrapper.classList.remove(classes.paused, classes.playing);
4606 }
4607
4608 function getVideoOptions(evt) {
4609 var id = evt.target.getIframe().id;
4610 return videos[id];
4611 }
4612
4613 function toggleExpandVideo(playerId, expand) {
4614 var video = videos[playerId];
4615 var elementTop =
4616 video.videoWrapper.getBoundingClientRect().top + window.pageYOffset;
4617 var playButton = video.videoWrapper.querySelector(selectors.playVideoBtn);
4618 var offset = 0;
4619 var newHeight = 0;
4620
4621 if (isMobile()) {
4622 video.videoWrapper.parentElement.classList.toggle('page-width', !expand);
4623 }
4624
4625 if (expand) {
4626 if (isMobile()) {
4627 newHeight = window.innerWidth / videoOptions.ratio;
4628 } else {
4629 newHeight = video.videoWrapper.offsetWidth / videoOptions.ratio;
4630 }
4631 offset = (window.innerHeight - newHeight) / 2;
4632
4633 video.videoWrapper.style.height =
4634 video.videoWrapper.getBoundingClientRect().height + 'px';
4635 video.videoWrapper.classList.remove(classes.wrapperMinHeight);
4636 video.videoWrapper.style.height = newHeight + 'px';
4637
4638 // Animate doesn't work in mobile editor, so we don't use it
4639 if (!(isMobile() && Shopify.designMode)) {
4640 var scrollBehavior = document.documentElement.style.scrollBehavior;
4641 document.documentElement.style.scrollBehavior = 'smooth';
4642 window.scrollTo({ top: elementTop - offset });
4643 document.documentElement.style.scrollBehavior = scrollBehavior;
4644 }
4645 } else {
4646 if (isMobile()) {
4647 newHeight = video.videoWrapper.dataset.mobileHeight;
4648 } else {
4649 newHeight = video.videoWrapper.dataset.desktopHeight;
4650 }
4651
4652 video.videoWrapper.style.height = newHeight + 'px';
4653
4654 setTimeout(function() {
4655 video.videoWrapper.classList.add(classes.wrapperMinHeight);
4656 }, 600);
4657 // Set focus on play button, but don't scroll page
4658 var x = window.scrollX;
4659 var y = window.scrollY;
4660 playButton.focus();
4661 window.scrollTo(x, y);
4662 }
4663 }
4664
4665 function togglePause(playerId) {
4666 var pauseButton = videos[playerId].videoWrapper.querySelector(
4667 selectors.pauseVideoBtn
4668 );
4669 var paused = pauseButton.classList.contains(classes.userPaused);
4670 if (paused) {
4671 pauseButton.classList.remove(classes.userPaused);
4672 customPlayVideo(playerId);
4673 } else {
4674 pauseButton.classList.add(classes.userPaused);
4675 pauseVideo(playerId);
4676 }
4677 pauseButton.setAttribute('aria-pressed', !paused);
4678 }
4679
4680 function startVideoOnClick(playerId) {
4681 var video = videos[playerId];
4682
4683 // add loading class to wrapper
4684 video.videoWrapper.classList.add(classes.loading);
4685
4686 // Explicity set the video wrapper height (needed for height transition)
4687 video.videoWrapper.style.height = video.videoWrapper.offsetHeight + 'px';
4688
4689 video.status = 'open';
4690
4691 switch (video.type) {
4692 case 'image_with_play':
4693 privatePlayVideo(playerId, true);
4694 break;
4695 case 'background':
4696 unsetBackgroundVideo(playerId, video);
4697 videoPlayers[playerId].unMute();
4698 privatePlayVideo(playerId, true);
4699 break;
4700 }
4701
4702 toggleExpandVideo(playerId, true);
4703
4704 // esc to close video player
4705 document.addEventListener('keydown', handleVideoPlayerKeydown);
4706 }
4707
4708 var handleVideoPlayerKeydown = function(evt) {
4709 var playerId = document.activeElement.dataset.controls;
4710 if (evt.keyCode !== slate.utils.keyboardKeys.ESCAPE || !playerId) {
4711 return;
4712 }
4713
4714 closeVideo(playerId);
4715 toggleExpandVideo(playerId, false);
4716 };
4717
4718 function sizeBackgroundVideos() {
4719 var backgroundVideos = document.querySelectorAll(
4720 '.' + classes.backgroundVideo
4721 );
4722 backgroundVideos.forEach(function(el) {
4723 sizeBackgroundVideo(el);
4724 });
4725 }
4726
4727 function sizeBackgroundVideo(videoPlayer) {
4728 if (!youtubeLoaded) {
4729 return;
4730 }
4731
4732 if (isMobile()) {
4733 videoPlayer.style.cssText = null;
4734 } else {
4735 var videoWrapper = videoPlayer.closest(selectors.videoWrapper);
4736 var videoWidth = videoWrapper.clientWidth;
4737 var playerWidth = videoPlayer.clientWidth;
4738 var desktopHeight = videoWrapper.dataset.desktopHeight;
4739
4740 // when screen aspect ratio differs from video, video must center and underlay one dimension
4741 if (videoWidth / videoOptions.ratio < desktopHeight) {
4742 playerWidth = Math.ceil(desktopHeight * videoOptions.ratio); // get new player width
4743 var styles =
4744 'width: ' +
4745 playerWidth +
4746 'px; height: ' +
4747 desktopHeight +
4748 'px; left: ' +
4749 (videoWidth - playerWidth) / 2 +
4750 'px; top: 0;';
4751 videoPlayer.style.cssText = styles;
4752 } else {
4753 // new video width < window width (gap to right)
4754 desktopHeight = Math.ceil(videoWidth / videoOptions.ratio); // get new player height
4755 var styles2 =
4756 'width: ' +
4757 videoWidth +
4758 'px; height: ' +
4759 desktopHeight +
4760 'px; top: ' +
4761 (desktopHeight - desktopHeight) / 2 +
4762 'px; left: 0;'; // player height is greater, offset top; reset left
4763 videoPlayer.style.cssText = styles2;
4764 }
4765
4766 theme.Helpers.prepareTransition(videoPlayer);
4767 videoWrapper.classList.add(classes.loaded);
4768 }
4769 }
4770
4771 function unsetBackgroundVideo(playerId) {
4772 // Switch the background video to a chrome-only player once played
4773 var player = document.getElementById(playerId);
4774 player.classList.remove(classes.backgroundVideo);
4775 player.classList.add(classes.videoWithImage);
4776
4777 setTimeout(function() {
4778 document.getElementById(playerId).style.cssText = null;
4779 }, 600);
4780
4781 videos[playerId].videoWrapper.classList.remove(
4782 classes.backgroundVideoWrapper
4783 );
4784 videos[playerId].videoWrapper.classList.add(classes.playing);
4785
4786 videos[playerId].status = 'open';
4787 }
4788
4789 function setBackgroundVideo(playerId) {
4790 var player = document.getElementById(playerId);
4791 player.classList.remove(classes.videoWithImage);
4792 player.classList.add(classes.backgroundVideo);
4793
4794 videos[playerId].videoWrapper.classList.add(classes.backgroundVideoWrapper);
4795
4796 videos[playerId].status = 'background';
4797 sizeBackgroundVideo(player);
4798 }
4799
4800 function isMobile() {
4801 return window.innerWidth < theme.breakpoints.medium;
4802 }
4803
4804 var handleWindowResize = theme.Helpers.debounce(function() {
4805 if (!youtubeLoaded) return;
4806 var key;
4807 var fullscreen = window.innerHeight === screen.height;
4808 sizeBackgroundVideos();
4809
4810 if (isMobile()) {
4811 for (key in videos) {
4812 if (videos.hasOwnProperty(key)) {
4813 if (videos[key].videoWrapper.classList.contains(classes.playing)) {
4814 if (!fullscreen) {
4815 pauseVideo(key);
4816 setAsPaused(videos[key]);
4817 }
4818 }
4819 videos[key].videoWrapper.style.height =
4820 document.documentElement.clientWidth / videoOptions.ratio + 'px';
4821 }
4822 }
4823 setAutoplaySupport(false);
4824 } else {
4825 setAutoplaySupport(true);
4826 for (key in videos) {
4827 var videosWithImage = videos[key].videoWrapper.querySelectorAll(
4828 '.' + classes.videoWithImage
4829 );
4830 if (videosWithImage.length) {
4831 continue;
4832 }
4833 videoPlayers[key].playVideo();
4834 setAsPlaying(videos[key]);
4835 }
4836 }
4837 }, 200);
4838
4839 var handleWindowScroll = theme.Helpers.debounce(function() {
4840 if (!youtubeLoaded) return;
4841
4842 for (var key in videos) {
4843 if (videos.hasOwnProperty(key)) {
4844 var videoWrapper = videos[key].videoWrapper;
4845 var condition =
4846 videoWrapper.getBoundingClientRect().top +
4847 window.pageYOffset +
4848 videoWrapper.offsetHeight * 0.75 <
4849 window.pageYOffset ||
4850 videoWrapper.getBoundingClientRect().top +
4851 window.pageYOffset +
4852 videoWrapper.offsetHeight * 0.25 >
4853 window.pageYOffset + window.innerHeight;
4854
4855 // Close the video if more than 75% of it is scrolled out of view
4856 if (videoWrapper.classList.contains(classes.playing)) {
4857 if (!condition) return;
4858 closeVideo(key);
4859 toggleExpandVideo(key, false);
4860 }
4861 }
4862 }
4863 }, 50);
4864
4865 function initEvents() {
4866 var playVideoBtns = document.querySelectorAll(selectors.playVideoBtn);
4867 var closeVideoBtns = document.querySelectorAll(selectors.closeVideoBtn);
4868 var pauseVideoBtns = document.querySelectorAll(selectors.pauseVideoBtn);
4869
4870 playVideoBtns.forEach(function(btn) {
4871 btn.addEventListener('click', function(evt) {
4872 var playerId = evt.currentTarget.dataset.controls;
4873 startVideoOnClick(playerId);
4874 });
4875 });
4876
4877 closeVideoBtns.forEach(function(btn) {
4878 btn.addEventListener('click', function(evt) {
4879 var playerId = evt.currentTarget.dataset.controls;
4880
4881 evt.currentTarget.blur();
4882 closeVideo(playerId);
4883 toggleExpandVideo(playerId, false);
4884 });
4885 });
4886
4887 pauseVideoBtns.forEach(function(btn) {
4888 btn.addEventListener('click', function(evt) {
4889 var playerId = evt.currentTarget.dataset.controls;
4890 togglePause(playerId);
4891 });
4892 });
4893
4894 // Listen to resize to keep a background-size:cover-like layout
4895 window.addEventListener('resize', handleWindowResize);
4896
4897 window.addEventListener('scroll', handleWindowScroll);
4898 }
4899
4900 function createPlayer(key) {
4901 var args = Object.assign(videoOptions, videos[key]);
4902
4903 args.playerVars.controls = args.controls;
4904 videoPlayers[key] = new YT.Player(key, args);
4905 }
4906
4907 function removeEvents() {
4908 document.removeEventListener('keydown', handleVideoPlayerKeydown);
4909 window.removeEventListener('resize', handleWindowResize);
4910 window.removeEventListener('scroll', handleWindowScroll);
4911 }
4912
4913 function setButtonLabels(videoWrapper, title) {
4914 var playButtons = videoWrapper.querySelectorAll(selectors.playVideoBtn);
4915 var closeButton = videoWrapper.querySelector(selectors.closeVideoBtn);
4916 var pauseButton = videoWrapper.querySelector(selectors.pauseVideoBtn);
4917 var closeButtonText = closeButton.querySelector(selectors.fallbackText);
4918
4919 var pauseButtonStop = pauseButton.querySelector(selectors.pauseVideoStop);
4920 var pauseButtonStopText = pauseButtonStop.querySelector(
4921 selectors.fallbackText
4922 );
4923
4924 var pauseButtonResume = pauseButton.querySelector(
4925 selectors.pauseVideoResume
4926 );
4927 var pauseButtonResumeText = pauseButtonResume.querySelector(
4928 selectors.fallbackText
4929 );
4930
4931 // Insert the video title retrieved from YouTube into the instructional text
4932 // for each button
4933 playButtons.forEach(function(playButton) {
4934 var playButtonText = playButton.querySelector(selectors.fallbackText);
4935
4936 playButtonText.textContent = playButtonText.textContent.replace(
4937 '[video_title]',
4938 title
4939 );
4940 });
4941 closeButtonText.textContent = closeButtonText.textContent.replace(
4942 '[video_title]',
4943 title
4944 );
4945 pauseButtonStopText.textContent = pauseButtonStopText.textContent.replace(
4946 '[video_title]',
4947 title
4948 );
4949 pauseButtonResumeText.textContent = pauseButtonResumeText.textContent.replace(
4950 '[video_title]',
4951 title
4952 );
4953 }
4954
4955 return {
4956 init: init,
4957 editorLoadVideo: editorLoadVideo,
4958 loadVideos: loadVideos,
4959 playVideo: customPlayVideo,
4960 pauseVideo: pauseVideo,
4961 removeEvents: removeEvents
4962 };
4963})();
4964
4965theme.ProductVideo = (function() {
4966 var videos = {};
4967
4968 var hosts = {
4969 shopify: 'shopify',
4970 external: 'external'
4971 };
4972
4973 var selectors = {
4974 productMediaWrapper: '[data-product-single-media-wrapper]'
4975 };
4976
4977 var attributes = {
4978 enableVideoLooping: 'enable-video-looping',
4979 videoId: 'video-id'
4980 };
4981
4982 function init(videoContainer, sectionId) {
4983 if (!videoContainer) {
4984 return;
4985 }
4986
4987 var videoElement = videoContainer.querySelector('iframe, video');
4988
4989 if (!videoElement) {
4990 return;
4991 }
4992
4993 var mediaId = videoContainer.getAttribute('data-media-id');
4994
4995 videos[mediaId] = {
4996 mediaId: mediaId,
4997 sectionId: sectionId,
4998 host: hostFromVideoElement(videoElement),
4999 container: videoContainer,
5000 element: videoElement,
5001 ready: function() {
5002 createPlayer(this);
5003 }
5004 };
5005
5006 window.Shopify.loadFeatures([
5007 {
5008 name: 'video-ui',
5009 version: '2.0',
5010 onLoad: setupVideos
5011 }
5012 ]);
5013 theme.LibraryLoader.load('plyrShopifyStyles');
5014 }
5015
5016 function setupVideos(errors) {
5017 if (errors) {
5018 fallbackToNativeVideo();
5019 return;
5020 }
5021
5022 loadVideos();
5023 }
5024
5025 function createPlayer(video) {
5026 if (video.player) {
5027 return;
5028 }
5029
5030 var productMediaWrapper = video.container.closest(
5031 selectors.productMediaWrapper
5032 );
5033
5034 var enableLooping =
5035 productMediaWrapper.getAttribute(
5036 'data-' + attributes.enableVideoLooping
5037 ) === 'true';
5038
5039 // eslint-disable-next-line no-undef
5040 video.player = new Shopify.Video(video.element, {
5041 loop: { active: enableLooping }
5042 });
5043
5044 var pauseVideo = function() {
5045 if (!video.player) return;
5046 video.player.pause();
5047 };
5048
5049 productMediaWrapper.addEventListener('mediaHidden', pauseVideo);
5050 productMediaWrapper.addEventListener('xrLaunch', pauseVideo);
5051
5052 productMediaWrapper.addEventListener('mediaVisible', function() {
5053 if (theme.Helpers.isTouch()) return;
5054 if (!video.player) return;
5055 video.player.play();
5056 });
5057 }
5058
5059 function hostFromVideoElement(video) {
5060 if (video.tagName === 'VIDEO') {
5061 return hosts.shopify;
5062 }
5063
5064 return hosts.external;
5065 }
5066
5067 function loadVideos() {
5068 for (var key in videos) {
5069 if (videos.hasOwnProperty(key)) {
5070 var video = videos[key];
5071 video.ready();
5072 }
5073 }
5074 }
5075
5076 function fallbackToNativeVideo() {
5077 for (var key in videos) {
5078 if (videos.hasOwnProperty(key)) {
5079 var video = videos[key];
5080
5081 if (video.nativeVideo) continue;
5082
5083 if (video.host === hosts.shopify) {
5084 video.element.setAttribute('controls', 'controls');
5085 video.nativeVideo = true;
5086 }
5087 }
5088 }
5089 }
5090
5091 function removeSectionVideos(sectionId) {
5092 for (var key in videos) {
5093 if (videos.hasOwnProperty(key)) {
5094 var video = videos[key];
5095
5096 if (video.sectionId === sectionId) {
5097 if (video.player) video.player.destroy();
5098 delete videos[key];
5099 }
5100 }
5101 }
5102 }
5103
5104 return {
5105 init: init,
5106 hosts: hosts,
5107 loadVideos: loadVideos,
5108 removeSectionVideos: removeSectionVideos
5109 };
5110})();
5111
5112theme.ProductModel = (function() {
5113 var modelJsonSections = {};
5114 var models = {};
5115 var xrButtons = {};
5116
5117 var selectors = {
5118 mediaGroup: '[data-product-single-media-group]',
5119 xrButton: '[data-shopify-xr]'
5120 };
5121
5122 function init(modelViewerContainers, sectionId) {
5123 modelJsonSections[sectionId] = {
5124 loaded: false
5125 };
5126
5127 modelViewerContainers.forEach(function(modelViewerContainer, index) {
5128 var mediaId = modelViewerContainer.getAttribute('data-media-id');
5129 var modelViewerElement = modelViewerContainer.querySelector(
5130 'model-viewer'
5131 );
5132 var modelId = modelViewerElement.getAttribute('data-model-id');
5133
5134 if (index === 0) {
5135 var mediaGroup = modelViewerContainer.closest(selectors.mediaGroup);
5136 var xrButton = mediaGroup.querySelector(selectors.xrButton);
5137 xrButtons[sectionId] = {
5138 element: xrButton,
5139 defaultId: modelId
5140 };
5141 }
5142
5143 models[mediaId] = {
5144 modelId: modelId,
5145 sectionId: sectionId,
5146 container: modelViewerContainer,
5147 element: modelViewerElement
5148 };
5149 });
5150
5151 window.Shopify.loadFeatures([
5152 {
5153 name: 'shopify-xr',
5154 version: '1.0',
5155 onLoad: setupShopifyXr
5156 },
5157 {
5158 name: 'model-viewer-ui',
5159 version: '1.0',
5160 onLoad: setupModelViewerUi
5161 }
5162 ]);
5163 theme.LibraryLoader.load('modelViewerUiStyles');
5164 }
5165
5166 function setupShopifyXr(errors) {
5167 if (errors) return;
5168
5169 if (!window.ShopifyXR) {
5170 document.addEventListener('shopify_xr_initialized', function() {
5171 setupShopifyXr();
5172 });
5173 return;
5174 }
5175
5176 for (var sectionId in modelJsonSections) {
5177 if (modelJsonSections.hasOwnProperty(sectionId)) {
5178 var modelSection = modelJsonSections[sectionId];
5179
5180 if (modelSection.loaded) continue;
5181 var modelJson = document.querySelector('#ModelJson-' + sectionId);
5182
5183 window.ShopifyXR.addModels(JSON.parse(modelJson.innerHTML));
5184 modelSection.loaded = true;
5185 }
5186 }
5187 window.ShopifyXR.setupXRElements();
5188 }
5189
5190 function setupModelViewerUi(errors) {
5191 if (errors) return;
5192
5193 for (var key in models) {
5194 if (models.hasOwnProperty(key)) {
5195 var model = models[key];
5196 if (!model.modelViewerUi) {
5197 model.modelViewerUi = new Shopify.ModelViewerUI(model.element);
5198 }
5199 setupModelViewerListeners(model);
5200 }
5201 }
5202 }
5203
5204 function setupModelViewerListeners(model) {
5205 var xrButton = xrButtons[model.sectionId];
5206
5207 model.container.addEventListener('mediaVisible', function() {
5208 xrButton.element.setAttribute('data-shopify-model3d-id', model.modelId);
5209 if (theme.Helpers.isTouch()) return;
5210 model.modelViewerUi.play();
5211 });
5212
5213 model.container.addEventListener('mediaHidden', function() {
5214 xrButton.element.setAttribute(
5215 'data-shopify-model3d-id',
5216 xrButton.defaultId
5217 );
5218 model.modelViewerUi.pause();
5219 });
5220
5221 model.container.addEventListener('xrLaunch', function() {
5222 model.modelViewerUi.pause();
5223 });
5224 }
5225
5226 function removeSectionModels(sectionId) {
5227 for (var key in models) {
5228 if (models.hasOwnProperty(key)) {
5229 var model = models[key];
5230 if (model.sectionId === sectionId) {
5231 models[key].modelViewerUi.destroy();
5232 delete models[key];
5233 }
5234 }
5235 }
5236 delete modelJsonSections[sectionId];
5237 }
5238
5239 return {
5240 init: init,
5241 removeSectionModels: removeSectionModels
5242 };
5243})();
5244
5245window.theme = window.theme || {};
5246
5247theme.FormStatus = (function() {
5248 var selectors = {
5249 statusMessage: '[data-form-status]'
5250 };
5251
5252 function init() {
5253 var statusMessages = document.querySelectorAll(selectors.statusMessage);
5254
5255 statusMessages.forEach(function(statusMessage) {
5256 statusMessage.setAttribute('tabindex', -1);
5257 statusMessage.focus();
5258
5259 statusMessage.addEventListener(
5260 'blur',
5261 function(evt) {
5262 evt.target.removeAttribute('tabindex');
5263 },
5264 { once: true }
5265 );
5266 });
5267 }
5268
5269 return {
5270 init: init
5271 };
5272})();
5273
5274theme.Hero = (function() {
5275 var classes = {
5276 indexSectionFlush: 'index-section--flush'
5277 };
5278
5279 var selectors = {
5280 heroFixedWidthContent: '.hero-fixed-width__content',
5281 heroFixedWidthImage: '.hero-fixed-width__image'
5282 };
5283
5284 function hero(el, sectionId) {
5285 var hero = document.querySelector(el);
5286 var layout = hero.getAttribute('data-layout');
5287 var parentSection = document.querySelector('#shopify-section-' + sectionId);
5288 var heroContent = parentSection.querySelector(
5289 selectors.heroFixedWidthContent
5290 );
5291 var heroImage = parentSection.querySelector(selectors.heroFixedWidthImage);
5292
5293 if (layout !== 'fixed_width') {
5294 return;
5295 }
5296
5297 parentSection.classList.remove(classes.indexSectionFlush);
5298 heroFixedHeight();
5299
5300 window.addEventListener('resize', function() {
5301 theme.Helpers.debounce(function() {
5302 heroFixedHeight();
5303 }, 50);
5304 });
5305
5306 function heroFixedHeight() {
5307 var contentHeight;
5308 var imageHeight;
5309
5310 if (heroContent) {
5311 contentHeight = heroContent.offsetHeight + 50;
5312 }
5313
5314 if (heroImage) {
5315 imageHeight = heroImage.offsetHeight;
5316 }
5317
5318 if (contentHeight > imageHeight) {
5319 heroImage.style.minHeight = contentHeight + 'px';
5320 }
5321 }
5322 }
5323
5324 return hero;
5325})();
5326
5327// prettier-ignore
5328window.theme = window.theme || {};
5329
5330theme.SearchResultsTemplate = (function() {
5331 function renderResults(products, isLoading, searchQuery) {
5332 return [
5333 '<div class="predictive-search">',
5334 renderHeader(products, isLoading),
5335 renderProducts(products, searchQuery),
5336 '</div>'
5337 ].join('');
5338 }
5339
5340 function renderHeader(products, isLoading) {
5341 if (products.length === 0) {
5342 return '';
5343 }
5344
5345 return [
5346 '<div class="predictive-search-title">',
5347 '<h3 id="predictive-search" class="predictive-search-title__content">' +
5348 theme.strings.products +
5349 '</h3>',
5350 '<span class="predictive-search-title__loading-spinner">' +
5351 (isLoading
5352 ? '<span class= "icon-predictive-search-spinner" ></span >'
5353 : '') +
5354 '</span>',
5355 '</div>'
5356 ].join('');
5357 }
5358
5359 function loadingState() {
5360 return [
5361 '<div class="predictive-search">',
5362 '<div class="predictive-search-loading">',
5363 '<span class="visually-hidden">' + theme.strings.loading + '</span>',
5364 '<span class="predictive-search-loading__icon">',
5365 '<span class="icon-predictive-search-spinner"></span>',
5366 '</span>',
5367 '</div>',
5368 '</div>'
5369 ].join('');
5370 }
5371
5372 function renderViewAll(searchQuery) {
5373 return [
5374 '<button type="submit" class="predictive-search-view-all__button" tabindex="-1">',
5375 theme.strings.searchFor +
5376 '<span class="predictive-search-view-all__query"> “' +
5377 _htmlEscape(searchQuery) +
5378 '”</span>',
5379 '</button>'
5380 ].join('');
5381 }
5382
5383 function renderProducts(products, searchQuery) {
5384 var resultsCount = products.length;
5385
5386 return [
5387 '<ul id="predictive-search-results" class="predictive-search__list" role="listbox" aria-labelledby="predictive-search">',
5388 products
5389 .map(function(product, index) {
5390 return renderProduct(normalizeProduct(product), index, resultsCount);
5391 })
5392 .join(''),
5393 '<li id="search-all" class="predictive-search-view-all" role="option" data-search-result>' +
5394 renderViewAll(searchQuery) +
5395 '</li>',
5396 '</ul>'
5397 ].join('');
5398 }
5399
5400 function renderProduct(product, index, resultsCount) {
5401 return [
5402 '<li id="search-result-' +
5403 index +
5404 '" class="predictive-search-item" role="option" data-search-result>',
5405 '<a class="predictive-search-item__link" href="' +
5406 product.url +
5407 '" tabindex="-1">',
5408 '<div class="predictive-search__column predictive-search__column--image" data-image-loading-animation>',
5409 renderProductImage(product),
5410 '</div>',
5411 '<div class="predictive-search__column predictive-search__column--content ' +
5412 (getDetailsCount() ? '' : 'predictive-search__column--center') +
5413 '">',
5414 '<span class="predictive-search-item__title">',
5415 '<span class="predictive-search-item__title-text">' +
5416 product.title +
5417 '</span>',
5418 '</span>' + (getDetailsCount() ? renderProductDetails(product) : ''),
5419 '<span class="visually-hidden">, </span>',
5420 '<span class="visually-hidden">' +
5421 getNumberOfResultsString(index + 1, resultsCount) +
5422 '</span>',
5423 '</div>',
5424 '</a>',
5425 '</li>'
5426 ].join('');
5427 }
5428
5429 function renderProductImage(product) {
5430 if (product.image === null) {
5431 return '';
5432 }
5433
5434 return (
5435 '<img class="predictive-search-item__image lazyload" src="' +
5436 product.image.url +
5437 '" data-src="' +
5438 product.image.url +
5439 '" data-image alt="' +
5440 product.image.alt +
5441 '" />'
5442 );
5443 }
5444
5445 function renderProductDetails(product) {
5446 return [
5447 '<dl class="predictive-search-item__details price' +
5448 (product.isOnSale ? ' price--on-sale' : '') +
5449 (!product.available ? ' price--sold-out' : '') +
5450 (!product.isPriceVaries && product.isCompareVaries
5451 ? ' price--compare-price-hidden'
5452 : '') +
5453 '">',
5454 '<div class="predictive-search-item__detail">',
5455 renderVendor(product),
5456 '</div>',
5457 '<div class="predictive-search-item__detail predictive-search-item__detail--inline">' +
5458 renderProductPrice(product),
5459 '</div>',
5460 '</dl>'
5461 ].join('');
5462 }
5463 function renderProductPrice(product) {
5464 if (!theme.settings.predictiveSearchShowPrice) {
5465 return '';
5466 }
5467
5468 var accessibilityAnnounceComma = '<span class="visually-hidden">, </span>';
5469
5470 var priceMarkup =
5471 '<div class="price__regular">' + renderPrice(product) + '</div>';
5472
5473 var salePriceMarkup =
5474 '<div class="price__sale">' + renderSalePrice(product) + '</div>';
5475
5476 return (
5477 accessibilityAnnounceComma +
5478 '<div class="price__pricing-group">' +
5479 (product.isOnSale ? salePriceMarkup : priceMarkup) +
5480 '</div>'
5481 );
5482 }
5483
5484 function renderSalePrice(product) {
5485 return [
5486 '<dt>',
5487 '<span class="visually-hidden">' + theme.strings.salePrice + '</span>',
5488 '</dt>',
5489 '<dd>',
5490 '<span class="predictive-search-item__price predictive-search-item__price--sale">' +
5491 (product.isPriceVaries
5492 ? theme.strings.fromLowestPrice.replace('[price]', product.price)
5493 : product.price) +
5494 '</span>',
5495 '</dd>',
5496 '<div class="price__compare">' + renderCompareAtPrice(product) + '</div>'
5497 ].join('');
5498 }
5499
5500 function renderCompareAtPrice(product) {
5501 return [
5502 '<dt>',
5503 '<span class="visually-hidden">' +
5504 theme.strings.regularPrice +
5505 '</span> ',
5506 '</dt>',
5507 '<dd>',
5508 '<span class="predictive-search-item__price predictive-search-item__price--compare">' +
5509 product.compareAtPrice +
5510 '</span>',
5511 '</dd>'
5512 ].join('');
5513 }
5514
5515 function renderPrice(product) {
5516 return [
5517 '<dt>',
5518 '<span class="visually-hidden">' + theme.strings.regularPrice + '</span>',
5519 '</dt>',
5520 '<dd>',
5521 '<span class="predictive-search-item__price">' +
5522 (product.isPriceVaries
5523 ? theme.strings.fromLowestPrice.replace('[price]', product.price)
5524 : product.price) +
5525 '</span>',
5526 '</dd>'
5527 ].join('');
5528 }
5529
5530 function renderVendor(product) {
5531 if (!theme.settings.predictiveSearchShowVendor || product.vendor === '') {
5532 return '';
5533 }
5534
5535 return [
5536 '<dt>',
5537 '<span class="visually-hidden">' + theme.strings.vendor + '</span>',
5538 '</dt>',
5539 '<dd class="predictive-search-item__vendor">' + product.vendor + '</dd>'
5540 ].join('');
5541 }
5542
5543 function normalizeProduct(product) {
5544 var productOrVariant =
5545 product.variants.length > 0 ? product.variants[0] : product;
5546
5547 return {
5548 url: productOrVariant.url,
5549 image: getProductImage(product),
5550 title: product.title,
5551 vendor: product.vendor || '',
5552 price: theme.Currency.formatMoney(product.price_min, theme.moneyFormat),
5553 compareAtPrice: theme.Currency.formatMoney(
5554 product.compare_at_price_min,
5555 theme.moneyFormat
5556 ),
5557 available: product.available,
5558 isOnSale: isOnSale(product),
5559 isPriceVaries: isPriceVaries(product),
5560 isCompareVaries: isCompareVaries(product)
5561 };
5562 }
5563
5564 function getProductImage(product) {
5565 var image;
5566 var featuredImage;
5567
5568 if (product.variants.length > 0 && product.variants[0].image !== null) {
5569 featuredImage = product.variants[0].featured_image;
5570 } else if (product.image) {
5571 featuredImage = product.featured_image;
5572 } else {
5573 image = null;
5574 }
5575
5576 if (image !== null) {
5577 image = {
5578 url: theme.Images.getSizedImageUrl(featuredImage.url, '100x'),
5579 alt: featuredImage.alt
5580 };
5581 }
5582
5583 return image;
5584 }
5585
5586 function isOnSale(product) {
5587 return (
5588 product.compare_at_price_min !== null &&
5589 parseInt(product.compare_at_price_min, 10) >
5590 parseInt(product.price_min, 10)
5591 );
5592 }
5593
5594 function isPriceVaries(product) {
5595 return product.price_max !== product.price_min;
5596 }
5597
5598 function isCompareVaries(product) {
5599 return product.compare_at_price_max !== product.compare_at_price_min;
5600 }
5601
5602 // Returns the number of optional product details to be shown,
5603 // values of the detailsList need to be boolean.
5604 function getDetailsCount() {
5605 var detailsList = [
5606 theme.settings.predictiveSearchShowPrice,
5607 theme.settings.predictiveSearchShowVendor
5608 ];
5609
5610 var detailsCount = detailsList.reduce(function(acc, detail) {
5611 return acc + (detail ? 1 : 0);
5612 }, 0);
5613
5614 return detailsCount;
5615 }
5616
5617 function getNumberOfResultsString(resultNumber, resultsCount) {
5618 return theme.strings.number_of_results
5619 .replace('[result_number]', resultNumber)
5620 .replace('[results_count]', resultsCount);
5621 }
5622
5623 function _htmlEscape(input) {
5624 return input
5625 .replace(/&/g, '&')
5626 .replace(/</g, '<')
5627 .replace(/>/g, '>')
5628 .replace(/"/g, '"')
5629 .replace(/'/g, ''');
5630 }
5631
5632 return function(data) {
5633 var products = data.products || [];
5634 var isLoading = data.isLoading;
5635 var searchQuery = data.searchQuery || '';
5636
5637 if (isLoading && products.length === 0) {
5638 return loadingState();
5639 }
5640
5641 return renderResults(products, isLoading, searchQuery);
5642 };
5643})();
5644
5645window.theme = window.theme || {};
5646
5647(function() {
5648 // (a11y) This function will be used by the Predictive Search Component
5649 // to announce the number of search results
5650 function numberOfResultsTemplateFct(data) {
5651 if (data.products.length === 1) {
5652 return theme.strings.one_result_found;
5653 } else {
5654 return theme.strings.number_of_results_found.replace(
5655 '[results_count]',
5656 data.products.length
5657 );
5658 }
5659 }
5660
5661 // (a11y) This function will be used by the Predictive Search Component
5662 // to announce that it's loading results
5663 function loadingResultsMessageTemplateFct() {
5664 return theme.strings.loading;
5665 }
5666
5667 function isPredictiveSearchSupported() {
5668 var shopifyFeatures = JSON.parse(
5669 document.getElementById('shopify-features').textContent
5670 );
5671
5672 return shopifyFeatures.predictiveSearch;
5673 }
5674
5675 function isPredictiveSearchEnabled() {
5676 return window.theme.settings.predictiveSearchEnabled;
5677 }
5678
5679 function canInitializePredictiveSearch() {
5680 return isPredictiveSearchSupported() && isPredictiveSearchEnabled();
5681 }
5682
5683 // listen for search submits and validate query
5684 function validateSearchHandler(searchEl, submitEl) {
5685 submitEl.addEventListener(
5686 'click',
5687 validateSearchInput.bind(this, searchEl)
5688 );
5689 }
5690
5691 // if there is nothing in the search field, prevent submit
5692 function validateSearchInput(searchEl, evt) {
5693 var isInputValueEmpty = searchEl.value.trim().length === 0;
5694 if (!isInputValueEmpty) {
5695 return;
5696 }
5697
5698 if (typeof evt !== 'undefined') {
5699 evt.preventDefault();
5700 }
5701
5702 searchEl.focus();
5703 }
5704
5705 window.theme.SearchPage = (function() {
5706 var selectors = {
5707 searchReset: '[data-search-page-predictive-search-clear]',
5708 searchInput: '[data-search-page-predictive-search-input]',
5709 searchSubmit: '[data-search-page-predictive-search-submit]',
5710 searchResults: '[data-predictive-search-mount="default"]'
5711 };
5712
5713 var componentInstance;
5714
5715 function init(config) {
5716 var searchInput = document.querySelector(selectors.searchInput);
5717 var searchSubmit = document.querySelector(selectors.searchSubmit);
5718 var searchUrl = searchInput.dataset.baseUrl;
5719
5720 componentInstance = new window.Shopify.theme.PredictiveSearchComponent({
5721 selectors: {
5722 input: selectors.searchInput,
5723 reset: selectors.searchReset,
5724 result: selectors.searchResults
5725 },
5726 searchUrl: searchUrl,
5727 resultTemplateFct: window.theme.SearchResultsTemplate,
5728 numberOfResultsTemplateFct: numberOfResultsTemplateFct,
5729 loadingResultsMessageTemplateFct: loadingResultsMessageTemplateFct,
5730 onOpen: function(nodes) {
5731 if (config.isTabletAndUp) {
5732 return;
5733 }
5734
5735 var searchInputBoundingRect = searchInput.getBoundingClientRect();
5736 var bodyHeight = document.body.offsetHeight;
5737 var offset = 50;
5738 var resultsMaxHeight =
5739 bodyHeight - searchInputBoundingRect.bottom - offset;
5740
5741 nodes.result.style.maxHeight = resultsMaxHeight + 'px';
5742 },
5743 onBeforeDestroy: function(nodes) {
5744 // If the viewport width changes from mobile to tablet
5745 // reset the top position of the results
5746 nodes.result.style.maxHeight = '';
5747 }
5748 });
5749
5750 validateSearchHandler(searchInput, searchSubmit);
5751 }
5752
5753 function unload() {
5754 if (!componentInstance) {
5755 return;
5756 }
5757 componentInstance.destroy();
5758 componentInstance = null;
5759 }
5760
5761 return {
5762 init: init,
5763 unload: unload
5764 };
5765 })();
5766
5767 window.theme.SearchHeader = (function() {
5768 var selectors = {
5769 searchInput: '[data-predictive-search-drawer-input]',
5770 searchResults: '[data-predictive-search-mount="drawer"]',
5771 searchFormContainer: '[data-search-form-container]',
5772 searchSubmit: '[data-search-form-submit]'
5773 };
5774
5775 var componentInstance;
5776
5777 function init(config) {
5778 var searchInput = document.querySelector(selectors.searchInput);
5779 var searchSubmit = document.querySelector(selectors.searchSubmit);
5780 var searchUrl = searchInput.dataset.baseUrl;
5781
5782 componentInstance = new window.Shopify.theme.PredictiveSearchComponent({
5783 selectors: {
5784 input: selectors.searchInput,
5785 result: selectors.searchResults
5786 },
5787 searchUrl: searchUrl,
5788 resultTemplateFct: window.theme.SearchResultsTemplate,
5789 numberOfResultsTemplateFct: numberOfResultsTemplateFct,
5790 numberOfResults: config.numberOfResults,
5791 loadingResultsMessageTemplateFct: loadingResultsMessageTemplateFct,
5792 onInputBlur: function() {
5793 return false;
5794 },
5795 onOpen: function(nodes) {
5796 var searchInputBoundingRect = searchInput.getBoundingClientRect();
5797
5798 // For tablet screens and up, stop the scroll area from extending past
5799 // the bottom of the screen because we're locking the body scroll
5800 var maxHeight =
5801 window.innerHeight -
5802 searchInputBoundingRect.bottom -
5803 (config.isTabletAndUp ? 20 : 0);
5804
5805 nodes.result.style.top = config.isTabletAndUp
5806 ? ''
5807 : searchInputBoundingRect.bottom + 'px';
5808 nodes.result.style.maxHeight = maxHeight + 'px';
5809 },
5810 onClose: function(nodes) {
5811 nodes.result.style.maxHeight = '';
5812 },
5813 onBeforeDestroy: function(nodes) {
5814 // If the viewport width changes from mobile to tablet
5815 // reset the top position of the results
5816 nodes.result.style.top = '';
5817 }
5818 });
5819
5820 validateSearchHandler(searchInput, searchSubmit);
5821 }
5822
5823 function unload() {
5824 if (!componentInstance) {
5825 return;
5826 }
5827
5828 componentInstance.destroy();
5829 componentInstance = null;
5830 }
5831
5832 function clearAndClose() {
5833 if (!componentInstance) {
5834 return;
5835 }
5836
5837 componentInstance.clearAndClose();
5838 }
5839
5840 return {
5841 init: init,
5842 unload: unload,
5843 clearAndClose: clearAndClose
5844 };
5845 })();
5846
5847 window.theme.Search = (function() {
5848 var classes = {
5849 searchTemplate: 'template-search'
5850 };
5851 var selectors = {
5852 siteHeader: '.site-header'
5853 };
5854 var mediaQueryList = {
5855 mobile: window.matchMedia('(max-width: 749px)'),
5856 tabletAndUp: window.matchMedia('(min-width: 750px)')
5857 };
5858
5859 function init() {
5860 if (!document.querySelector(selectors.siteHeader)) {
5861 return;
5862 }
5863
5864 if (!canInitializePredictiveSearch()) {
5865 return;
5866 }
5867
5868 Object.keys(mediaQueryList).forEach(function(device) {
5869 mediaQueryList[device].addListener(initSearchAccordingToViewport);
5870 });
5871
5872 initSearchAccordingToViewport();
5873 }
5874
5875 function initSearchAccordingToViewport() {
5876 theme.SearchDrawer.close();
5877 theme.SearchHeader.unload();
5878 theme.SearchPage.unload();
5879
5880 if (mediaQueryList.mobile.matches) {
5881 theme.SearchHeader.init({
5882 numberOfResults: 4,
5883 isTabletAndUp: false
5884 });
5885
5886 if (isSearchPage()) {
5887 theme.SearchPage.init({ isTabletAndUp: false });
5888 }
5889 } else {
5890 // Tablet and up
5891 theme.SearchHeader.init({
5892 numberOfResults: 4,
5893 isTabletAndUp: true
5894 });
5895
5896 if (isSearchPage()) {
5897 theme.SearchPage.init({ isTabletAndUp: true });
5898 }
5899 }
5900 }
5901
5902 function isSearchPage() {
5903 return document.body.classList.contains(classes.searchTemplate);
5904 }
5905
5906 function unload() {
5907 theme.SearchHeader.unload();
5908 theme.SearchPage.unload();
5909 }
5910
5911 return {
5912 init: init,
5913 unload: unload
5914 };
5915 })();
5916})();
5917
5918window.theme = window.theme || {};
5919
5920theme.SearchDrawer = (function() {
5921 var selectors = {
5922 headerSection: '[data-header-section]',
5923 drawer: '[data-predictive-search-drawer]',
5924 drawerOpenButton: '[data-predictive-search-open-drawer]',
5925 headerSearchInput: '[data-predictive-search-drawer-input]',
5926 predictiveSearchWrapper: '[data-predictive-search-mount="drawer"]'
5927 };
5928
5929 var drawerInstance;
5930
5931 function init() {
5932 setAccessibilityProps();
5933
5934 drawerInstance = new theme.Drawers('SearchDrawer', 'top', {
5935 onDrawerOpen: function() {
5936 setHeight();
5937 theme.MobileNav.closeMobileNav();
5938 lockBodyScroll();
5939 },
5940 onDrawerClose: function() {
5941 theme.SearchHeader.clearAndClose();
5942 var drawerOpenButton = document.querySelector(
5943 selectors.drawerOpenButton
5944 );
5945
5946 if (drawerOpenButton) drawerOpenButton.focus();
5947
5948 unlockBodyScroll();
5949 },
5950 withPredictiveSearch: true,
5951 elementToFocusOnOpen: document.querySelector(selectors.headerSearchInput)
5952 });
5953 }
5954
5955 function setAccessibilityProps() {
5956 var drawerOpenButton = document.querySelector(selectors.drawerOpenButton);
5957
5958 if (drawerOpenButton) {
5959 drawerOpenButton.setAttribute('aria-controls', 'SearchDrawer');
5960 drawerOpenButton.setAttribute('aria-expanded', 'false');
5961 drawerOpenButton.setAttribute('aria-controls', 'dialog');
5962 }
5963 }
5964
5965 function setHeight() {
5966 var searchDrawer = document.querySelector(selectors.drawer);
5967 var headerHeight = document.querySelector(selectors.headerSection)
5968 .offsetHeight;
5969
5970 searchDrawer.style.height = headerHeight + 'px';
5971 }
5972
5973 function close() {
5974 drawerInstance.close();
5975 }
5976
5977 function lockBodyScroll() {
5978 theme.Helpers.enableScrollLock();
5979 }
5980
5981 function unlockBodyScroll() {
5982 theme.Helpers.disableScrollLock();
5983 }
5984
5985 return {
5986 init: init,
5987 close: close
5988 };
5989})();
5990
5991theme.Disclosure = (function() {
5992 var selectors = {
5993 disclosureForm: '[data-disclosure-form]',
5994 disclosureList: '[data-disclosure-list]',
5995 disclosureToggle: '[data-disclosure-toggle]',
5996 disclosureInput: '[data-disclosure-input]',
5997 disclosureOptions: '[data-disclosure-option]'
5998 };
5999
6000 var classes = {
6001 listVisible: 'disclosure-list--visible'
6002 };
6003
6004 function Disclosure(disclosure) {
6005 this.container = disclosure;
6006 this._cacheSelectors();
6007 this._setupListeners();
6008 }
6009
6010 Disclosure.prototype = Object.assign({}, Disclosure.prototype, {
6011 _cacheSelectors: function() {
6012 this.cache = {
6013 disclosureForm: this.container.closest(selectors.disclosureForm),
6014 disclosureList: this.container.querySelector(selectors.disclosureList),
6015 disclosureToggle: this.container.querySelector(
6016 selectors.disclosureToggle
6017 ),
6018 disclosureInput: this.container.querySelector(
6019 selectors.disclosureInput
6020 ),
6021 disclosureOptions: this.container.querySelectorAll(
6022 selectors.disclosureOptions
6023 )
6024 };
6025 },
6026
6027 _setupListeners: function() {
6028 this.eventHandlers = this._setupEventHandlers();
6029
6030 this.cache.disclosureToggle.addEventListener(
6031 'click',
6032 this.eventHandlers.toggleList
6033 );
6034
6035 this.cache.disclosureOptions.forEach(function(disclosureOption) {
6036 disclosureOption.addEventListener(
6037 'click',
6038 this.eventHandlers.connectOptions
6039 );
6040 }, this);
6041
6042 this.container.addEventListener(
6043 'keyup',
6044 this.eventHandlers.onDisclosureKeyUp
6045 );
6046
6047 this.cache.disclosureList.addEventListener(
6048 'focusout',
6049 this.eventHandlers.onDisclosureListFocusOut
6050 );
6051
6052 this.cache.disclosureToggle.addEventListener(
6053 'focusout',
6054 this.eventHandlers.onDisclosureToggleFocusOut
6055 );
6056
6057 document.body.addEventListener('click', this.eventHandlers.onBodyClick);
6058 },
6059
6060 _setupEventHandlers: function() {
6061 return {
6062 connectOptions: this._connectOptions.bind(this),
6063 toggleList: this._toggleList.bind(this),
6064 onBodyClick: this._onBodyClick.bind(this),
6065 onDisclosureKeyUp: this._onDisclosureKeyUp.bind(this),
6066 onDisclosureListFocusOut: this._onDisclosureListFocusOut.bind(this),
6067 onDisclosureToggleFocusOut: this._onDisclosureToggleFocusOut.bind(this)
6068 };
6069 },
6070
6071 _connectOptions: function(event) {
6072 event.preventDefault();
6073
6074 this._submitForm(event.currentTarget.dataset.value);
6075 },
6076
6077 _onDisclosureToggleFocusOut: function(event) {
6078 var disclosureLostFocus =
6079 this.container.contains(event.relatedTarget) === false;
6080
6081 if (disclosureLostFocus) {
6082 this._hideList();
6083 }
6084 },
6085
6086 _onDisclosureListFocusOut: function(event) {
6087 var childInFocus = event.currentTarget.contains(event.relatedTarget);
6088
6089 var isVisible = this.cache.disclosureList.classList.contains(
6090 classes.listVisible
6091 );
6092
6093 if (isVisible && !childInFocus) {
6094 this._hideList();
6095 }
6096 },
6097
6098 _onDisclosureKeyUp: function(event) {
6099 if (event.which !== slate.utils.keyboardKeys.ESCAPE) return;
6100 this._hideList();
6101 this.cache.disclosureToggle.focus();
6102 },
6103
6104 _onBodyClick: function(event) {
6105 var isOption = this.container.contains(event.target);
6106 var isVisible = this.cache.disclosureList.classList.contains(
6107 classes.listVisible
6108 );
6109
6110 if (isVisible && !isOption) {
6111 this._hideList();
6112 }
6113 },
6114
6115 _submitForm: function(value) {
6116 this.cache.disclosureInput.value = value;
6117 this.cache.disclosureForm.submit();
6118 },
6119
6120 _hideList: function() {
6121 this.cache.disclosureList.classList.remove(classes.listVisible);
6122 this.cache.disclosureToggle.setAttribute('aria-expanded', false);
6123 },
6124
6125 _toggleList: function() {
6126 var ariaExpanded =
6127 this.cache.disclosureToggle.getAttribute('aria-expanded') === 'true';
6128 this.cache.disclosureList.classList.toggle(classes.listVisible);
6129 this.cache.disclosureToggle.setAttribute('aria-expanded', !ariaExpanded);
6130 },
6131
6132 destroy: function() {
6133 this.cache.disclosureToggle.removeEventListener(
6134 'click',
6135 this.eventHandlers.toggleList
6136 );
6137
6138 this.cache.disclosureOptions.forEach(function(disclosureOption) {
6139 disclosureOption.removeEventListener(
6140 'click',
6141 this.eventHandlers.connectOptions
6142 );
6143 }, this);
6144
6145 this.container.removeEventListener(
6146 'keyup',
6147 this.eventHandlers.onDisclosureKeyUp
6148 );
6149
6150 this.cache.disclosureList.removeEventListener(
6151 'focusout',
6152 this.eventHandlers.onDisclosureListFocusOut
6153 );
6154
6155 this.cache.disclosureToggle.removeEventListener(
6156 'focusout',
6157 this.eventHandlers.onDisclosureToggleFocusOut
6158 );
6159
6160 document.body.removeEventListener(
6161 'click',
6162 this.eventHandlers.onBodyClick
6163 );
6164 }
6165 });
6166
6167 return Disclosure;
6168})();
6169
6170theme.Zoom = (function() {
6171 var selectors = {
6172 imageZoom: '[data-image-zoom]'
6173 };
6174
6175 var classes = {
6176 zoomImg: 'zoomImg'
6177 };
6178
6179 var attributes = {
6180 imageZoomTarget: 'data-image-zoom-target'
6181 };
6182
6183 function Zoom(container) {
6184 this.container = container;
6185 this.cache = {};
6186 this.url = container.dataset.zoom;
6187
6188 this._cacheSelectors();
6189
6190 if (!this.cache.sourceImage) return;
6191
6192 this._duplicateImage();
6193 }
6194
6195 Zoom.prototype = Object.assign({}, Zoom.prototype, {
6196 _cacheSelectors: function() {
6197 this.cache = {
6198 sourceImage: this.container.querySelector(selectors.imageZoom)
6199 };
6200 },
6201
6202 _init: function() {
6203 var targetWidth = this.cache.targetImage.width;
6204 var targetHeight = this.cache.targetImage.height;
6205
6206 if (this.cache.sourceImage === this.cache.targetImage) {
6207 this.sourceWidth = targetWidth;
6208 this.sourceHeight = targetHeight;
6209 } else {
6210 this.sourceWidth = this.cache.sourceImage.width;
6211 this.sourceHeight = this.cache.sourceImage.height;
6212 }
6213
6214 this.xRatio =
6215 (this.cache.sourceImage.width - targetWidth) / this.sourceWidth;
6216 this.yRatio =
6217 (this.cache.sourceImage.height - targetHeight) / this.sourceHeight;
6218 },
6219
6220 _start: function(e) {
6221 this._init();
6222 this._move(e);
6223 },
6224
6225 _stop: function() {
6226 this.cache.targetImage.style.opacity = 0;
6227 },
6228
6229 /**
6230 * Sets the correct coordinates top and left position in px
6231 * It sets a limit within between 0 and the max height of the image
6232 * So when the mouse leaves the target image, it could
6233 * never go above or beyond the target image zone
6234 */
6235 _setTopLeftMaxValues: function(top, left) {
6236 return {
6237 left: Math.max(Math.min(left, this.sourceWidth), 0),
6238 top: Math.max(Math.min(top, this.sourceHeight), 0)
6239 };
6240 },
6241
6242 _move: function(e) {
6243 // get left and top position within the "source image" zone
6244 var left =
6245 e.pageX -
6246 (this.cache.sourceImage.getBoundingClientRect().left + window.scrollX);
6247 var top =
6248 e.pageY -
6249 (this.cache.sourceImage.getBoundingClientRect().top + window.scrollY);
6250 // make sure the left and top position don't go
6251 // above or beyond the target image zone
6252 var position = this._setTopLeftMaxValues(top, left);
6253
6254 top = position.top;
6255 left = position.left;
6256
6257 this.cache.targetImage.style.left = -(left * -this.xRatio) + 'px';
6258 this.cache.targetImage.style.top = -(top * -this.yRatio) + 'px';
6259 this.cache.targetImage.style.opacity = 1;
6260 },
6261
6262 /**
6263 * This loads a high resolution image
6264 * via the data attributes url
6265 * It adds all necessary CSS styles and adds to the container
6266 */
6267 _duplicateImage: function() {
6268 this._loadImage()
6269 .then(
6270 function(image) {
6271 this.cache.targetImage = image;
6272 image.style.width = image.width + 'px';
6273 image.style.height = image.height + 'px';
6274 image.style.position = 'absolute';
6275 image.style.maxWidth = 'none';
6276 image.style.maxHeight = 'none';
6277 image.style.opacity = 0;
6278 image.style.border = 'none';
6279 image.style.left = 0;
6280 image.style.top = 0;
6281
6282 this.container.appendChild(image);
6283
6284 this._init();
6285
6286 this._start = this._start.bind(this);
6287 this._stop = this._stop.bind(this);
6288 this._move = this._move.bind(this);
6289
6290 this.container.addEventListener('mouseenter', this._start);
6291 this.container.addEventListener('mouseleave', this._stop);
6292 this.container.addEventListener('mousemove', this._move);
6293
6294 this.container.style.position = 'relative';
6295 this.container.style.overflow = 'hidden';
6296 }.bind(this)
6297 )
6298 .catch(function(error) {
6299 // eslint-disable-next-line no-console
6300 console.warn('Error fetching image', error);
6301 });
6302 },
6303
6304 _loadImage: function() {
6305 // eslint-disable-next-line
6306 return new Promise(function(resolve, reject) {
6307 var image = new Image();
6308 image.setAttribute('role', 'presentation');
6309 image.setAttribute(attributes.imageZoomTarget, true);
6310 image.classList.add(classes.zoomImg);
6311 image.src = this.url;
6312
6313 image.addEventListener('load', function() {
6314 resolve(image);
6315 });
6316
6317 image.addEventListener('error', function(error) {
6318 reject(error);
6319 });
6320 }.bind(this)
6321 );
6322 },
6323
6324 unload: function() {
6325 var targetImage = this.container.querySelector(
6326 '[' + attributes.imageZoomTarget + ']'
6327 );
6328 if (targetImage) {
6329 targetImage.remove();
6330 }
6331
6332 this.container.removeEventListener('mouseenter', this._start);
6333 this.container.removeEventListener('mouseleave', this._stop);
6334 this.container.removeEventListener('mousemove', this._move);
6335 }
6336 });
6337
6338 return Zoom;
6339})();
6340
6341
6342/* ================ TEMPLATES ================ */
6343(function() {
6344 var filterBys = document.querySelectorAll('[data-blog-tag-filter]');
6345
6346 if (!filterBys.length) return;
6347
6348 slate.utils.resizeSelects(filterBys);
6349
6350 filterBys.forEach(function(filterBy) {
6351 filterBy.addEventListener('change', function(evt) {
6352 location.href = evt.target.value;
6353 });
6354 });
6355})();
6356
6357window.theme = theme || {};
6358
6359theme.customerTemplates = (function() {
6360 var selectors = {
6361 RecoverHeading: '#RecoverHeading',
6362 RecoverEmail: '#RecoverEmail',
6363 LoginHeading: '#LoginHeading'
6364 };
6365
6366 function initEventListeners() {
6367 this.recoverHeading = document.querySelector(selectors.RecoverHeading);
6368 this.recoverEmail = document.querySelector(selectors.RecoverEmail);
6369 this.loginHeading = document.querySelector(selectors.LoginHeading);
6370 var recoverPassword = document.getElementById('RecoverPassword');
6371 var hideRecoverPasswordLink = document.getElementById(
6372 'HideRecoverPasswordLink'
6373 );
6374
6375 // Show reset password form
6376 if (recoverPassword) {
6377 recoverPassword.addEventListener(
6378 'click',
6379 function(evt) {
6380 evt.preventDefault();
6381 showRecoverPasswordForm();
6382 this.recoverHeading.setAttribute('tabindex', '-1');
6383 this.recoverHeading.focus();
6384 }.bind(this)
6385 );
6386 }
6387
6388 // Hide reset password form
6389 if (hideRecoverPasswordLink) {
6390 hideRecoverPasswordLink.addEventListener(
6391 'click',
6392 function(evt) {
6393 evt.preventDefault();
6394 hideRecoverPasswordForm();
6395 this.loginHeading.setAttribute('tabindex', '-1');
6396 this.loginHeading.focus();
6397 }.bind(this)
6398 );
6399 }
6400
6401 if (this.recoverHeading) {
6402 this.recoverHeading.addEventListener('blur', function(evt) {
6403 evt.target.removeAttribute('tabindex');
6404 });
6405 }
6406
6407 if (this.loginHeading) {
6408 this.loginHeading.addEventListener('blur', function(evt) {
6409 evt.target.removeAttribute('tabindex');
6410 });
6411 }
6412 }
6413
6414 /**
6415 *
6416 * Show/Hide recover password form
6417 *
6418 */
6419
6420 function showRecoverPasswordForm() {
6421 document.getElementById('RecoverPasswordForm').classList.remove('hide');
6422 document.getElementById('CustomerLoginForm').classList.add('hide');
6423
6424 if (this.recoverEmail.getAttribute('aria-invalid') === 'true') {
6425 this.recoverEmail.focus();
6426 }
6427 }
6428
6429 function hideRecoverPasswordForm() {
6430 document.getElementById('RecoverPasswordForm').classList.add('hide');
6431 document.getElementById('CustomerLoginForm').classList.remove('hide');
6432 }
6433
6434 /**
6435 *
6436 * Show reset password success message
6437 *
6438 */
6439 function resetPasswordSuccess() {
6440 var formState = document.querySelector('.reset-password-success');
6441
6442 // check if reset password form was successfully submited.
6443 if (!formState) {
6444 return;
6445 }
6446
6447 // show success message
6448 var resetSuccess = document.getElementById('ResetSuccess');
6449 resetSuccess.classList.remove('hide');
6450 resetSuccess.focus();
6451 }
6452
6453 /**
6454 *
6455 * Show/hide customer address forms
6456 *
6457 */
6458 function customerAddressForm() {
6459 var newAddressForm = document.getElementById('AddressNewForm');
6460 var newAddressFormButton = document.getElementById('AddressNewButton');
6461
6462 if (!newAddressForm) {
6463 return;
6464 }
6465
6466 // Initialize observers on address selectors, defined in shopify_common.js
6467 if (Shopify) {
6468 // eslint-disable-next-line no-new
6469 new Shopify.CountryProvinceSelector(
6470 'AddressCountryNew',
6471 'AddressProvinceNew',
6472 {
6473 hideElement: 'AddressProvinceContainerNew'
6474 }
6475 );
6476 }
6477
6478 // Initialize each edit form's country/province selector
6479 document
6480 .querySelectorAll('.address-country-option')
6481 .forEach(function(option) {
6482 var formId = option.dataset.formId;
6483 var countrySelector = 'AddressCountry_' + formId;
6484 var provinceSelector = 'AddressProvince_' + formId;
6485 var containerSelector = 'AddressProvinceContainer_' + formId;
6486
6487 // eslint-disable-next-line no-new
6488 new Shopify.CountryProvinceSelector(countrySelector, provinceSelector, {
6489 hideElement: containerSelector
6490 });
6491 });
6492
6493 // Toggle new/edit address forms
6494 document.querySelectorAll('.address-new-toggle').forEach(function(button) {
6495 button.addEventListener('click', function() {
6496 var isExpanded =
6497 newAddressFormButton.getAttribute('aria-expanded') === 'true';
6498
6499 newAddressForm.classList.toggle('hide');
6500 newAddressFormButton.setAttribute('aria-expanded', !isExpanded);
6501 newAddressFormButton.focus();
6502 });
6503 });
6504
6505 document.querySelectorAll('.address-edit-toggle').forEach(function(button) {
6506 button.addEventListener('click', function(evt) {
6507 var formId = evt.target.dataset.formId;
6508 var editButton = document.getElementById('EditFormButton_' + formId);
6509 var editAddress = document.getElementById('EditAddress_' + formId);
6510 var isExpanded = editButton.getAttribute('aria-expanded') === 'true';
6511
6512 editAddress.classList.toggle('hide');
6513 editButton.setAttribute('aria-expanded', !isExpanded);
6514 editButton.focus();
6515 });
6516 });
6517
6518 document.querySelectorAll('.address-delete').forEach(function(button) {
6519 button.addEventListener('click', function(evt) {
6520 var target = evt.target.dataset.target;
6521 var confirmMessage = evt.target.dataset.confirmMessage;
6522
6523 // eslint-disable-next-line no-alert
6524 if (
6525 confirm(
6526 confirmMessage || 'Are you sure you wish to delete this address?'
6527 )
6528 ) {
6529 Shopify.postLink(target, {
6530 parameters: { _method: 'delete' }
6531 });
6532 }
6533 });
6534 });
6535 }
6536
6537 /**
6538 *
6539 * Check URL for reset password hash
6540 *
6541 */
6542 function checkUrlHash() {
6543 var hash = window.location.hash;
6544
6545 // Allow deep linking to recover password form
6546 if (hash === '#recover') {
6547 showRecoverPasswordForm.bind(this)();
6548 }
6549 }
6550
6551 return {
6552 init: function() {
6553 initEventListeners();
6554 checkUrlHash();
6555 resetPasswordSuccess();
6556 customerAddressForm();
6557 }
6558 };
6559})();
6560
6561
6562/*================ SECTIONS ================*/
6563window.theme = window.theme || {};
6564
6565theme.Cart = (function() {
6566 var selectors = {
6567 cartCount: '[data-cart-count]',
6568 cartCountBubble: '[data-cart-count-bubble]',
6569 cartDiscount: '[data-cart-discount]',
6570 cartDiscountTitle: '[data-cart-discount-title]',
6571 cartDiscountAmount: '[data-cart-discount-amount]',
6572 cartDiscountWrapper: '[data-cart-discount-wrapper]',
6573 cartErrorMessage: '[data-cart-error-message]',
6574 cartErrorMessageWrapper: '[data-cart-error-message-wrapper]',
6575 cartItem: '[data-cart-item]',
6576 cartItemDetails: '[data-cart-item-details]',
6577 cartItemDiscount: '[data-cart-item-discount]',
6578 cartItemDiscountedPriceGroup: '[data-cart-item-discounted-price-group]',
6579 cartItemDiscountTitle: '[data-cart-item-discount-title]',
6580 cartItemDiscountAmount: '[data-cart-item-discount-amount]',
6581 cartItemDiscountList: '[data-cart-item-discount-list]',
6582 cartItemFinalPrice: '[data-cart-item-final-price]',
6583 cartItemImage: '[data-cart-item-image]',
6584 cartItemLinePrice: '[data-cart-item-line-price]',
6585 cartItemOriginalPrice: '[data-cart-item-original-price]',
6586 cartItemPrice: '[data-cart-item-price]',
6587 cartItemPriceList: '[data-cart-item-price-list]',
6588 cartItemProperty: '[data-cart-item-property]',
6589 cartItemPropertyName: '[data-cart-item-property-name]',
6590 cartItemPropertyValue: '[data-cart-item-property-value]',
6591 cartItemRegularPriceGroup: '[data-cart-item-regular-price-group]',
6592 cartItemRegularPrice: '[data-cart-item-regular-price]',
6593 cartItemTitle: '[data-cart-item-title]',
6594 cartItemOption: '[data-cart-item-option]',
6595 cartItemSellingPlanName: '[data-cart-item-selling-plan-name]',
6596 cartLineItems: '[data-cart-line-items]',
6597 cartNote: '[data-cart-notes]',
6598 cartQuantityErrorMessage: '[data-cart-quantity-error-message]',
6599 cartQuantityErrorMessageWrapper:
6600 '[data-cart-quantity-error-message-wrapper]',
6601 cartRemove: '[data-cart-remove]',
6602 cartStatus: '[data-cart-status]',
6603 cartSubtotal: '[data-cart-subtotal]',
6604 cartTableCell: '[data-cart-table-cell]',
6605 cartWrapper: '[data-cart-wrapper]',
6606 emptyPageContent: '[data-empty-page-content]',
6607 quantityInput: '[data-quantity-input]',
6608 quantityInputMobile: '[data-quantity-input-mobile]',
6609 quantityInputDesktop: '[data-quantity-input-desktop]',
6610 quantityLabelMobile: '[data-quantity-label-mobile]',
6611 quantityLabelDesktop: '[data-quantity-label-desktop]',
6612 inputQty: '[data-quantity-input]',
6613 thumbnails: '.cart__image',
6614 unitPrice: '[data-unit-price]',
6615 unitPriceBaseUnit: '[data-unit-price-base-unit]',
6616 unitPriceGroup: '[data-unit-price-group]'
6617 };
6618
6619 var classes = {
6620 cartNoCookies: 'cart--no-cookies',
6621 cartRemovedProduct: 'cart__removed-product',
6622 thumbnails: 'cart__image',
6623 hide: 'hide',
6624 inputError: 'input--error'
6625 };
6626
6627 var attributes = {
6628 cartItemIndex: 'data-cart-item-index',
6629 cartItemKey: 'data-cart-item-key',
6630 cartItemQuantity: 'data-cart-item-quantity',
6631 cartItemTitle: 'data-cart-item-title',
6632 cartItemUrl: 'data-cart-item-url',
6633 quantityItem: 'data-quantity-item'
6634 };
6635
6636 var mediumUpQuery = '(min-width: ' + theme.breakpoints.medium + 'px)';
6637
6638 function Cart(container) {
6639 this.container = container;
6640 this.thumbnails = this.container.querySelectorAll(selectors.thumbnails);
6641 this.quantityInputs = this.container.querySelectorAll(selectors.inputQty);
6642 this.ajaxEnabled =
6643 this.container.getAttribute('data-ajax-enabled') === 'true';
6644
6645 this.cartRoutes = JSON.parse(
6646 document.querySelector('[data-cart-routes]').innerHTML
6647 );
6648
6649 this._handleInputQty = theme.Helpers.debounce(
6650 this._handleInputQty.bind(this),
6651 500
6652 );
6653 this.setQuantityFormControllers = this.setQuantityFormControllers.bind(
6654 this
6655 );
6656 this._onNoteChange = this._onNoteChange.bind(this);
6657 this._onRemoveItem = this._onRemoveItem.bind(this);
6658
6659 if (!theme.Helpers.cookiesEnabled()) {
6660 this.container.classList.add(classes.cartNoCookies);
6661 }
6662
6663 this.thumbnails.forEach(function(element) {
6664 element.style.cursor = 'pointer';
6665 });
6666
6667 this.container.addEventListener('click', this._handleThumbnailClick);
6668 this.container.addEventListener('change', this._handleInputQty);
6669
6670 this.mql = window.matchMedia(mediumUpQuery);
6671 this.mql.addListener(this.setQuantityFormControllers);
6672
6673 this.setQuantityFormControllers();
6674
6675 if (this.ajaxEnabled) {
6676 /**
6677 * Because the entire cart is recreated when a cart item is updated,
6678 * we cannot cache the elements in the cart. Instead, we add the event
6679 * listeners on the cart's container to allow us to retain the event
6680 * listeners after rebuilding the cart when an item is updated.
6681 */
6682 this.container.addEventListener('click', this._onRemoveItem);
6683 this.container.addEventListener('change', this._onNoteChange);
6684
6685 this._setupCartTemplates();
6686 }
6687 }
6688
6689 Cart.prototype = Object.assign({}, Cart.prototype, {
6690 _setupCartTemplates: function() {
6691 var cartItem = this.container.querySelector(selectors.cartItem);
6692 if (!cartItem) return;
6693
6694 this.itemTemplate = this.container
6695 .querySelector(selectors.cartItem)
6696 .cloneNode(true);
6697
6698 this.itemDiscountTemplate = this.itemTemplate
6699 .querySelector(selectors.cartItemDiscount)
6700 .cloneNode(true);
6701
6702 this.cartDiscountTemplate = this.container
6703 .querySelector(selectors.cartDiscount)
6704 .cloneNode(true);
6705
6706 this.itemPriceListTemplate = this.itemTemplate
6707 .querySelector(selectors.cartItemPriceList)
6708 .cloneNode(true);
6709
6710 this.itemOptionTemplate = this.itemTemplate
6711 .querySelector(selectors.cartItemOption)
6712 .cloneNode(true);
6713
6714 this.itemPropertyTemplate = this.itemTemplate
6715 .querySelector(selectors.cartItemProperty)
6716 .cloneNode(true);
6717
6718 this.itemSellingPlanNameTemplate = this.itemTemplate
6719 .querySelector(selectors.cartItemSellingPlanName)
6720 .cloneNode(true);
6721 },
6722
6723 _handleInputQty: function(evt) {
6724 if (!evt.target.hasAttribute('data-quantity-input')) return;
6725
6726 var input = evt.target;
6727 var itemElement = input.closest(selectors.cartItem);
6728
6729 var itemIndex = Number(input.getAttribute('data-quantity-item'));
6730
6731 var itemQtyInputs = this.container.querySelectorAll(
6732 "[data-quantity-item='" + itemIndex + "']"
6733 );
6734
6735 var value = parseInt(input.value);
6736
6737 var isValidValue = !(value < 0 || isNaN(value));
6738
6739 itemQtyInputs.forEach(function(element) {
6740 element.value = value;
6741 });
6742
6743 this._hideCartError();
6744 this._hideQuantityErrorMessage();
6745
6746 if (!isValidValue) {
6747 this._showQuantityErrorMessages(itemElement);
6748 return;
6749 }
6750
6751 if (isValidValue && this.ajaxEnabled) {
6752 this._updateItemQuantity(itemIndex, itemElement, itemQtyInputs, value);
6753 }
6754 },
6755
6756 _updateItemQuantity: function(
6757 itemIndex,
6758 itemElement,
6759 itemQtyInputs,
6760 value
6761 ) {
6762 var key = itemElement.getAttribute(attributes.cartItemKey);
6763 var index = Number(itemElement.getAttribute(attributes.cartItemIndex));
6764
6765 var request = {
6766 method: 'POST',
6767 headers: {
6768 'Content-Type': 'application/json;'
6769 },
6770 body: JSON.stringify({
6771 line: index,
6772 quantity: value
6773 })
6774 };
6775
6776 fetch(this.cartRoutes.cartChangeUrl + '.js', request)
6777 .then(function(response) {
6778 return response.json();
6779 })
6780 .then(
6781 function(state) {
6782 this._setCartCountBubble(state.item_count);
6783
6784 if (!state.item_count) {
6785 this._emptyCart();
6786 return;
6787 }
6788
6789 // Cache current in-focus element, used later to restore focus
6790 var inFocus = document.activeElement;
6791
6792 this._createCart(state);
6793
6794 if (!value) {
6795 this._showRemoveMessage(itemElement.cloneNode(true));
6796 return;
6797 }
6798
6799 var item = this.getItem(key, state);
6800
6801 this._updateLiveRegion(item);
6802
6803 // Restore focus to the "equivalent" element after the DOM has been updated
6804 if (!inFocus) return;
6805 var row = inFocus.closest('[' + attributes.cartItemIndex + ']');
6806 if (!row) return;
6807 var target = this.container.querySelector(
6808 '[' +
6809 attributes.cartItemIndex +
6810 '="' +
6811 row.getAttribute(attributes.cartItemIndex) +
6812 '"] [data-role="' +
6813 inFocus.getAttribute('data-role') +
6814 '"]'
6815 );
6816 if (!target) return;
6817 target.focus();
6818 }.bind(this)
6819 )
6820 .catch(
6821 function() {
6822 this._showCartError(null);
6823 }.bind(this)
6824 );
6825 },
6826
6827 getItem: function(key, state) {
6828 return state.items.find(function(item) {
6829 return item.key === key;
6830 });
6831 },
6832
6833 _liveRegionText: function(item) {
6834 // Dummy content for live region
6835 var liveRegionText =
6836 theme.strings.update +
6837 ': [QuantityLabel]: [Quantity], [Regular] [$$] [DiscountedPrice] [$]. [PriceInformation]';
6838
6839 // Update Quantity
6840 liveRegionText = liveRegionText
6841 .replace('[QuantityLabel]', theme.strings.quantity)
6842 .replace('[Quantity]', item.quantity);
6843
6844 // Update pricing information
6845 var regularLabel = '';
6846 var regularPrice = theme.Currency.formatMoney(
6847 item.original_line_price,
6848 theme.moneyFormat
6849 );
6850 var discountLabel = '';
6851 var discountPrice = '';
6852 var discountInformation = '';
6853
6854 if (item.original_line_price > item.final_line_price) {
6855 regularLabel = theme.strings.regularTotal;
6856
6857 discountLabel = theme.strings.discountedTotal;
6858 discountPrice = theme.Currency.formatMoney(
6859 item.final_line_price,
6860 theme.moneyFormat
6861 );
6862
6863 discountInformation = theme.strings.priceColumn;
6864 }
6865
6866 liveRegionText = liveRegionText
6867 .replace('[Regular]', regularLabel)
6868 .replace('[$$]', regularPrice)
6869 .replace('[DiscountedPrice]', discountLabel)
6870 .replace('[$]', discountPrice)
6871 .replace('[PriceInformation]', discountInformation)
6872 .trim();
6873
6874 return liveRegionText;
6875 },
6876
6877 _updateLiveRegion: function(item) {
6878 if (!item) return;
6879
6880 var liveRegion = this.container.querySelector(selectors.cartStatus);
6881
6882 liveRegion.textContent = this._liveRegionText(item);
6883 liveRegion.setAttribute('aria-hidden', false);
6884
6885 setTimeout(function() {
6886 liveRegion.setAttribute('aria-hidden', true);
6887 }, 1000);
6888 },
6889
6890 _createCart: function(state) {
6891 var cartDiscountList = this._createCartDiscountList(state);
6892
6893 var cartTable = this.container.querySelector(selectors.cartLineItems);
6894 cartTable.innerHTML = '';
6895
6896 this._createLineItemList(state).forEach(function(lineItem) {
6897 cartTable.appendChild(lineItem);
6898 });
6899
6900 this.setQuantityFormControllers();
6901
6902 this.cartNotes =
6903 this.cartNotes || this.container.querySelector(selectors.cartNote);
6904
6905 if (this.cartNotes) {
6906 this.cartNotes.value = state.note;
6907 }
6908
6909 var discountWrapper = this.container.querySelector(
6910 selectors.cartDiscountWrapper
6911 );
6912
6913 if (cartDiscountList.length === 0) {
6914 discountWrapper.innerHTML = '';
6915 discountWrapper.classList.add(classes.hide);
6916 } else {
6917 discountWrapper.innerHTML = '';
6918
6919 cartDiscountList.forEach(function(discountItem) {
6920 discountWrapper.appendChild(discountItem);
6921 });
6922
6923 discountWrapper.classList.remove(classes.hide);
6924 }
6925
6926 this.container.querySelector(
6927 selectors.cartSubtotal
6928 ).innerHTML = theme.Currency.formatMoney(
6929 state.total_price,
6930 theme.moneyFormatWithCurrency
6931 );
6932 },
6933
6934 _createCartDiscountList: function(cart) {
6935 return cart.cart_level_discount_applications.map(
6936 function(discount) {
6937 var discountNode = this.cartDiscountTemplate.cloneNode(true);
6938
6939 discountNode.querySelector(selectors.cartDiscountTitle).textContent =
6940 discount.title;
6941
6942 discountNode.querySelector(
6943 selectors.cartDiscountAmount
6944 ).innerHTML = theme.Currency.formatMoney(
6945 discount.total_allocated_amount,
6946 theme.moneyFormat
6947 );
6948
6949 return discountNode;
6950 }.bind(this)
6951 );
6952 },
6953
6954 _createLineItemList: function(state) {
6955 return state.items.map(
6956 function(item, index) {
6957 var itemNode = this.itemTemplate.cloneNode(true);
6958
6959 var itemPriceList = this.itemPriceListTemplate.cloneNode(true);
6960
6961 this._setLineItemAttributes(itemNode, item, index);
6962 this._setLineItemImage(itemNode, item.featured_image);
6963
6964 var cartItemTitle = itemNode.querySelector(selectors.cartItemTitle);
6965 cartItemTitle.textContent = item.product_title;
6966 cartItemTitle.setAttribute('href', item.url);
6967
6968 var selling_plan_name = item.selling_plan_allocation
6969 ? item.selling_plan_allocation.selling_plan.name
6970 : null;
6971
6972 var productDetailsList = this._createProductDetailsList(
6973 item.product_has_only_default_variant,
6974 item.options_with_values,
6975 item.properties,
6976 selling_plan_name
6977 );
6978
6979 this._setProductDetailsList(itemNode, productDetailsList);
6980
6981 this._setItemRemove(itemNode, item.title);
6982
6983 itemPriceList.innerHTML = this._createItemPrice(
6984 item.original_price,
6985 item.final_price
6986 ).outerHTML;
6987
6988 if (item.unit_price_measurement) {
6989 itemPriceList.appendChild(
6990 this._createUnitPrice(
6991 item.unit_price,
6992 item.unit_price_measurement
6993 )
6994 );
6995 }
6996
6997 this._setItemPrice(itemNode, itemPriceList);
6998
6999 var itemDiscountList = this._createItemDiscountList(item);
7000 this._setItemDiscountList(itemNode, itemDiscountList);
7001 this._setQuantityInputs(itemNode, item, index);
7002
7003 var itemLinePrice = this._createItemPrice(
7004 item.original_line_price,
7005 item.final_line_price
7006 );
7007
7008 this._setItemLinePrice(itemNode, itemLinePrice);
7009
7010 return itemNode;
7011 }.bind(this)
7012 );
7013 },
7014
7015 _setLineItemAttributes: function(itemNode, item, index) {
7016 itemNode.setAttribute(attributes.cartItemKey, item.key);
7017 itemNode.setAttribute(attributes.cartItemUrl, item.url);
7018 itemNode.setAttribute(attributes.cartItemTitle, item.title);
7019 itemNode.setAttribute(attributes.cartItemIndex, index + 1);
7020 itemNode.setAttribute(attributes.cartItemQuantity, item.quantity);
7021 },
7022
7023 _setLineItemImage: function(itemNode, featuredImage) {
7024 var image = itemNode.querySelector(selectors.cartItemImage);
7025
7026 var sizedImageUrl =
7027 featuredImage.url !== null
7028 ? theme.Images.getSizedImageUrl(featuredImage.url, 'x190')
7029 : null;
7030
7031 if (sizedImageUrl) {
7032 image.setAttribute('alt', featuredImage.alt);
7033 image.setAttribute('src', sizedImageUrl);
7034 image.classList.remove(classes.hide);
7035 } else {
7036 image.parentNode.removeChild(image);
7037 }
7038 },
7039
7040 _setProductDetailsList: function(item, productDetailsList) {
7041 var itemDetails = item.querySelector(selectors.cartItemDetails);
7042
7043 if (productDetailsList.length) {
7044 itemDetails.classList.remove(classes.hide);
7045 itemDetails.innerHTML = productDetailsList.reduce(function(
7046 result,
7047 element
7048 ) {
7049 return result + element.outerHTML;
7050 },
7051 '');
7052
7053 return;
7054 }
7055
7056 itemDetails.classList.add(classes.hide);
7057 itemDetails.textContent = '';
7058 },
7059
7060 _setItemPrice: function(item, price) {
7061 item.querySelector(selectors.cartItemPrice).innerHTML = price.outerHTML;
7062 },
7063
7064 _setItemDiscountList: function(item, discountList) {
7065 var itemDiscountList = item.querySelector(selectors.cartItemDiscountList);
7066
7067 if (discountList.length === 0) {
7068 itemDiscountList.innerHTML = '';
7069 itemDiscountList.classList.add(classes.hide);
7070 } else {
7071 itemDiscountList.innerHTML = discountList.reduce(function(
7072 result,
7073 element
7074 ) {
7075 return result + element.outerHTML;
7076 },
7077 '');
7078
7079 itemDiscountList.classList.remove(classes.hide);
7080 }
7081 },
7082
7083 _setItemRemove: function(item, title) {
7084 item
7085 .querySelector(selectors.cartRemove)
7086 .setAttribute(
7087 'aria-label',
7088 theme.strings.removeLabel.replace('[product]', title)
7089 );
7090 },
7091
7092 _setQuantityInputs: function(itemNode, item, index) {
7093 var mobileInput = itemNode.querySelector(selectors.quantityInputMobile);
7094 var desktopInput = itemNode.querySelector(selectors.quantityInputDesktop);
7095
7096 mobileInput.setAttribute('id', 'updates_' + item.key);
7097 desktopInput.setAttribute('id', 'updates_large_' + item.key);
7098
7099 [mobileInput, desktopInput].forEach(function(element) {
7100 element.setAttribute(attributes.quantityItem, index + 1);
7101 element.value = item.quantity;
7102 });
7103
7104 itemNode
7105 .querySelector(selectors.quantityLabelMobile)
7106 .setAttribute('for', 'updates_' + item.key);
7107
7108 itemNode
7109 .querySelector(selectors.quantityLabelDesktop)
7110 .setAttribute('for', 'updates_large_' + item.key);
7111 },
7112
7113 setQuantityFormControllers: function() {
7114 var desktopQuantityInputs = document.querySelectorAll(
7115 selectors.quantityInputDesktop
7116 );
7117
7118 var mobileQuantityInputs = document.querySelectorAll(
7119 selectors.quantityInputMobile
7120 );
7121
7122 if (this.mql.matches) {
7123 addNameAttribute(desktopQuantityInputs);
7124 removeNameAttribute(mobileQuantityInputs);
7125 } else {
7126 addNameAttribute(mobileQuantityInputs);
7127 removeNameAttribute(desktopQuantityInputs);
7128 }
7129
7130 function addNameAttribute(inputs) {
7131 inputs.forEach(function(element) {
7132 element.setAttribute('name', 'updates[]');
7133 });
7134 }
7135
7136 function removeNameAttribute(inputs) {
7137 inputs.forEach(function(element) {
7138 element.removeAttribute('name');
7139 });
7140 }
7141 },
7142
7143 _setItemLinePrice: function(item, price) {
7144 item.querySelector(selectors.cartItemLinePrice).innerHTML =
7145 price.outerHTML;
7146 },
7147
7148 _createProductDetailsList: function(
7149 product_has_only_default_variant,
7150 options,
7151 properties,
7152 selling_plan_name
7153 ) {
7154 var optionsPropertiesHTML = [];
7155
7156 if (!product_has_only_default_variant) {
7157 optionsPropertiesHTML = optionsPropertiesHTML.concat(
7158 this._getOptionList(options)
7159 );
7160 }
7161
7162 if (selling_plan_name) {
7163 optionsPropertiesHTML = optionsPropertiesHTML.concat(
7164 this._getSellingPlanName(selling_plan_name)
7165 );
7166 }
7167
7168 if (properties !== null && Object.keys(properties).length !== 0) {
7169 optionsPropertiesHTML = optionsPropertiesHTML.concat(
7170 this._getPropertyList(properties)
7171 );
7172 }
7173
7174 return optionsPropertiesHTML;
7175 },
7176
7177 _getOptionList: function(options) {
7178 return options.map(
7179 function(option) {
7180 var optionElement = this.itemOptionTemplate.cloneNode(true);
7181
7182 optionElement.textContent = option.name + ': ' + option.value;
7183 optionElement.classList.remove(classes.hide);
7184
7185 return optionElement;
7186 }.bind(this)
7187 );
7188 },
7189
7190 _getPropertyList: function(properties) {
7191 var propertiesArray =
7192 properties !== null ? Object.entries(properties) : [];
7193
7194 var filteredPropertiesArray = propertiesArray.filter(function(property) {
7195 // Line item properties prefixed with an underscore are not to be displayed
7196 // if the property value has a length of 0 (empty), don't display it
7197 if (property[0].charAt(0) === '_' || property[1].length === 0) {
7198 return false;
7199 } else {
7200 return true;
7201 }
7202 });
7203
7204 return filteredPropertiesArray.map(
7205 function(property) {
7206 var propertyElement = this.itemPropertyTemplate.cloneNode(true);
7207
7208 propertyElement.querySelector(
7209 selectors.cartItemPropertyName
7210 ).textContent = property[0] + ': ';
7211
7212 if (property[0].indexOf('/uploads/') === -1) {
7213 propertyElement.querySelector(
7214 selectors.cartItemPropertyValue
7215 ).textContent = property[1];
7216 } else {
7217 propertyElement.querySelector(
7218 selectors.cartItemPropertyValue
7219 ).innerHTML =
7220 '<a href="' +
7221 property[1] +
7222 '"> ' +
7223 property[1].split('/').pop() +
7224 '</a>';
7225 }
7226
7227 propertyElement.classList.remove(classes.hide);
7228
7229 return propertyElement;
7230 }.bind(this)
7231 );
7232 },
7233
7234 _getSellingPlanName: function(selling_plan_name) {
7235 var sellingPlanNameElement = this.itemSellingPlanNameTemplate.cloneNode(
7236 true
7237 );
7238
7239 sellingPlanNameElement.textContent = selling_plan_name;
7240 sellingPlanNameElement.classList.remove(classes.hide);
7241
7242 return sellingPlanNameElement;
7243 },
7244
7245 _createItemPrice: function(original_price, final_price) {
7246 var originalPriceHTML = theme.Currency.formatMoney(
7247 original_price,
7248 theme.moneyFormat
7249 );
7250
7251 var resultHTML;
7252
7253 if (original_price !== final_price) {
7254 resultHTML = this.itemPriceListTemplate
7255 .querySelector(selectors.cartItemDiscountedPriceGroup)
7256 .cloneNode(true);
7257
7258 resultHTML.querySelector(
7259 selectors.cartItemOriginalPrice
7260 ).innerHTML = originalPriceHTML;
7261
7262 resultHTML.querySelector(
7263 selectors.cartItemFinalPrice
7264 ).innerHTML = theme.Currency.formatMoney(
7265 final_price,
7266 theme.moneyFormat
7267 );
7268 } else {
7269 resultHTML = this.itemPriceListTemplate
7270 .querySelector(selectors.cartItemRegularPriceGroup)
7271 .cloneNode(true);
7272
7273 resultHTML.querySelector(
7274 selectors.cartItemRegularPrice
7275 ).innerHTML = originalPriceHTML;
7276 }
7277
7278 resultHTML.classList.remove(classes.hide);
7279 return resultHTML;
7280 },
7281
7282 _createUnitPrice: function(unitPrice, unitPriceMeasurement) {
7283 var unitPriceGroup = this.itemPriceListTemplate
7284 .querySelector(selectors.unitPriceGroup)
7285 .cloneNode(true);
7286
7287 var unitPriceBaseUnit =
7288 (unitPriceMeasurement.reference_value !== 1
7289 ? unitPriceMeasurement.reference_value
7290 : '') + unitPriceMeasurement.reference_unit;
7291
7292 unitPriceGroup.querySelector(
7293 selectors.unitPriceBaseUnit
7294 ).textContent = unitPriceBaseUnit;
7295
7296 unitPriceGroup.querySelector(
7297 selectors.unitPrice
7298 ).innerHTML = theme.Currency.formatMoney(unitPrice, theme.moneyFormat);
7299
7300 unitPriceGroup.classList.remove(classes.hide);
7301
7302 return unitPriceGroup;
7303 },
7304
7305 _createItemDiscountList: function(item) {
7306 return item.line_level_discount_allocations.map(
7307 function(discount) {
7308 var discountNode = this.itemDiscountTemplate.cloneNode(true);
7309
7310 discountNode.querySelector(
7311 selectors.cartItemDiscountTitle
7312 ).textContent = discount.discount_application.title;
7313
7314 discountNode.querySelector(
7315 selectors.cartItemDiscountAmount
7316 ).innerHTML = theme.Currency.formatMoney(
7317 discount.amount,
7318 theme.moneyFormat
7319 );
7320
7321 return discountNode;
7322 }.bind(this)
7323 );
7324 },
7325
7326 _showQuantityErrorMessages: function(itemElement) {
7327 itemElement
7328 .querySelectorAll(selectors.cartQuantityErrorMessage)
7329 .forEach(function(element) {
7330 element.textContent = theme.strings.quantityMinimumMessage;
7331 });
7332
7333 itemElement
7334 .querySelectorAll(selectors.cartQuantityErrorMessageWrapper)
7335 .forEach(function(element) {
7336 element.classList.remove(classes.hide);
7337 });
7338
7339 itemElement
7340 .querySelectorAll(selectors.inputQty)
7341 .forEach(function(element) {
7342 element.classList.add(classes.inputError);
7343 element.focus();
7344 });
7345 },
7346
7347 _hideQuantityErrorMessage: function() {
7348 var errorMessages = document.querySelectorAll(
7349 selectors.cartQuantityErrorMessageWrapper
7350 );
7351
7352 errorMessages.forEach(function(element) {
7353 element.classList.add(classes.hide);
7354
7355 element.querySelector(selectors.cartQuantityErrorMessage).textContent =
7356 '';
7357 });
7358
7359 this.container
7360 .querySelectorAll(selectors.inputQty)
7361 .forEach(function(element) {
7362 element.classList.remove(classes.inputError);
7363 });
7364 },
7365
7366 _handleThumbnailClick: function(evt) {
7367 if (!evt.target.classList.contains(classes.thumbnails)) return;
7368
7369 window.location.href = evt.target
7370 .closest(selectors.cartItem)
7371 .getAttribute('data-cart-item-url');
7372 },
7373
7374 _onNoteChange: function(evt) {
7375 if (!evt.target.hasAttribute('data-cart-notes')) return;
7376
7377 var note = evt.target.value;
7378 this._hideCartError();
7379 this._hideQuantityErrorMessage();
7380
7381 var headers = new Headers({ 'Content-Type': 'application/json' });
7382
7383 var request = {
7384 method: 'POST',
7385 headers: headers,
7386 body: JSON.stringify({ note: note })
7387 };
7388
7389 fetch('/cart/update.js', request).catch(
7390 function() {
7391 this._showCartError(evt.target);
7392 }.bind(this)
7393 );
7394 },
7395
7396 _showCartError: function(elementToFocus) {
7397 document.querySelector(selectors.cartErrorMessage).textContent =
7398 theme.strings.cartError;
7399
7400 document
7401 .querySelector(selectors.cartErrorMessageWrapper)
7402 .classList.remove(classes.hide);
7403
7404 if (!elementToFocus) return;
7405 elementToFocus.focus();
7406 },
7407
7408 _hideCartError: function() {
7409 document
7410 .querySelector(selectors.cartErrorMessageWrapper)
7411 .classList.add(classes.hide);
7412 document.querySelector(selectors.cartErrorMessage).textContent = '';
7413 },
7414
7415 _onRemoveItem: function(evt) {
7416 if (!evt.target.hasAttribute('data-cart-remove')) return;
7417
7418 evt.preventDefault();
7419 var lineItem = evt.target.closest(selectors.cartItem);
7420 var index = Number(lineItem.getAttribute(attributes.cartItemIndex));
7421
7422 this._hideCartError();
7423
7424 var request = {
7425 method: 'POST',
7426 headers: {
7427 'Content-Type': 'application/json;'
7428 },
7429 body: JSON.stringify({
7430 line: index,
7431 quantity: 0
7432 })
7433 };
7434
7435 fetch(this.cartRoutes.cartChangeUrl + '.js', request)
7436 .then(function(response) {
7437 return response.json();
7438 })
7439 .then(
7440 function(state) {
7441 if (state.item_count === 0) {
7442 this._emptyCart();
7443 } else {
7444 this._createCart(state);
7445 this._showRemoveMessage(lineItem.cloneNode(true));
7446 }
7447
7448 this._setCartCountBubble(state.item_count);
7449 }.bind(this)
7450 )
7451 .catch(
7452 function() {
7453 this._showCartError(null);
7454 }.bind(this)
7455 );
7456 },
7457
7458 _showRemoveMessage: function(lineItem) {
7459 var index = lineItem.getAttribute('data-cart-item-index');
7460 var removeMessage = this._getRemoveMessage(lineItem);
7461
7462 if (index - 1 === 0) {
7463 this.container
7464 .querySelector('[data-cart-item-index="1"]')
7465 .insertAdjacentHTML('beforebegin', removeMessage.outerHTML);
7466 } else {
7467 this.container
7468 .querySelector("[data-cart-item-index='" + (index - 1) + "']")
7469 .insertAdjacentHTML('afterend', removeMessage.outerHTML);
7470 }
7471
7472 this.container.querySelector('[data-removed-item-row]').focus();
7473 },
7474
7475 _getRemoveMessage: function(lineItem) {
7476 var formattedMessage = this._formatRemoveMessage(lineItem);
7477
7478 var tableCell = lineItem
7479 .querySelector(selectors.cartTableCell)
7480 .cloneNode(true);
7481
7482 tableCell.removeAttribute('class');
7483 tableCell.classList.add(classes.cartRemovedProduct);
7484 tableCell.setAttribute('colspan', '4');
7485 tableCell.innerHTML = formattedMessage;
7486
7487 lineItem.setAttribute('role', 'alert');
7488 lineItem.setAttribute('tabindex', '-1');
7489 lineItem.setAttribute('data-removed-item-row', true);
7490 lineItem.innerHTML = tableCell.outerHTML;
7491
7492 return lineItem;
7493 },
7494
7495 _formatRemoveMessage: function(lineItem) {
7496 var quantity = lineItem.getAttribute('data-cart-item-quantity');
7497 var url = lineItem.getAttribute(attributes.cartItemUrl);
7498 var title = lineItem.getAttribute(attributes.cartItemTitle);
7499
7500 return theme.strings.removedItemMessage
7501 .replace('[quantity]', quantity)
7502 .replace(
7503 '[link]',
7504 '<a ' +
7505 'href="' +
7506 url +
7507 '" class="text-link text-link--accent">' +
7508 title +
7509 '</a>'
7510 );
7511 },
7512
7513 _setCartCountBubble: function(quantity) {
7514 this.cartCountBubble =
7515 this.cartCountBubble ||
7516 document.querySelector(selectors.cartCountBubble);
7517
7518 this.cartCount =
7519 this.cartCount || document.querySelector(selectors.cartCount);
7520
7521 if (quantity > 0) {
7522 this.cartCountBubble.classList.remove(classes.hide);
7523 this.cartCount.textContent = quantity;
7524 } else {
7525 this.cartCountBubble.classList.add(classes.hide);
7526 this.cartCount.textContent = '';
7527 }
7528 },
7529
7530 _emptyCart: function() {
7531 this.emptyPageContent =
7532 this.emptyPageContent ||
7533 this.container.querySelector(selectors.emptyPageContent);
7534
7535 this.cartWrapper =
7536 this.cartWrapper || this.container.querySelector(selectors.cartWrapper);
7537
7538 this.emptyPageContent.classList.remove(classes.hide);
7539 this.cartWrapper.classList.add(classes.hide);
7540 }
7541 });
7542
7543 return Cart;
7544})();
7545
7546window.theme = window.theme || {};
7547
7548theme.Filters = (function() {
7549 var settings = {
7550 mediaQueryMediumUp: '(min-width: ' + theme.breakpoints.medium + 'px)'
7551 };
7552
7553 var selectors = {
7554 filterSelection: '#FilterTags',
7555 sortSelection: '#SortBy',
7556 selectInput: '[data-select-input]'
7557 };
7558
7559 function Filters(container) {
7560 this.filterSelect = container.querySelector(selectors.filterSelection);
7561 this.sortSelect = container.querySelector(selectors.sortSelection);
7562
7563 this.selects = document.querySelectorAll(selectors.selectInput);
7564
7565 if (this.sortSelect) {
7566 this.defaultSort = this._getDefaultSortValue();
7567 }
7568
7569 if (this.selects.length) {
7570 this.selects.forEach(function(select) {
7571 select.classList.remove('hidden');
7572 });
7573 }
7574
7575 this.initBreakpoints = this._initBreakpoints.bind(this);
7576
7577 this.mql = window.matchMedia(settings.mediaQueryMediumUp);
7578 this.mql.addListener(this.initBreakpoints);
7579
7580 if (this.filterSelect) {
7581 this.filterSelect.addEventListener(
7582 'change',
7583 this._onFilterChange.bind(this)
7584 );
7585 }
7586
7587 if (this.sortSelect) {
7588 this.sortSelect.addEventListener('change', this._onSortChange.bind(this));
7589 }
7590
7591 theme.Helpers.promiseStylesheet().then(
7592 function() {
7593 this._initBreakpoints();
7594 }.bind(this)
7595 );
7596 this._initParams();
7597 }
7598
7599 Filters.prototype = Object.assign({}, Filters.prototype, {
7600 _initBreakpoints: function() {
7601 if (this.mql.matches) {
7602 slate.utils.resizeSelects(this.selects);
7603 }
7604 },
7605
7606 _initParams: function() {
7607 this.queryParams = {};
7608 if (location.search.length) {
7609 var aKeyValue;
7610 var aCouples = location.search.substr(1).split('&');
7611 for (var i = 0; i < aCouples.length; i++) {
7612 aKeyValue = aCouples[i].split('=');
7613 if (aKeyValue.length > 1) {
7614 this.queryParams[
7615 decodeURIComponent(aKeyValue[0])
7616 ] = decodeURIComponent(aKeyValue[1]);
7617 }
7618 }
7619 }
7620 },
7621
7622 _onSortChange: function() {
7623 this.queryParams.sort_by = this._getSortValue();
7624
7625 if (this.queryParams.page) {
7626 delete this.queryParams.page;
7627 }
7628
7629 window.location.search = decodeURIComponent(
7630 new URLSearchParams(Object.entries(this.queryParams)).toString()
7631 );
7632 },
7633
7634 _onFilterChange: function() {
7635 document.location.href = this._getFilterValue();
7636 },
7637
7638 _getFilterValue: function() {
7639 return this.filterSelect.value;
7640 },
7641
7642 _getSortValue: function() {
7643 return this.sortSelect.value || this.defaultSort;
7644 },
7645
7646 _getDefaultSortValue: function() {
7647 return this.sortSelect.dataset.defaultSortby;
7648 },
7649
7650 onUnload: function() {
7651 if (this.filterSelect) {
7652 this.filterSelect.removeEventListener('change', this._onFilterChange);
7653 }
7654
7655 if (this.sortSelect) {
7656 this.sortSelect.removeEventListener('change', this._onSortChange);
7657 }
7658
7659 this.mql.removeListener(this.initBreakpoints);
7660 }
7661 });
7662
7663 return Filters;
7664})();
7665
7666window.theme = window.theme || {};
7667
7668theme.HeaderSection = (function() {
7669 function Header() {
7670 theme.Header.init();
7671 theme.MobileNav.init();
7672 theme.SearchDrawer.init();
7673 theme.Search.init();
7674 }
7675
7676 Header.prototype = Object.assign({}, Header.prototype, {
7677 onUnload: function() {
7678 theme.Header.unload();
7679 theme.Search.unload();
7680 theme.MobileNav.unload();
7681 }
7682 });
7683
7684 return Header;
7685})();
7686
7687theme.Maps = (function() {
7688 var config = {
7689 zoom: 14
7690 };
7691 var apiStatus = null;
7692 var mapsToLoad = [];
7693
7694 var errors = {
7695 addressNoResults: theme.strings.addressNoResults,
7696 addressQueryLimit: theme.strings.addressQueryLimit,
7697 addressError: theme.strings.addressError,
7698 authError: theme.strings.authError
7699 };
7700
7701 var selectors = {
7702 section: '[data-section-type="map"]',
7703 map: '[data-map]',
7704 mapOverlay: '[data-map-overlay]'
7705 };
7706
7707 var classes = {
7708 mapError: 'map-section--load-error',
7709 errorMsg: 'map-section__error errors text-center'
7710 };
7711
7712 // Global function called by Google on auth errors.
7713 // Show an auto error message on all map instances.
7714 // eslint-disable-next-line camelcase, no-unused-vars
7715 window.gm_authFailure = function() {
7716 if (!Shopify.designMode) {
7717 return;
7718 }
7719
7720 document.querySelector(selectors.section).classList.add(classes.mapError);
7721 document.querySelector(selectors.map).remove();
7722 document
7723 .querySelector(selectors.mapOverlay)
7724 .insertAdjacentHTML(
7725 'afterend',
7726 '<div class="' +
7727 classes.errorMsg +
7728 '">' +
7729 theme.strings.authError +
7730 '</div>'
7731 );
7732 };
7733
7734 function Map(container) {
7735 this.map = container.querySelector(selectors.map);
7736 if (!this.map) return;
7737 this.key = this.map.dataset.apiKey;
7738
7739 if (typeof this.key === 'undefined') {
7740 return;
7741 }
7742
7743 if (apiStatus === 'loaded') {
7744 this.createMap();
7745 } else {
7746 mapsToLoad.push(this);
7747
7748 if (apiStatus !== 'loading') {
7749 apiStatus = 'loading';
7750 if (typeof window.google === 'undefined') {
7751 theme.Helpers.getScript(
7752 'https://maps.googleapis.com/maps/api/js?key=' + this.key
7753 ).then(function() {
7754 apiStatus = 'loaded';
7755 initAllMaps();
7756 });
7757 }
7758 }
7759 }
7760 }
7761
7762 function initAllMaps() {
7763 // API has loaded, load all Map instances in queue
7764 mapsToLoad.forEach(function(map) {
7765 map.createMap();
7766 });
7767 }
7768
7769 function geolocate(map) {
7770 return new Promise(function(resolve, reject) {
7771 var geocoder = new google.maps.Geocoder();
7772 var address = map.dataset.addressSetting;
7773
7774 geocoder.geocode({ address: address }, function(results, status) {
7775 if (status !== google.maps.GeocoderStatus.OK) {
7776 reject(status);
7777 }
7778
7779 resolve(results);
7780 });
7781 });
7782 }
7783
7784 Map.prototype = Object.assign({}, Map.prototype, {
7785 createMap: function() {
7786 return geolocate(this.map)
7787 .then(
7788 function(results) {
7789 var mapOptions = {
7790 zoom: config.zoom,
7791 center: results[0].geometry.location,
7792 draggable: false,
7793 clickableIcons: false,
7794 scrollwheel: false,
7795 disableDoubleClickZoom: true,
7796 disableDefaultUI: true
7797 };
7798
7799 var map = (this.map = new google.maps.Map(this.map, mapOptions));
7800 var center = (this.center = map.getCenter());
7801
7802 //eslint-disable-next-line no-unused-vars
7803 var marker = new google.maps.Marker({
7804 map: map,
7805 position: map.getCenter()
7806 });
7807
7808 google.maps.event.addDomListener(
7809 window,
7810 'resize',
7811 theme.Helpers.debounce(
7812 function() {
7813 google.maps.event.trigger(map, 'resize');
7814 map.setCenter(center);
7815 this.map.removeAttribute('style');
7816 }.bind(this),
7817 250
7818 )
7819 );
7820 }.bind(this)
7821 )
7822 .catch(
7823 function() {
7824 var errorMessage;
7825
7826 switch (status) {
7827 case 'ZERO_RESULTS':
7828 errorMessage = errors.addressNoResults;
7829 break;
7830 case 'OVER_QUERY_LIMIT':
7831 errorMessage = errors.addressQueryLimit;
7832 break;
7833 case 'REQUEST_DENIED':
7834 errorMessage = errors.authError;
7835 break;
7836 default:
7837 errorMessage = errors.addressError;
7838 break;
7839 }
7840
7841 // Show errors only to merchant in the editor.
7842 if (Shopify.designMode) {
7843 this.map.parentNode.classList.add(classes.mapError);
7844 this.map.parentNode.innerHTML =
7845 '<div class="' +
7846 classes.errorMsg +
7847 '">' +
7848 errorMessage +
7849 '</div>';
7850 }
7851 }.bind(this)
7852 );
7853 },
7854
7855 onUnload: function() {
7856 if (this.map) {
7857 google.maps.event.clearListeners(this.map, 'resize');
7858 }
7859 }
7860 });
7861
7862 return Map;
7863})();
7864
7865/* eslint-disable no-new */
7866theme.Product = (function() {
7867 function Product(container) {
7868 this.container = container;
7869 var sectionId = container.getAttribute('data-section-id');
7870 this.zoomPictures = [];
7871 this.ajaxEnabled = container.getAttribute('data-ajax-enabled') === 'true';
7872
7873 this.settings = {
7874 // Breakpoints from src/stylesheets/global/variables.scss.liquid
7875 mediaQueryMediumUp: 'screen and (min-width: 750px)',
7876 mediaQuerySmall: 'screen and (max-width: 749px)',
7877 bpSmall: false,
7878 enableHistoryState:
7879 container.getAttribute('data-enable-history-state') === 'true',
7880 namespace: '.slideshow-' + sectionId,
7881 sectionId: sectionId,
7882 sliderActive: false,
7883 zoomEnabled: false
7884 };
7885
7886 this.selectors = {
7887 addToCart: '[data-add-to-cart]',
7888 addToCartText: '[data-add-to-cart-text]',
7889 cartCount: '[data-cart-count]',
7890 cartCountBubble: '[data-cart-count-bubble]',
7891 cartPopup: '[data-cart-popup]',
7892 cartPopupCartQuantity: '[data-cart-popup-cart-quantity]',
7893 cartPopupClose: '[data-cart-popup-close]',
7894 cartPopupDismiss: '[data-cart-popup-dismiss]',
7895 cartPopupImage: '[data-cart-popup-image]',
7896 cartPopupImageWrapper: '[data-cart-popup-image-wrapper]',
7897 cartPopupImagePlaceholder: '[data-image-loading-animation]',
7898 cartPopupProductDetails: '[data-cart-popup-product-details]',
7899 cartPopupQuantity: '[data-cart-popup-quantity]',
7900 cartPopupQuantityLabel: '[data-cart-popup-quantity-label]',
7901 cartPopupTitle: '[data-cart-popup-title]',
7902 cartPopupWrapper: '[data-cart-popup-wrapper]',
7903 loader: '[data-loader]',
7904 loaderStatus: '[data-loader-status]',
7905 quantity: '[data-quantity-input]',
7906 SKU: '.variant-sku',
7907 productStatus: '[data-product-status]',
7908 originalSelectorId: '#ProductSelect-' + sectionId,
7909 productForm: '[data-product-form]',
7910 errorMessage: '[data-error-message]',
7911 errorMessageWrapper: '[data-error-message-wrapper]',
7912 imageZoomWrapper: '[data-image-zoom-wrapper]',
7913 productMediaWrapper: '[data-product-single-media-wrapper]',
7914 productThumbImages: '.product-single__thumbnail--' + sectionId,
7915 productThumbs: '.product-single__thumbnails-' + sectionId,
7916 productThumbListItem: '.product-single__thumbnails-item',
7917 productThumbsWrapper: '.thumbnails-wrapper',
7918 saleLabel: '.product-price__sale-label-' + sectionId,
7919 singleOptionSelector: '.single-option-selector-' + sectionId,
7920 shopifyPaymentButton: '.shopify-payment-button',
7921 productMediaTypeVideo: '[data-product-media-type-video]',
7922 productMediaTypeModel: '[data-product-media-type-model]',
7923 priceContainer: '[data-price]',
7924 regularPrice: '[data-regular-price]',
7925 salePrice: '[data-sale-price]',
7926 unitPrice: '[data-unit-price]',
7927 unitPriceBaseUnit: '[data-unit-price-base-unit]',
7928 productPolicies: '[data-product-policies]',
7929 storeAvailabilityContainer: '[data-store-availability-container]'
7930 };
7931
7932 this.classes = {
7933 cartPopupWrapperHidden: 'cart-popup-wrapper--hidden',
7934 hidden: 'hide',
7935 visibilityHidden: 'visibility-hidden',
7936 inputError: 'input--error',
7937 jsZoomEnabled: 'js-zoom-enabled',
7938 productOnSale: 'price--on-sale',
7939 productUnitAvailable: 'price--unit-available',
7940 productUnavailable: 'price--unavailable',
7941 productSoldOut: 'price--sold-out',
7942 cartImage: 'cart-popup-item__image',
7943 productFormErrorMessageWrapperHidden:
7944 'product-form__error-message-wrapper--hidden',
7945 activeClass: 'active-thumb',
7946 variantSoldOut: 'product-form--variant-sold-out'
7947 };
7948
7949 this.eventHandlers = {};
7950
7951 this.quantityInput = container.querySelector(this.selectors.quantity);
7952 this.errorMessageWrapper = container.querySelector(
7953 this.selectors.errorMessageWrapper
7954 );
7955 this.productForm = container.querySelector(this.selectors.productForm);
7956 this.addToCart = container.querySelector(this.selectors.addToCart);
7957 this.addToCartText = this.addToCart.querySelector(
7958 this.selectors.addToCartText
7959 );
7960 this.shopifyPaymentButton = container.querySelector(
7961 this.selectors.shopifyPaymentButton
7962 );
7963 this.priceContainer = container.querySelector(
7964 this.selectors.priceContainer
7965 );
7966 this.productPolicies = container.querySelector(
7967 this.selectors.productPolicies
7968 );
7969 this.storeAvailabilityContainer = container.querySelector(
7970 this.selectors.storeAvailabilityContainer
7971 );
7972 if (this.storeAvailabilityContainer) {
7973 this._initStoreAvailability();
7974 }
7975
7976 this.loader = this.addToCart.querySelector(this.selectors.loader);
7977 this.loaderStatus = container.querySelector(this.selectors.loaderStatus);
7978
7979 this.imageZoomWrapper = container.querySelectorAll(
7980 this.selectors.imageZoomWrapper
7981 );
7982
7983 // Stop parsing if we don't have the product json script tag when loading
7984 // section in the Theme Editor
7985 var productJson = document.getElementById('ProductJson-' + sectionId);
7986 if (!productJson || !productJson.innerHTML.length) {
7987 return;
7988 }
7989
7990 this.productSingleObject = JSON.parse(productJson.innerHTML);
7991
7992 // Initial state for global productState object
7993 this.productState = {
7994 available: true,
7995 soldOut: false,
7996 onSale: false,
7997 showUnitPrice: false
7998 };
7999
8000 this.settings.zoomEnabled =
8001 this.imageZoomWrapper.length > 0
8002 ? this.imageZoomWrapper[0].classList.contains(
8003 this.classes.jsZoomEnabled
8004 )
8005 : false;
8006
8007 this.cartRoutes = JSON.parse(
8008 document.querySelector('[data-cart-routes]').innerHTML
8009 );
8010
8011 this.initMobileBreakpoint = this._initMobileBreakpoint.bind(this);
8012 this.initDesktopBreakpoint = this._initDesktopBreakpoint.bind(this);
8013
8014 this.mqlSmall = window.matchMedia(this.settings.mediaQuerySmall);
8015 this.mqlSmall.addListener(this.initMobileBreakpoint);
8016
8017 this.mqlMediumUp = window.matchMedia(this.settings.mediaQueryMediumUp);
8018 this.mqlMediumUp.addListener(this.initDesktopBreakpoint);
8019
8020 this.initMobileBreakpoint();
8021 this.initDesktopBreakpoint();
8022 this._stringOverrides();
8023 this._initVariants();
8024 this._initMediaSwitch();
8025 this._initAddToCart();
8026 this._setActiveThumbnail();
8027 this._initProductVideo();
8028 this._initModelViewerLibraries();
8029 this._initShopifyXrLaunch();
8030 }
8031
8032 Product.prototype = Object.assign({}, Product.prototype, {
8033 _stringOverrides: function() {
8034 theme.productStrings = theme.productStrings || {};
8035 theme.strings = Object.assign({}, theme.strings, theme.productStrings);
8036 },
8037
8038 _initStoreAvailability: function() {
8039 this.storeAvailability = new theme.StoreAvailability(
8040 this.storeAvailabilityContainer
8041 );
8042
8043 var storeAvailabilityModalOpenedCallback = function(event) {
8044 if (
8045 this.cartPopupWrapper &&
8046 !this.cartPopupWrapper.classList.contains(
8047 this.classes.cartPopupWrapperHidden
8048 )
8049 ) {
8050 this._hideCartPopup(event);
8051 }
8052 };
8053
8054 // hide cart popup modal if the store availability modal is also opened
8055 this.storeAvailabilityContainer.addEventListener(
8056 'storeAvailabilityModalOpened',
8057 storeAvailabilityModalOpenedCallback.bind(this)
8058 );
8059 },
8060
8061 _initMobileBreakpoint: function() {
8062 if (this.mqlSmall.matches) {
8063 // initialize thumbnail slider on mobile if more than four thumbnails
8064 if (
8065 this.container.querySelectorAll(this.selectors.productThumbImages)
8066 .length > 4
8067 ) {
8068 this._initThumbnailSlider();
8069 }
8070
8071 // destroy image zooming if enabled
8072 if (this.settings.zoomEnabled) {
8073 this.imageZoomWrapper.forEach(
8074 function(element, index) {
8075 this._destroyZoom(index);
8076 }.bind(this)
8077 );
8078 }
8079
8080 this.settings.bpSmall = true;
8081 } else {
8082 if (this.settings.sliderActive) {
8083 this._destroyThumbnailSlider();
8084 }
8085
8086 this.settings.bpSmall = false;
8087 }
8088 },
8089
8090 _initDesktopBreakpoint: function() {
8091 if (this.mqlMediumUp.matches && this.settings.zoomEnabled) {
8092 this.imageZoomWrapper.forEach(
8093 function(element, index) {
8094 this._enableZoom(element, index);
8095 }.bind(this)
8096 );
8097 }
8098 },
8099
8100 _initVariants: function() {
8101 var options = {
8102 container: this.container,
8103 enableHistoryState:
8104 this.container.getAttribute('data-enable-history-state') === 'true',
8105 singleOptionSelector: this.selectors.singleOptionSelector,
8106 originalSelectorId: this.selectors.originalSelectorId,
8107 product: this.productSingleObject
8108 };
8109
8110 this.variants = new slate.Variants(options);
8111 if (this.storeAvailability && this.variants.currentVariant.available) {
8112 this.storeAvailability.updateContent(this.variants.currentVariant.id);
8113 }
8114
8115 this.eventHandlers.updateAvailability = this._updateAvailability.bind(
8116 this
8117 );
8118 this.eventHandlers.updateMedia = this._updateMedia.bind(this);
8119 this.eventHandlers.updatePrice = this._updatePrice.bind(this);
8120 this.eventHandlers.updateSKU = this._updateSKU.bind(this);
8121
8122 this.container.addEventListener(
8123 'variantChange',
8124 this.eventHandlers.updateAvailability
8125 );
8126 this.container.addEventListener(
8127 'variantImageChange',
8128 this.eventHandlers.updateMedia
8129 );
8130 this.container.addEventListener(
8131 'variantPriceChange',
8132 this.eventHandlers.updatePrice
8133 );
8134 this.container.addEventListener(
8135 'variantSKUChange',
8136 this.eventHandlers.updateSKU
8137 );
8138 },
8139
8140 _initMediaSwitch: function() {
8141 if (!document.querySelector(this.selectors.productThumbImages)) {
8142 return;
8143 }
8144
8145 var self = this;
8146
8147 var productThumbImages = document.querySelectorAll(
8148 this.selectors.productThumbImages
8149 );
8150
8151 this.eventHandlers.handleMediaFocus = this._handleMediaFocus.bind(this);
8152
8153 productThumbImages.forEach(function(el) {
8154 el.addEventListener('click', function(evt) {
8155 evt.preventDefault();
8156 var mediaId = el.getAttribute('data-thumbnail-id');
8157
8158 self._switchMedia(mediaId);
8159 self._setActiveThumbnail(mediaId);
8160 });
8161 el.addEventListener('keyup', self.eventHandlers.handleMediaFocus);
8162 });
8163 },
8164
8165 _initAddToCart: function() {
8166 this.productForm.addEventListener(
8167 'submit',
8168 function(evt) {
8169 if (this.addToCart.getAttribute('aria-disabled') === 'true') {
8170 evt.preventDefault();
8171 return;
8172 }
8173
8174 if (!this.ajaxEnabled) return;
8175
8176 evt.preventDefault();
8177
8178 this.previouslyFocusedElement = document.activeElement;
8179
8180 var isInvalidQuantity =
8181 !!this.quantityInput && this.quantityInput.value <= 0;
8182
8183 if (isInvalidQuantity) {
8184 this._showErrorMessage(theme.strings.quantityMinimumMessage);
8185 return;
8186 }
8187
8188 if (!isInvalidQuantity && this.ajaxEnabled) {
8189 // disable the addToCart and dynamic checkout button while
8190 // request/cart popup is loading and handle loading state
8191 this._handleButtonLoadingState(true);
8192 this._addItemToCart(this.productForm);
8193 return;
8194 }
8195 }.bind(this)
8196 );
8197 },
8198
8199 _initProductVideo: function() {
8200 var sectionId = this.settings.sectionId;
8201
8202 var productMediaTypeVideo = this.container.querySelectorAll(
8203 this.selectors.productMediaTypeVideo
8204 );
8205 productMediaTypeVideo.forEach(function(el) {
8206 theme.ProductVideo.init(el, sectionId);
8207 });
8208 },
8209
8210 _initModelViewerLibraries: function() {
8211 var modelViewerElements = this.container.querySelectorAll(
8212 this.selectors.productMediaTypeModel
8213 );
8214 if (modelViewerElements.length < 1) return;
8215 theme.ProductModel.init(modelViewerElements, this.settings.sectionId);
8216 },
8217
8218 _initShopifyXrLaunch: function() {
8219 this.eventHandlers.initShopifyXrLaunchHandler = this._initShopifyXrLaunchHandler.bind(
8220 this
8221 );
8222 document.addEventListener(
8223 'shopify_xr_launch',
8224 this.eventHandlers.initShopifyXrLaunchHandler
8225 );
8226 },
8227
8228 _initShopifyXrLaunchHandler: function() {
8229 var currentMedia = this.container.querySelector(
8230 this.selectors.productMediaWrapper +
8231 ':not(.' +
8232 self.classes.hidden +
8233 ')'
8234 );
8235 currentMedia.dispatchEvent(
8236 new CustomEvent('xrLaunch', {
8237 bubbles: true,
8238 cancelable: true
8239 })
8240 );
8241 },
8242
8243 _addItemToCart: function(form) {
8244 var self = this;
8245
8246 fetch(this.cartRoutes.cartAddUrl + '.js', {
8247 method: 'POST',
8248 credentials: 'same-origin',
8249 headers: {
8250 'Content-Type': 'application/x-www-form-urlencoded',
8251 'X-Requested-With': 'XMLHttpRequest'
8252 },
8253 body: theme.Helpers.serialize(form)
8254 })
8255 .then(function(response) {
8256 return response.json();
8257 })
8258 .then(function(json) {
8259 if (json.status && json.status !== 200) {
8260 var error = new Error(json.description);
8261 error.isFromServer = true;
8262 throw error;
8263 }
8264 self._hideErrorMessage();
8265 self._setupCartPopup(json);
8266 })
8267 .catch(function(error) {
8268 self.previouslyFocusedElement.focus();
8269 self._showErrorMessage(
8270 error.isFromServer && error.message.length
8271 ? error.message
8272 : theme.strings.cartError
8273 );
8274 self._handleButtonLoadingState(false);
8275 // eslint-disable-next-line no-console
8276 console.log(error);
8277 });
8278 },
8279
8280 _handleButtonLoadingState: function(isLoading) {
8281 if (isLoading) {
8282 this.addToCart.setAttribute('aria-disabled', true);
8283 this.addToCartText.classList.add(this.classes.hidden);
8284 this.loader.classList.remove(this.classes.hidden);
8285
8286 if (this.shopifyPaymentButton) {
8287 this.shopifyPaymentButton.setAttribute('disabled', true);
8288 }
8289
8290 this.loaderStatus.setAttribute('aria-hidden', false);
8291 } else {
8292 this.addToCart.removeAttribute('aria-disabled');
8293 this.addToCartText.classList.remove(this.classes.hidden);
8294 this.loader.classList.add(this.classes.hidden);
8295
8296 if (this.shopifyPaymentButton) {
8297 this.shopifyPaymentButton.removeAttribute('disabled');
8298 }
8299
8300 this.loaderStatus.setAttribute('aria-hidden', true);
8301 }
8302 },
8303
8304 _showErrorMessage: function(errorMessage) {
8305 var errorMessageContainer = this.container.querySelector(
8306 this.selectors.errorMessage
8307 );
8308 errorMessageContainer.innerHTML = errorMessage;
8309
8310 if (this.quantityInput) {
8311 this.quantityInput.classList.add(this.classes.inputError);
8312 }
8313
8314 this.errorMessageWrapper.classList.remove(
8315 this.classes.productFormErrorMessageWrapperHidden
8316 );
8317 this.errorMessageWrapper.setAttribute('aria-hidden', true);
8318 this.errorMessageWrapper.removeAttribute('aria-hidden');
8319 },
8320
8321 _hideErrorMessage: function() {
8322 this.errorMessageWrapper.classList.add(
8323 this.classes.productFormErrorMessageWrapperHidden
8324 );
8325
8326 if (this.quantityInput) {
8327 this.quantityInput.classList.remove(this.classes.inputError);
8328 }
8329 },
8330
8331 _setupCartPopup: function(item) {
8332 this.cartPopup =
8333 this.cartPopup || document.querySelector(this.selectors.cartPopup);
8334 this.cartPopupWrapper =
8335 this.cartPopupWrapper ||
8336 document.querySelector(this.selectors.cartPopupWrapper);
8337 this.cartPopupTitle =
8338 this.cartPopupTitle ||
8339 document.querySelector(this.selectors.cartPopupTitle);
8340 this.cartPopupQuantity =
8341 this.cartPopupQuantity ||
8342 document.querySelector(this.selectors.cartPopupQuantity);
8343 this.cartPopupQuantityLabel =
8344 this.cartPopupQuantityLabel ||
8345 document.querySelector(this.selectors.cartPopupQuantityLabel);
8346 this.cartPopupClose =
8347 this.cartPopupClose ||
8348 document.querySelector(this.selectors.cartPopupClose);
8349 this.cartPopupDismiss =
8350 this.cartPopupDismiss ||
8351 document.querySelector(this.selectors.cartPopupDismiss);
8352 this.cartPopupImagePlaceholder =
8353 this.cartPopupImagePlaceholder ||
8354 document.querySelector(this.selectors.cartPopupImagePlaceholder);
8355
8356 this._setupCartPopupEventListeners();
8357
8358 this._updateCartPopupContent(item);
8359 },
8360
8361 _updateCartPopupContent: function(item) {
8362 var self = this;
8363
8364 var quantity = this.quantityInput ? this.quantityInput.value : 1;
8365
8366 var selling_plan_name = item.selling_plan_allocation
8367 ? item.selling_plan_allocation.selling_plan.name
8368 : null;
8369
8370 this.cartPopupTitle.textContent = item.product_title;
8371 this.cartPopupQuantity.textContent = quantity;
8372 this.cartPopupQuantityLabel.textContent = theme.strings.quantityLabel.replace(
8373 '[count]',
8374 quantity
8375 );
8376
8377 this._setCartPopupPlaceholder(item.featured_image.url);
8378 this._setCartPopupImage(item.featured_image.url, item.featured_image.alt);
8379 this._setCartPopupProductDetails(
8380 item.product_has_only_default_variant,
8381 item.options_with_values,
8382 item.properties,
8383 selling_plan_name
8384 );
8385
8386 fetch(this.cartRoutes.cartUrl + '.js', { credentials: 'same-origin' })
8387 .then(function(response) {
8388 return response.json();
8389 })
8390 .then(function(cart) {
8391 self._setCartQuantity(cart.item_count);
8392 self._setCartCountBubble(cart.item_count);
8393 self._showCartPopup();
8394 })
8395 .catch(function(error) {
8396 // eslint-disable-next-line no-console
8397 console.log(error);
8398 });
8399 },
8400
8401 _setupCartPopupEventListeners: function() {
8402 this.eventHandlers.cartPopupWrapperKeyupHandler = this._cartPopupWrapperKeyupHandler.bind(
8403 this
8404 );
8405 this.eventHandlers.hideCartPopup = this._hideCartPopup.bind(this);
8406 this.eventHandlers.onBodyClick = this._onBodyClick.bind(this);
8407
8408 this.cartPopupWrapper.addEventListener(
8409 'keyup',
8410 this.eventHandlers.cartPopupWrapperKeyupHandler
8411 );
8412 this.cartPopupClose.addEventListener(
8413 'click',
8414 this.eventHandlers.hideCartPopup
8415 );
8416 this.cartPopupDismiss.addEventListener(
8417 'click',
8418 this.eventHandlers.hideCartPopup
8419 );
8420 document.body.addEventListener('click', this.eventHandlers.onBodyClick);
8421 },
8422
8423 _cartPopupWrapperKeyupHandler: function(event) {
8424 if (event.keyCode === slate.utils.keyboardKeys.ESCAPE) {
8425 this._hideCartPopup(event);
8426 }
8427 },
8428
8429 _setCartPopupPlaceholder: function(imageUrl) {
8430 this.cartPopupImageWrapper =
8431 this.cartPopupImageWrapper ||
8432 document.querySelector(this.selectors.cartPopupImageWrapper);
8433
8434 if (imageUrl === null) {
8435 this.cartPopupImageWrapper.classList.add(this.classes.hidden);
8436 return;
8437 }
8438 },
8439
8440 _setCartPopupImage: function(imageUrl, imageAlt) {
8441 if (imageUrl === null) return;
8442
8443 this.cartPopupImageWrapper.classList.remove(this.classes.hidden);
8444 var sizedImageUrl = theme.Images.getSizedImageUrl(imageUrl, '200x');
8445 var image = document.createElement('img');
8446 image.src = sizedImageUrl;
8447 image.alt = imageAlt;
8448 image.classList.add(this.classes.cartImage);
8449 image.setAttribute('data-cart-popup-image', '');
8450
8451 image.onload = function() {
8452 this.cartPopupImagePlaceholder.removeAttribute(
8453 'data-image-loading-animation'
8454 );
8455 this.cartPopupImageWrapper.append(image);
8456 }.bind(this);
8457 },
8458
8459 _setCartPopupProductDetails: function(
8460 product_has_only_default_variant,
8461 options,
8462 properties,
8463 selling_plan_name
8464 ) {
8465 this.cartPopupProductDetails =
8466 this.cartPopupProductDetails ||
8467 document.querySelector(this.selectors.cartPopupProductDetails);
8468 var variantPropertiesHTML = '';
8469
8470 if (!product_has_only_default_variant) {
8471 variantPropertiesHTML =
8472 variantPropertiesHTML + this._getVariantOptionList(options);
8473 }
8474
8475 if (selling_plan_name) {
8476 variantPropertiesHTML =
8477 variantPropertiesHTML + this._getSellingPlanHTML(selling_plan_name);
8478 }
8479
8480 if (properties !== null && Object.keys(properties).length !== 0) {
8481 variantPropertiesHTML =
8482 variantPropertiesHTML + this._getPropertyList(properties);
8483 }
8484
8485 if (variantPropertiesHTML.length === 0) {
8486 this.cartPopupProductDetails.innerHTML = '';
8487 this.cartPopupProductDetails.setAttribute('hidden', '');
8488 } else {
8489 this.cartPopupProductDetails.innerHTML = variantPropertiesHTML;
8490 this.cartPopupProductDetails.removeAttribute('hidden');
8491 }
8492 },
8493
8494 _getVariantOptionList: function(variantOptions) {
8495 var variantOptionListHTML = '';
8496
8497 variantOptions.forEach(function(variantOption) {
8498 variantOptionListHTML =
8499 variantOptionListHTML +
8500 '<li class="product-details__item product-details__item--variant-option">' +
8501 variantOption.name +
8502 ': ' +
8503 variantOption.value +
8504 '</li>';
8505 });
8506
8507 return variantOptionListHTML;
8508 },
8509
8510 _getPropertyList: function(properties) {
8511 var propertyListHTML = '';
8512 var propertiesArray = Object.entries(properties);
8513
8514 propertiesArray.forEach(function(property) {
8515 // Line item properties prefixed with an underscore are not to be displayed
8516 if (property[0].charAt(0) === '_') return;
8517
8518 // if the property value has a length of 0 (empty), don't display it
8519 if (property[1].length === 0) return;
8520
8521 propertyListHTML =
8522 propertyListHTML +
8523 '<li class="product-details__item product-details__item--property">' +
8524 '<span class="product-details__property-label">' +
8525 property[0] +
8526 ': </span>' +
8527 property[1];
8528 ': ' + '</li>';
8529 });
8530
8531 return propertyListHTML;
8532 },
8533
8534 _getSellingPlanHTML: function(selling_plan_name) {
8535 var sellingPlanHTML =
8536 '<li class="product-details__item product-details__item--property">' +
8537 selling_plan_name +
8538 '</li>';
8539
8540 return sellingPlanHTML;
8541 },
8542
8543 _setCartQuantity: function(quantity) {
8544 this.cartPopupCartQuantity =
8545 this.cartPopupCartQuantity ||
8546 document.querySelector(this.selectors.cartPopupCartQuantity);
8547 var ariaLabel;
8548
8549 if (quantity === 1) {
8550 ariaLabel = theme.strings.oneCartCount;
8551 } else if (quantity > 1) {
8552 ariaLabel = theme.strings.otherCartCount.replace('[count]', quantity);
8553 }
8554
8555 this.cartPopupCartQuantity.textContent = quantity;
8556 this.cartPopupCartQuantity.setAttribute('aria-label', ariaLabel);
8557 },
8558
8559 _setCartCountBubble: function(quantity) {
8560 this.cartCountBubble =
8561 this.cartCountBubble ||
8562 document.querySelector(this.selectors.cartCountBubble);
8563 this.cartCount =
8564 this.cartCount || document.querySelector(this.selectors.cartCount);
8565
8566 this.cartCountBubble.classList.remove(this.classes.hidden);
8567 this.cartCount.textContent = quantity;
8568 },
8569
8570 _showCartPopup: function() {
8571 theme.Helpers.prepareTransition(this.cartPopupWrapper);
8572
8573 this.cartPopupWrapper.classList.remove(
8574 this.classes.cartPopupWrapperHidden
8575 );
8576 this._handleButtonLoadingState(false);
8577
8578 slate.a11y.trapFocus({
8579 container: this.cartPopupWrapper,
8580 elementToFocus: this.cartPopup,
8581 namespace: 'cartPopupFocus'
8582 });
8583 },
8584
8585 _hideCartPopup: function(event) {
8586 var setFocus = event.detail === 0 ? true : false;
8587 theme.Helpers.prepareTransition(this.cartPopupWrapper);
8588 this.cartPopupWrapper.classList.add(this.classes.cartPopupWrapperHidden);
8589
8590 var cartPopupImage = document.querySelector(
8591 this.selectors.cartPopupImage
8592 );
8593 if (cartPopupImage) {
8594 cartPopupImage.remove();
8595 }
8596 this.cartPopupImagePlaceholder.setAttribute(
8597 'data-image-loading-animation',
8598 ''
8599 );
8600
8601 slate.a11y.removeTrapFocus({
8602 container: this.cartPopupWrapper,
8603 namespace: 'cartPopupFocus'
8604 });
8605
8606 if (setFocus) this.previouslyFocusedElement.focus();
8607
8608 this.cartPopupWrapper.removeEventListener(
8609 'keyup',
8610 this.eventHandlers.cartPopupWrapperKeyupHandler
8611 );
8612 this.cartPopupClose.removeEventListener(
8613 'click',
8614 this.eventHandlers.hideCartPopup
8615 );
8616 this.cartPopupDismiss.removeEventListener(
8617 'click',
8618 this.eventHandlers.hideCartPopup
8619 );
8620 document.body.removeEventListener(
8621 'click',
8622 this.eventHandlers.onBodyClick
8623 );
8624 },
8625
8626 _onBodyClick: function(event) {
8627 var target = event.target;
8628
8629 if (
8630 target !== this.cartPopupWrapper &&
8631 !target.closest(this.selectors.cartPopup)
8632 ) {
8633 this._hideCartPopup(event);
8634 }
8635 },
8636
8637 _setActiveThumbnail: function(mediaId) {
8638 // If there is no element passed, find it by the current product image
8639 if (typeof mediaId === 'undefined') {
8640 var productMediaWrapper = this.container.querySelector(
8641 this.selectors.productMediaWrapper + ':not(.hide)'
8642 );
8643
8644 if (!productMediaWrapper) return;
8645 mediaId = productMediaWrapper.getAttribute('data-media-id');
8646 }
8647
8648 var thumbnailWrappers = this.container.querySelectorAll(
8649 this.selectors.productThumbListItem + ':not(.slick-cloned)'
8650 );
8651
8652 var activeThumbnail;
8653 thumbnailWrappers.forEach(
8654 function(el) {
8655 var current = el.querySelector(
8656 this.selectors.productThumbImages +
8657 "[data-thumbnail-id='" +
8658 mediaId +
8659 "']"
8660 );
8661 if (current) {
8662 activeThumbnail = current;
8663 }
8664 }.bind(this)
8665 );
8666
8667 var productThumbImages = document.querySelectorAll(
8668 this.selectors.productThumbImages
8669 );
8670 productThumbImages.forEach(
8671 function(el) {
8672 el.classList.remove(this.classes.activeClass);
8673 el.removeAttribute('aria-current');
8674 }.bind(this)
8675 );
8676
8677 if (activeThumbnail) {
8678 activeThumbnail.classList.add(this.classes.activeClass);
8679 activeThumbnail.setAttribute('aria-current', true);
8680 this._adjustThumbnailSlider(activeThumbnail);
8681 }
8682 },
8683
8684 _adjustThumbnailSlider: function(activeThumbnail) {
8685 var sliderItem = activeThumbnail.closest('[data-slider-item]');
8686 if (!sliderItem) return;
8687
8688 var slideGroupLeaderIndex =
8689 Math.floor(
8690 Number(sliderItem.getAttribute('data-slider-slide-index')) / 3
8691 ) * 3;
8692
8693 window.setTimeout(
8694 function() {
8695 if (!this.slideshow) return;
8696 this.slideshow.goToSlideByIndex(slideGroupLeaderIndex);
8697 }.bind(this),
8698 251
8699 );
8700 },
8701
8702 _switchMedia: function(mediaId) {
8703 var currentMedia = this.container.querySelector(
8704 this.selectors.productMediaWrapper +
8705 ':not(.' +
8706 this.classes.hidden +
8707 ')'
8708 );
8709
8710 var newMedia = this.container.querySelector(
8711 this.selectors.productMediaWrapper + "[data-media-id='" + mediaId + "']"
8712 );
8713
8714 var otherMedia = this.container.querySelectorAll(
8715 this.selectors.productMediaWrapper +
8716 ":not([data-media-id='" +
8717 mediaId +
8718 "'])"
8719 );
8720
8721 currentMedia.dispatchEvent(
8722 new CustomEvent('mediaHidden', {
8723 bubbles: true,
8724 cancelable: true
8725 })
8726 );
8727 newMedia.classList.remove(this.classes.hidden);
8728 newMedia.dispatchEvent(
8729 new CustomEvent('mediaVisible', {
8730 bubbles: true,
8731 cancelable: true
8732 })
8733 );
8734 otherMedia.forEach(
8735 function(el) {
8736 el.classList.add(this.classes.hidden);
8737 }.bind(this)
8738 );
8739 },
8740
8741 _handleMediaFocus: function(evt) {
8742 if (evt.keyCode !== slate.utils.keyboardKeys.ENTER) return;
8743
8744 var mediaId = evt.currentTarget.getAttribute('data-thumbnail-id');
8745
8746 var productMediaWrapper = this.container.querySelector(
8747 this.selectors.productMediaWrapper + "[data-media-id='" + mediaId + "']"
8748 );
8749 productMediaWrapper.focus();
8750 },
8751
8752 _initThumbnailSlider: function() {
8753 setTimeout(
8754 function() {
8755 this.slideshow = new theme.Slideshow(
8756 this.container.querySelector('[data-thumbnail-slider]'),
8757 {
8758 canUseTouchEvents: true,
8759 type: 'slide',
8760 slideActiveClass: 'slick-active',
8761 slidesToShow: 3,
8762 slidesToScroll: 3
8763 }
8764 );
8765
8766 this.settings.sliderActive = true;
8767 }.bind(this),
8768 250
8769 );
8770 },
8771
8772 _destroyThumbnailSlider: function() {
8773 var sliderButtons = this.container.querySelectorAll(
8774 '[data-slider-button]'
8775 );
8776 var sliderTrack = this.container.querySelector('[data-slider-track]');
8777 var sliderItems = sliderTrack.querySelectorAll('[data-slider-item');
8778 this.settings.sliderActive = false;
8779
8780 if (sliderTrack) {
8781 sliderTrack.removeAttribute('style');
8782 sliderItems.forEach(function(sliderItem) {
8783 var sliderItemLink = sliderItem.querySelector(
8784 '[data-slider-item-link]'
8785 );
8786 sliderItem.classList.remove('slick-active');
8787 sliderItem.removeAttribute('style');
8788 sliderItem.removeAttribute('tabindex');
8789 sliderItem.removeAttribute('aria-hidden');
8790 sliderItemLink.removeAttribute('tabindex');
8791 });
8792 }
8793
8794 sliderButtons.forEach(function(sliderButton) {
8795 sliderButton.removeAttribute('aria-disabled');
8796 });
8797
8798 this.slideshow.destroy();
8799 this.slideshow = null;
8800 },
8801
8802 _liveRegionText: function(variant) {
8803 // Dummy content for live region
8804 var liveRegionText =
8805 '[Availability] [Regular] [$$] [Sale] [$]. [UnitPrice] [$$$]';
8806
8807 if (!this.productState.available) {
8808 liveRegionText = theme.strings.unavailable;
8809 return liveRegionText;
8810 }
8811
8812 // Update availability
8813 var availability = this.productState.soldOut
8814 ? theme.strings.soldOut + ','
8815 : '';
8816 liveRegionText = liveRegionText.replace('[Availability]', availability);
8817
8818 // Update pricing information
8819 var regularLabel = '';
8820 var regularPrice = theme.Currency.formatMoney(
8821 variant.price,
8822 theme.moneyFormat
8823 );
8824 var saleLabel = '';
8825 var salePrice = '';
8826 var unitLabel = '';
8827 var unitPrice = '';
8828
8829 if (this.productState.onSale) {
8830 regularLabel = theme.strings.regularPrice;
8831 regularPrice =
8832 theme.Currency.formatMoney(
8833 variant.compare_at_price,
8834 theme.moneyFormat
8835 ) + ',';
8836 saleLabel = theme.strings.sale;
8837 salePrice = theme.Currency.formatMoney(
8838 variant.price,
8839 theme.moneyFormat
8840 );
8841 }
8842
8843 if (this.productState.showUnitPrice) {
8844 unitLabel = theme.strings.unitPrice;
8845 unitPrice =
8846 theme.Currency.formatMoney(variant.unit_price, theme.moneyFormat) +
8847 ' ' +
8848 theme.strings.unitPriceSeparator +
8849 ' ' +
8850 this._getBaseUnit(variant);
8851 }
8852
8853 liveRegionText = liveRegionText
8854 .replace('[Regular]', regularLabel)
8855 .replace('[$$]', regularPrice)
8856 .replace('[Sale]', saleLabel)
8857 .replace('[$]', salePrice)
8858 .replace('[UnitPrice]', unitLabel)
8859 .replace('[$$$]', unitPrice)
8860 .trim();
8861
8862 return liveRegionText;
8863 },
8864
8865 _updateLiveRegion: function(evt) {
8866 var variant = evt.detail.variant;
8867 var liveRegion = this.container.querySelector(
8868 this.selectors.productStatus
8869 );
8870 liveRegion.innerHTML = this._liveRegionText(variant);
8871 liveRegion.setAttribute('aria-hidden', false);
8872 // hide content from accessibility tree after announcement
8873 setTimeout(function() {
8874 liveRegion.setAttribute('aria-hidden', true);
8875 }, 1000);
8876 },
8877
8878 _enableAddToCart: function(message) {
8879 this.addToCart.removeAttribute('aria-disabled');
8880 this.addToCart.setAttribute('aria-label', message);
8881 this.addToCartText.innerHTML = message;
8882 this.productForm.classList.remove(this.classes.variantSoldOut);
8883 },
8884
8885 _disableAddToCart: function(message) {
8886 message = message || theme.strings.unavailable;
8887 this.addToCart.setAttribute('aria-disabled', true);
8888 this.addToCart.setAttribute('aria-label', message);
8889 this.addToCartText.innerHTML = message;
8890 this.productForm.classList.add(this.classes.variantSoldOut);
8891 },
8892
8893 _updateAddToCart: function() {
8894 if (!this.productState.available) {
8895 this._disableAddToCart(theme.strings.unavailable);
8896 return;
8897 }
8898 if (this.productState.soldOut) {
8899 this._disableAddToCart(theme.strings.soldOut);
8900 return;
8901 }
8902
8903 this._enableAddToCart(theme.strings.addToCart);
8904 },
8905
8906 /**
8907 * The returned productState object keeps track of a number of properties about the current variant and product
8908 * Multiple functions within product.js leverage the productState object to determine how to update the page's UI
8909 * @param {object} evt - object returned from variant change event
8910 * @return {object} productState - current product variant's state
8911 * productState.available - true if current product options result in valid variant
8912 * productState.soldOut - true if variant is sold out
8913 * productState.onSale - true if variant is on sale
8914 * productState.showUnitPrice - true if variant has unit price value
8915 */
8916 _setProductState: function(evt) {
8917 var variant = evt.detail.variant;
8918
8919 if (!variant) {
8920 this.productState.available = false;
8921 return;
8922 }
8923
8924 this.productState.available = true;
8925 this.productState.soldOut = !variant.available;
8926 this.productState.onSale = variant.compare_at_price > variant.price;
8927 this.productState.showUnitPrice = !!variant.unit_price;
8928 },
8929
8930 _updateAvailability: function(evt) {
8931 // remove error message if one is showing
8932 this._hideErrorMessage();
8933
8934 // set product state
8935 this._setProductState(evt);
8936
8937 // update store availabilities info
8938 this._updateStoreAvailabilityContent(evt);
8939 // update form submit
8940 this._updateAddToCart();
8941 // update live region
8942 this._updateLiveRegion(evt);
8943
8944 this._updatePriceComponentStyles(evt);
8945 },
8946
8947 _updateStoreAvailabilityContent: function(evt) {
8948 if (!this.storeAvailability) {
8949 return;
8950 }
8951
8952 if (this.productState.available && !this.productState.soldOut) {
8953 this.storeAvailability.updateContent(evt.detail.variant.id);
8954 } else {
8955 this.storeAvailability.clearContent();
8956 }
8957 },
8958
8959 _updateMedia: function(evt) {
8960 var variant = evt.detail.variant;
8961 var mediaId = variant.featured_media.id;
8962 var sectionMediaId = this.settings.sectionId + '-' + mediaId;
8963
8964 this._switchMedia(sectionMediaId);
8965 this._setActiveThumbnail(sectionMediaId);
8966 },
8967
8968 _hidePriceComponent: function() {
8969 this.priceContainer.classList.add(this.classes.productUnavailable);
8970 this.priceContainer.setAttribute('aria-hidden', true);
8971 if (this.productPolicies) {
8972 this.productPolicies.classList.add(this.classes.visibilityHidden);
8973 }
8974 },
8975
8976 _updatePriceComponentStyles: function(evt) {
8977 var variant = evt.detail.variant;
8978
8979 var unitPriceBaseUnit = this.priceContainer.querySelector(
8980 this.selectors.unitPriceBaseUnit
8981 );
8982
8983 if (!this.productState.available) {
8984 this._hidePriceComponent();
8985 return;
8986 }
8987
8988 if (this.productState.soldOut) {
8989 this.priceContainer.classList.add(this.classes.productSoldOut);
8990 } else {
8991 this.priceContainer.classList.remove(this.classes.productSoldOut);
8992 }
8993
8994 if (this.productState.showUnitPrice) {
8995 unitPriceBaseUnit.innerHTML = this._getBaseUnit(variant);
8996 this.priceContainer.classList.add(this.classes.productUnitAvailable);
8997 } else {
8998 this.priceContainer.classList.remove(this.classes.productUnitAvailable);
8999 }
9000
9001 if (this.productState.onSale) {
9002 this.priceContainer.classList.add(this.classes.productOnSale);
9003 } else {
9004 this.priceContainer.classList.remove(this.classes.productOnSale);
9005 }
9006
9007 this.priceContainer.classList.remove(this.classes.productUnavailable);
9008 this.priceContainer.removeAttribute('aria-hidden');
9009 if (this.productPolicies) {
9010 this.productPolicies.classList.remove(this.classes.visibilityHidden);
9011 }
9012 },
9013
9014 _updatePrice: function(evt) {
9015 var variant = evt.detail.variant;
9016
9017 var regularPrices = this.priceContainer.querySelectorAll(
9018 this.selectors.regularPrice
9019 );
9020 var salePrice = this.priceContainer.querySelector(
9021 this.selectors.salePrice
9022 );
9023 var unitPrice = this.priceContainer.querySelector(
9024 this.selectors.unitPrice
9025 );
9026
9027 var formatRegularPrice = function(regularPriceElement, price) {
9028 regularPriceElement.innerHTML = theme.Currency.formatMoney(
9029 price,
9030 theme.moneyFormat
9031 );
9032 };
9033
9034 // On sale
9035 if (this.productState.onSale) {
9036 regularPrices.forEach(function(regularPrice) {
9037 formatRegularPrice(regularPrice, variant.compare_at_price);
9038 });
9039 salePrice.innerHTML = theme.Currency.formatMoney(
9040 variant.price,
9041 theme.moneyFormat
9042 );
9043 } else {
9044 // Regular price
9045 regularPrices.forEach(function(regularPrice) {
9046 formatRegularPrice(regularPrice, variant.price);
9047 });
9048 }
9049
9050 // Unit price
9051 if (this.productState.showUnitPrice) {
9052 unitPrice.innerHTML = theme.Currency.formatMoney(
9053 variant.unit_price,
9054 theme.moneyFormat
9055 );
9056 }
9057 },
9058
9059 _getBaseUnit: function(variant) {
9060 return variant.unit_price_measurement.reference_value === 1
9061 ? variant.unit_price_measurement.reference_unit
9062 : variant.unit_price_measurement.reference_value +
9063 variant.unit_price_measurement.reference_unit;
9064 },
9065
9066 _updateSKU: function(evt) {
9067 var variant = evt.detail.variant;
9068
9069 // Update the sku
9070 var sku = document.querySelector(this.selectors.SKU);
9071 if (!sku) return;
9072 sku.innerHTML = variant.sku;
9073 },
9074
9075 _enableZoom: function(el, index) {
9076 this.zoomPictures[index] = new theme.Zoom(el);
9077 },
9078
9079 _destroyZoom: function(index) {
9080 if (this.zoomPictures.length === 0) return;
9081 this.zoomPictures[index].unload();
9082 },
9083
9084 onUnload: function() {
9085 this.container.removeEventListener(
9086 'variantChange',
9087 this.eventHandlers.updateAvailability
9088 );
9089 this.container.removeEventListener(
9090 'variantImageChange',
9091 this.eventHandlers.updateMedia
9092 );
9093 this.container.removeEventListener(
9094 'variantPriceChange',
9095 this.eventHandlers.updatePrice
9096 );
9097 this.container.removeEventListener(
9098 'variantSKUChange',
9099 this.eventHandlers.updateSKU
9100 );
9101 theme.ProductVideo.removeSectionVideos(this.settings.sectionId);
9102 theme.ProductModel.removeSectionModels(this.settings.sectionId);
9103
9104 if (this.mqlSmall) {
9105 this.mqlSmall.removeListener(this.initMobileBreakpoint);
9106 }
9107
9108 if (this.mqlMediumUp) {
9109 this.mqlMediumUp.removeListener(this.initDesktopBreakpoint);
9110 }
9111 }
9112 });
9113
9114 return Product;
9115})();
9116
9117theme.ProductRecommendations = (function() {
9118 function ProductRecommendations(container) {
9119 var baseUrl = container.dataset.baseUrl;
9120 var productId = container.dataset.productId;
9121 var recommendationsSectionUrl =
9122 baseUrl +
9123 '?section_id=product-recommendations&product_id=' +
9124 productId +
9125 '&limit=4';
9126
9127 window.performance.mark(
9128 'debut:product:fetch_product_recommendations.start'
9129 );
9130
9131 fetch(recommendationsSectionUrl)
9132 .then(function(response) {
9133 return response.text();
9134 })
9135 .then(function(productHtml) {
9136 if (productHtml.trim() === '') return;
9137
9138 container.innerHTML = productHtml;
9139 container.innerHTML = container.firstElementChild.innerHTML;
9140
9141 window.performance.mark(
9142 'debut:product:fetch_product_recommendations.end'
9143 );
9144
9145 performance.measure(
9146 'debut:product:fetch_product_recommendations',
9147 'debut:product:fetch_product_recommendations.start',
9148 'debut:product:fetch_product_recommendations.end'
9149 );
9150 });
9151 }
9152
9153 return ProductRecommendations;
9154})();
9155
9156theme.Quotes = (function() {
9157 var config = {
9158 mediaQuerySmall: 'screen and (max-width: 749px)',
9159 mediaQueryMediumUp: 'screen and (min-width: 750px)',
9160 slideCount: 0
9161 };
9162
9163 var defaults = {
9164 canUseKeyboardArrows: false,
9165 type: 'slide',
9166 slidesToShow: 3
9167 };
9168
9169 function Quotes(container) {
9170 this.container = container;
9171 var sectionId = container.getAttribute('data-section-id');
9172 this.slider = document.getElementById('Quotes-' + sectionId);
9173
9174 this.sliderActive = false;
9175
9176 this.mobileOptions = Object.assign({}, defaults, {
9177 canUseTouchEvents: true,
9178 slidesToShow: 1
9179 });
9180
9181 this.desktopOptions = Object.assign({}, defaults, {
9182 slidesToShow: Math.min(
9183 defaults.slidesToShow,
9184 this.slider.getAttribute('data-count')
9185 )
9186 });
9187
9188 this.initMobileSlider = this._initMobileSlider.bind(this);
9189 this.initDesktopSlider = this._initDesktopSlider.bind(this);
9190
9191 this.mqlSmall = window.matchMedia(config.mediaQuerySmall);
9192 this.mqlSmall.addListener(this.initMobileSlider);
9193
9194 this.mqlMediumUp = window.matchMedia(config.mediaQueryMediumUp);
9195 this.mqlMediumUp.addListener(this.initDesktopSlider);
9196
9197 this.initMobileSlider();
9198 this.initDesktopSlider();
9199 }
9200
9201 Quotes.prototype = Object.assign({}, Quotes.prototype, {
9202 onUnload: function() {
9203 this.mqlSmall.removeListener(this.initMobileSlider);
9204 this.mqlMediumUp.removeListener(this.initDesktopSlider);
9205 this.slideshow.destroy();
9206 },
9207
9208 // eslint-disable-next-line no-unused-vars
9209 onBlockSelect: function(evt) {
9210 var slide = document.querySelector(
9211 '.quotes-slide--' + evt.detail.blockId
9212 );
9213 var slideIndex = Number(slide.getAttribute('data-slider-slide-index'));
9214
9215 if (this.mqlMediumUp.matches) {
9216 slideIndex = Math.max(
9217 0,
9218 Math.min(slideIndex, this.slideshow.slides.length - 3)
9219 );
9220 }
9221
9222 this.slideshow.goToSlideByIndex(slideIndex);
9223 },
9224
9225 _initMobileSlider: function() {
9226 if (this.mqlSmall.matches) {
9227 this._initSlider(this.mobileOptions);
9228 }
9229 },
9230
9231 _initDesktopSlider: function() {
9232 if (this.mqlMediumUp.matches) {
9233 this._initSlider(this.desktopOptions);
9234 }
9235 },
9236
9237 // eslint-disable-next-line no-unused-vars
9238 _initSlider: function(args) {
9239 if (this.sliderActive) {
9240 this.slideshow.destroy();
9241 this.sliderActive = false;
9242 }
9243
9244 this.slideshow = new theme.Slideshow(this.container, args);
9245 this.sliderActive = true;
9246 }
9247 });
9248
9249 return Quotes;
9250})();
9251
9252theme.SlideshowSection = (function() {
9253 var selectors = {
9254 sliderMobileContentIndex: '[data-slider-mobile-content-index]'
9255 };
9256
9257 function SlideshowSection(container) {
9258 var sectionId = container.dataset.sectionId;
9259
9260 this.container = container;
9261 this.eventHandlers = {};
9262 this.slideshowDom = container.querySelector('#Slideshow-' + sectionId);
9263 this.sliderMobileContentIndex = container.querySelectorAll(
9264 selectors.sliderMobileContentIndex
9265 );
9266
9267 this.slideshow = new theme.Slideshow(container, {
9268 autoplay: this.slideshowDom.getAttribute('data-autorotate') === 'true',
9269 slideInterval: this.slideshowDom.getAttribute('data-speed')
9270 });
9271 this._setupEventListeners();
9272 }
9273
9274 return SlideshowSection;
9275})();
9276
9277theme.SlideshowSection.prototype = Object.assign(
9278 {},
9279 theme.SlideshowSection.prototype,
9280 {
9281 _setupEventListeners: function() {
9282 this.eventHandlers.onSliderSlideChanged = function(event) {
9283 this._onSliderSlideChanged(event.detail);
9284 }.bind(this);
9285
9286 this.container.addEventListener(
9287 'slider_slide_changed',
9288 this.eventHandlers.onSliderSlideChanged
9289 );
9290 },
9291
9292 _onSliderSlideChanged: function(slideIndex) {
9293 var activeClass = 'slideshow__text-content--mobile-active';
9294
9295 this.sliderMobileContentIndex.forEach(function(element) {
9296 if (
9297 Number(element.getAttribute('data-slider-mobile-content-index')) ===
9298 slideIndex
9299 ) {
9300 element.classList.add(activeClass);
9301 } else {
9302 element.classList.remove(activeClass);
9303 }
9304 });
9305 },
9306
9307 onUnload: function() {
9308 this.slideshow.destroy();
9309 },
9310
9311 onBlockSelect: function(evt) {
9312 if (this.slideshow.adaptHeight) {
9313 this.slideshow.setSlideshowHeight();
9314 }
9315
9316 // Get slide's index using theme editor's id
9317 var slide = this.container.querySelector(
9318 '.slideshow__slide--' + evt.detail.blockId
9319 );
9320 var slideIndex = slide.getAttribute('data-slider-slide-index');
9321
9322 // Go to selected slide, pause auto-rotate
9323 this.slideshow.setSlide(slideIndex);
9324 this.slideshow.stopAutoplay();
9325 },
9326
9327 onBlockDeselect: function() {
9328 // Resume auto-rotate
9329 this.slideshow.startAutoplay();
9330 }
9331 }
9332);
9333
9334window.theme = window.theme || {};
9335
9336theme.StoreAvailability = (function() {
9337 var selectors = {
9338 storeAvailabilityModalOpen: '[data-store-availability-modal-open]',
9339 storeAvailabilityModalProductTitle:
9340 '[data-store-availability-modal-product-title]',
9341 storeAvailabilityModalVariantTitle:
9342 '[data-store-availability-modal-variant-title]'
9343 };
9344
9345 var classes = {
9346 hidden: 'hide'
9347 };
9348
9349 function StoreAvailability(container) {
9350 this.container = container;
9351 this.productTitle = this.container.dataset.productTitle;
9352 this.hasOnlyDefaultVariant =
9353 this.container.dataset.hasOnlyDefaultVariant === 'true';
9354 }
9355
9356 StoreAvailability.prototype = Object.assign({}, StoreAvailability.prototype, {
9357 updateContent: function(variantId) {
9358 var variantSectionUrl =
9359 this.container.dataset.baseUrl +
9360 '/variants/' +
9361 variantId +
9362 '/?section_id=store-availability';
9363 var self = this;
9364
9365 var storeAvailabilityModalOpen = self.container.querySelector(
9366 selectors.storeAvailabilityModalOpen
9367 );
9368
9369 this.container.style.opacity = 0.5;
9370 if (storeAvailabilityModalOpen) {
9371 storeAvailabilityModalOpen.disabled = true;
9372 storeAvailabilityModalOpen.setAttribute('aria-busy', true);
9373 }
9374
9375 fetch(variantSectionUrl)
9376 .then(function(response) {
9377 return response.text();
9378 })
9379 .then(function(storeAvailabilityHTML) {
9380 if (storeAvailabilityHTML.trim() === '') {
9381 return;
9382 }
9383 self.container.innerHTML = storeAvailabilityHTML;
9384 self.container.innerHTML = self.container.firstElementChild.innerHTML;
9385 self.container.style.opacity = 1;
9386
9387 // Need to query this again because we updated the DOM
9388 storeAvailabilityModalOpen = self.container.querySelector(
9389 selectors.storeAvailabilityModalOpen
9390 );
9391
9392 if (!storeAvailabilityModalOpen) {
9393 return;
9394 }
9395
9396 storeAvailabilityModalOpen.addEventListener(
9397 'click',
9398 self._onClickModalOpen.bind(self)
9399 );
9400
9401 self.modal = self._initModal();
9402 self._updateProductTitle();
9403 if (self.hasOnlyDefaultVariant) {
9404 self._hideVariantTitle();
9405 }
9406 });
9407 },
9408
9409 clearContent: function() {
9410 this.container.innerHTML = '';
9411 },
9412
9413 _onClickModalOpen: function() {
9414 this.container.dispatchEvent(
9415 new CustomEvent('storeAvailabilityModalOpened', {
9416 bubbles: true,
9417 cancelable: true
9418 })
9419 );
9420 },
9421
9422 _initModal: function() {
9423 return new window.Modals(
9424 'StoreAvailabilityModal',
9425 'store-availability-modal',
9426 {
9427 close: '.js-modal-close-store-availability-modal',
9428 closeModalOnClick: true,
9429 openClass: 'store-availabilities-modal--active'
9430 }
9431 );
9432 },
9433
9434 _updateProductTitle: function() {
9435 var storeAvailabilityModalProductTitle = this.container.querySelector(
9436 selectors.storeAvailabilityModalProductTitle
9437 );
9438 storeAvailabilityModalProductTitle.textContent = this.productTitle;
9439 },
9440
9441 _hideVariantTitle: function() {
9442 var storeAvailabilityModalVariantTitle = this.container.querySelector(
9443 selectors.storeAvailabilityModalVariantTitle
9444 );
9445 storeAvailabilityModalVariantTitle.classList.add(classes.hidden);
9446 }
9447 });
9448
9449 return StoreAvailability;
9450})();
9451
9452theme.VideoSection = (function() {
9453 function VideoSection(container) {
9454 container.querySelectorAll('.video').forEach(function(el) {
9455 theme.Video.init(el);
9456 theme.Video.editorLoadVideo(el.id);
9457 });
9458 }
9459
9460 return VideoSection;
9461})();
9462
9463theme.VideoSection.prototype = Object.assign({}, theme.VideoSection.prototype, {
9464 onUnload: function() {
9465 theme.Video.removeEvents();
9466 }
9467});
9468
9469theme.heros = {};
9470
9471theme.HeroSection = (function() {
9472 function HeroSection(container) {
9473 var sectionId = container.getAttribute('data-section-id');
9474 var hero = '#Hero-' + sectionId;
9475 theme.heros[hero] = new theme.Hero(hero, sectionId);
9476 }
9477
9478 return HeroSection;
9479})();
9480
9481window.theme = window.theme || {};
9482
9483var selectors = {
9484 disclosureLocale: '[data-disclosure-locale]',
9485 disclosureCountry: '[data-disclosure-country]'
9486};
9487
9488theme.FooterSection = (function() {
9489 function Footer(container) {
9490 this.container = container;
9491 this.cache = {};
9492 this.cacheSelectors();
9493
9494 if (this.cache.localeDisclosure) {
9495 this.localeDisclosure = new theme.Disclosure(this.cache.localeDisclosure);
9496 }
9497
9498 if (this.cache.countryDisclosure) {
9499 this.countryDisclosure = new theme.Disclosure(
9500 this.cache.countryDisclosure
9501 );
9502 }
9503 }
9504
9505 Footer.prototype = Object.assign({}, Footer.prototype, {
9506 cacheSelectors: function() {
9507 this.cache = {
9508 localeDisclosure: this.container.querySelector(
9509 selectors.disclosureLocale
9510 ),
9511 countryDisclosure: this.container.querySelector(
9512 selectors.disclosureCountry
9513 )
9514 };
9515 },
9516
9517 onUnload: function() {
9518 if (this.cache.localeDisclosure) {
9519 this.localeDisclosure.destroy();
9520 }
9521
9522 if (this.cache.countryDisclosure) {
9523 this.countryDisclosure.destroy();
9524 }
9525 }
9526 });
9527
9528 return Footer;
9529})();
9530
9531
9532document.addEventListener('DOMContentLoaded', function() {
9533 var sections = new theme.Sections();
9534
9535 sections.register('cart-template', theme.Cart);
9536 sections.register('product', theme.Product);
9537 sections.register('collection-template', theme.Filters);
9538 sections.register('product-template', theme.Product);
9539 sections.register('header-section', theme.HeaderSection);
9540 sections.register('map', theme.Maps);
9541 sections.register('slideshow-section', theme.SlideshowSection);
9542 sections.register('store-availability', theme.StoreAvailability);
9543 sections.register('video-section', theme.VideoSection);
9544 sections.register('quotes', theme.Quotes);
9545 sections.register('hero-section', theme.HeroSection);
9546 sections.register('product-recommendations', theme.ProductRecommendations);
9547 sections.register('footer-section', theme.FooterSection);
9548
9549 theme.customerTemplates.init();
9550
9551 // Theme-specific selectors to make tables scrollable
9552 var tableSelectors = '.rte table,' + '.custom__item-inner--html table';
9553
9554 slate.rte.wrapTable({
9555 tables: document.querySelectorAll(tableSelectors),
9556 tableWrapperClass: 'scrollable-wrapper'
9557 });
9558
9559 // Theme-specific selectors to make iframes responsive
9560 var iframeSelectors =
9561 '.rte iframe[src*="youtube.com/embed"],' +
9562 '.rte iframe[src*="player.vimeo"],' +
9563 '.custom__item-inner--html iframe[src*="youtube.com/embed"],' +
9564 '.custom__item-inner--html iframe[src*="player.vimeo"]';
9565
9566 slate.rte.wrapIframe({
9567 iframes: document.querySelectorAll(iframeSelectors),
9568 iframeWrapperClass: 'video-wrapper'
9569 });
9570
9571 // Common a11y fixes
9572 slate.a11y.pageLinkFocus(
9573 document.getElementById(window.location.hash.substr(1))
9574 );
9575
9576 var inPageLink = document.querySelector('.in-page-link');
9577 if (inPageLink) {
9578 inPageLink.addEventListener('click', function(evt) {
9579 slate.a11y.pageLinkFocus(
9580 document.getElementById(evt.currentTarget.hash.substr(1))
9581 );
9582 });
9583 }
9584
9585 document.querySelectorAll('a[href="#"]').forEach(function(anchor) {
9586 anchor.addEventListener('click', function(evt) {
9587 evt.preventDefault();
9588 });
9589 });
9590
9591 slate.a11y.accessibleLinks({
9592 messages: {
9593 newWindow: theme.strings.newWindow,
9594 external: theme.strings.external,
9595 newWindowExternal: theme.strings.newWindowExternal
9596 },
9597 links: document.querySelectorAll(
9598 'a[href]:not([aria-describedby]), .product-single__thumbnail'
9599 )
9600 });
9601
9602 theme.FormStatus.init();
9603
9604 var selectors = {
9605 image: '[data-image]',
9606 lazyloaded: '.lazyloaded'
9607 };
9608
9609 document.addEventListener('lazyloaded', function(evt) {
9610 var image = evt.target;
9611
9612 removeImageLoadingAnimation(image);
9613
9614 if (document.body.classList.contains('template-index')) {
9615 var mainContent = document.getElementById('MainContent');
9616
9617 if (mainContent && mainContent.children && mainContent.children.length) {
9618 var firstSection = document.getElementsByClassName('index-section')[0];
9619
9620 if (!firstSection.contains(image)) return;
9621
9622 window.performance.mark('debut:index:first_image_visible');
9623 }
9624 }
9625
9626 if (image.hasAttribute('data-bgset')) {
9627 var innerImage = image.querySelector(selectors.lazyloaded);
9628
9629 if (innerImage) {
9630 var alt = image.getAttribute('data-alt');
9631 var src = innerImage.hasAttribute('data-src')
9632 ? innerImage.getAttribute('data-src')
9633 : image.getAttribute('data-bg');
9634
9635 image.setAttribute('alt', alt ? alt : '');
9636 image.setAttribute('src', src ? src : '');
9637 }
9638 }
9639
9640 if (!image.hasAttribute('data-image')) {
9641 return;
9642 }
9643 });
9644
9645 // When the theme loads, lazysizes might load images before the "lazyloaded"
9646 // event listener has been attached. When this happens, the following function
9647 // hides the loading placeholders.
9648 function onLoadHideLazysizesAnimation() {
9649 var alreadyLazyloaded = document.querySelectorAll('.lazyloaded');
9650 alreadyLazyloaded.forEach(function(image) {
9651 removeImageLoadingAnimation(image);
9652 });
9653 }
9654
9655 onLoadHideLazysizesAnimation();
9656
9657 document.addEventListener(
9658 'touchstart',
9659 function() {
9660 theme.Helpers.setTouch();
9661 },
9662 { once: true }
9663 );
9664
9665 if (document.fonts) {
9666 document.fonts.ready.then(function() {
9667 window.performance.mark('debut:fonts_loaded');
9668 });
9669 }
9670});
9671
9672// Youtube API callback
9673// eslint-disable-next-line no-unused-vars
9674function onYouTubeIframeAPIReady() {
9675 theme.Video.loadVideos();
9676}
9677
9678function removeImageLoadingAnimation(image) {
9679 // Remove loading animation
9680 var imageWrapper = image.hasAttribute('data-image-loading-animation')
9681 ? image
9682 : image.closest('[data-image-loading-animation]');
9683
9684 if (imageWrapper) {
9685 imageWrapper.removeAttribute('data-image-loading-animation');
9686 }
9687}
9688