\n
\n \n
\n */\n class QuickAddToCart extends Widget {\n prefs() {\n return {\n isDisabled: false,\n showMinicartOnProductAdd: false,\n autoCloseMinicart: false,\n autoCloseTimeout: 5000,\n classHidden: 'h-hidden',\n shipToRestrictedStates: [],\n isHandledRestrictedStates: false,\n ...super.prefs()\n };\n }\n\n init() {\n super.init();\n\n // handle shipping restrictions\n this.eventBus().on('shipto.restrictions.updated', 'handleStateRestrictions');\n this.eventBus().on('minicart.removed.product', 'handleRemovedProduct');\n this.handleStateRestrictions();\n }\n\n /**\n * @param stateID\n * @description Handle update of shipping restrictions\n */\n handleStateRestrictions(stateID) {\n if (!this.prefs().isHandledRestrictedStates) {\n return;\n }\n\n stateID = getStateID(stateID);\n if (!stateID) {\n return;\n }\n\n var isRestricted = isStateRestricted(stateID, this.prefs().shipToRestrictedStates);\n var classHidden = this.prefs().classHidden;\n\n // handle `quickAddToCart` block\n this.ref('self').toggleClass(classHidden, isRestricted);\n\n // handle disabled button\n var disabledButtons = document.querySelectorAll('[data-id=producttile_disabledbtn_' + this.prefs().productId + ']');\n\n disabledButtons.forEach(function (disabledBtn) {\n disabledBtn.classList.toggle(classHidden, !isRestricted);\n });\n }\n\n /**\n * @description post action after product adding. Handling success/error here\n * @param {object} response server response object\n */\n postAddProduct(response) {\n const { autoCloseMinicart, autoCloseTimeout } = this.prefs();\n const cartModel = response.cart;\n let element = document.getElementById('add-to-cart-' + this.prefs().productId);\n\n if (!response.error && cartModel) {\n const analytics = this.data('analytics');\n\n if (analytics) {\n analytics.quantity = Number(this.getQtyValue());\n this.data('analytics', JSON.stringify(analytics));\n }\n element?.setAttribute('data-add-to-cart-success', 'true');\n const accessibilityAlert = this.prefs().accessibilityAlerts.addedtocart;\n this.showAlertMessage(accessibilityAlert, false);\n if (!isMediumViewAndDown()) {\n this.showAlertMessage(accessibilityAlert, false);\n }\n\n // Hide mini cart pop up for CSC Agent\n if (response.isCSCAgent) {\n cartModel.showMinicart = false;\n } else {\n cartModel.showMinicart = this.prefs().showMinicartOnProductAdd;\n }\n this.eventBus().emit('product.added.to.cart', cartModel, this);\n\n if (autoCloseMinicart) {\n this.eventBus().emit('auto.close.minicart', autoCloseTimeout);\n }\n\n this.updateAvailabilityComponent();\n } else if (response.error) {\n element?.setAttribute('data-add-to-cart-success', 'false');\n this.showAlertMessage(response.message, true);\n }\n\n // set low in stock status\n if (response.isNotEnoughATS && response.errorData) {\n // eslint-disable-next-line max-len\n const HarmonyQuantityStepper = /** @type {HarmonyQuantityStepper} */(this.getConstructor('harmonyQuantityStepper'));\n\n this.eachChild(child => {\n if (!(child instanceof HarmonyQuantityStepper)) {\n return;\n }\n\n // set max available value\n child.ref('field').val(response.errorData.qtyValue);\n });\n }\n\n // set out of stock status\n if (response.isOutOfStock && response.errorData) {\n // eslint-disable-next-line max-len\n const HarmonyQuantityStepper = /** @type {HarmonyQuantityStepper} */(this.getConstructor('harmonyQuantityStepper'));\n const ProcessButton = /** @type {ProcessButton} */(this.getConstructor('processButton'));\n\n this.eachChild(child => {\n if ((child instanceof HarmonyQuantityStepper)) {\n child.ref('field').val(response.errorData.qtyValue);\n child.toggleDisable(true);\n }\n\n if ((child instanceof ProcessButton)) {\n child.ref('self').setText(this.prefs().outOfStockLabel);\n child.disable();\n }\n });\n }\n }\n\n /**\n * @description Get value from input quantity block\n * @returns {number} quantity value for current tile\n */\n getQtyValue() {\n return this.getById('input-quantity', (qtyInput) => {\n return (qtyInput && qtyInput.ref('field') && qtyInput.ref('field').val());\n });\n }\n\n /**\n * @param {RefElement} button add to cart button itself\n */\n addToCart(button) {\n if (this.prefs().isDisabled) {\n return;\n }\n\n this.eventBus().emit('addtocart.button.clicked', true);\n\n // Prevent twice clicks on a button\n if (button.isBusy) {\n return;\n }\n\n const addToCartBtnPrefs = button.prefs();\n // eslint-disable-next-line max-len\n var wineClubProdList = document.getElementById('wineClubProdList') != null ? document.getElementById('wineClubProdList').value : '';\n const promise = submitFormJson(addToCartBtnPrefs.addToCartUrl, {\n pid: this.currentProductID || addToCartBtnPrefs.pid,\n quantity: this.getQtyValue() || this.selectedQuantity || 1,\n wineClubProdList: wineClubProdList,\n childProducts: this.getChildProducts() || []\n }).then(response => this.postAddProduct(response))\n .catch(() => {\n //once bundle perfomance issue is fixed, Remove if condition.\n if(addToCartBtnPrefs.isbundle === undefined && !addToCartBtnPrefs.isbundle){\n this.showAlertMessage(this.prefs().textNetworkError, true);\n }\n })\n .finally(() => {\n this.eventBus().emit('addtocart.button.clicked', false);\n });\n\n this.eventBus().emit('loader.start', promise);\n }\n\n /**\n * @description Emits global alert\n * @param {string} message Text for an alert\n * @param {boolean} isError is error flag\n */\n showAlertMessage(message, isError) {\n this.eventBus().emit('alert.show', {\n accessibilityAlert: message,\n errorClass: isError\n });\n }\n\n /**\n * @description Add to cart trigger click handler\n */\n triggerClick() {\n const ProcessButton = /** @type {ProcessButton} */(this.getConstructor('processButton'));\n\n this.eachChild(child => {\n if (child instanceof ProcessButton) {\n this.addToCart(child);\n }\n });\n }\n\n /**\n * @description Toggle busy of addToCartBtn\n * @param {object} initiator - widget initiator\n * @param {boolean} isBusy - is busy processing flag\n */\n toggleBusy(initiator, isBusy) {\n const ProcessButton = /** @type {ProcessButton} */(this.getConstructor('processButton'));\n\n this.eachChild(child => {\n if (!(child instanceof ProcessButton)) {\n return;\n }\n\n if (isBusy) {\n child.startProcess();\n } else {\n child.stopProcess();\n }\n });\n }\n\n /**\n * @description method definition is obligatory when using in conjunction with a QuantityStepper component\n */\n changeAttribute() {}\n\n renderAddToCartText(params) {\n this.getById('addToCart', (addToCart) => {\n addToCart.render('template', {\n isAvailable: params.available,\n isPreorder: params.availability && params.availability.isPreorder\n }, addToCart.ref('container'));\n });\n }\n\n /**\n * @description get value from button data attribute\n * @returns {any} childProducts\n */\n getChildProducts() {\n const ProcessButton = /** @type {ProcessButton} */(this.getConstructor('processButton'));\n let childProducts = null;\n\n this.eachChild(child => {\n if (!(child instanceof ProcessButton)) {\n return;\n }\n\n childProducts = JSON.stringify((child && child.config && child.config.childProducts));\n });\n\n return childProducts;\n }\n\n /**\n * @description handle removed product\n * @param {string} pid - product id\n */\n handleRemovedProduct(pid) {\n if (pid === this.prefs().productId) {\n this.updateAvailabilityComponent();\n }\n }\n\n /**\n * @description update availability component\n */\n updateAvailabilityComponent() {\n // eslint-disable-next-line max-len\n const HarmonyQuantityStepper = /** @type {HarmonyQuantityStepper} */(this.getConstructor('harmonyQuantityStepper'));\n\n this.eachChild(child => {\n if ((child instanceof HarmonyQuantityStepper)) {\n child.changeValue();\n }\n });\n }\n }\n\n return QuickAddToCart;\n}\n","import { getStateID, isStateRestricted } from 'harmony/toolbox/shipToUtils';\n\n/**\n * @description ShipToError widget implementation\n * @param {Widget} Widget Base widget for extending\n * @returns {typeof ShipToError} - ShipToStates instance\n */\nexport default function (Widget) {\n return class ShipToError extends Widget {\n prefs() {\n return {\n shipToRestrictedStates: [],\n ...super.prefs()\n };\n }\n\n init() {\n super.init();\n\n // handle shipping restrictions\n this.eventBus().on('shipto.restrictions.updated', 'handleStateRestrictions');\n this.eventBus().on('shipto.restrictions.updated.error.msg', 'handleStateRestrictions');\n this.handleStateRestrictions();\n }\n\n /**\n * @description Handle shipping restrictions\n */\n handleStateRestrictions(stateID) {\n stateID = getStateID(stateID);\n if (!stateID) {\n return;\n }\n\n if (isStateRestricted(stateID, this.prefs().shipToRestrictedStates)) {\n this.show();\n } else {\n this.hide();\n }\n }\n };\n}\n","/**\n * @description Handle ViewMore button behaviour and show hidden refinement after click on it.\n * @param {typeof import('widgets/Widget').default} Widget Base widget for extending\n */\n\nexport default function (Widget) {\n return class ReserveBarCart extends Widget {\n prefs() {\n return {\n ...super.prefs() //extend preferences from parent widget class\n };\n }\n init() {\n setTimeout(() => {\n this.eventBus().emit('rbCart.init', this);\n }, 0);\n \n }\n handleCartClick() {\n this.eventBus().emit('reserveBarCart.toggle', this);\n }\n };\n}\n","/**\n * @description Handle ViewMore button behaviour and show hidden refinement after click on it.\n * @param {typeof import('widgets/Widget').default} Widget Base widget for extending\n */\n\nexport default function (Widget) {\n return class ReserveBarMgr extends Widget {\n prefs() {\n return {\n autoCompleteResults: [],\n shoppingCart: null,\n domCart: '',\n domSearch: '',\n liquid: null,\n selectedAddress: null,\n productContainers: [],\n groupingIDS: [],\n emptyCart: '',\n ...super.prefs() //extend preferences from parent widget class\n };\n }\n init = async () => {\n this.groupingIDS = [];\n this.autoCompleteResults = this.prefs().autoCompleteResults;\n this.shoppingCart = this.prefs().shoppingCart;\n this.liquid = this.prefs().liquid;\n this.selectedAddress = this.prefs().selectedAddress;\n this.productContainers = this.prefs().productContainers;\n this.domCart = this.prefs().domCart;\n this.domSearch = this.prefs().domSearch;\n this.emptyCart = this.config.emptycart;\n\n this.eventBus().on('rbSearch.init', 'initDomSearch');\n this.eventBus().on('rbCart.init', 'initDomCart');\n this.eventBus().on('reserveBarCart.toggle', 'toggleReserveBarCart');\n this.eventBus().on('reserveBarAddress.update', 'getAddress');\n this.eventBus().on('reserveBarProduct.init', 'initProduct');\n this.initLiquid().then(()=> this.initCart());\n \n }\n // takes instance of cart widget\n toggleReserveBarCart(cart) {\n const cartDrawer = cart.ref('reserveBarCartDrawer').get();\n const cartBg = cart.ref('reserveBarCartBG').get();\n if (cartDrawer) {\n cartDrawer.classList.toggle('translate-x-full');\n cartDrawer.classList.toggle('translate-x-0');\n }\n if (cartBg) {\n cartBg.classList.toggle('show');\n }\n }\n initLiquid = async () => {\n this.liquid = await Liquid({\n clientId: 'e53d4d04a676395ccd1c2d5b6b94c3aa44452330e502c999892d8091',\n env: 'production'\n });\n \n }\n initCart = async () => {\n const storedCart = JSON.parse(localStorage.getItem('RbCartID'));\n if (storedCart === null || storedCart.length === 0) {\n // If no cart is found in local storage, create a new one\n const liquidCart = await this.liquid.cart({\n cartItems: [],\n giftOrder: false,\n });\n this.shoppingCart = liquidCart;\n } else {\n // If a cart is found in local storage, set it in the state\n const preExistingCart = await this.liquid.cart({\n id: storedCart.id \n });\n //handles coming back from checkout if cart ahs been emptied.\n if (preExistingCart.cartItmes?.length < 0) {\n const liquidCart = await this.liquid.cart({\n cartItems: [],\n giftOrder: false,\n });\n this.shoppingCart = liquidCart;\n } else {\n this.shoppingCart = preExistingCart;\n }\n \n this.renderShoppingCart(this.shoppingCart);\n }\n const savedAddress = JSON.parse(localStorage.getItem('RbAddress'));\n if (this.selectedAddress == null && savedAddress != null && typeof (this.domSearch) != 'string'\n ) {\n try {\n \n this.selectedAddress = savedAddress;\n const search = this.domSearch.ref('reserveBarSearch').get();\n search.value = this.selectedAddress.formattedAddress;\n const productList = await this.fetchProducts(this.selectedAddress.formattedAddress);\n // Render the productList\n this.renderRetailers(productList);\n } catch (error) {\n console.log(error);\n }\n }\n this.handleUpdateCartQty(this.domCart);\n }\n initProduct(product) {\n this.groupingIDS.push(product.config.grouping);\n this.productContainers.push(product);\n }\n initDomCart(cart) {\n this.domCart = cart;\n }\n initDomSearch(search) {\n this.domSearch = search;\n }\n getAddress = async (searchWidget) => {\n const search = searchWidget.ref('reserveBarSearch').get();\n let autoCompleteResults;\n if (search.value.trim().length === 0) {\n autoCompleteResults = [];\n this.renderAutoCompleteResults();\n return;\n }\n try {\n /// Get address suggestion from liquid\n autoCompleteResults = await this.liquid.address({\n search: search.value,\n });\n /// Render the address suggestion\n this.renderAutoCompleteResults(searchWidget, autoCompleteResults, this.config.emptycart);\n } catch (error) {\n console.log(error);\n }\n }\n renderAutoCompleteResults(searchWidget, autoCompleteResults, emptyCartMessage) {\n // Clear the previous results\n if (searchWidget == undefined) return;\n const autoCompleteList = searchWidget.ref('autoCompleteList').get();\n autoCompleteList.innerHTML = '';\n autoCompleteList.classList.remove('results');\n \n if (autoCompleteResults == undefined) return;\n // // Render the new results\n autoCompleteResults.forEach((result) => {\n const listItem = document.createElement('li');\n listItem.setAttribute('tabindex', '0');\n listItem.textContent = result.description;\n listItem.addEventListener('click', () => this.handleAddressClick(searchWidget, result, emptyCartMessage));\n listItem.addEventListener('keypress', (event) => { if (event.key === \"Enter\") { this.handleAddressClick(searchWidget, result, emptyCartMessage); } })\n autoCompleteList.classList.add('results');\n autoCompleteList.appendChild(listItem);\n });\n }\n handleAddressClick = async (searchWidget, result, emptyCartMessage) => {\n // Clear the previous results\n const searchInput = searchWidget.ref('reserveBarSearch').get();\n searchInput.value = result.description;\n if (searchWidget != undefined) {\n const autoCompleteList = searchWidget.ref('autoCompleteList').get();\n autoCompleteList.innerHTML = '';\n autoCompleteList.classList.remove('results'); \n }\n\n this.selectedAddress = '';\n //clear shopping cart\n this.clearCart().then(() => this.handleUpdateCartQty(this.domCart));\n localStorage.removeItem('RbAddress');\n \n const productCartDiv = this.domCart.ref('productCart').get();\n productCartDiv.innerHTML = emptyCartMessage;\n\n this.productContainers.forEach(element => {\n const retailersContainer = element.ref('reserveBarRetailers').get();\n if (retailersContainer) {\n retailersContainer.style.display = 'block';\n }\n });\n\n const productList = await this.fetchProducts(result.description);\n // Render the productList\n this.renderRetailers(productList);\n\n try {\n // Get the full address details\n const placeDetail = await this.liquid.address({\n placeId: result.placeId\n });\n localStorage.setItem('RbAddress', JSON.stringify(placeDetail));\n this.selectedAddress = placeDetail;\n \n } catch (error) {\n console.log(error);\n }\n };\n\n /// Fetch products from liquid\n fetchProducts = async (address) => {\n try {\n return await this.liquid.product({\n ids: this.groupingIDS ,\n shipAddress: address,\n top100: false,\n });\n } catch (error) {\n console.log(error);\n }\n };\n\n renderRetailers = (productList) => {\n\n\n this.productContainers.forEach(element => {\n const product = productList.find(product => product.id == element.config.grouping)\n const retailersContainer = element.ref('reserveBarRetailers').get();\n const productGrid = document.createElement('div');\n productGrid.classList.add('b-reserve_bar--productGrid');\n const productContainer = document.createElement('div');\n const sizeContainer = document.createElement('div');\n if (retailersContainer) {\n retailersContainer.innerHTML = \"\";\n }\n if (product) {\n if (product.variants.length > 0) {\n const sizeSelect = document.createElement('div');\n sizeSelect.classList.add('b-reserve_bar--sizeSelect');\n sizeSelect.innerHTML = '
Select a Size
';\n product.variants.forEach((variant) => {\n if (variant) {\n sizeSelect.innerHTML += `
`;\n sizeSelect.addEventListener('click', (event) => {\n\n \n const allsizeButtons = document.querySelectorAll('[data-sizeselect]');\n [...allsizeButtons].forEach(button => {\n if (button == event.target) {\n event.target.classList.remove('g-button_alt');\n event.target.classList.add('g-button_main');\n } else {\n button.classList.add('g-button_alt');\n button.classList.remove('g-button_main');\n }\n });\n const allRetailers = retailersContainer.querySelectorAll('.productRetailer');\n allRetailers.forEach(retailer => {\n retailer.style.display = 'none';\n });\n const availabilityLabels = retailersContainer.querySelectorAll('.productAvailability');\n availabilityLabels.forEach(label => {\n label.style.display = 'none';\n });\n const selectedRetailers = retailersContainer.querySelectorAll(`[data-size=\"${event.target.dataset.sizeselect}\"]`);\n selectedRetailers.forEach(selected => {\n selected.style.display = 'block';\n });\n })\n sizeContainer.appendChild(sizeSelect);\n variant.availability.forEach((availability) => {\n const availabilityLabel = document.createElement('h3');\n let label = '';\n if (availability == 'shipped') {\n label= \"Ship\"\n }\n if (availability == 'onDemand') {\n label = 'Delivery Same Day or Schedule';\n }\n availabilityLabel.innerHTML = label;\n availabilityLabel.classList.add('productAvailability');\n availabilityLabel.classList.add('t-heading_5');\n availabilityLabel.setAttribute('data-size', variant.size.replace(', bottle', ''));\n availabilityLabel.style.display = 'none';\n productContainer.appendChild(availabilityLabel); \n variant.retailers.forEach((retailer) => {\n if (retailer.type == availability) {\n const productDetails = document.createElement('div');\n productDetails.style.display = 'none';\n productDetails.classList.add('productRetailer');\n if (retailer.shippingMethod.deliveryExpectation == 'On backorder') {\n productDetails.classList.add('disabled');\n }\n productDetails.classList.add('t-paragraph_1');\n productDetails.setAttribute('data-size', variant.size.replace(', bottle', ''));\n productDetails.innerHTML = `\n
${retailer.retailer.name}\n
${retailer.shippingMethod.deliveryExpectation}\n
Price: $${retailer.price}\n `;\n productDetails.addEventListener('click', () => {\n retailer['productImages'] = variant.images;\n retailer['productName'] = product.name;\n this.handleRetailerClick(retailer, retailer.type, element);\n });\n\n productContainer.appendChild(productDetails);\n productGrid.appendChild(productContainer);\n }\n });\n });\n }\n\n });\n\n retailersContainer.appendChild(sizeContainer);\n retailersContainer.appendChild(productGrid);\n } else {\n retailersContainer.innerHTML = `
${element.config.unavailable}
`;\n }\n }\n });\n };\n handleRetailerClick(selectedRetailer, availability, element) {\n /// hide the retailers container\n const retailersContainer = element.ref('reserveBarRetailers').get();\n retailersContainer.style.display = 'none';\n const selectedProductDetailsContainer = element.ref('reserveBarSelectedProductDetails').get();\n\n if (selectedRetailer) {\n const productDetailsContainer = document.createElement('div');\n const productQuantityContainer = document.createElement('div');\n //// Create back button\n const buttonWrap = document.createElement('span');\n const backButton = document.createElement('button');\n backButton.type = 'button';\n backButton.textContent = '<< Back';\n backButton.addEventListener('click', () => {\n retailersContainer.style.display = 'block';\n selectedProductDetailsContainer.innerHTML = '';\n //handle error message text\n });\n buttonWrap.appendChild(backButton);\n //Create product name element\n const productName = document.createElement('h3');\n productName.textContent = selectedRetailer.productName;\n productName.classList.add('t-heading_4');\n\n /// Create availability title element\n const retailerDetails = document.createElement('p');\n retailerDetails.textContent = `Sold By ${selectedRetailer.retailer.name}`;\n\n /// Create delivery expectation element\n const deliveryExpectation = document.createElement('p');\n deliveryExpectation.textContent = selectedRetailer.shippingMethod.deliveryExpectation;\n \n const selectWrap = document.createElement('div');\n /// Create quantity element\n const quantityLabel = document.createElement('label');\n quantityLabel.textContent = 'Quantity:';\n quantityLabel.classList.add('f-input_text-label');\n /// Create quantity select element\n const quantitySelect = document.createElement('select');\n quantitySelect.dataset.ref = 'quantitySelect';\n quantitySelect.addEventListener('change', () => this.handleProductQtyChange(element, selectedRetailer.price, quantitySelect.value));\n quantitySelect.classList.add('f-input_select-field');\n \n\n /// Populate quantity select element\n //setting artifical limit to account for typos like 99999 bottles in stock\n Array.from({\n length: Number(selectedRetailer.inStock <= 99 ? selectedRetailer.inStock : 99)\n },\n (_, index) => index + 1\n ).forEach((quantity) => {\n const option = document.createElement('option');\n option.value = quantity.toString();\n option.textContent = quantity.toString();\n quantitySelect.appendChild(option);\n });\n\n selectWrap.appendChild(quantityLabel);\n selectWrap.appendChild(quantitySelect);\n /// Create price element\n const price = document.createElement('p');\n price.innerHTML = `
$${parseFloat(selectedRetailer.price).toFixed(\n 2\n )}`;\n\n /// Create error message element\n const errorMessageElement = document.createElement('p');\n errorMessageElement.id = 'errorMessage';\n\n /// Create total price element\n const total = document.createElement('p');\n total.dataset.ref = 'totalPrice';\n total.innerHTML = `
Total: $${(parseFloat(selectedRetailer.price) * Number(quantitySelect.value)).toFixed(2)}`;\n\n /// Create `add to cart` button\n const addToCartButton = document.createElement('button');\n addToCartButton.classList.add('g-button_main');\n addToCartButton.textContent = 'Add To Cart';\n addToCartButton.addEventListener('click', () => {\n this.updateCart(selectedRetailer, element).then(()=>{ retailersContainer.style.display = 'block';\n selectedProductDetailsContainer.innerHTML = '';});\n });\n\n /// Create product unavailable element\n const productUnavailable = document.createElement('p');\n productUnavailable.textContent = 'Product is Unavailable';\n\n\n /// Add elements to the product details container\n \n productDetailsContainer.appendChild(productName);\n productDetailsContainer.appendChild(price);\n productDetailsContainer.appendChild(retailerDetails);\n productDetailsContainer.appendChild(deliveryExpectation);\n \n\n productQuantityContainer.appendChild(selectWrap);\n productQuantityContainer.appendChild(total);\n \n selectedProductDetailsContainer.appendChild(buttonWrap);\n selectedProductDetailsContainer.appendChild(productDetailsContainer);\n selectedProductDetailsContainer.appendChild(productQuantityContainer);\n selectedProductDetailsContainer.appendChild(addToCartButton);\n\n selectedProductDetailsContainer.appendChild(errorMessageElement);\n }\n\n\n\n\n\n\n };\n handleProductQtyChange(element, price, quantity) {\n const total = element.ref('totalPrice').get();\n total.innerHTML = `
Total: $${(parseFloat(price) * quantity).toFixed(2)}`;\n }\n\n updateCart = async (retailer, element) => {\n let qty;\n try {\n if (this.shoppingCart !== null) {\n const inCart = this.shoppingCart.cartItems.find(\n (o) => o.product.id === retailer?.variantId\n );\n qty = Number(element.ref('quantitySelect').get().value);\n // if already in the cart incrament the quantity instead of resetting to 1\n if (inCart) {\n const index = this.shoppingCart.cartItems.findIndex(\n (o) => o.product.id === retailer?.variantId\n );\n\n if (index !== -1) {\n qty = this.shoppingCart.cartItems[index].quantity + qty;\n }\n }\n }\n const currentCart = this.shoppingCart.cartItems ? this.shoppingCart.cartItems : [];\n currentCart.push({\n variantId: retailer?.variantId,\n quantity: qty,\n })\n const newCart = {\n id: this.shoppingCart.id,\n cartItems: currentCart\n };\n\n const result = await this.liquid.cart(newCart);\n this.shoppingCart = result;\n localStorage.setItem('RbCartID', JSON.stringify(this.shoppingCart));\n\n // Update the this.shoppingCart object in the productCart div\n this.renderShoppingCart(result).then(() => { this.handleUpdateCartQty(this.domCart); this.toggleReserveBarCart(this.domCart); });\n \n } catch (error) {\n console.log(error);\n alert('Failed to add product to cart. Please try again later.');\n }\n };\n renderShoppingCart = async (cart) => {\n const productCartDiv = this.domCart.ref('productCart').get();\n \n if (cart) {\n productCartDiv.innerHTML = '';\n const cartContainer = document.createElement('div');\n let counter = 0;\n\n /// Render the new shopping cart items\n if (cart.cartItems != null) {\n cart.cartItems.forEach((item) => {\n const cartItemDiv = document.createElement('div');\n cartItemDiv.classList.add('b-rb_cart--list__item');\n const cartQuantitySelect = document.createElement('select');\n cartQuantitySelect.id = `cartQuantitySelect_${++counter}`;\n cartQuantitySelect.addEventListener('change', (event) => {\n this.handleCartQtyChange(event, item);\n });\n\n /// Populate quantity select element\n Array.from(\n { length: Number(item.product.inStock) },\n (_, index) => index + 1\n ).forEach((quantity) => {\n const option = document.createElement('option');\n option.value = quantity;\n option.textContent = quantity;\n cartQuantitySelect.appendChild(option);\n if (quantity === item.quantity) {\n option.selected = true;\n }\n });\n\n \n const cartImage = document.createElement('img');\n cartImage.width = 100;\n cartImage.height = 200;\n cartImage.src = item.product.thumbUrl;\n cartImage.alt = item.product.name\n const cartItemWrap = document.createElement('div');\n const cartItemTitle = document.createElement('h3');\n cartItemTitle.innerHTML = item.productGrouping.name;\n const cartItemVolume = document.createElement('p');\n cartItemVolume.classList.add('volume');\n cartItemVolume.innerHTML = item.product.volume;\n const cartQtyContainer = document.createElement('p');\n cartQtyContainer.appendChild(cartQuantitySelect);\n cartQtyContainer.classList.add('cartQty');\n const cartItemPrice = document.createElement('p');\n cartItemPrice.innerHTML = `
Price: ${item.product.price}`;\n cartQtyContainer.appendChild(cartItemPrice);\n const cartItemFulfillment = document.createElement('p');\n cartItemFulfillment.innerHTML = `Fulfilled by ${item.retailerName}`;\n // const cartItemDelivery = document.createElement('p');\n // cartItemDelivery.innerHTML = item.shortDeliveryExpectation;\n const cartItemRemove = document.createElement('button');\n cartItemRemove.setAttribute('aria-label', 'Remove Item');\n cartItemRemove.innerHTML = '
'\n\n cartItemRemove.addEventListener('click', () => {\n this.handleRemoveFromCart(item.product.id).then(() => this.handleUpdateCartQty(this.domCart));\n });\n cartItemWrap.appendChild(cartItemTitle);\n cartItemWrap.appendChild(cartItemVolume);\n cartItemWrap.appendChild(cartItemFulfillment);\n // cartItemWrap.appendChild(cartItemDelivery);\n cartItemWrap.appendChild(cartQtyContainer);\n\n \n cartItemDiv.appendChild(cartImage);\n cartItemDiv.appendChild(cartItemWrap);\n cartItemDiv.appendChild(cartItemRemove); \n cartContainer.appendChild(cartItemDiv);\n });\n \n }\n\n\n /// Create checkout button\n const checkoutButton = document.createElement('button');\n checkoutButton.textContent = 'Checkout';\n checkoutButton.classList.add('g-button_main')\n checkoutButton.addEventListener('click', () => {\n this.handleCheckout();\n });\n\n if (cart.cartItems.length > 0) {\n const subTotalWrap = document.createElement('div')\n subTotalWrap.classList.add('b-rb_cart--subtotal');\n subTotalWrap.id = 'rbCart-subtotal';\n const subTotalItems = document.createElement('p');\n subTotalItems.innerHTML = `
Subtotal (${cart.itemCount} ${cart.itemCount > 1 ? 'items' : 'item'})`;\n const subTotal = document.createElement('p');\n subTotal.innerHTML = `
$ ${cart.subtotal}`;\n subTotalWrap.appendChild(subTotalItems);\n subTotalWrap.appendChild(subTotal)\n productCartDiv.appendChild(cartContainer);\n productCartDiv.appendChild(subTotalWrap);\n productCartDiv.appendChild(checkoutButton);\n\n } else {\n productCartDiv.textContent = this.emptyCart;\n }\n } else {\n productCartDiv.textContent = this.emptyCart;\n }\n }\n handleRemoveFromCart = async (productId) => {\n if (!productId) return;\n const updatedCart = {\n id: this.shoppingCart.id,\n cartItems: [\n ...this.shoppingCart.cartItems,\n ...[\n {\n variantId: productId,\n quantity: 0,\n },\n ],\n ],\n };\n\n try {\n this.shoppingCart = await this.liquid.cart(updatedCart);\n localStorage.setItem('RbCartID', JSON.stringify(this.shoppingCart));\n this.renderShoppingCart(this.shoppingCart);\n } catch (error) {\n console.log(error);\n }\n };\n \n handleCartQtyChange = async (event, item) => {\n const qty = Number(event.target?.value);\n const updatedCart = {\n id: this.shoppingCart.id,\n cartItems: [\n ...this.shoppingCart.cartItems,\n ...[\n {\n variantId: item.product.id,\n quantity: qty,\n },\n ],\n ],\n };\n try {\n const result = await this.liquid.cart(updatedCart);\n this.shoppingCart = result;\n localStorage.setItem('RbCartID', JSON.stringify(this.shoppingCart));\n const subTotal = document.getElementById('rbCart-subtotal');\n const subTotalItems = document.createElement('p');\n subTotalItems.innerHTML = `Subtotal(${this.shoppingCart.itemCount} ${this.shoppingCart.itemCount > 1 ? 'items' : 'item'})`;\n const subTotalPrice = document.createElement('p');\n subTotalPrice.innerHTML = `
$ ${this.shoppingCart.subtotal}`;\n subTotal.innerHTML = '';\n subTotal.appendChild(subTotalItems);\n subTotal.appendChild(subTotalPrice);\n } catch (error) {\n // log error\n console.log(error);\n }\n };\n handleCheckout = async () => {\n try {\n if (!this.selectedAddress?.address1.trim()) {\n alert('Please enter a valid delivery address');\n return;\n }\n const checkoutUrl = await this.liquid.checkout({\n cartId: this.shoppingCart.id,\n address: {\n address1: this.selectedAddress.address1,\n city: this.selectedAddress.city,\n state: this.selectedAddress.state,\n zipCode: this.selectedAddress.zipCode,\n },\n });\n\n if (checkoutUrl.url) {\n // Redirect to checkout\n window.location = checkoutUrl.url;\n }\n } catch (error) {\n console.log(error);\n }\n };\n handleUpdateCartQty = async (domCart) => {\n const cart = await this.liquid.cart({id: this.shoppingCart.id});\n const domQty = this.domCart.ref('reserveBarCartQty').get();\n if (this.shoppingCart.itemCount > 0) {\n domQty.classList.add('show');\n } else {\n domQty.classList.remove('show');\n }\n domQty.innerHTML = this.shoppingCart.itemCount;\n }\n async clearCart() {\n const newCart = await this.liquid.cart({\n cartItems: [],\n giftOrder: false,\n });\n this.shoppingCart = newCart;\n }\n };\n \n}\n","/**\n * @description Handle ViewMore button behaviour and show hidden refinement after click on it.\n * @param {typeof import('widgets/Widget').default} Widget Base widget for extending\n */\n\nexport default function (Widget) {\n return class ReserveBarProduct extends Widget {\n prefs() {\n return {\n ...super.prefs() //extend preferences from parent widget class\n };\n }\n init() {\n this.eventBus().emit('reserveBarProduct.init', this);\n }\n };\n}\n","/**\n * @description Handle ViewMore button behaviour and show hidden refinement after click on it.\n * @param {typeof import('widgets/Widget').default} Widget Base widget for extending\n */\n\nexport default function (Widget) {\n return class ReserveBarSearch extends Widget {\n prefs() {\n return {\n ...super.prefs() //extend preferences from parent widget class\n };\n }\n init() {\n this.eventBus().emit('rbSearch.init', this);\n }\n handleSearch() {\n this.eventBus().emit('reserveBarAddress.update', this);\n }\n };\n}\n","import { scrollElementTo } from 'widgets/toolbox/scroll';\nimport { timeout } from 'widgets/toolbox/util';\n\n/**\n * @typedef {ReturnType
} SearchBox\n */\n\n/**\n * @description Search form implementation. That consists of form and combobox with suggestion functionality\n * @param {SearchBox} SearchBox Base widget for extending\n * @returns {typeof HarmonySearchBox} HarmonySearchBox class\n */\nexport default function (SearchBox) {\n class HarmonySearchBox extends SearchBox {\n /**\n * @description Preferences\n * @returns {object} Preferences object\n */\n prefs() {\n return {\n classesSuggestionsActive: 'm-active_suggestions',\n ...super.prefs()\n };\n }\n\n init() {\n super.init();\n\n this.toggleClearButton(!!this.ref('input').val());\n }\n\n /**\n * @description Shows suggestions popup in initial state.\n *
Initial state can be a slot/asset markup, rendered in backed\n *
and placed in `searchSuggestionsInitial` template\n */\n showInDefaultState() {\n const input = this.ref('input').get();\n const clearBtnElem = document.getElementById('clearButton');\n if (input.value) {\n clearBtnElem.removeAttribute(\"tabindex\");\n } else {\n clearBtnElem.setAttribute(\"tabindex\",'-1');\n }\n\n if (input) {\n input.focus();\n }\n this.lastSearchedTerm = '';\n this.render('searchSuggestionsInitial', {}, this.ref('listboxInner'))\n .then(() => {\n this.selectedIndex = -1;\n this.resultsCount = this.getRealItemsCount();\n this.openListbox();\n });\n this.ref('dialog').removeClass(this.prefs().classesSuggestionsActive);\n }\n\n /**\n * @param {string} query - a query, used for fetching Combobox inner items\n * @param {number} resultsCount - obtained inner items count\n * @param {Array} products - arary of products object\n */\n afterSuggestionsUpdate(query, resultsCount, products) {\n super.afterSuggestionsUpdate(query, resultsCount);\n this.ref('dialog').addClass(this.prefs().classesSuggestionsActive);\n this.eventBus().emit('searchbox.products.init', products);\n }\n\n /**\n * @param {string} query - customer's search query\n * @param {object} response - backend response with suggestions\n */\n processResponse(query, response) {\n if (response && response.suggestions && response.isAvailableSuggestions) {\n if (this.ref('input').val() !== query) {\n // if value changed during request, we do not show outdated suggestions\n return;\n }\n\n if (document.activeElement !== this.ref('input').get()) {\n this.toggleSpinner(false);\n this.showInDefaultState();\n return;\n }\n\n this.render(undefined, response, this.ref('listboxInner')).then(() => {\n timeout(() => {\n const listbox = this.ref('listbox').get();\n if (listbox) {\n scrollElementTo(listbox);\n }\n }, 10);\n this.afterSuggestionsUpdate(query, response.suggestions.total, response.suggestions.product.products);\n });\n } else {\n this.showNoSuggestions();\n }\n\n this.toggleSpinner(false);\n }\n\n /**\n * Display no suggestions found state (message + default suggestions)\n */\n showNoSuggestions() {\n const input = this.ref('input').get();\n if (input) {\n input.focus();\n }\n this.lastSearchedTerm = '';\n this.render('searchNoSuggestions', {}, this.ref('listboxInner'))\n .then(() => {\n this.selectedIndex = -1;\n this.resultsCount = this.getRealItemsCount();\n this.openListbox();\n });\n this.ref('dialog').removeClass(this.prefs().classesSuggestionsActive);\n }\n\n closeSearch() {\n this.emit('closesearch');\n // @ts-ignore\n this.closeModal();\n this.closeListbox();\n this.clearInput(false);\n }\n\n clearInput(...args) {\n super.clearInput(...args);\n\n const searchInput = this.ref('input').get();\n searchInput.defaultValue = '';\n }\n }\n\n return HarmonySearchBox;\n}\n","window.$ = require('jquery');\n\n$(document).ready(function() {\n // Function to display error messages below the field\n function showError($field) {\n var errorMessage = $field.data('parsley-required-message');\n var errorList = $('').addClass('sfcc-store-locator-error');\n var errorItem = $('- ').text(errorMessage);\n errorList.append(errorItem);\n $field.after(errorList);\n }\n\n // Listening to the form's submission\n $('#sfccStoreLocator').submit(function(event) {\n // Prevent the default form submission action\n event.preventDefault();\n\n // Clear previous errors\n $('.sfcc-store-locator-error').remove();\n\n // Get zipCode and radius values from the form\n var $zipCode = $('#zipCode');\n var $radius = $('#radius');\n\n var zipCodeValue = $zipCode.val();\n var radiusValue = $radius.val();\n\n var hasError = false;\n\n // Check if zipCode and radius are set and show errors if needed\n if (!zipCodeValue) {\n showError($zipCode);\n hasError = true;\n }\n if (!radiusValue) {\n showError($radius);\n hasError = true;\n }\n if (hasError) return;\n\n // Construct the endpoint URL with the required parameters\n var endpoint = $(this).attr('action');\n var fullURL = endpoint + '?postalCode=' + zipCodeValue + '&radius=' + radiusValue;\n\n // Making the AJAX GET request\n $.ajax({\n url: fullURL,\n type: 'GET',\n dataType: 'json',\n success: function(data) {\n var hasResults = data.stores.length > 0;\n\n if (!hasResults) {\n $('.store-locator-no-results').show();\n } else {\n $('.store-locator-no-results').hide();\n }\n var $resultsDiv = $('.b-store-wrapper');\n $resultsDiv.empty();\n // Handle successful API response\n // You can populate the data in '.b-store_locator-content' or handle it differently\n if (data.storesResultsHtml) {\n $resultsDiv.append(data.storesResultsHtml);\n }\n }\n });\n });\n});\n","const FOCUSABLE_ELEMENTS = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]:not([tabindex=\"-1\"])'];\n\n/**\n *\n * @description Get keyboard-focusable elements\n * @param {HTMLElement} ctx context where focusable elements needs to be queried\n * @returns {HTMLElement[]} array containing focusable elements, or empty array if no elements found\n */\nexport function getFocusableElements(ctx) {\n return ctx.querySelectorAll(FOCUSABLE_ELEMENTS.join(', '));\n}\n\n/**\n *\n * @description Remove elements inside context from tabIndex order - remove ability to focus element by TAB\n * @param {HTMLElement} ctx context where focusable elements needs to be queried\n * @param {boolean} [isAvailable=true] state that defines whether elements inside context needs to be disabled for TAB focus\n */\nexport function toggleAvailability(ctx, isAvailable = true) {\n if ((!isAvailable && ctx.isAvailable === false)\n || (isAvailable && ctx.isAvailable)\n ) { return; }\n\n const focusableElements = getFocusableElements(ctx);\n\n focusableElements.forEach(el => {\n if (!el.dataset.initialTabIndex) {\n el.dataset.initialTabIndex = el.tabIndex;\n }\n\n el.tabIndex = isAvailable ? el.dataset.initialTabIndex : -1;\n });\n\n ctx.isAvailable = isAvailable;\n}\n\n/**\n * Executes inline scripts\n *\n * @param {HTMLElement} ctx context where inline scripts needs to be executed\n */\nexport function executeInlineScripts(ctx) {\n const scripts = (ctx.querySelectorAll(\n 'script:not(src):not([type*=\"template\"]):not([type*=\"json\"])'\n ));\n\n Array.from(scripts).forEach(script => {\n const tempScript = document.createElement('script');\n\n tempScript.text = script.text;\n ctx.appendChild(tempScript);\n script.parentNode.removeChild(script);\n });\n}\n","/**\n * Formats date to mm/dd/yyyy\n * @param {string} date -- date from input field.\n */\nexport function formatDate(dateString) {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit'\n });\n}\n\n/**\n * Format phone number which will be applicable for Cognito.\n * @param phoneNumber\n * @return {string}\n */\nexport function formatPhoneNumber(phoneNumber) {\n const phone = phoneNumber?.match(/[0-9]/g);\n\n // Append country code, as on storefront phone won't have it.\n phone.unshift('+1');\n \n return phone.join('')\n}\n","import { getViewType, SMALL, MEDIUM } from 'widgets/toolbox/viewtype';\n\nconst STATE_ID_SELECTOR = '[data-selector-id=\"state-id\"]';\nconst HEADER_SELECTOR = '[data-selector-id=\"harmonyHeader\"]';\n\n/**\n * Return ID of current state\n * @return {string}\n */\nexport function getStateID(stateID) {\n if (!stateID && document.querySelectorAll(STATE_ID_SELECTOR).length) {\n stateID = document.querySelectorAll(STATE_ID_SELECTOR)[0].dataset.refSelectedState;\n }\n\n return stateID;\n}\n\n/**\n * Check state restriction\n * @return {boolean}\n */\nexport function isStateRestricted(stateID, restrictedStates = []) {\n return restrictedStates.indexOf(stateID) > -1;\n}\n\n/**\n * Check header's type\n * @return {string} type of header\n */\nexport function getHeaderType() {\n var headerType = '';\n var viewType = getViewType();\n var $header = document.querySelectorAll(HEADER_SELECTOR);\n var isHeaderAlignmentLeft = $header.length && $header[0].dataset.isHeaderAlignmentLeft === 'true';\n\n if (viewType === SMALL || viewType === MEDIUM) {\n headerType = 'mobileHeader';\n } else if (isHeaderAlignmentLeft) {\n headerType = 'stickyHeader';\n } else {\n headerType = 'header';\n }\n\n return headerType;\n}\n","window.$ = require('jquery');\n\n$(document).ready(function() {\n var $footerSocial = $('.b-footer-social');\n if ($footerSocial.length > 0) {\n var baseUrl = $footerSocial.data('attr');\n if (baseUrl) {\n var socialClasses = {\n 'm-facebook': 'facebook',\n 'm-instagram': 'instagram',\n 'm-youtube': 'youtube',\n 'm-pinterest': 'pinterest',\n 'm-twitter': 'twitter'\n };\n\n var styleElement = document.createElement('style');\n styleElement.type = 'text/css';\n var cssText = '';\n\n $('.b-footer_social-link').each(function() {\n var $this = $(this);\n var socialType = Object.keys(socialClasses).find(function(cls) {\n return $this.hasClass(cls);\n });\n\n if (socialType && socialClasses[socialType]) {\n var fullUrl = baseUrl + '#' + socialClasses[socialType];\n\n // Generate CSS for the ::before pseudo-element\n cssText += `\n .b-footer_social-link.${socialType}::before {\n mask: url(${fullUrl});\n -webkit-mask: url(${fullUrl});\n }\n `;\n }\n });\n\n if (cssText) {\n styleElement.appendChild(document.createTextNode(cssText));\n document.head.appendChild(styleElement);\n }\n }\n }\n});","/* eslint-disable no-unused-vars */\n/* eslint-disable indent */\n/* eslint-disable sonarjs/no-duplicate-string */\n/* sonar-ignore-start */\n\nwindow.$ = require('jquery');\nrequire('./collapsibleItem');\nrequire('./sfccStoreLocator');\nrequire('./updateSocialLoginURL');\nconst [, pdp, clp, plp, wishlist, cart, checkout, orderconfirmation, account, , page, storeLocator, corporatepayment, corporatepaymentlink] = [\n 'home',\n 'pdp',\n 'clp',\n 'plp',\n 'wishlist',\n 'cart',\n 'checkout',\n 'orderconfirmation',\n 'account',\n 'static',\n 'page',\n 'storeLocator',\n 'corporatepayment',\n 'corporatepaymentlink'\n]\n// @ts-ignore\n .map(ctx => window.contexts.includes(ctx));\n\nimport WidgetsMgr from 'widgets/widgetsMgr';\nimport AccessibilityFocusTrapMixin from 'widgets/global/AccessibilityFocusTrapMixin';\nimport AccessibilityFocusMixin from 'widgets/global/AccessibilityFocusMixin';\nimport SearchBox from 'widgets/search/SearchBox';\nimport Modal from 'widgets/global/Modal';\nimport SideBar from 'harmony/global/SideBar';\nimport MixMiniCart from 'harmony/cart/MixMiniCart';\nimport VideoPlayer from 'harmony/global/VideoPlayer';\nimport CarouselSwiper from 'widgets/global/CarouselSwiper';\nimport ExtendedCarouselSwiper from 'harmony/global/ExtendedCarouselSwiper';\nimport HarmonyCarousel from 'harmony/global/HarmonyCarousel';\nimport EinsteinCarousel from 'harmony/global/EinsteinCarousel';\nimport ShipToTitle from 'harmony/ShipToTitle';\nimport ShipToStates from 'harmony/ShipToStates';\nimport ProductPositionMapping from 'harmony/ProductPositionMapping';\nimport HarmonyStickyHeader from 'harmony/global/HarmonyStickyHeader';\nimport HarmonyHeader from 'harmony/global/HarmonyHeader';\nimport HarmonyMenuPanel from 'harmony/global/HarmonyMenuPanel';\nimport HarmonyHamburgerMenuItem from 'harmony/global/HarmonyHamburgerMenuItem';\nimport CookieDismissBlock from 'harmony/global/CookieDismissBlock';\nimport Overlay from 'harmony/global/Overlay';\nimport Loader from 'harmony/global/Loader';\nimport HarmonySearchBox from 'harmony/search/HarmonySearchBox';\nimport HarmonyQuantityStepper from 'harmony/forms/HarmonyQuantityStepper';\nimport BrowsingOnlyModal from 'harmony/global/BrowsingOnlyModal';\nimport HarmonyModal from 'harmony/global/HarmonyModal';\nimport shipToSideBar from 'harmony/global/ShipToSideBar';\nimport HeaderSideBar from 'harmony/global/HeaderSideBar';\nimport ViewMore from 'harmony/global/ViewMore';\nimport SecureCheckoutButton from 'harmony/cart/SecureCheckoutButton';\nimport ShippingSelector from 'harmony/cart/ShippingSelector';\nimport HarmonyBasicInput from 'harmony/forms/HarmonyBasicInput';\nimport ChangePasswordForm from 'harmony/forms/ajax/ChangePasswordForm';\nimport RegisterForm from 'harmony/forms/ajax/RegisterForm';\nimport ProfileDetailsForm from 'harmony/account/ProfileDetailsForm';\nimport Cognito from 'harmony/aws/Cognito';\nimport HarmonyPasswordResetForm from 'harmony/forms/ajax/HarmonyPasswordResetForm';\nimport GeolocationConsent from 'harmony/GeolocationConsent';\nimport HarmonyAccordionItem from 'harmony/global/HarmonyAccordionItem';\nimport HarmonyBackToTop from 'harmony/global/HarmonyBackToTop';\nimport HarmonyFocusHighlighter from 'harmony/global/HarmonyFocusHighlighter';\nimport HarmonyGlobalAlerts from 'harmony/global/HarmonyGlobalAlerts';\nimport HarmonyConsentTracking from 'harmony/global/HarmonyConsentTracking';\nimport HarmonyAjaxFormResponseContainer from 'harmony/forms/HarmonyAjaxFormResponseContainer';\nimport HarmonyAddToWishlistMixin from 'harmony/wishlist/HarmonyAddToWishlistMixin';\nimport HarmonyButton from 'harmony/global/HarmonyButton';\nimport HarmonyEmitBusEvent from 'harmony/global/HarmonyEmitBusEvent';\nimport QuickAddToCart from 'harmony/product/QuickAddToCart';\nimport ProcessButton from 'widgets/global/ProcessButton';\nimport HarmonyAjaxForm from 'harmony/forms/HarmonyAjaxForm';\nimport HarmonyInputPassword from 'harmony/forms/HarmonyInputPassword';\nimport HarmonyEmailSubscribe from 'harmony/forms/ajax/HarmonyEmailSubscribe';\nimport ShipToError from 'harmony/product/ShipToError';\nimport InputBirthdayDate from 'harmony/forms/InputBirthdayDate';\nimport StatesInputRadio from 'harmony/forms/StatesInputRadio';\nimport cancelOrderModal from 'harmony/cancelOrderModal';\nimport HarmonyLoginForm from 'harmony/forms/ajax/HarmonyLoginForm';\nimport ListAccessibility from 'widgets/global/ListAccessibility';\nimport LoginForm from 'widgets/forms/ajax/LoginForm';\nimport VerificationCodeForm from 'harmony/account/VerificationCodeForm';\nimport ReserveBarMgr from './reserveBar/ReserveBarMgr';\nimport ReserveBarCart from './reserveBar/ReserveBarCart';\nimport ReserveBarSearch from './reserveBar/ReserveBarSearch';\n\n// TODO:HARMONY:BE: Remove InputNumber here when Boilerplate will complete it.\n// As for now it is not in widgetsList.js\nimport InputNumber from 'widgets/forms/InputNumber';\nimport ReserveBarProduct from './reserveBar/ReserveBarProduct';\nimport PDFiltersMgr from './experience/filters';\n\n\n\n\nWidgetsMgr.addWidgetsList('app_harmony', () => [\n ['accessibilityFocusTrapMixin', AccessibilityFocusTrapMixin],\n ['sideBar', SideBar, 'accessibilityFocusTrapMixin'],\n ['button', HarmonyButton, 'button'],\n ['ajaxform', HarmonyAjaxForm, 'ajaxform'],\n ['videoPlayer', VideoPlayer],\n ['carouselSwiper', CarouselSwiper],\n ['carouselSwiper', ExtendedCarouselSwiper, 'carouselSwiper'],\n ['shipToStates', ShipToStates, 'ajaxform'],\n ['productPositionMapping', ProductPositionMapping],\n ['shipToTitle', ShipToTitle, 'emitBusEvent'],\n ['mixMiniCart', MixMiniCart, 'minicart'],\n ['carousel', HarmonyCarousel, 'carousel'],\n ['einsteinCarousel', EinsteinCarousel, 'carousel'],\n ['inputNumber', InputNumber, 'basicInput'],\n ['harmonyQuantityStepper', HarmonyQuantityStepper, 'inputNumber'],\n ['stickyHeader', HarmonyStickyHeader, 'stickyHeader'],\n ['harmonyHeader', HarmonyHeader],\n ['loginForm', LoginForm, 'ajaxform'],\n ['harmonyLoginForm', HarmonyLoginForm, 'loginForm'],\n ['harmonyLoginForm', ListAccessibility, 'harmonyLoginForm'],\n ['harmonyMenuPanel', HarmonyMenuPanel, 'menuPanel'],\n ['hamburgerMenuItem', HarmonyHamburgerMenuItem, 'hamburgerMenuItem'],\n ['cookieDismissBlock', CookieDismissBlock],\n ['overlay', Overlay],\n ['loader', Loader],\n ['harmonySearchBox', SearchBox, 'combobox'],\n ['harmonySearchBox', AccessibilityFocusTrapMixin, 'harmonySearchBox'],\n ['harmonySearchBox', Modal, 'harmonySearchBox'],\n ['harmonySearchBox', HarmonyModal, 'harmonySearchBox'],\n ['harmonySearchBox', HarmonySearchBox, 'harmonySearchBox'],\n ['browsingOnlyModal', BrowsingOnlyModal, 'confirmDialog'],\n ['modal', HarmonyModal, 'modal'],\n ['shipToSideBar', shipToSideBar, 'sideBar'],\n ['headerSideBar', HeaderSideBar, 'sideBar'],\n ['secureCheckoutButton', SecureCheckoutButton, 'button'],\n ['shippingSelector', ShippingSelector, 'inputSelect'],\n ['viewMore', ViewMore],\n ['basicInput', HarmonyBasicInput, 'basicInput'],\n ['changePasswordForm', ChangePasswordForm, 'ajaxform'],\n ['cognito', Cognito],\n ['harmonyPasswordResetForm', HarmonyPasswordResetForm, 'ajaxform'],\n ['harmonyPasswordResetForm', AccessibilityFocusMixin, 'harmonyPasswordResetForm'],\n ['registerForm', RegisterForm, 'ajaxform'],\n ['verificationCodeForm', VerificationCodeForm, 'ajaxform'],\n ['verificationCodeForm', ListAccessibility, 'verificationCodeForm'],\n ['geolocationConsent', GeolocationConsent],\n ['accordionItem', HarmonyAccordionItem, 'accordionItem'],\n ['globalAlerts', HarmonyGlobalAlerts, 'globalAlerts'],\n ['backtotop', HarmonyBackToTop, 'backtotop'],\n ['focusHighlighter', HarmonyFocusHighlighter, 'focusHighlighter'],\n ['harmonyConsentTracking', HarmonyConsentTracking, 'consentTracking'],\n ['productTile', HarmonyAddToWishlistMixin, 'productTile'],\n ['harmonyAjaxFormResponseContainer', HarmonyAjaxFormResponseContainer, 'ajaxFormResponseContainer'],\n ['emitBusEvent', HarmonyEmitBusEvent, 'emitBusEvent'],\n ['quickAddToCart', QuickAddToCart],\n ['processButton', ProcessButton, 'button'],\n ['emailSubscribe', HarmonyEmailSubscribe, 'emailSubscribe'],\n ['inputPassword', HarmonyInputPassword, 'inputPassword'],\n ['shipToError', ShipToError],\n ['inputBirthdayDate', InputBirthdayDate, 'inputText'],\n ['statesInputRadio', StatesInputRadio, 'inputRadio'],\n ['cancelOrderModal', cancelOrderModal, 'harmonyModal'],\n ['reserveBarMgr', ReserveBarMgr],\n ['reserveBarCart', ReserveBarCart],\n ['reserveBarSearch', ReserveBarSearch], \n ['reserveBarProduct', ReserveBarProduct],\n ['pdFiltersMgr', PDFiltersMgr]\n\n]);\n\nif (corporatepayment) {\n require('./corporatePayment');\n}\n\nif (corporatepaymentlink) {\n require('./corporatepaymentlink');\n}\n\nif (plp) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'harmony.search.widgets' */ 'harmony/search/HarmonyRefinementsPanel'),\n import(/* webpackChunkName: 'harmony.search.widgets' */ 'harmony/search/HarmonyProductListingMgr'),\n import(/* webpackChunkName: 'harmony.search.widgets' */ 'harmony/search/HarmonyRefinement')\n ]).then(deps => {\n const [\n HarmonyRefinementsPanel,\n HarmonyProductListingMgr,\n HarmonyRefinement\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'harmony.search.widgets',\n widgetsDefinition: () => [\n // listAccessibility\n ['harmonyRefinementsPanel', HarmonyRefinementsPanel, 'modal'],\n ['harmonyProductListingMgr', HarmonyProductListingMgr, 'productListingMgr'],\n ['harmonyRefinement', HarmonyRefinement, 'refinement']\n ]\n };\n }));\n}\n\nif (!checkout) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'harmony.minicart' */ 'harmony/cart/HarmonyMiniCartDialog')\n ]).then(deps => {\n const [\n HarmonyMiniCartDialog\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'harmony.minicart',\n widgetsDefinition: () => [\n // modal\n ['minicartDialog', HarmonyMiniCartDialog, 'minicartDialog']\n ]\n };\n }));\n}\n\nif (pdp || plp || cart || wishlist) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import('harmony/product/HarmonyProductDetail'),\n import('harmony/product/HarmonyAddToCartMixin'),\n import('harmony/product/HarmonyVariationSwatch'),\n import('harmony/product/HarmonyProductImages'),\n import('harmony/product/ProductDescriptionItem'),\n import('harmony/wishlist/HarmonyAddToWishlistMixin'),\n import('harmony/product/ProductSet'),\n import('harmony/product/NotifyMeForm'),\n import('harmony/product/HarmonyProductTile')\n ]).then(deps => {\n const [\n HarmonyProductDetail,\n HarmonyAddToCartMixin,\n HarmonyVariationSwatch,\n HarmonyProductImages,\n ProductDescriptionItem,\n HarmonyAddToWishListMixin,\n ProductSet,\n NotifyMeForm,\n HarmonyProductTile\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'harmony.product.widgets',\n widgetsDefinition: () => [\n ['productDetail', HarmonyProductDetail, 'productDetail'],\n ['productDetail', HarmonyAddToCartMixin, 'productDetail'],\n ['productDetail', HarmonyAddToWishListMixin, 'productDetail'],\n ['productSet', ProductSet],\n ['variationSwatch', HarmonyVariationSwatch, 'colorSwatch'],\n ['productImages', HarmonyProductImages, 'productImages'],\n ['productDescriptionItem', ProductDescriptionItem],\n ['productTile', HarmonyProductTile, 'productTile'],\n ['productTile', HarmonyAddToWishlistMixin, 'productTile'],\n ['notifyMeForm', NotifyMeForm, 'ajaxform']\n ]\n };\n }));\n}\n\nif (cart) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'harmony.cart' */ 'harmony/cart/HarmonyCartMgr'),\n import(/* webpackChunkName: 'harmony.cart' */ 'harmony/forms/HarmonyCouponForm')\n ]).then(deps => {\n const [\n HarmonyCartMgr,\n HarmonyCouponForm\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'harmony.cart',\n widgetsDefinition: () => [\n ['cartMgr', HarmonyCartMgr, 'cartMgr'],\n ['cartMgr', AccessibilityFocusMixin, 'cartMgr'],\n ['couponform', HarmonyCouponForm, 'couponform']\n ]\n };\n }));\n}\n\nif (account) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'harmony.account' */ 'harmony/global/HarmonyTabs'),\n /* eslint-disable */\n import(/* webpackChunkName: 'harmony.account' */ 'harmony/account/VerificationCodeForm'),\n /** eslint-enable */\n import(/* webpackChunkName: 'checkout.widgets' */ 'widgets/forms/ajax/LoginForm'),\n import(/* webpackChunkName: 'harmony.account' */ 'harmony/forms/ajax/HarmonyLoginForm'),\n import(/* webpackChunkName: 'harmony.account' */ 'harmony/account/HarmonyPaymentsList'),\n import(/* webpackChunkName: 'harmony.account' */ 'widgets/checkout/InputCreditCardNumber'),\n import(/* webpackChunkName: 'harmony.account' */ 'harmony/account/PaymentForm'),\n import(/* webpackChunkName: 'harmony.account' */ 'authorize/payment/AuthorizeNetPaymentMixin'),\n import(/* webpackChunkName: 'harmony.account' */ 'authorize/account/AuthorizeNetPaymentForm'),\n import(/* webpackChunkName: 'account.widgets' */ 'harmony/forms/HarmonyAddressBookForm'),\n import(/* webpackChunkName: 'account.widgets' */ 'widgets/global/ListAccessibility'),\n import(/* webpackChunkName: 'account.widgets' */ 'harmony/account/HarmonyAddressList'),\n import(/* webpackChunkName: 'account.widgets' */ 'harmony/checkout/HarmonyInputCreditCardNumber'),\n import(/* webpackChunkName: 'checkout.widgets' */ 'widgets/checkout/RadioSelector')\n ]).then(deps => {\n const [\n HarmonyTabs,\n VerificationCodeForm,\n LoginForm,\n HarmonyLoginForm,\n HarmonyPaymentsList,\n InputCreditCardNumber,\n PaymentForm,\n AuthorizeNetPaymentMixin,\n AuthorizeNetPaymentForm,\n HarmonyAddressBookForm,\n ListAccessibility,\n HarmonyAddressList,\n HarmonyInputCreditCardNumber,\n RadioSelector\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'harmony.account',\n widgetsDefinition: () => [\n ['harmonyTabs', HarmonyTabs, 'tabs'],\n ['verificationCodeForm', VerificationCodeForm, 'ajaxform'],\n ['verificationCodeForm', ListAccessibility, 'verificationCodeForm'],\n ['loginForm', LoginForm, 'ajaxform'],\n ['harmonyLoginForm', HarmonyLoginForm, 'loginForm'],\n ['harmonyLoginForm', ListAccessibility, 'harmonyLoginForm'],\n ['profileDetailsForm', ProfileDetailsForm, 'ajaxform'],\n ['paymentList', HarmonyPaymentsList, 'paymentList'],\n ['inputCreditCardNumber', InputCreditCardNumber, 'basicInput'],\n ['inputCreditCardNumber', HarmonyInputCreditCardNumber, 'inputCreditCardNumber'],\n ['paymentsList', HarmonyPaymentsList, 'paymentsList'],\n ['paymentForm', PaymentForm, 'ajaxform'],\n ['authorizeNetPaymentForm', AuthorizeNetPaymentMixin, 'paymentForm'],\n ['authorizeNetPaymentForm', AuthorizeNetPaymentForm, 'authorizeNetPaymentForm'],\n ['harmonyAddressBookForm', HarmonyAddressBookForm, 'addressBookForm'],\n ['addressList', HarmonyAddressList, 'addressList'],\n ['radioSelector', RadioSelector, 'inputRadio']\n ]\n };\n }));\n}\n\nif (checkout) {\n // @ts-ignore\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'harmony.account' */ 'harmony/account/VerificationCodeForm'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'harmony/checkout/HarmonyAddressCheckoutForm'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'harmony/checkout/HarmonyCheckoutMgr'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'harmony/checkout/HarmonyShippingFrom'),\n import(/* webpackChunkName: 'checkout.widgets' */ 'widgets/forms/ajax/LoginForm'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'harmony/forms/ajax/HarmonyLoginForm'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'harmony/checkout/HarmonyPaymentAccordion'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'harmony/checkout/HarmonyBillingForm'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'harmony/checkout/HarmonyPaymentAccordionItemCREDIT_CARD'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'authorize/payment/AuthorizeNetPaymentMixin'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'authorize/checkout/AuthorizeNetBillingForm'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'shipCompliant/checkout/ShipCompliantCheckoutMgrMixin'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'shipCompliant/checkout/ShipCompliantShippingFormMixin'),\n import(/* webpackChunkName: 'account.widgets' */ 'widgets/global/ListAccessibility'),\n import(/* webpackChunkName: 'account.widgets' */ 'harmony/checkout/HarmonyInputCreditCardNumber'),\n import(/* webpackChunkName: 'checkout.widgets' */ 'harmony/forms/InputFutureShippingDate'),\n import(/* webpackChunkName: 'harmony.cart' */ 'harmony/forms/HarmonyCouponForm'),\n import(/* webpackChunkName: 'cart.widgets' */ 'widgets/forms/CouponForm'),\n import(/* webpackChunkName: 'cart.widgets' */ 'widgets/cart/PromoCodeDisclosure')\n ]).then(deps => {\n const [\n VerificationCodeForm,\n HarmonyAddressCheckoutForm,\n HarmonyCheckoutMgr,\n HarmonyShippingFrom,\n LoginForm,\n HarmonyLoginForm,\n HarmonyPaymentAccordion,\n HarmonyBillingForm,\n HarmonyPaymentAccordionItemCREDITCARD,\n AuthorizeNetPaymentMixin,\n AuthorizeNetBillingForm,\n ShipCompliantCheckoutMgrMixin,\n ShipCompliantShippingFormMixin,\n ListAccessibility,\n HarmonyInputCreditCardNumber,\n InputFutureShippingDate,\n HarmonyCouponForm,\n CouponForm,\n PromoCodeDisclosure\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'harmony.checkout',\n widgetsDefinition: () => [\n ['verificationCodeForm', VerificationCodeForm, 'ajaxform'],\n ['verificationCodeForm', ListAccessibility, 'verificationCodeForm'],\n ['addressCheckoutForm', HarmonyAddressCheckoutForm, 'addressCheckoutForm'],\n ['checkoutMgr', HarmonyCheckoutMgr, 'checkoutMgr'],\n ['shippingForm', HarmonyShippingFrom, 'shippingForm'],\n ['harmonyLoginForm', HarmonyLoginForm, 'checkoutLoginForm'],\n ['harmonyLoginForm', ListAccessibility, 'harmonyLoginForm'],\n ['loginForm', LoginForm, 'ajaxform'],\n ['paymentAccordion', HarmonyPaymentAccordion, 'paymentAccordion'],\n ['billingForm', HarmonyBillingForm, 'billingForm'],\n ['paymentAccordionItemCREDIT_CARD', HarmonyPaymentAccordionItemCREDITCARD, 'paymentAccordionItemCREDIT_CARD'],\n ['authorizeNetBillingForm', AuthorizeNetPaymentMixin, 'billingForm'],\n ['authorizeNetBillingForm', AuthorizeNetBillingForm, 'authorizeNetBillingForm'],\n ['checkoutMgr', ShipCompliantCheckoutMgrMixin, 'checkoutMgr'],\n ['shippingForm', ShipCompliantShippingFormMixin, 'shippingForm'],\n ['inputCreditCardNumber', HarmonyInputCreditCardNumber, 'inputCreditCardNumber'],\n ['inputFutureShippingDate', InputFutureShippingDate, 'inputText'],\n ['couponform', CouponForm, 'ajaxform'],\n ['couponform', HarmonyCouponForm, 'couponform'],\n ['promoCodeDisclosure', PromoCodeDisclosure, 'disclosure']\n ]\n };\n }));\n}\n\nif (wishlist) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'wishlist.widgets' */ 'widgets/wishlist/WishlistMgr'),\n import(/* webpackChunkName: 'wishlist.widgets' */ 'widgets/wishlist/WishListItem'),\n import(/* webpackChunkName: 'wishlist.widgets' */ 'harmony/wishlist/HarmonyWishListItem'),\n import(/* webpackChunkName: 'wishlist.widgets' */ 'harmony/wishlist/HarmonyWishlistMgr')\n ]).then(deps => {\n const [WishlistMgr, WishListItem, HarmonyWishlistItem, HarmonyWishlistMgr] = deps.map(dep => dep.default);\n\n return {\n listId: 'wishlist.widgets',\n widgetsDefinition: () => [\n ['wishlistMgr', WishlistMgr],\n ['wishListItem', WishListItem],\n ['wishListItem', HarmonyWishlistItem, 'wishListItem'],\n ['wishListItem', QuickAddToCart, 'wishListItem'],\n ['wishlistMgr', HarmonyWishlistMgr, 'wishlistMgr']\n ]\n };\n }));\n}\n\nif (orderconfirmation) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'orderconfirmation.widgets' */ 'harmony/forms/ajax/ConfirmationCreateAccount'),\n import(/* webpackChunkName: 'harmony.account' */ 'harmony/account/VerificationCodeForm'),\n import(/* webpackChunkName: 'account.widgets' */ 'widgets/global/ListAccessibility')\n ]).then(deps => {\n const [\n ConfirmationCreateAccount,\n VerificationCodeForm,\n ListAccessibility\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'orderconfirmation.widgets',\n widgetsDefinition: () => [\n ['confirmationCreateAccount', ConfirmationCreateAccount, 'ajaxform'],\n ['verificationCodeForm', VerificationCodeForm, 'ajaxform'],\n ['verificationCodeForm', ListAccessibility, 'verificationCodeForm']\n ]\n };\n }));\n}\n\nif (storeLocator) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'storelocator.widgets' */ 'harmony/forms/ajax/WhereToBuyForm')\n ]).then(deps => {\n const [\n WhereToBuyForm,\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'storelocator.widgets',\n widgetsDefinition: () => [\n ['whereToBuyForm', WhereToBuyForm, 'ajaxform']\n ]\n };\n }));\n require('./globalFindNearMe');\n}\n\nif(clp) {\n WidgetsMgr.asyncListsPromises.push(Promise.all([\n import(/* webpackChunkName: 'checkout.widgets' */ 'widgets/forms/ajax/LoginForm'),\n import(/* webpackChunkName: 'harmony.checkout' */ 'harmony/forms/ajax/HarmonyLoginForm'),\n import(/* webpackChunkName: 'account.widgets' */ 'widgets/global/ListAccessibility'),\n /* eslint-disable */\n import(/* webpackChunkName: 'harmony.account' */ 'harmony/account/VerificationCodeForm'),\n ]).then(deps => {\n const [\n LoginForm,\n HarmonyLoginForm,\n ListAccessibility,\n VerificationCodeForm\n ] = deps.map(dep => dep.default);\n\n return {\n listId: 'clp.widgets',\n widgetsDefinition: () => [\n ['loginForm', LoginForm, 'ajaxform'],\n ['harmonyLoginForm', HarmonyLoginForm, 'loginForm'],\n ['harmonyLoginForm', ListAccessibility, 'harmonyLoginForm'],\n ['verificationCodeForm', VerificationCodeForm, 'ajaxform'],\n ['verificationCodeForm', ListAccessibility, 'verificationCodeForm'],\n ]\n };\n }));\n}\n","import { submitFormJson, getJSONByUrl } from 'widgets/toolbox/ajax';\n\n/**\n * @description Extended HarmonyAddToWishlistMixin implementation.\n * @param {AddToWishlistMixin} AddToWishlistMixin Base widget for extending\n * @returns {typeof HarmonyAddToWishlistMixin} Harmony Add to Wishlist Mixin class\n */\nexport default function (AddToWishlistMixin) {\n class HarmonyAddToWishlistMixin extends AddToWishlistMixin {\n init() {\n super.init();\n this.pid = this.prefs().masterPid || this.prefs().pid;\n\n // set add to wishlist state on load\n this.getById(this.prefs().addToWishlistBtn, (/** @type {button} */ button) => {\n this.addedToWishList = button.prefs().addedToWithlist;\n });\n\n this.eventBus().on('update.wishlist.state', 'updateWishlistState');\n }\n\n prefs() {\n return {\n textRemovedFromWishlist: '',\n activeClasses: 'm-added',\n ...super.prefs()\n };\n }\n\n /**\n * @description Show message. The function was overloaded. It is empty, because the message isn't used.\n * @param {string} msg Message\n * @param {boolean} error Error flag\n */\n showWishlistMessage() { }\n\n /**\n * @description Hide message. The function was overloaded. It is empty, because the message isn't used.\n * @returns {void}\n */\n hideWishlistMessage() { }\n\n /**\n * @param {object} button - button\n * @description Add to Wishlist\n * @returns {void}\n */\n addToWishlist(button) {\n const addTWishlistBtnPrefs = button.prefs();\n\n button.busy();\n\n const promise = submitFormJson(addTWishlistBtnPrefs.addToWishlistUrl, {\n pid: this.pid || '',\n qty: this.selectedQuantity || ''\n }).then(response => {\n this.handleSuccess(response, button);\n this.eventBus().emit('product.added.to.wishlist', this.config.analytics);\n }).finally(() => {\n button.unbusy();\n }).catch(() => {\n this.showWishlistMessage(this.prefs().textNetworkError, true);\n });\n\n this.eventBus().emit('loader.start', promise);\n }\n\n /**\n * @param {object} button - button\n * @description Remove from Wishlist\n * @returns {void}\n */\n removeFromWishlist(button) {\n const addTWishlistBtnPrefs = button.prefs();\n\n button.busy();\n\n const promise = getJSONByUrl(addTWishlistBtnPrefs.removeFromWishlistUrl, {\n pid: this.pid || ''\n }).then(response => {\n this.handleRemoveSuccess(response, button);\n this.eventBus().emit('product.removed.from.wishlist', this.config.analytics);\n }).finally(() => {\n button.unbusy();\n }).catch(() => {\n this.showWishlistMessage(this.prefs().textNetworkError, true);\n });\n\n this.eventBus().emit('loader.start', promise);\n }\n\n /**\n * @description Handle success response\n * @param {object} response - Response object\n * @param {object} button - button\n * @returns {void}\n */\n handleSuccess(response, button) {\n const addToWishlistBtn = button || this.getById(\n this.prefs().addToWishlistBtn,\n (/** @type {button} */ element) => element\n );\n\n if (!addToWishlistBtn) {\n return;\n }\n\n if (response.success) {\n this.addedToWishList = true;\n\n addToWishlistBtn\n .activate()\n .press();\n\n if (!this.addToWishlistHideTexts) {\n addToWishlistBtn.setText(this.prefs().textAddedToWishlist);\n }\n\n const accessibilityAlert = this.prefs().accessibilityAlerts.addedtowishlist;\n // TODO add event description\n this.eventBus().emit('alert.show', {\n accessibilityAlert\n });\n }\n\n if (!this.addToWishlistHideTexts) {\n this.showWishlistMessage(response.msg, response.error);\n }\n }\n\n /**\n * @description Handle remove success response\n * @param {object} response - Response object\n * @param {object} button - button\n * @returns {void}\n */\n handleRemoveSuccess(response, button) {\n const addToWishlistBtn = button || this.getById(\n this.prefs().addToWishlistBtn,\n (/** @type {button} */ element) => element\n );\n\n if (!addToWishlistBtn) {\n return;\n }\n\n if (response.success) {\n this.addedToWishList = false;\n\n addToWishlistBtn\n .deactivate()\n .unpress();\n\n if (!this.addToWishlistHideTexts) {\n addToWishlistBtn.setText(this.prefs().textRemovedFromWishlist);\n }\n\n const accessibilityAlert = this.prefs().accessibilityAlerts.removedfromwishlist;\n // TODO add event description\n this.eventBus().emit('alert.show', {\n accessibilityAlert\n });\n\n if (response.isEmpty) {\n this.eventBus().emit('wishlist.empty');\n }\n }\n\n if (!this.addToWishlistHideTexts) {\n this.showWishlistMessage(response.msg, response.error);\n }\n }\n\n /**\n * @description Method to revert initial state for `Add to Wishlist` button and messages\n * Usually used when re-rendering PDP/QuickView after variation change\n * @param {object} product - Product object\n * @returns {void}\n */\n renderWishList(product) {\n super.renderWishList(product);\n const masterPid = product?.master?.pid;\n if (masterPid) {\n this.pid = masterPid;\n }\n }\n\n /**\n * @param {object} button - button\n * @description Process Wishlist\n * @returns {void}\n */\n processWishlist(button) {\n if (this.addedToWishList) {\n this.removeFromWishlist(button);\n } else {\n this.addToWishlist(button);\n }\n }\n\n /**\n * @description Update wishlist state according to incloming state\n * @param {boolean} isAdded - is added to wishlist\n */\n updateWishlistState(isAdded) {\n this.getById(this.prefs().addToWishlistBtn, (/** @type {button} */ button) => {\n if (isAdded) {\n button.activate().press();\n this.addedToWishList = true;\n } else {\n button.deactivate().unpress();\n this.addedToWishList = false;\n }\n });\n }\n }\n\n return HarmonyAddToWishlistMixin;\n}\n","/* eslint-disable consistent-return */\n/* eslint-disable no-underscore-dangle */\nimport { log } from './toolbox/util';\nimport { RefElement } from 'widgets/toolbox/RefElement';\nimport EventBusWrapper from 'widgets/toolbox/EventBusWrapper';\nimport widgetsMgr from './widgetsMgr';\n\nconst templateProp = '@@@_template';\nconst noop = () => { };\n\n/**\n * @description save template during webpack HMR\n * @param {RefElement} renderTo rendering element\n * @param {string} template template string\n */\nfunction saveTemplateForHotReload(renderTo, template) {\n if (!PRODUCTION) { // save template in element for hot reload\n const tmpEl = renderTo.get();\n if (tmpEl) {\n tmpEl[templateProp] = template;\n }\n }\n}\n\n/**\n * @description Find modified element\n * @param {ChildNode} nodeOrig element\n * @param {number[]} routeOrig path to element\n * @returns {ChildNode|false} modified element or false if not found\n */\nfunction getFromRoute(nodeOrig, routeOrig) {\n let node = nodeOrig;\n const route = routeOrig.slice();\n while (route.length > 0) {\n if (node && node.childNodes) {\n const c = route.splice(0, 1)[0];\n node = node.childNodes[c];\n } else {\n return false;\n }\n }\n return node;\n}\n\n/**\n * @typedef Diff\n * @property {string} name\n * @property {string} action\n * @property {string} value\n * @property {string} oldValue\n * @property {string} newValue\n * @property {number[]} route\n */\n\n/**\n * @typedef Info\n * @property {HTMLElement} node\n * @property {Diff} diff\n */\n\n/**\n * @description Callback assigned on diff-dom post hook after applying changes\n * @param {string} action diff-dom action happens with DOM node\n * @param {HTMLElement} node changed element\n * @param {Info} info diff-dom changes object\n * @returns {() => void} callback\n */\nfunction getDelayedCallback(action, node, info) {\n return () => {\n if (action === 'modifyAttribute') {\n widgetsMgr.removeAttribute(node, info.diff);\n widgetsMgr.addAttribute(node, info.diff);\n } else if (action === 'removeAttribute') {\n widgetsMgr.removeAttribute(node, info.diff);\n } else if (action === 'addAttribute') {\n widgetsMgr.addAttribute(node, info.diff);\n } else {\n throw new Error(`Unknown action \"${action}\"`);\n }\n };\n}\n\n/** Core component to extend for each widget */\n/**\n * @category widgets\n * @subcategory framework\n */\nclass Widget {\n /**\n * @description Creates self RefElement.js wrapper, add initial states, configuration.\n * @param {HTMLElement} el DOM element\n * @param {{[x: string]: object|string|number|boolean|null|undefined}} config widget config\n */\n constructor(el, config = {}) {\n /**\n * @description RefElements related to current widget\n * @type {{[key : string] : RefElement} | undefined}\n */\n this.refs = Object.create(null);\n if (this.refs) { // for type check\n this.refs.self = new RefElement([el]);\n }\n\n /**\n * @description config from data attributes\n * @type {{[x: string]: object|string|number|boolean|null|undefined}}\n */\n this.config = config;\n\n /**\n * @description functions which executing during destructuring of widget\n * @type {Array|undefined}\n */\n this.disposables = undefined;\n /**\n * @description function assigned by WidgetsMgr after constructor call to\n * provide ability for parent widget listen child widget event\n * fired by method `this.emit()`\n * @type {(eventName: string, ...args: any) => void}\n */\n this.parentHandler = noop;\n\n this.getConstructor = id => id;\n\n /**\n * @description children widgets\n * @type {Widget[]|undefined}\n */\n this.items = [];\n\n if (this.ref('self').attr('id')) {\n this.id = this.ref('self').attr('id');\n }\n if (!this.id && this.config.id) {\n this.id = this.config.id;\n }\n\n /**\n * @type {boolean} state explain widget is shown\n */\n this.shown = !this.config.hidden && !this.ref('self').hasAttr('hidden');\n\n if (typeof this.config.passEvents === 'string') {\n if (!PRODUCTION) {\n log.warn('Usage of \"data-pass-events\" has been deprecated. Please take a look \"data-forward-to-parent\"');\n }\n this.config.passEvents.split(':').forEach(pair => {\n const [methodName, emitEvent] = pair.split('-');\n const self = this;\n // @ts-ignore\n this[methodName] = (...args) => {\n self.parentHandler(emitEvent, ...args);\n };\n });\n }\n\n this.isRefreshingWidget = false;\n }\n\n get length() {\n return 1;\n }\n\n /**\n * @description call class method provided in argument `name` with arguments `(classContext, value, ...args)` or\n * call `RefElement.data(name, value)` for wrapper widget DOM node to get or set data attributes\n * @param {string} name of class method or data-attribute\n * @param {any} [value] if class has method `name` - will be used as argument, otherwise will be used as `value` to set into data attribute `name`\n * @param {any} [args] if class has method `name` - will be used as argument, otherwise would not be used\n * @returns {any}\n * - execution result of the method specified as `name` argument\n * - if class has no method `name` - get or set data attribute `name` depending on provided or no `value` argument\n */\n data(name, value, ...args) {\n /**\n * @type {Function}\n */\n // @ts-ignore\n const classMethod = this[name];\n if (typeof classMethod === 'function') {\n return classMethod.call(this, value, ...args);\n }\n return this.ref('self').data(name, value);\n }\n\n /**\n * @description call class method provided in argument `name` with arguments `...args` if method exists\n *\n * @param {string} name of class method\n * @param {any} [args] if class has method `name` - will be used as arguments\n * @returns {any} result of call (any) or undefined, if method not found\n */\n callIfExists(name, ...args) {\n const classMethod = this[name];\n if (typeof classMethod === 'function') {\n return classMethod.call(this, ...args);\n }\n }\n\n /**\n * @description Emit widget event that will be listened by parrent handler with context of current widget\n * @param {string} eventName name of event\n * @param {...any} args argument to pass\n * @returns {void}\n */\n emit(eventName, ...args) {\n this.parentHandler(eventName, this, ...args);\n }\n\n /**\n * @description Emit widget event that will be listened by parrent handler without context of current widget\n * @param {string} eventName name of event\n * @param {...any} args argument to pass\n * @returns {void}\n */\n emitWithoutContext(eventName, ...args) {\n this.parentHandler(eventName, ...args);\n }\n\n /**\n * @description In case if you need to emit/subscribe global event you may get an event bus with this method.\n * @description Get widget's EventBusWrapper instance\n * @returns {EventBusWrapper} Instance of EventBusWrapper\n */\n eventBus() {\n if (!this._eventBus) {\n this._eventBus = new EventBusWrapper(this);\n this.onDestroy(() => {\n this._eventBus = undefined;\n });\n }\n return this._eventBus;\n }\n\n /**\n * @description Merge data-attribute properties to default widget properties (defined in widget javascript file, or extended from parent widget)\n * and returns widget configuration map\n * @returns {{[key: string]: any}} config widget config\n */\n prefs() {\n return {\n /** is component hidden */\n hidden: false,\n /** class of component during loading */\n classesLoading: 'm-widget-loading',\n /** class of component once component loaded and inited */\n /** id of component */\n id: '',\n // configs form data attributes\n ...this.config\n };\n }\n\n /**\n * @description This method executed in the end of [Widgets Application Lifecycle,]{@link tutorial-WidgetsApplicationLifecycle.html}\n * in order to add business logic before initialization is finished.\n */\n init() {\n this.ref('self').removeClass(this.prefs().classesLoading);\n }\n\n /**\n * @description Get child refElement by key from prefs() or id\n * @param {string} name Id of RefElement or preference key that contains id\n * @returns {RefElement} found RefElement instance or empty RefElement if doesn't exist\n * @protected\n */\n ref(name) {\n const prefsName = this.prefs();\n const prefsValue = prefsName[name];\n\n let ref;\n\n if (prefsValue) {\n ref = this.refs && this.refs[prefsValue];\n\n if (ref) {\n return ref;\n }\n }\n\n ref = this.refs && this.refs[name];\n\n if (ref) {\n return ref;\n }\n if (!PRODUCTION) {\n log.warn(`Reference \"${name}\" is not found in widget \"${this.constructor.name}\" `, this);\n }\n return new RefElement([]);\n }\n\n /**\n * @description search `refElement` inside of widget by `name`\n * - if `cb` exist - run `cb` with found `refElement` as argument\n * - otherwise return existing state\n *\n * @param {string} name Id of widget/refElement or preference that contain id of widget/refElement\n * @param {(arg: RefElement) => void} [cb] callback will be executed if element found\n * @returns {boolean} true if found `refElement`\n */\n has(name, cb) {\n const ref = this.refs && this.refs[name];\n\n if (ref) {\n if (cb) {\n cb(ref);\n }\n\n return true;\n }\n return false;\n }\n\n /**\n * @description Destroys widgets. Only for internal usage\n *\n * @protected\n */\n destroy() {\n if (this.disposables) {\n this.disposables.forEach(disposable => disposable());\n this.disposables = undefined;\n }\n\n if (this.items && this.items.length) {\n this.items.forEach(item => {\n if (item && typeof item.destroy === 'function') {\n item.destroy();\n }\n });\n }\n\n this.items = undefined;\n this.refs = undefined;\n }\n\n /**\n * @description Attach an event handler function for one or more events to the selected elements.\n * @param {string} eventName ex: 'click', 'change'\n * @param {(this: this, element: HTMLElement, event: Event) => any} cb callback\n * @param {string|EventTarget} selector CSS selector\n * @param {boolean} passive is handler passive?\n * @returns {(() => void)[]} dispose functions for each elemenet event handler\n *\n * @protected\n */\n ev(eventName, cb, selector = '', passive = true) {\n /**\n * @type EventTarget[]\n */\n var elements = [];\n var self = this;\n\n if (selector instanceof Element || selector === window) {\n elements = [selector];\n } else if (typeof selector === 'string' && this.refs && this.refs.self) {\n const el = this.refs.self.get();\n if (el) {\n elements = Array.from(el.querySelectorAll(selector));\n }\n } else if (this.refs && this.refs.self) {\n const el = this.refs.self.get();\n if (el) {\n elements = [el];\n }\n }\n\n return elements.map(element => {\n // @ts-ignore\n let fn = function fn(...args) {\n // @ts-ignore\n return cb.apply(self, [this, ...args]);\n };\n\n element.addEventListener(eventName, fn, passive ? { passive: true } : { passive: false });\n const dispose = () => {\n if (fn) {\n element.removeEventListener(eventName, fn);\n // @ts-ignore\n fn = undefined;\n }\n };\n this.onDestroy(dispose);\n dispose.eventName = eventName;\n return dispose;\n });\n }\n\n /**\n * @description Assign function to be executed during widget destructuring\n * @param {Function} fn function to be executed during destroy\n * @returns {Function} called function\n */\n onDestroy(fn) {\n if (!this.disposables) {\n this.disposables = [];\n }\n this.disposables.push(fn);\n return fn;\n }\n\n /**\n * @description executed when widget is re-rendered\n */\n onRefresh() {\n // executed when widget is re-rendered\n this.shown = !this.config.hidden && !this.ref('self').hasAttr('hidden');\n\n if (this.ref('self').attr('id')) {\n this.id = this.ref('self').attr('id');\n } else if (this.ref('self').data('id')) {\n this.id = this.ref('self').data('id');\n }\n }\n\n /**\n * @description Search for child component instance and execute callback with this instance as argument\n * @template T\n * @param {string} id of component\n * @param {(args0: any) => T} cb callback with widget\n * @returns {T|undefined} callback result if element found, otherwise undefined\n */\n getById(id, cb) {\n if (id && this.items && this.items.length) {\n for (var c = 0; c < this.items.length; c += 1) {\n const item = this.items[c];\n\n if (item && item.id === id) {\n return cb.call(this, item);\n }\n }\n }\n\n if (!PRODUCTION) {\n log.warn(`Widget with id \"${id}\" is not found in children of \"${this.constructor.name}\" `, this);\n }\n }\n\n /**\n * Travels over nearest/next level child components\n *\n * @template T\n * @param {(args0: any) => T} fn callback\n * @returns {T[]} arrays of callback results\n */\n eachChild(fn) {\n if (this.items && this.items.length) {\n return this.items.map(item => {\n return fn(item);\n });\n }\n return [];\n }\n\n /**\n * @description Hide widget\n * @returns {this} current instance for chaining\n */\n hide() {\n if (this.shown) {\n this.ref('self').hide();\n this.shown = false;\n }\n\n return this;\n }\n\n /**\n * @description Show widget\n * @returns {this} current instance for chaining\n */\n show() {\n if (!this.shown) {\n this.ref('self').show();\n this.shown = true;\n }\n\n return this;\n }\n\n /**\n * @description Show or hide widget element\n * @param {boolean} [display] Use true to show the element or false to hide it.\n * @returns {this} current instance for chaining\n */\n toggle(display) {\n const state = typeof display !== 'undefined' ? display : !this.shown;\n\n this[state ? 'show' : 'hide']();\n return this;\n }\n\n /**\n * @description Returns whether the widget is hidden\n * @returns {boolean} Hidden flag\n */\n isHidden() {\n return !this.shown;\n }\n\n /**\n * @description Returns whether the widget is shown\n * @returns {boolean} Shown flag\n */\n isShown() {\n return this.shown;\n }\n\n /**\n * @description This method provides ability to dynamically render HTML for widgets.\n * @param {string} templateRefId id of template\n * @param {object} data data to render\n * @param {RefElement} [renderTo] render into element\n * @param {string} [strToRender] pre-rendered template\n * @returns {Promise} resolved if rendered or rejected if no found template promise\n *\n * @protected\n */\n render(templateRefId = 'template', data = {}, renderTo = this.ref('self'), strToRender = '') {\n return import(/* webpackChunkName: 'dynamic-render' */'mustache').then((Mustache) => {\n // eslint-disable-next-line complexity\n if (!this.cachedTemplates) {\n /**\n * @description Container to cache templates for rendering\n * @type {{[x: string]: string|undefined}}\n */\n this.cachedTemplates = {};\n }\n\n let template = this.cachedTemplates && this.cachedTemplates[templateRefId];\n\n if (!strToRender && !template) {\n const templateElement = this.ref(templateRefId).get();\n\n if (templateElement) {\n template = templateElement.textContent || templateElement.innerHTML;\n Mustache.parse(template);\n this.cachedTemplates[templateRefId] = template;\n\n saveTemplateForHotReload(renderTo, template);\n } else {\n // eslint-disable-next-line no-lonely-if\n if (!PRODUCTION) {\n const tmpEl = renderTo.get();\n if (tmpEl && tmpEl[templateProp]) {\n template = tmpEl[templateProp];\n } else {\n log.error(`Unable find template ${templateRefId}`, this);\n return Promise.reject(new Error(`Unable find template ${templateRefId}`));\n }\n log.error(`Unable find template ${templateRefId}`, this);\n return Promise.reject(new Error(`Unable find template ${templateRefId}`));\n }\n }\n }\n\n if (data) {\n data.lower = function () {\n return function (text, render) {\n return render(text).toLowerCase();\n };\n };\n }\n\n const renderedStr = strToRender || Mustache.render(template || '', data);\n const el = renderTo.get();\n\n if (el && el.parentNode) {\n // use new document to avoid loading images when diffing\n const newHTMLDocument = document.implementation.createHTMLDocument('diffDOM');\n const diffNode = /** @type {HTMLElement} */(newHTMLDocument.createElement('div'));\n\n diffNode.innerHTML = renderedStr;\n\n return this.applyDiff(el, diffNode);\n } else {\n log.error(`Missing el to render ${templateRefId}`, this);\n }\n\n return Promise.resolve();\n });\n }\n\n /**\n * @description Find diff between `el` and `diffNode` and apply diff by `diff-dom`\n * @param {HTMLElement} el Element before change\n * @param {HTMLElement} diffNode Changed element to find diff\n * @returns {Promise} when diff founded\n */\n applyDiff(el, diffNode) {\n return import(/* webpackChunkName: 'dynamic-render' */ 'diff-dom/src/index')\n .then(/** @type {Function[]} */ ({ DiffDOM }) => {\n const delayedAttrModification = [];\n const dd = new DiffDOM({\n // disableGroupRelocation: true,\n filterOuterDiff(/** @type {any} */t1) {\n // @ts-ignore\n if (t1.attributes && t1.attributes['data-skip-render']) {\n // will not diff childNodes\n t1.innerDone = true;\n }\n },\n /**\n * @param {Info} info changes from diff-dom\n */\n postDiffApply(info) {\n const { action, name } = info.diff;\n if (\n ['removeAttribute', 'addAttribute', 'modifyAttribute'].includes(action)\n && typeof name === 'string'\n && name.startsWith('data-') // handle only data attr changes\n && info.node instanceof HTMLElement\n ) {\n const node = getFromRoute(el, info.diff.route);\n\n if (node && node instanceof HTMLElement) {\n const delayedCallback = getDelayedCallback(action, node, info);\n // data-initialized should be executed at last point\n delayedAttrModification[name === 'data-initialized' ? 'push' : 'unshift'](delayedCallback);\n }\n }\n if (\n (action === 'addAttribute' || action === 'removeAttribute')\n && info.node.nodeName === 'INPUT'\n && name === 'checked'\n ) {\n const node = /** @type {HTMLInputElement} */(info.node);\n if (node.type === 'checkbox' || node.type === 'radio') {\n node.checked = (action === 'addAttribute');\n }\n }\n }\n });\n // Normalize DOM tree before applying diff to prevent infinite loop\n // Infinite loop appear in case when few text nodes became one by one\n el.normalize();\n\n const diff = dd.diff(el, diffNode.firstElementChild);\n\n if (diff && diff.length) {\n // console.log(diff);\n dd.apply(el, diff);\n }\n // report attr modification once app changes are applied\n delayedAttrModification.forEach(action => action());\n\n if (diff && diff.length) {\n this.eventBus().emit('rendering.applied');\n }\n });\n }\n}\n\nexport default Widget;\n\n/**\n * @typedef IEvent\n * @property {string} [events.childID]\n * @property {Function} [events.childClass]\n * @property {string} events.eventName\n * @property {Function} events.fn\n * @property {Function} [cb]\n */\n","/**\n * https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttributeNames#Polyfill\n *\n * Needed for MS Edge and IE\n */\nif (Element.prototype.getAttributeNames === undefined) {\n Element.prototype.getAttributeNames = function getAttributeNames() {\n const attributes = this.attributes;\n const length = attributes.length;\n const result = [];\n result.length = length;\n for (var i = 0; i < length; i += 1) {\n result[i] = attributes[i].name;\n }\n return result;\n };\n}\n","// CLARIFY: get rid of smoothscroll-polyfill due to big size and not critical value\nimport './getAttributeNames'; // needed for MS Edge\n\nif (!('scrollBehavior' in document.documentElement.style)) { // needed for MS Edge\n setTimeout(() => {\n import(/* webpackChunkName: 'smoothscroll' */ 'smoothscroll-polyfill').then(smoothscroll => {\n smoothscroll.default.polyfill();\n });\n }, 0);\n}\n","import { submitFormJson, errorFallbackMessage } from 'widgets/toolbox/ajax';\nimport { scrollWindowTo, scrollToTop } from 'widgets/toolbox/scroll';\nimport { timeout } from 'widgets/toolbox/util';\n\n/**\n * @typedef {ReturnType } BasicForm\n * @typedef {ReturnType} Button\n */\n\n/**\n * @param {BasicForm} BasicForm Base widget for extending\n * @returns {typeof AjaxForm} Ajax Form class\n */\nexport default function (BasicForm) {\n /**\n * @category widgets\n * @subcategory forms\n * @class AjaxForm\n * @augments BasicForm\n * @classdesc Represents AjaxForm component with next features:\n * 1. Allow submit and handle submit Form\n * 2. Allow handle server response\n * 3. Allow handle form errors (form level and field level) that comes from backend\n * AjaxForm widget should contain {@link Button} widgets that implement submit Form button.\n * Widget has next relationship:\n * * Handle Form submit using method {@link AjaxForm#handleSubmit} by event {@link Button#event:click} from {@link Button} widget.\n * @example Example of AjaxForm widget usage\n * \n * @property {string} data-widget - Widget name `ajaxform`\n * @property {string} data-event-submit - Event listener for form submission\n */\n class AjaxForm extends BasicForm {\n /**\n * @description Handles submit Form\n * @returns {void|boolean} return bolean value if Form fields doen't valid\n */\n handleSubmit() {\n if (this.isChildrenValid() && !this.submitting) {\n this.submitting = true;\n this.showProgressBar();\n this.getById(this.prefs().submitButton, submitButton => submitButton.busy());\n this.ref(this.prefs().errorMessageLabel).hide();\n\n submitFormJson(\n this.getFormUrl(),\n this.getFormFields(),\n this.ref('self').attr('method') === 'GET' ? 'GET' : 'POST'\n )\n .then(this.onSubmitted.bind(this))\n .catch(this.onError.bind(this))\n .finally(this.afterSubmission.bind(this));\n }\n\n return false;\n }\n\n /**\n * @description Handles server response\n * @emits AjaxForm#submit\n * @param {object} data Server JSON response once form submitted\n * @param {string} [data.success] - If form submission was success\n * @param {string} [data.redirectUrl] - if not empty - redirect to specified location should be executed\n * @param {boolean|string|string[]} [data.error] - error messages/message from server\n * @param {object[]} [data.fields] - form fields with errors\n * @returns {void}\n */\n onSubmitted(data) {\n if (data.success && data.redirectUrl) {\n window.location.assign(data.redirectUrl);\n } else if (data.error) {\n if (data.redirectUrl) {\n window.location.assign(data.redirectUrl);\n } else {\n // @ts-ignore\n this.setError(Array.isArray(data.error) ? data.error.join('
') : data.error);\n }\n } else if (data.fields) {\n Object.entries(data.fields).forEach(([name, errorMsg]) => {\n this.getById(name, (input) => {\n input.setError(errorMsg);\n });\n });\n } else {\n timeout(() => { // lets hideProgress finishes first\n /**\n * @description Event to submit AjaxForm\n * @event AjaxForm#submit\n */\n this.emit('submit', data);\n this.eventBus().emit('submit', data);\n // This eventBus event required to emit\n // form submit action to perform callback action\n });\n }\n }\n\n /**\n * @description Handles an error, which happens during request (for ex. 500 response)\n * @param {Error} e - error, which happend during request\n * @returns {void}\n */\n onError(e) {\n this.setError(e.message || errorFallbackMessage);\n const errorElement = this.ref(this.prefs().errorMessageLabel).get();\n if (errorElement) {\n scrollWindowTo(errorElement, true);\n } else {\n scrollToTop();\n }\n }\n\n /**\n * @description Changes Form state after submission\n * @returns {void}\n */\n afterSubmission() {\n this.getById(this.prefs().submitButton, submitButton => submitButton.unbusy());\n this.hideProgressBar();\n this.submitting = false;\n }\n\n /**\n * @description Set Form or Form fields errors\n * @param {string} msg - Generic error message, if no custom message - show generic one\n * @param {boolean} [scrollToError] - defines using of scroll to error\n * @param {string} refElem - RefElement id\n * @returns {void}\n */\n setError(msg, scrollToError = true, refElem = 'errorMessageLabel') {\n const errorMessageLabel = this.ref(refElem)\n .setText(msg || '')\n .show();\n\n if (!scrollToError) {\n return;\n }\n\n const element = errorMessageLabel.get();\n\n if (element) {\n scrollWindowTo(element, true);\n } else {\n scrollToTop();\n }\n }\n }\n\n return AjaxForm;\n}\n","/**\n * @typedef {ReturnType} Modal\n * @typedef {typeof import('widgets/forms/AjaxForm').default} AjaxForm\n */\n\n/**\n * @param {Modal} Modal Base widget for extending\n * @returns {typeof AjaxFormResponseContainer} AjaxFormResponseContainer widget\n */\nexport default function (Modal) {\n /**\n * @category widgets\n * @subcategory forms\n * @class AjaxFormResponseContainer\n * @augments Modal\n * @classdesc Represents AjaxFormResponseContainer component with next features:\n * 1. Used to rerender part of page after success response from server using mustache template\n *\n * Widget has next relationship:\n * * Rerender part of the page using method {@link AjaxFormResponseContainer#onSuccessSubmit} by event {@link AjaxForm#event:submit} from {@link AjaxForm} widget.\n * @property {string} data-widget - Widget name `AjaxFormResponseContainer`\n * @example Example of AjaxFormResponseContainer widget usage\n * \n *
\n * ${Resource.msg('passwordReset.warning', 'login', null)}\n *
\n\n *
\n\n *
\n * ${Resource.msg('passwordReset.title', 'login', null)}\n *
\n\n *
\n * \n *
\n */\n class AjaxFormResponseContainer extends Modal {\n /**\n * @description Get response from server and render it\n * @listens AjaxForm#submit\n * @param {typeof import('widgets/forms/AjaxForm').default} _form - ajaxForm Widget\n * @param {object} data - response form server\n * @returns {void}\n */\n onSuccessSubmit(_form, data) {\n this.render(undefined, data);\n }\n }\n\n return AjaxFormResponseContainer;\n}\n","import { timeout } from '../toolbox/util';\nimport { getJSONByUrl } from 'widgets/toolbox/ajax';\n\n/**\n * @typedef {typeof import('widgets/Widget').default} Widget\n * @typedef {InstanceType} refElement\n * @typedef {ReturnType} Button\n * @typedef {ReturnType} BasicInput\n */\n\n/**\n * @param {Widget} Widget base widget class\n * @returns {typeof BasicForm} Basic Form class\n */\nexport default function (Widget) {\n /**\n * @category widgets\n * @subcategory forms\n * @class BasicForm\n * @augments Widget\n * @classdesc Represents BasicForm component with next features:\n * 1. Allow show/hide Form progress bar\n * 2. Allow get submit button name, Form fields, Form action url\n * 3. Allow submit and handle submit Form\n * 4. Allow set value to Form fields\n * 5. Allow validate Form and Form fields\n * 6. Allow updates Form html body by sending an AJAX request to server with params object\n * BasicForm widget should contain {@link Button} widgets that implement submit Form button.\n * Widget has next relationship:\n * * Handle Form submit using method {@link BasicForm#handleSubmit} by event {@link Button#event:click} from {@link Button} widget.\n * @example Example of BasicForm widget usage\n * \n * @property {string} data-widget - Widget name `form`\n * @property {string} data-event-submit - Event listener for form submission\n */\n class BasicForm extends Widget {\n prefs() {\n return {\n submitEmpty: true,\n emptyFormErrorMsg: '',\n submitButton: 'submitButton',\n errorMessageLabel: 'errorMessageLabel',\n SUBMITTING_TIMEOUT: 5000,\n formDefinitionUrl: '',\n ...super.prefs()\n };\n }\n\n /**\n * @description Set aria-busy attribute value true\n * @returns {void}\n */\n showProgressBar() {\n this.ref('self').attr('aria-busy', 'true');\n }\n\n /**\n * @description Set aria-busy attribute value false\n * @returns {void}\n */\n hideProgressBar() {\n this.ref('self').attr('aria-busy', 'false');\n }\n\n /**\n * @description Get submit button name\n * @returns {string|null|undefined} Submit button name\n */\n getSubmitButtonName() {\n return this.submitButtonName;\n }\n\n /**\n * @description Initialize widget logic\n * @returns {void}\n */\n init() {\n this.submitButtonName = null;\n super.init();\n\n /**\n * @type {HTMLFormElement|any}\n */\n const form = this.ref('self').get();\n\n this.formId = form ? form.dataset.widget : '';\n this.formName = form ? form.name : '';\n\n /**\n * Below code is needed to initiate correct comparison configuration for certain fields\n * It iterates all BasicInput childs and if will find in validation compare data - will\n * sets up needed data on a proper child BasicInput widgets\n */\n const BasicInput = /** @type {BasicInput} */(this.getConstructor('basicInput'));\n\n const formName = this.formName;\n\n this.eachChild(widget => {\n if (widget instanceof BasicInput) {\n // set form name for gtm data\n widget.formName = formName;\n const widgetValidation = widget.prefs().validationConfig || {};\n const compareFieldId = widgetValidation.compareWith || '';\n if (compareFieldId) {\n this.getById(compareFieldId, targetWidget => {\n if (targetWidget.length) {\n const compareOptions = {\n field: compareFieldId,\n msg: widgetValidation.errors.compareWith || ''\n };\n widget.data('setMatchCmp', targetWidget, compareOptions);\n }\n });\n }\n }\n });\n }\n\n /**\n * @description Save submit button\n * @param {HTMLInputElement} el submit button element\n * @returns {void}\n */\n saveSubmitButton(el) {\n this.submitButtonName = el.name;\n }\n\n /**\n * @description Submit Form\n * @returns {void}\n */\n submit() {\n /**\n * @type {HTMLElement|undefined}\n */\n var elem = this.ref(this.prefs().submitButton).get();\n\n if (elem) {\n elem.click();\n }\n }\n\n /**\n * @description Handle submit Form\n * @emits BasicForm#submit\n * @param {refElement} _el event source element\n * @param {(Event|undefined)} event event instance if DOM event\n * @returns {void}\n */\n handleSubmit(_el, event) {\n this.clearError();\n const valid = this.isChildrenValid();\n\n if ((!valid || this.submitting) && (event && event instanceof Event)) {\n event.preventDefault();\n return;\n }\n this.ref(this.prefs().submitButton).disable();\n\n this.submitting = true;\n\n this.onDestroy(timeout(() => {\n this.submitting = false;\n }, this.prefs().SUBMITTING_TIMEOUT));\n\n this.emit('submit');\n }\n\n /**\n * @description Get Form fiels\n * @returns {object} Object contains form fields\n */\n getFormFields() {\n /**\n * @type {{[x: string]: string}}\n */\n var fields = {};\n const BasicInput = /** @type {BasicInput} */(this.getConstructor('basicInput'));\n\n this.eachChild(widget => {\n if (widget instanceof BasicInput && !(widget.skipSubmission && widget.skipSubmission())) {\n const name = widget.getName && widget.getName();\n\n if (name) {\n fields[name.toString()] = widget.getValue();\n }\n }\n });\n return fields;\n }\n\n /**\n * @description Sets related fields values, can be executed silently, without triggering `change` event\n * @param {object} formFields - Structured object with name: value pairs for input fields\n * @param {(boolean|undefined)} [silently] - if set to `true` - input should not be validated against a new value\n * @returns {void}\n */\n setFormFields(formFields, silently = false) {\n const BasicInput = /** @type {BasicInput} */(this.getConstructor('basicInput'));\n\n this.eachChild(widget => {\n if (widget instanceof BasicInput) {\n const name = widget.getName && widget.getName();\n widget.setValue(formFields[name], silently);\n }\n });\n }\n\n /**\n * @description Check is Form fields valid\n * @param {Function} [cb] callback called if child inputs are valid\n * @returns {boolean} - boolean value is Form input valid\n */\n isChildrenValid(cb) {\n var valid = true;\n const BasicInput = /** @type {BasicInput} */(this.getConstructor('basicInput'));\n\n this.eachChild(item => {\n if (item instanceof BasicInput && typeof item.validate === 'function' && !item.validate()) {\n if (valid && item.setFocus) {\n item.setFocus();\n }\n valid = false;\n }\n });\n\n if (valid && typeof cb === 'function') {\n cb();\n }\n\n if (!this.prefs().submitEmpty) {\n const fieldsValues = this.getFormFields();\n\n if (Object.keys(fieldsValues).every((key) => !fieldsValues[key])) {\n valid = false;\n\n this.ref(this.prefs().errorMessageLabel)\n .setText(this.prefs().emptyFormErrorMsg);\n\n this.ref(this.prefs().errorMessageLabel).show();\n }\n }\n return valid;\n }\n\n /**\n * @description Form validate\n * @returns {boolean} boolean value is Form input valid\n */\n validate() {\n return this.isChildrenValid();\n }\n\n /**\n * @description Check is Form valid based on Form fields validation\n * @returns {boolean} boolean value is Form valid\n */\n isValid() {\n var valid = true;\n const BasicInput = /** @type {BasicInput} */(this.getConstructor('basicInput'));\n\n this.eachChild(itemCmp => {\n if (itemCmp instanceof BasicInput && typeof itemCmp.isValid === 'function' && !itemCmp.isValid()) {\n valid = false;\n return false;\n }\n return true;\n });\n return valid;\n }\n\n /**\n * @description Interface that can be override\n * @returns {void}\n */\n setFocus() {\n }\n\n /**\n * @description Get Form action url\n * @returns {string} form action url\n */\n getFormUrl() {\n // @ts-ignore\n return this.ref('self').attr('action');\n }\n\n /**\n * @description Clear Form Error\n * @param {string} refID RefElement ID\n * @returns {void}\n */\n clearError(refID = 'errorMessageLabel') {\n this.ref(refID)\n .hide()\n .setText('');\n }\n\n /**\n * @description Updates form html body by sending an AJAX request to server with params object\n *
(possible param key is `countryCode`)\n *
Obtained template injected instead of old fields\n *
Data, entered in fields previosly will be restored\n * @param {object} params - request parameters\n * @param {string} params.countryCode - A country code to get country-specific form\n * @returns {InstanceType } - new Promise\n */\n updateFormData(params) {\n const formDefinitionUrl = this.prefs().formDefinitionUrl;\n\n if (formDefinitionUrl && params) {\n return new Promise((resolve) => {\n getJSONByUrl(formDefinitionUrl, params, true).then((response) => {\n if (response.formDefinition) {\n const formFields = this.getFormFields();\n this.render('', {}, this.ref('fieldset'), response.formDefinition).then(() => {\n this.setFormFields(formFields, true);\n resolve();\n setTimeout(() => this.formDataUpdated(), 0);\n /* eslint-disable */\n var defaultWineclubShipAdd = document.querySelector(\"div[data-id='dwfrm_address_setAsWineClubDefaultShipping']\");\n if (defaultWineclubShipAdd) {\n if (params.countryCode !== 'US') {\n defaultWineclubShipAdd.classList.add('h-hidden');\n }\n else {\n defaultWineclubShipAdd.classList.remove('h-hidden');\n }\n }\n var defaultAddress = document.querySelector(\"div[data-id='dwfrm_address_setAsDefault']\");\n if (defaultAddress) {\n if (params.countryCode !== 'US') {\n defaultAddress.classList.add('h-hidden');\n }\n else {\n defaultAddress.classList.remove('h-hidden');\n }\n }\n /* eslint-enable */\n });\n }\n });\n });\n }\n\n return new Promise((resolve) => {\n resolve();\n this.formDataUpdated();\n });\n }\n\n /**\n * @description Template method called once form definitions were reloaded\n * @returns {void}\n */\n formDataUpdated() {\n\n }\n }\n\n return BasicForm;\n}\n","// TO BE REVIEWED\n// @ts-nocheck\nimport { scrollIntoView } from 'widgets/toolbox/scroll';\nimport { get } from 'widgets/toolbox/util';\n\n/**\n * @typedef {typeof import('widgets/Widget').default} Widget\n * @typedef {InstanceType} refElement\n */\n\n/**\n * @description Base BasicInput implementation\n * @param {Widget} Widget Base widget for extending\n * @returns {typeof BasicInput} Basic Input class\n */\nexport default function (Widget) {\n /**\n * @category widgets\n * @subcategory forms\n * @class BasicInput\n * @augments Widget\n * @classdesc Basic Input Widget (like abstract ancestor), contains basic validation and input management logic.\n * Supposes valid unified input markup. Contains also error messaging.\n * Usually is not used directly, and relevant subclasses should be used.\n * @property {boolean} data-skip-validation - if input needs to skip validation\n * @property {string} data-classes-error-state - classes for input's error state\n * @property {string} data-classes-valid-state - classes for input's valid state\n * @property {string} data-classes-disabled - classes for disabled input\n * @property {string} data-classes-locked - classes for locked input (disabled + readonly)\n * @property {string} data-classes-wrapper-error-state - classes for input wrapper, when input in error state\n * @property {string} data-classes-wrapper-valid-state - classes for input wrapper, when input in valid state\n * @property {object} data-validation-config - validation rules and error messages for input\n */\n class BasicInput extends Widget {\n prefs() {\n return {\n skipValidation: false,\n classesErrorState: 'm-invalid',\n classesValidState: 'm-valid',\n classesDisabled: 'm-disabled',\n classesLocked: 'm-locked',\n classesWrapperErrorState: 'm-invalid',\n classesWrapperValidState: 'm-valid',\n validationConfig: {},\n ...super.prefs()\n };\n }\n\n init() {\n this.initValue = this.getValue();\n if (!this.id && this.ref('field').attr('name')) {\n this.id = this.ref('field').attr('name');\n }\n this.disabled = this.ref('field').attr('disabled') === 'disabled';\n this.formName = '';\n }\n\n /**\n * @description Get input value\n * @returns {string} - return input name\n */\n getValue() {\n // @ts-ignore\n return (this.ref('field').val());\n }\n\n /**\n * @description Get input name\n * @returns {string} - return input name\n */\n getName() {\n // @ts-ignore\n return this.ref('field').attr('name');\n }\n\n /**\n * @description Set focus to input\n * @returns {void}\n */\n focus() {\n var field = this.ref('field').get();\n if (field) {\n field.focus();\n }\n }\n\n /**\n * @description Set focus to input and scroll element into viewport\n * @returns {void}\n */\n setFocus() {\n const elementToScroll = this.ref('self').get();\n\n if (elementToScroll) {\n scrollIntoView(elementToScroll);\n }\n\n this.focus();\n }\n\n /**\n * @description Blur (unfocus) an input\n * @returns {void}\n */\n blur() {\n var field = this.ref('field').get();\n if (field) {\n field.blur();\n }\n }\n\n /**\n * @description Set input value\n * \"checkRanges\" method needs to validate min\\max length after changing input value by JS\n * https://stackoverflow.com/questions/53226031/html-input-validity-not-checked-when-value-is-changed-by-javascript\n * @param {(string|number|undefined)} [newVal] - set this value to input\n * @param {(boolean|undefined)} [silently] - if set to `true` - input\n * should not be validated against a new value and no events should be fired\n * @returns {void}\n */\n setValue(newVal = '', silently = false) {\n const refField = this.ref('field');\n\n refField.val(String(newVal));\n\n this.checkRanges(refField);\n\n if (!silently) {\n this.update();\n }\n }\n\n /**\n * @description Check min/max length when value is being set and if it out of boundaries, set custom validity.\n * After setting value via code, a min/max length rule does not work until the value is changed by user.\n * @param {refElement} refField - Field ref object\n * @returns {void}\n * */\n checkRanges(refField) {\n const value = /** @type {string} */(refField.val());\n const field = refField.get();\n const rangesError = this.getRangesError(value);\n\n if (rangesError && (field instanceof HTMLInputElement)) {\n field.setCustomValidity(rangesError);\n }\n }\n\n /**\n * @description Check ranges and return appropriate error message or empty string\n * @param {string} val - Value that is being set\n * @returns {string} Ranges error message or empty string\n */\n getRangesError(val) {\n if (!val) {\n return '';\n }\n\n const refField = this.ref('field');\n const field = refField.get();\n\n if (field instanceof HTMLInputElement) {\n const validationConfig = this.prefs().validationConfig;\n const valueLength = String(val).length;\n\n if (field.minLength && valueLength < field.minLength) {\n return get(validationConfig, 'errors.minLength', '');\n } else if (field.maxLength && valueLength > field.maxLength) {\n return get(validationConfig, 'errors.maxLength', '');\n }\n }\n\n return '';\n }\n\n /**\n * @description Updates custom validity state\n * @returns {void}\n */\n updateCustomValidityState() {\n const field = this.ref('field');\n const validity = field.getValidity();\n\n if (validity && validity.state.customError) {\n this.checkRanges(field);\n }\n }\n\n /**\n * @description Validate input and trigger `change` event\n * @emits BasicInput#change\n * @returns {void}\n */\n update() {\n this.validate();\n /**\n * @description Event, indicates input value was changed\n * @event BasicInput#change\n */\n this.emit('change', this);\n }\n\n /**\n * @description Clears input error\n * @emits BasicInput#inputstatechanged\n * @returns {void}\n */\n clearError() {\n this.ref('field').removeClass(this.prefs().classesErrorState);\n this.ref('field').removeClass(this.prefs().classesValidState);\n this.ref('self').removeClass(this.prefs().classesWrapperErrorState);\n this.ref('self').removeClass(this.prefs().classesWrapperValidState);\n\n this.ref('errorFeedback').hide();\n\n /**\n * @description Event, indicates input state was changed\n * @event BasicInput#inputstatechanged\n */\n this.emit('inputstatechanged');\n }\n\n /**\n * @description Set/unset error state into input (message and classes)\n * @param {string} [error] error message - if defined, will set error staet, and valid state - otherwise\n * @emits BasicInput#inputstatechanged\n * @returns {void}\n */\n setError(error) {\n if (error) {\n this.ref('field').removeClass(this.prefs().classesValidState);\n this.ref('self').removeClass(this.prefs().classesWrapperValidState);\n\n this.ref('field').addClass(this.prefs().classesErrorState);\n this.ref('self').addClass(this.prefs().classesWrapperErrorState);\n\n this.ref('errorFeedback').setText(error).show();\n } else {\n this.ref('field').removeClass(this.prefs().classesErrorState);\n this.ref('self').removeClass(this.prefs().classesWrapperErrorState);\n\n this.ref('field').addClass(this.prefs().classesValidState);\n this.ref('self').addClass(this.prefs().classesWrapperValidState);\n\n this.ref('errorFeedback').hide();\n }\n\n this.emit('inputstatechanged');\n }\n\n /**\n * @description Indicates, that input value is (is not) valid against HTML5 native constraints (input patterns, min/max etc).\n * Also cares about case, when some field value should match another field value.\n * Sets error message in case of input is not valid. Message is taken from JSON `validationConfig` data-attribute\n * @returns {boolean} is input valid or not\n */\n isValid() {\n const field = this.ref('field');\n const validation = this.prefs().validationConfig;\n\n this.updateCustomValidityState();\n\n var { state, msg } = field.getValidity()\n || { msg: '', state: /** @type {ValidityState} */({ valid: true }) };\n\n if ((state.patternMismatch || state.typeMismatch)) {\n msg = validation.errors.parse || validation.errors.security;\n } else if (\n (state.rangeOverflow || state.rangeUnderflow || state.tooLong || state.tooShort)\n ) {\n if (state.rangeOverflow || state.tooLong) {\n msg = validation.errors.maxLength;\n } else if (state.rangeUnderflow || state.tooShort) {\n msg = validation.errors.minLength;\n }\n } else if (state.valueMissing) {\n msg = validation.errors.required;\n }\n\n if (state.valid && this.widgetToMatch\n && this.widgetToMatchOpts\n && this.widgetToMatch.data('getValue') !== this.getValue()\n ) {\n this.error = this.widgetToMatchOpts.msg;\n return false;\n }\n\n // user should be prevented from saving a non-compliant state as their wine club shipping address\n var isWineClubShippAddChecked = document.getElementById('dwfrm_address_setAsWineClubDefaultShipping');\n var isDefaultAddresChecked = document.getElementById('dwfrm_address_setAsDefault');\n // eslint-disable-next-line max-len\n if (isWineClubShippAddChecked !== null && isWineClubShippAddChecked.checked && this.config.id === 'dwfrm_address_setAsWineClubDefaultShipping') {\n return this.nonCompliantState();\n }\n if (isDefaultAddresChecked !== null && isDefaultAddresChecked.checked && this.config.id === 'dwfrm_address_setAsDefault') {\n return this.nonCompliantState();\n }\n this.error = msg;\n return state.valid;\n }\n\n /**\n * @description Triggers input validation process\n * @returns {boolean} input validation result\n */\n validate() {\n if (!this.shown || this.disabled || this.prefs().skipValidation) {\n return true;\n }\n\n const valid = this.isValid();\n\n if (valid) {\n this.setError();\n } else {\n this.setError(this.error);\n }\n\n return valid;\n }\n\n /**\n * @description Disables an input\n * @returns {this} `this` instance for chaining\n */\n disable() {\n this.disabled = true;\n this.ref('field').disable();\n this.ref('self').addClass(this.prefs().classesDisabled);\n return this;\n }\n\n /**\n * @description Enables an input\n * @returns {this} `this` instance for chaining\n */\n enable() {\n this.disabled = false;\n this.ref('field').enable();\n this.ref('self').removeClass(this.prefs().classesDisabled);\n return this;\n }\n\n /**\n * @description Locks an input (adds `readonly` attribute)\n * @returns {void}\n */\n lock() {\n this.locked = true;\n this.ref('field').attr('readonly', true);\n this.ref('self').addClass(this.prefs().classesLocked);\n }\n\n /**\n * @description Unlocks an input (removes `readonly` attribute)\n * @returns {void}\n */\n unlock() {\n this.locked = false;\n this.ref('field').attr('readonly', false);\n this.ref('self').removeClass(this.prefs().classesLocked);\n }\n\n /**\n * @description Checks if input is disabled\n * @returns {boolean} result\n */\n isDisabled() {\n return !!this.disabled;\n }\n\n /**\n * @description Saves on widget level target widget for comparison validation to use it further in `isValid` method\n * @param {BasicInput} widgetToMatch cmp\n * @param {{[x: string]: string|undefined}} options to compare\n * @returns {void}\n */\n setMatchCmp(widgetToMatch, options = {}) {\n this.widgetToMatch = widgetToMatch;\n this.widgetToMatchOpts = options;\n }\n\n /**\n * @description To be either included or not into the form submission to server.\n * @returns {boolean} result\n */\n skipSubmission() {\n return false;\n }\n\n /**\n * @description user should be prevented from saving a non-compliant state as their wine club shipping address\n * @returns {boolean} result\n */\n nonCompliantState() {\n var stateDropDown = document.getElementById('dwfrm_address_states_stateCode');\n const nonCompliantStateArr = document.getElementById('nonCompliantState');\n const nonCompliantStateErrorMsg = document.getElementsByClassName('non-CompliantState-errorMsg')[0];\n if (stateDropDown !== null && stateDropDown.value !== '' && nonCompliantStateArr !== null) {\n var selectedVal = stateDropDown.value;\n const nonCompliantState = JSON.parse(nonCompliantStateArr.value);\n const isNonCompliantState = nonCompliantState.includes(selectedVal);\n if (isNonCompliantState) {\n window.scrollTo({\n top: 0,\n behavior: 'smooth'\n })\n nonCompliantStateErrorMsg.removeAttribute('hidden');\n return false;\n }\n }\n // eslint-disable-next-line\n if(nonCompliantStateErrorMsg!== undefined){\n nonCompliantStateErrorMsg.setAttribute('hidden', 'hidden');\n }\n return true;\n }\n }\n\n return BasicInput;\n}\n","import { clickOutside } from 'widgets/toolbox/util';\n\nconst KEY_DOWN = 40;\nconst KEY_UP = 38;\nconst KEY_ESCAPE = 27;\nconst KEY_RETURN = 13;\nconst KEY_TAB = 9;\nconst ACTIVE_DESCENDANT = 'aria-activedescendant';\n\n/**\n * @typedef {InstanceType} refElement\n * @typedef {ReturnType} BasicInput\n */\n\n/**\n * @description Base Combobox implementation\n * @param {BasicInput} BasicInput Base widget for extending\n * @returns {typeof Combobox} Combobox class\n */\nexport default function (BasicInput) {\n /*\n * This content is based on w3.org design pattern examples and licensed according to the\n * W3C Software License at\n * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document\n * Please see specification:\n * https://www.w3.org/TR/wai-aria-practices/#combobox\n */\n\n /**\n * @category widgets\n * @subcategory forms\n * @class Combobox\n * @augments BasicInput\n * @classdesc Combobox implementation in accordance with accessibility statements.\n * Class could be extended to implement any provider-specific remote/local data fetching for Combobox inner elements.\n * It is needed to implement `getSuggestions` method in a way, that is specific for subclasses\n * Supports keyboard navigation among Combobox items.\n * As far as Combobox could be used only in a subclasses, example, given below, is related to known subclass `searchBox`\n * @property {string} data-widget - Widget name `searchBox`\n * @property {string} data-url - URL to obtain serach suggestions from server based on customer's input\n * @property {boolean} data-close-on-tab - If true - `tab` keypress will close listbox\n * @property {string} data-widget-event-closesearch - An event, fired when `close` element was pressed\n * @property {string} data-event-keydown - event handler for `keydown` event\n * @property {boolean} data-close-from-outside - config, which shows, if combobox should be closed when click outside\n * @property {number} data-min-chars - minimum characters to trigger show combobox\n * @property {number} data-max-chars - maximum characters to enter\n * @property {number} data-updating-delay - update listbox delay\n * @property {string} data-classes-focused-item - class for currently focused item\n * @property {string} data-classes-active-list-box - active listbox class\n * @example\n * // use this code to display minicart widget\n * \n *
\n * ...\n *
\n * \n *
\n */\n class Combobox extends BasicInput {\n constructor(el, config) {\n super(el, config);\n this.lastSearchedTerm = '';\n this.selectedIndex = -1;\n this.resultsCount = 0;\n this.isListboxOpen = false;\n }\n\n prefs() {\n return {\n minChars: 3,\n maxChars: 50,\n updatingDelay: 300,\n closeFromOutside: true,\n closeOnTab: true,\n classesFocusedItem: 'm-focused',\n classesActiveListBox: 'm-active',\n ...super.prefs()\n };\n }\n\n /**\n * @description Handles customer input\n * @returns {void}\n */\n handleInput() {\n const inputLength = this.ref('input').prop('value').length;\n if (inputLength >= this.prefs().minChars && inputLength <= this.prefs().maxChars) {\n this.updateListbox();\n } else {\n if (this.timeout) {\n clearTimeout(this.timeout);\n }\n this.closeListbox();\n }\n }\n\n /**\n * @description Marks element as hower, once mouse over\n * @listens dom#mouseenter\n * @returns {void}\n */\n markHover() {\n this.hasHoverWithin = true;\n }\n\n /**\n * @description Unmarks element as hower, once mouse out\n * @listens dom#mouseleave\n * @returns {void}\n */\n unMarkHover() {\n this.hasHoverWithin = false;\n }\n\n /**\n * @description Handles `focus` on combobox input\n * @listens dom#focus\n * @returns {void}\n */\n handleFocus() {\n this.updateListbox();\n }\n\n /**\n * @description Handles `blur` on combobox input\n * @listens dom#blur\n * @returns {void}\n */\n handleBlur() {\n if (this.hasHoverWithin || this.selectedIndex < 0) {\n return;\n }\n this.closeListbox();\n }\n\n /**\n * @description Handles `keydown` on combobox input\n * @param {HTMLElement} _ - source of keydown event\n * @param {KeyboardEvent} event - keydown event object\n * @listens dom#keydown\n * @returns {void}\n */\n handleKeydown(_, event) {\n let preventEventActions = false;\n\n switch (event.keyCode) {\n case KEY_ESCAPE:\n this.closeListbox();\n preventEventActions = true;\n break;\n case KEY_UP:\n this.setSelectToNextItem();\n this.selectItem(this.getItemByIndex());\n preventEventActions = true;\n break;\n case KEY_DOWN:\n this.setSelectToPreviousItem();\n this.selectItem(this.getItemByIndex());\n preventEventActions = true;\n break;\n case KEY_RETURN:\n this.activateItem(this.getItemByIndex());\n return;\n case KEY_TAB:\n if (this.prefs().closeOnTab) {\n this.closeListbox();\n }\n return;\n default:\n return;\n }\n\n if (preventEventActions) {\n event.stopPropagation();\n event.preventDefault();\n }\n }\n\n /**\n * @description Sets previous item as selected, when navigating from keyboard\n * @returns {void}\n */\n setSelectToPreviousItem() {\n if (this.selectedIndex === -1 || this.selectedIndex >= this.resultsCount - 1) {\n this.selectedIndex = 0;\n } else {\n this.selectedIndex += 1;\n }\n }\n\n /**\n * @description Sets next item as selected, when navigating from keyboard\n * @returns {void}\n */\n setSelectToNextItem() {\n if (this.selectedIndex <= 0) {\n this.selectedIndex = this.resultsCount - 1;\n } else {\n this.selectedIndex -= 1;\n }\n }\n\n /**\n * @description Selects combobox dropdown item\n * @param {null|refElement} selectedItem - item, selected by user\n * @returns {void}\n */\n selectItem(selectedItem) {\n if (this.currentSelected) {\n this.deselectItem(this.currentSelected);\n }\n\n if (selectedItem) {\n this.ref('input').attr(ACTIVE_DESCENDANT, `result-item-${this.selectedIndex}`);\n selectedItem\n .addClass(this.prefs().classesFocusedItem)\n .attr('aria-selected', 'true');\n this.afterItemSelected(selectedItem);\n } else {\n this.ref('input').attr(ACTIVE_DESCENDANT, '');\n }\n\n this.currentSelected = selectedItem;\n }\n\n /**\n * @description Executes logic (sets input value) after item was selected\n * @param {refElement} item - item, selected by user\n * @returns {void}\n */\n afterItemSelected(item) {\n this.ref('input').prop('value', item.data('suggestionValue'));\n }\n\n /**\n * @description Deselectes item in combobox dropdown when navigating from keyboard.\n * @param {refElement} item - item, selected by user\n * @returns {void}\n */\n deselectItem(item) {\n item\n .removeClass(this.prefs().classesFocusedItem)\n .attr('aria-selected', 'false');\n }\n\n /**\n * @description \"Activate\" item by press Enter on selected item or mouse click on item\n * @param {null|refElement} activeItem - item, selected by user\n * @returns {void}\n */\n activateItem(activeItem) {\n if (activeItem) {\n this.ref('input')\n .prop('value', activeItem.data('suggestionValue'))\n .get().focus();\n this.closeListbox();\n }\n }\n\n /**\n * @description Update Combobox Listbox (dropdown). Initiates items retrieveing either from server or from any other source, like an array.\n * @returns {void}\n */\n updateListbox() {\n const inputValue = this.ref('input').prop('value');\n\n if (this.lastSearchedTerm === inputValue || inputValue.length < this.prefs().minChars) {\n // could be triggered from focus so we need additionally check minChars\n return;\n }\n\n if (this.timeout) {\n clearTimeout(this.timeout);\n }\n this.timeout = setTimeout(this.getSuggestions.bind(this, inputValue), this.prefs().updatingDelay);\n }\n\n /**\n * @description Get items list for Combobox. Could be fetched from any proper source.\n * Usuaslly is implemented in childs.\n * @param {string} query - a query, which needs to be processed in subclasses\n * @returns {void}\n */\n getSuggestions(query) {\n // This is example of getSuggestion method to implement\n // Comboboxes like search or address should implement his own methods\n // of how to get suggestion and render it. Template should follow this:\n // - ${results[0]}
\n // ...\n // - ${results.length} suggestion found
\n // You should always include ids of suggestion and status about how much\n // suggestion total.\n const resultsTotal = 1;\n this.afterSuggestionsUpdate(query, resultsTotal);\n }\n\n /**\n * @description Executes after a list of items (suggestions) of a Combobox has being updated.\n * @param {string} query - a query, used for fetching Combobox inner items\n * @param {number} resultsCount - obtained inner items count\n * @returns {void}\n */\n afterSuggestionsUpdate(query, resultsCount) {\n this.resultsCount = resultsCount;\n this.selectedIndex = -1;\n this.lastSearchedTerm = query;\n this.openListbox();\n }\n\n /**\n * @description Open a Listbox (Combobox dropdown)\n * @returns {void}\n */\n openListbox() {\n this.isListboxOpen = true;\n\n const listbox = this.ref('listbox');\n listbox.addClass(this.prefs().classesActiveListBox);\n listbox.attr('aria-hidden', 'false');\n const input = this.ref('input');\n input.attr(ACTIVE_DESCENDANT, '');\n input.attr('aria-expanded', 'true'); // Should be combobox node by specs\n this.toggleOverlay(true);\n\n if (this.prefs().closeFromOutside) {\n this.bodyClickListener = clickOutside(this.ref('self'), this.closeListbox.bind(this));\n if (this.bodyClickListener) {\n this.onDestroy(this.bodyClickListener);\n }\n }\n }\n\n /**\n * @description Close a Listbox (Combobox dropdown)\n * @returns {void}\n */\n closeListbox() {\n this.resultsCount = 0;\n this.selectedIndex = -1;\n this.lastSearchedTerm = '';\n this.isListboxOpen = false;\n\n const listbox = this.ref('listbox');\n listbox.removeClass(this.prefs().classesActiveListBox);\n listbox.attr('aria-hidden', 'true');\n const input = this.ref('input');\n input.attr(ACTIVE_DESCENDANT, '');\n input.attr('aria-expanded', 'false'); // Should be combobox nore by specs\n this.toggleOverlay(false);\n\n if (this.bodyClickListener) {\n this.bodyClickListener();\n this.bodyClickListener = undefined;\n }\n\n this.afterCloseListbox();\n }\n\n /**\n * @description Executes after Listbox (Combobox dropdown) was closed\n * @returns {void}\n */\n afterCloseListbox() {\n this.ref('listbox').empty();\n }\n\n /**\n * @description Toggles Combobox page overlay\n * @param {boolean} isShown - does overlay should be shown\n * @returns {void}\n */\n toggleOverlay(isShown) {\n this.isOverlayVisible = isShown;\n }\n\n /**\n * @description Get Combobox item (suggestion) by index, stored in `this.selectedIndex` class property.\n * @returns {refElement|null} - founded item or null\n */\n getItemByIndex() {\n if (this.selectedIndex < 0) {\n return null;\n }\n\n const listItem = this.ref(`result-item-${this.selectedIndex}`);\n if (listItem.length) {\n return listItem;\n }\n\n return null;\n }\n }\n\n return Combobox;\n}\n","/**\n * @typedef {ReturnType} BasicForm\n */\n\n/**\n * @param {BasicForm} BasicForm Base widget for extending\n * @returns {typeof Form} Form class\n */\nexport default function (BasicForm) {\n /**\n * @class Form\n * @augments BasicForm\n * @classdesc Represents Form component\n * @example Example of Form widget usage\n * \n * @property {string} data-widget - Widget name `form`\n * @property {string} data-event-submit - Event listener for form submission\n */\n class Form extends BasicForm {\n\n }\n\n return Form;\n}\n","const ariaChecked = 'aria-checked';\n\n/**\n * @typedef {ReturnType } BasicInput\n */\n\n/**\n * @description Base InputCheckbox implementation\n * @param {BasicInput} BasicInput Base widget for extending\n * @returns {typeof InputCheckbox} Input Checkox class\n */\nexport default function (BasicInput) {\n /**\n * @category widgets\n * @subcategory forms\n * @class InputCheckbox\n * @augments BasicInput\n * @classdesc Checkbox input implementation. Represents input `checkbox` element together with widget-related HTML markup.\n * HTML structure assembled on backend and injected into resulted html markup by using `formElement` component\n * and dynamic forms configuration JSON.\n * @property {string} data-widget - Widget name `inputCheckbox`\n * @example InputCheckbox definition in dynamicforms.json\n * ...\n * // fields -> input -> checkbox\n * checkbox: {\n * 'label.after': true,\n * 'element.type': 'checkbox'\n * },\n * ...\n * // fields -> generic -> setAsDefault\n * setAsDefault: {\n * extends: 'fields.input.checkbox',\n * 'label.text': 'form.address.setDefault'\n * },\n * ...\n * @example Insertion of InputCheckbox inside ISML templates\n * \n * ...\n * \n * @example Resulted HTML structure for InputCheckbox\n * \n */\n class InputCheckbox extends BasicInput {\n /**\n * @description Get value of a checkbox input. Returns empty string if checkbox is not checked.\n * @returns {string} result\n */\n getValue() {\n if (this.ref('field').prop('checked')) {\n return super.getValue();\n }\n return '';\n }\n\n /**\n * @description Set value to checkbox input. By fact - checks it if setted value is not empty.\n * @param {string} newVal New value to set.\n * @param {boolean} silently If true - there will be no event fired, that indicates input value change.\n * @emits BasicInput#change\n * @returns {void}\n */\n setValue(newVal = '', silently = false) {\n this.ref('field').prop('checked', !!newVal);\n\n if (!silently) {\n this.emit('change', this);\n }\n }\n\n /**\n * @description Checks the checkbox. Also updates accessibility properties of input accordingly.\n * @emits BasicInput#change\n * @returns {void}\n */\n check() {\n this.ref('field').attr(ariaChecked, 'true');\n this.ref('field').prop('checked', true);\n\n this.emit('change', this);\n }\n\n /**\n * @description Unchecks the checkbox. Also updates accessibility properties of input accordingly.\n * @emits BasicInput#change\n * @returns {void}\n */\n uncheck() {\n this.ref('field').attr(ariaChecked, 'false');\n this.ref('field').prop('checked', false);\n\n this.emit('change', this);\n }\n\n /**\n * @description Upadate accessibility `aria-checked` depending on checkbox state.\n * @returns {void}\n */\n updateAriaChecked() {\n if (this.ref('field').prop('checked')) {\n this.ref('field').attr(ariaChecked, 'true');\n } else {\n this.ref('field').attr(ariaChecked, 'false');\n }\n }\n\n /**\n * @description Used to handle change checkbox state.\n * Usually used in dynamic forms config to handle `change` event on checkbox like:\n * @example\n * ...\n * element: {\n * attributes: {\n * 'data-event-change': 'handleChange'\n * }\n * }\n * ...\n * @emits BasicInput#change\n * @returns {void}\n */\n handleChange() {\n this.updateAriaChecked();\n this.emit('change');\n }\n }\n\n return InputCheckbox;\n}\n","// TO BE REVIEWED\n/**\n * @typedef {ReturnType} InputText\n */\n/**\n * @description Base InputEmail implementation\n * @param {InputText} InputText Base widget for extending\n * @returns {typeof InputEmail} Input Email class\n */\nexport default function (InputText) {\n /**\n * @category widgets\n * @subcategory forms\n * @class InputEmail\n * @augments InputText\n * @classdesc Input type Email implementation. Represents input `email` element together with widget-related HTML markup.\n * HTML structure assembled on backend and injected into resulted html markup by using `formElement` component\n * and dynamic forms configuration JSON.\n * @property {string} data-widget - Widget name `inputEmail`\n * @example InputEmail definition in dynamicforms.json\n * ...\n * // fields -> generic -> email\n * email: {\n * extends: 'fields.input.base',\n * element: {\n * attributes: {\n * autocomplete: 'email'\n * },\n * maxLength: 50,\n * type: 'email',\n * required: true\n * },\n * validation: {\n * 'patterns.parse': 'validation.patterns.parse.email',\n * 'errors.parse': 'validation.errors.email'\n * },\n * 'label.text': 'form.profile.email'\n * },\n * ...\n * @example Insertion of InputEmail inside ISML templates\n * \n * ...\n * \n * @example Resulted HTML structure for InputEmail\n * \n */\n class InputEmail extends InputText {\n\n }\n\n return InputEmail;\n}\n","// TO BE REVIEWED\n/**\n * @typedef {ReturnType} BasicInput\n */\n/**\n * @param {BasicInput} BasicInput Base widget for extending\n * @returns {typeof InputHidden} Input Hidden class\n */\nexport default function (BasicInput) {\n /**\n * @category widgets\n * @subcategory forms\n * @class InputHidden\n * @augments BasicInput\n * @classdesc Widget used to handle hidden inputs. Represents input `hidden` element together with widget-related HTML markup.\n * HTML structure assembled on backend and injected into resulted html markup by using `formElement` component\n * and dynamic forms configuration JSON.\n * Form validation will skip hidden inputs from validation perspective and will treat it as valid.\n * @property {string} data-widget - Widget name `inputHidden`\n * @example InputHidden definition in dynamicforms.json\n * ...\n * // fields -> generic -> hidden\n * hidden: {\n * extends: 'fields.input.base',\n * element: {\n * type: 'hidden'\n * }\n * },\n * ...\n * @example Insertion of InputHidden inside ISML templates\n * \n * ...\n * \n * @example Resulted HTML structure for InputHidden\n * \n * \n *
\n */\n class InputHidden extends BasicInput {\n prefs() {\n return {\n ...super.prefs(),\n skipValidation: true\n };\n }\n }\n\n return InputHidden;\n}\n","// TO BE REVIEWED\n// TODO: should be implemented as https://www.w3.org/TR/wai-aria-practices/#spinbutton\nimport { timeout } from 'widgets/toolbox/util';\n\n/**\n * @typedef {ReturnType} BasicInput\n */\n\n/**\n * @description Base InputNumber implementation\n * @param {BasicInput} BasicInput Base widget for extending\n * @returns {typeof InputNumber} Input Number class\n */\nexport default function (BasicInput) {\n /**\n * @category widgets\n * @subcategory forms\n * @class InputNumber\n * @augments BasicInput\n * @classdesc Widget used as a quantity selector for product with +/- buttons functionality.\n * - Uses `minValue` and `maxValue` properties to restrict allowed input range.\n * - Handles keyboard input, allows step +/- configuration.\n * @property {string} data-widget - Widget name `inputNumber`\n * @example Example HTML structure for InputNumber\n * \n */\n class InputNumber extends BasicInput {\n prefs() {\n return {\n maxValue: 99,\n minValue: 0,\n allowEmpty: false,\n informAttemptDecreaseMinVal: false,\n ...super.prefs()\n };\n }\n\n init() {\n super.init();\n\n this.maxLengthAttr = parseInt(this.ref('field').attr('maxlength').toString(), 10);\n this.step = parseFloat(this.ref('field').attr('step').toString()) || 1;\n\n this.ref('field').attr('maxlength', this.prefs().maxValue.toString().length.toFixed());\n\n this.initValue = this.getValue();\n }\n\n /**\n * @description Handler for `keydown` event on element\n * @param {HTMLInputElement} el - element, which triggers an event\n * @param {KeyboardEvent} incomingEvent - event object\n * @listens dom#keydown\n * @returns {void}\n */\n handleKeyDown(el, incomingEvent) {\n let event = incomingEvent;\n if (!event && window.event) {\n // @ts-ignore\n event = window.event;\n }\n const keyCode = event.keyCode || event.which;\n\n // special handling for old devices\n if (this.maxLengthAttr && (el.value.toString().length + 1) >= this.maxLengthAttr) {\n this.onDestroy(timeout(() => {\n this.setValue(this.getValue(), true);\n }, 0));\n } else if (keyCode === 13) {\n // Enter pressed\n this.changeValue();\n event.preventDefault();\n }\n }\n\n /**\n * @description Tries to change value of input. In case if new value is behind specific number input constrains - returns previous or default value.\n * @emits InputNumber#attemptdecreaseminval\n * @listens dom#change\n * @returns {void}\n */\n changeValue() {\n if (Number(this.getValue()) < this.prefs().minValue && this.prefs().informAttemptDecreaseMinVal) {\n this.onDestroy(timeout(() => {\n /**\n * @description Event, indicates that attempt to set number input value less than specified in constraints\n * @event InputNumber#attemptdecreaseminval\n */\n this.emit('attemptdecreaseminval');\n this.setValue(String(this.prevVal || this.initValue), true);\n }, 100));\n } else {\n this.setValue(this.getValue());\n this.validate();\n }\n }\n\n /**\n * @description Decrements value in number input. In case if new value is behind specific number input constrains - emits `attemptdecreaseminval` and skips value.\n * @emits InputNumber#attemptdecreaseminval\n * @returns {void}\n */\n decrement() {\n if (!this.isDisabled()) {\n const newVal = parseFloat(this.getValue()) - (this.step || 0);\n\n if (newVal >= this.prefs().minValue) {\n this.setValue(newVal.toString());\n } else if (this.prefs().informAttemptDecreaseMinVal) {\n this.emit('attemptdecreaseminval');\n }\n }\n }\n\n /**\n * @description Increments value in number input. In case if new value is behind specific number input constrains - emits `quantityNotAllowed` and skips value.\n * @emits InputNumber#quantityNotAllowed\n * @returns {void}\n */\n increment() {\n if (!this.isDisabled()) {\n const newVal = parseFloat(this.getValue()) + (this.step || 0);\n\n if (newVal <= this.prefs().maxValue) {\n this.setValue(newVal.toString());\n } else {\n /**\n * @description Event, indicates that attempt to set number input value not valid as per constraints.\n * @event InputNumber#quantityNotAllowed\n */\n this.emit('quantityNotAllowed');\n }\n }\n }\n\n /**\n * @description Sets value for number input. Checks it on constraints first.\n * Manages input buttons less/more.\n * @param {string} val - set this value to input\n * @param {boolean} silent - if set to `true` - input should not be validated against a new value\n * @returns {void}\n */\n setValue(val, silent = false) {\n if (val === '' && this.prefs().allowEmpty) {\n super.setValue('', silent);\n } else {\n const floatValue = typeof val !== 'number' ? parseFloat(val) : val;\n\n if (floatValue >= this.prefs().minValue && floatValue <= this.prefs().maxValue) {\n super.setValue(floatValue.toString(), silent);\n this.prevVal = floatValue;\n } else if (floatValue > this.prefs().maxValue) {\n super.setValue(String(this.prefs().maxValue), silent);\n } else if (floatValue < this.prefs().minValue || Number.isNaN(floatValue)) {\n super.setValue(String(this.prevVal || this.prefs().minValue), silent);\n } else {\n super.setValue('', silent);\n }\n }\n\n this.ref('moreBtn').enable();\n this.ref('lessBtn').enable();\n\n const value = parseFloat(this.getValue());\n if (this.prefs().minValue > value - (this.step || 0)) {\n if (!this.prefs().informAttemptDecreaseMinVal) {\n this.ref('lessBtn').disable();\n }\n } else if (this.prefs().maxValue < value + (this.step || 0)) {\n this.ref('moreBtn').disable();\n }\n }\n\n /**\n * @description Executes after widget was refreshed\n * @returns {void}\n */\n onRefresh() {\n this.setValue(this.ref('field').attr('value').toString(), true);\n this.ref('field').attr('maxlength', this.prefs().maxValue.toString().length.toFixed());\n }\n\n /**\n * @description Implements number input specific logic to indicate if field value is valid.\n * @returns {boolean} - input validation result\n */\n isValid() {\n const isSuperValid = super.isValid();\n\n if (isSuperValid) {\n const val = parseFloat(this.getValue());\n\n if (val < this.prefs().minValue || val > this.prefs().maxValue) {\n const validation = this.prefs().validationConfig;\n\n this.error = validation.errors.range || validation.errors.parse;\n return false;\n }\n }\n return isSuperValid;\n }\n\n /**\n * @description Initiates field validation process. Manages validation error messages on input.\n * @returns {boolean} - input validation result\n */\n validate() {\n if (this.disabled) {\n return true;\n }\n const isValid = this.isValid();\n if (isValid) {\n this.clearError();\n } else {\n this.setError(this.error);\n }\n return isValid;\n }\n }\n\n return InputNumber;\n}\n","/**\n * @typedef {ReturnType} BasicInput\n * @typedef {InstanceType} RefElement\n */\n\n/**\n * @description Base InputPassword implementation\n * @param {BasicInput} BasicInput Base widget for extending\n * @returns {typeof InputPassword} Input Password class\n */\nexport default function (BasicInput) {\n /**\n * @category widgets\n * @subcategory forms\n * @class InputPassword\n * @augments BasicInput\n * @classdesc Specific Password input, contains specific password validation logic. Represents input `password` element together with widget-related HTML markup.\n * HTML structure assembled on backend and injected into resulted html markup by using `formElement` component\n * and dynamic forms configuration JSON.\n * Uses 'show/hide mask' functionality, which allows customer to see/hide printed password characters.\n * Adds additional validation constraints rules, which are password-specific (like special characters, uppercased characters etc).\n * @property {string} data-widget - Widget name `inputPassword`\n * @example InputPassword definition in dynamicforms.json\n * ...\n * // fields -> input -> password\n * password: {\n * extends: 'fields.input.base',\n * 'caption.show': true,\n * 'caption.text': 'form.profile.password.caption',\n * 'label.text': 'form.profile.password',\n * element: {\n * type: 'password',\n * required: true,\n * minLength: 8,\n * maxLength: 255,\n * attributes: {\n * 'data-event-input': 'onPasswordInput'\n * }\n * }\n * },\n * ...\n * @example Insertion of InputPassword inside ISML templates\n * \n * ...\n * \n * @example Resulted HTML structure for InputPassword\n * \n */\n class InputPassword extends BasicInput {\n prefs() {\n return {\n upperCaseAmount: 0,\n lowerCaseAmount: 0,\n numbersAmount: 0,\n specialCharsAmount: 0,\n specialCharsList: '$%/()[]{}=?!.,-_*|+~#',\n ...super.prefs()\n };\n }\n\n /**\n * @description Checks if entered data is number\n * @param {string} val - entered password\n * @returns {boolean} check result\n */\n isNumber(val) {\n return '0123456789'.includes(val);\n }\n\n /**\n * @description Checks if entered data is simple character (not a number or special character)\n * @param {string} val - entered password\n * @returns {boolean} check result\n */\n isSimpleChar(val) {\n return !this.isNumber(val) && !this.prefs().specialCharsList.includes(val);\n }\n\n /**\n * @description Verifies if entered password is valid and encount all password constraints.\n * @returns {boolean} check result\n */\n isValid() {\n const val = this.getValue();\n\n if (!super.isValid()) {\n return false;\n }\n\n if (typeof val === 'string' && val) {\n const valChars = val.split('');\n\n if (\n this.checkLowerCaseAmount(valChars)\n && this.checkUpperCaseAmount(valChars)\n && this.checkNumberAmount(valChars)\n && this.checkSpecialCharsAmount(valChars)\n ) {\n if (this.widgetToMatch && this.widgetToMatchOpts && this.widgetToMatch.data('getValue') !== val) {\n this.error = this.widgetToMatchOpts.msg;\n return false;\n }\n } else {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * @description Checks special characters amount in entered password as per corresponding constraint.\n * @param {string[]} valChars - array of entered password symbols\n * @returns {boolean} check results\n */\n checkSpecialCharsAmount(valChars) {\n if (this.prefs().specialCharsAmount) {\n const specialCharsList = this.prefs().specialCharsList;\n const specialCharsLetters = valChars.filter(char => specialCharsList.includes(char));\n if (specialCharsLetters.length < this.prefs().specialCharsAmount) {\n this.error = this.prefs().validationConfig.errors.specialCharsAmount;\n return false;\n }\n }\n return true;\n }\n\n /**\n * @description Checks numbers amount in entered password as per corresponding constraint.\n * @param {string[]} valChars - array of entered password symbols\n * @returns {boolean} check result\n */\n checkNumberAmount(valChars) {\n if (this.prefs().numbersAmount) {\n const numberLetters = valChars.filter(char => this.isNumber(char));\n if (numberLetters.length < this.prefs().numbersAmount) {\n this.error = this.prefs().validationConfig.errors.numbersAmount;\n return false;\n }\n }\n return true;\n }\n\n /**\n * @description Checks lowercased characters amount in entered password as per corresponding constraint.\n * @param {string[]} valChars - array of entered password symbols\n * @returns {boolean} check result\n */\n checkLowerCaseAmount(valChars) {\n if (this.prefs().lowerCaseAmount) {\n const lowerCaseLetters = valChars.filter(char => char === char.toLowerCase() && this.isSimpleChar(char));\n if (lowerCaseLetters.length < this.prefs().lowerCaseAmount) {\n this.error = this.prefs().validationConfig.errors.lowerCaseAmount;\n return false;\n }\n }\n return true;\n }\n\n /**\n * @description Checks uppercased characters amount in entered password as per corresponding constraint.\n * @param {string[]} valChars - array of entered password symbols\n * @returns {boolean} check results\n */\n checkUpperCaseAmount(valChars) {\n if (this.prefs().upperCaseAmount) {\n const upperCaseLetters = valChars.filter(char => char === char.toUpperCase() && this.isSimpleChar(char));\n if (upperCaseLetters.length < this.prefs().upperCaseAmount) {\n this.error = this.prefs().validationConfig.errors.upperCaseAmount;\n return false;\n }\n }\n return true;\n }\n\n /**\n * @description Show mask button inside password field\n * @returns {void}\n */\n showMaskButton() {\n this.ref('showButton').show();\n }\n\n /**\n * @description Handle click on button Show/Hide mask. Toggle type based on input type.\n * @listens dom#click\n * @returns {void}\n */\n toggleMask() {\n const input = this.ref('field');\n const inputType = input.attr('type');\n const showButton = this.ref('showButton');\n const showText = showButton.data('buttonTextShow');\n const hideText = showButton.data('buttonTextHide');\n\n if (inputType === 'password') {\n input.attr('type', 'text');\n showButton.attr('aria-pressed', 'true');\n showButton.setText(hideText);\n } else {\n input.attr('type', 'password');\n showButton.attr('aria-pressed', 'false');\n showButton.setText(showText);\n }\n }\n\n /**\n * @description Listener for `input` event.\n * Usually used to display Show/Hide mask button when entered needed amount of symbols into password field.\n * @listens dom#input\n * @param {RefElement} el event source element\n * @returns {void}\n */\n onPasswordInput(el) {\n const entered = el && el.val();\n if (entered && entered.length) {\n this.showMaskButton();\n }\n }\n }\n\n return InputPassword;\n}\n","const DATA_FIELD = '[data-ref=\"field\"]';\n\n/**\n * @typedef {ReturnType} BasicInput\n */\n\n/**\n * @description Base InputRadio implementation\n * @param {BasicInput} BasicInput Base widget for extending\n * @returns {typeof InputRadio} Input Radio class\n */\nexport default function (BasicInput) {\n /**\n * @category widgets\n * @subcategory forms\n * @class InputRadio\n * @augments BasicInput\n * @classdesc Radio input implementation. Represents input `radio` element together with widget-related HTML markup.\n * @property {string} data-widget - Widget name `inputRadio`\n * @example InputRadio HTML markup example\n *