{"version":3,"file":"swatch-form.js","names":["#renderSwatches","#initialiseSwatchForm","#handleDetailChangeClick","#syncBrochureState"],"sources":["../resources/javascript/swatch-form.js"],"sourcesContent":["import { renderSwatchGrid } from './swatch/render-swatch-grid';\nimport { Loqate } from './form/loqate';\nimport { getBrochureOptin, setBrochureOptin } from './swatch/local-storage';\n\nconst VALIDATE_TIMEOUT = 20000;\nconst TIMEOUT_ERROR_MESSAGE =\n    'Sorry, that took too long. Please check your connection and try again.';\nconst GENERIC_ERROR_MESSAGE =\n    'Sorry, we could not check your details. Please try again.';\n\n/**\n * Class representing a form for managing swatches.\n * Handles swatch form initialization, rendering swatches, and form interactions.\n */\nclass SwatchForm {\n    constructor() {\n        this.form = document.querySelector('[data-swatch-form]');\n\n        this.options = {\n            onValidationError: function (error) {},\n            afterErrorsScrollToField: false,\n            afterErrorsFocusField: false,\n        };\n    }\n\n    init() {\n        this.#renderSwatches();\n        this.#initialiseSwatchForm();\n    }\n\n    /**\n     * Initializes the swatch form by setting up event listeners.\n     * @private\n     */\n    #initialiseSwatchForm() {\n        const buttons = document.querySelectorAll(\n            '.swatch-form-option-select__checkbox'\n        );\n\n        buttons.forEach(button => {\n            button.addEventListener('click', e => {\n                if (\n                    e.target.classList.contains(\n                        'swatch-form-option-select--chosen'\n                    )\n                ) {\n                    e.target.classList.remove(\n                        'swatch-form-option-select--chosen'\n                    );\n                } else {\n                    e.target.classList.add('swatch-form-option-select--chosen');\n                }\n            });\n        });\n\n        const detailChangeBtn = document.querySelector(\n            '.js-checkout-section__change-detail button'\n        );\n\n        detailChangeBtn?.addEventListener('click', e =>\n            this.#handleDetailChangeClick(e)\n        );\n\n        this.submits = this.form.querySelectorAll('[type=\"submit\"]');\n\n        this.form.addEventListener('submit', e => {\n            this.disableSubmit();\n            this.validate(e);\n        });\n\n        new Loqate(this.form);\n\n        this.#syncBrochureState();\n    }\n\n    /**\n     * Renders swatches in the swatch form.\n     * @private\n     */\n    #renderSwatches() {\n        const swatchGridForm = document.querySelector('.swatch-grid--form');\n        const hasSwatches =\n            swatchGridForm?.querySelector('.swatch-grid__swatch') ?? null;\n\n        if (hasSwatches) {\n            return;\n        }\n\n        const swatchesContainer = document.querySelector(\n            '[data-swatch-form-list]'\n        );\n        const swatchUrlnamesInput = document.querySelector(\n            '[data-swatch-urlnames]'\n        );\n\n        if (!swatchesContainer || !swatchUrlnamesInput) {\n            return;\n        }\n\n        // Retrieves swatches from local storage or defaults to an empty array\n        const swatches = JSON.parse(localStorage.getItem('swatches')) ?? [];\n\n        if (swatches.length === 0) {\n            this.addBannerError(\n                'No swatches found. <a href=\"/swatches\" class=\"link\">Add Swatches</a>'\n            );\n        }\n\n        // Enrich swatches with image URLs from server-side data attribute\n        const imageMapRaw = swatchesContainer?.dataset.swatchImages;\n        if (imageMapRaw) {\n            try {\n                const imageMap = JSON.parse(imageMapRaw);\n                for (const swatch of swatches) {\n                    const urlname = JSON.parse(swatch.value)?.colour;\n                    if (urlname && imageMap[urlname]) {\n                        swatch.imageUrl = imageMap[urlname];\n                    }\n                }\n            } catch {\n                // ignore parse errors\n            }\n        }\n\n        // Generates a swatch grid and appends it to the swatch container\n        const swatchGrid = renderSwatchGrid(\n            swatches,\n            ['swatch-grid--form', 'swatch-grid--no-hover'],\n            {\n                addSwatchCategoryName: true,\n            }\n        );\n\n        swatchesContainer?.appendChild(swatchGrid);\n\n        let swatchUrlNames = [];\n\n        swatches.forEach(swatch => {\n            swatchUrlNames.push(JSON.parse(swatch.value).colour);\n        });\n\n        if (swatchUrlnamesInput) {\n            swatchUrlnamesInput.value = swatchUrlNames.join(',');\n        }\n    }\n\n    #handleDetailChangeClick(e) {\n        e.preventDefault();\n\n        const detailsInput = document.querySelector(\n            '.checkout-address-form__customer'\n        );\n\n        detailsInput?.classList.remove('d3r-hidden');\n    }\n\n    /**\n     * Validate over ajax, then submit the form if the server is happy.\n     *\n     * Stays async: WebKit aborts synchronous main-thread requests after ten\n     * seconds with \"The operation timed out.\" (LF-8326).\n     *\n     * @param {SubmitEvent} event\n     * @returns {Promise<void>}\n     */\n    async validate(event) {\n        event.preventDefault();\n\n        this.abortController?.abort();\n        const controller = new AbortController();\n        this.abortController = controller;\n\n        let timedOut = false;\n        const timer = setTimeout(() => {\n            timedOut = true;\n            controller.abort();\n        }, VALIDATE_TIMEOUT);\n\n        this.clearErrors();\n\n        const body = new FormData(this.form);\n        body.set('action', 'validate');\n\n        try {\n            const response = await fetch(this.form.getAttribute('action'), {\n                method: 'POST',\n                headers: { 'X-Requested-With': 'XMLHttpRequest' },\n                body: new URLSearchParams(body),\n                credentials: 'same-origin',\n                signal: controller.signal,\n            });\n\n            if (!response.ok) {\n                throw new Error(\n                    `Swatch validation failed with status ${response.status}`\n                );\n            }\n\n            const payload = await response.json();\n\n            if (!payload.result) {\n                this.addBannerError(payload.payload.message);\n                this.addErrors(payload.payload);\n                this.enableSubmit();\n                return;\n            }\n\n            localStorage.removeItem('swatches');\n            setBrochureOptin(false);\n\n            // submit() does not fire the submit event, so this cannot loop.\n            HTMLFormElement.prototype.submit.call(this.form);\n        } catch (error) {\n            // A newer submit superseded this one, so let that one report.\n            if (controller.signal.aborted && !timedOut) {\n                return;\n            }\n\n            this.addBannerError(\n                timedOut ? TIMEOUT_ERROR_MESSAGE : GENERIC_ERROR_MESSAGE\n            );\n            this.enableSubmit();\n            this.options.onValidationError.call(this, error);\n        } finally {\n            clearTimeout(timer);\n        }\n    }\n\n    /**\n     * Add errors to the form\n     *\n     * @param {object} errors\n     */\n    addErrors(errors) {\n        for (const field in errors.order) {\n            const error = errors.order[field];\n            this.addError(field, error);\n        }\n\n        let scrollFunction = this.options.afterErrorsScrollToField\n            ? 'scrollToFieldError'\n            : 'scrollToMessageError';\n        setTimeout(this[scrollFunction].bind(this), 10);\n    }\n\n    /**\n     * Add an error message to a field\n     *\n     * @param {string} field\n     * @param {string} message\n     */\n    addError(field, message) {\n        let wrap = this.form.querySelector('#' + field + '_wrap');\n\n        if (!wrap) {\n            return;\n        }\n\n        if (\n            wrap.parentElement.classList.contains(\n                'swatch-form-address-form__address'\n            )\n        ) {\n            wrap.parentElement.parentElement.classList.add(\n                'swatch-form-address-form--manual'\n            );\n        }\n\n        wrap.classList.add('field-error');\n\n        let errorP = wrap.querySelector('p.error');\n        if (!errorP) {\n            errorP = document.createElement('p');\n            errorP.classList.add('error');\n            wrap.appendChild(errorP);\n        }\n\n        errorP.innerHTML = message;\n    }\n\n    /**\n     * Ensure all errors are purged from the view\n     */\n    clearErrors() {\n        let error = document.getElementById('swatch-form-error');\n        error.style.display = 'none';\n\n        var errors = this.form.querySelectorAll('p.error');\n        for (let index = 0; index < errors.length; index++) {\n            const element = errors[index];\n            element.textContent = '';\n        }\n\n        var fields = this.form.querySelectorAll('.field-error');\n        for (let index = 0; index < fields.length; index++) {\n            const element = fields[index];\n            element.classList.remove('field-error');\n        }\n    }\n\n    /**\n     * Disable every submit control in the form\n     */\n    disableSubmit() {\n        this.submits?.forEach(button => {\n            button.disabled = true;\n        });\n    }\n\n    /**\n     * Enable every submit control in the form\n     */\n    enableSubmit() {\n        this.submits?.forEach(button => {\n            button.disabled = false;\n        });\n    }\n\n    /**\n     * Scroll to the generic error\n     */\n    scrollToMessageError() {\n        document.querySelector('.message--error').scrollIntoView({\n            behavior: 'smooth',\n        });\n    }\n\n    /**\n     * Scroll to the first field error\n     */\n    scrollToFieldError() {\n        document.querySelector('.field-error').scrollIntoView({\n            behavior: 'smooth',\n        });\n\n        if (this.options.afterErrorsFocusField) {\n            document\n                .querySelector('.field-error')\n                .querySelector('input, textarea, select')\n                .focus();\n        }\n    }\n\n    /**\n     * Add message to page header\n     *\n     * @param {string} message\n     */\n    addBannerError(message) {\n        let error = document.getElementById('swatch-form-error');\n        error.style.display = 'block';\n        error.querySelector('.message').innerHTML = message;\n    }\n\n    /**\n     * Syncs between the basket brochure state hidden input field,\n     * relies on default CSS behaviour to change checkbox state\n     * @private\n     */\n    #syncBrochureState = () => {\n        const targetInput = document.querySelector('[data-swatch-checkbox]');\n        const submitInput = document.querySelector(\n            '[data-form-brochure-optin]'\n        );\n\n        if (!targetInput || !submitInput) {\n            return;\n        }\n\n        const hasBrochureQuery =\n            new URL(window.location.href).searchParams.get('brochure') ===\n            'true';\n        const isBrochureSelected =\n            hasBrochureQuery ||\n            getBrochureOptin() ||\n            submitInput.value === 'true';\n\n        targetInput.checked = isBrochureSelected;\n        submitInput.value = isBrochureSelected ? 'true' : 'false';\n        setBrochureOptin(isBrochureSelected);\n\n        targetInput.addEventListener('change', () => {\n            submitInput.value = targetInput.checked ? 'true' : 'false';\n            setBrochureOptin(targetInput.checked);\n        });\n    };\n}\n\nwindow.Loaf.SwatchForm = window.Loaf.SwatchForm || {};\nwindow.Loaf.SwatchForm = new SwatchForm();\nwindow.Loaf.SwatchForm.init();\n"],"mappings":"iWAcA,IAAM,GAAA,EAAA,EAAA,sBAAA,EAAA,EAAA,EAAA,gBAAA,EAAA,EAAA,EAAA,yBAAA,EAAA,EAAA,EAAA,mBAAA,EAAN,KAAiB,CACb,aAAc,CAAA,OAAA,eAAA,KAAA,EAAA,CAAA,MAAA,CAAA,CAAA,4HAwVd,UAA2B,CACvB,IAAM,EAAc,SAAS,cAAc,wBAAwB,EAC7D,EAAc,SAAS,cACzB,4BACJ,EAEA,GAAI,CAAC,GAAe,CAAC,EACjB,OAMJ,IAAM,EAFF,IAAI,IAAI,OAAO,SAAS,IAAI,CAAC,CAAC,aAAa,IAAI,UAAU,IACzD,QAGA,EAAiB,GACjB,EAAY,QAAU,OAE1B,EAAY,QAAU,EACtB,EAAY,MAAQ,EAAqB,OAAS,QAClD,EAAiB,CAAkB,EAEnC,EAAY,iBAAiB,aAAgB,CACzC,EAAY,MAAQ,EAAY,QAAU,OAAS,QACnD,EAAiB,EAAY,OAAO,CACxC,CAAC,CACL,CAAA,CAAA,EAjXI,KAAK,KAAO,SAAS,cAAc,oBAAoB,EAEvD,KAAK,QAAU,CACX,kBAAmB,SAAU,EAAO,CAAC,EACrC,yBAA0B,GAC1B,sBAAuB,EAC3B,CACJ,CAEA,MAAO,CACH,EAAA,KAAA,CAAA,CAAA,CAAA,EAAA,CAAA,EACA,EAAA,KAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CACJ,CAyIA,MAAM,SAAS,EAAO,CAAA,IAAA,EAClB,EAAM,eAAe,GAErB,EAAA,KAAK,kBAAA,MAAL,EAAsB,MAAM,EAC5B,IAAM,EAAa,IAAI,gBACvB,KAAK,gBAAkB,EAEvB,IAAI,EAAW,GACT,EAAQ,eAAiB,CAC3B,EAAW,GACX,EAAW,MAAM,CACrB,EAAG,GAAgB,EAEnB,KAAK,YAAY,EAEjB,IAAM,EAAO,IAAI,SAAS,KAAK,IAAI,EACnC,EAAK,IAAI,SAAU,UAAU,EAE7B,GAAI,CACA,IAAM,EAAW,MAAM,MAAM,KAAK,KAAK,aAAa,QAAQ,EAAG,CAC3D,OAAQ,OACR,QAAS,CAAE,mBAAoB,gBAAiB,EAChD,KAAM,IAAI,gBAAgB,CAAI,EAC9B,YAAa,cACb,OAAQ,EAAW,MACvB,CAAC,EAED,GAAI,CAAC,EAAS,GACV,MAAU,MACN,wCAAwC,EAAS,QACrD,EAGJ,IAAM,EAAU,MAAM,EAAS,KAAK,EAEpC,GAAI,CAAC,EAAQ,OAAQ,CACjB,KAAK,eAAe,EAAQ,QAAQ,OAAO,EAC3C,KAAK,UAAU,EAAQ,OAAO,EAC9B,KAAK,aAAa,EAClB,MACJ,CAEA,aAAa,WAAW,UAAU,EAClC,EAAiB,EAAK,EAGtB,gBAAgB,UAAU,OAAO,KAAK,KAAK,IAAI,CACnD,OAAS,EAAO,CAEZ,GAAI,EAAW,OAAO,SAAW,CAAC,EAC9B,OAGJ,KAAK,eACD,EAAW,yEAAwB,2DACvC,EACA,KAAK,aAAa,EAClB,KAAK,QAAQ,kBAAkB,KAAK,KAAM,CAAK,CACnD,QAAU,CACN,aAAa,CAAK,CACtB,CACJ,CAOA,UAAU,EAAQ,CACd,IAAK,IAAM,KAAS,EAAO,MAAO,CAC9B,IAAM,EAAQ,EAAO,MAAM,GAC3B,KAAK,SAAS,EAAO,CAAK,CAC9B,CAEA,IAAI,EAAiB,KAAK,QAAQ,yBAC5B,qBACA,uBACN,WAAW,KAAK,EAAe,CAAC,KAAK,IAAI,EAAG,EAAE,CAClD,CAQA,SAAS,EAAO,EAAS,CACrB,IAAI,EAAO,KAAK,KAAK,cAAc,IAAM,EAAQ,OAAO,EAExD,GAAI,CAAC,EACD,OAIA,EAAK,cAAc,UAAU,SACzB,mCACJ,GAEA,EAAK,cAAc,cAAc,UAAU,IACvC,kCACJ,EAGJ,EAAK,UAAU,IAAI,aAAa,EAEhC,IAAI,EAAS,EAAK,cAAc,SAAS,EACpC,IACD,EAAS,SAAS,cAAc,GAAG,EACnC,EAAO,UAAU,IAAI,OAAO,EAC5B,EAAK,YAAY,CAAM,GAG3B,EAAO,UAAY,CACvB,CAKA,aAAc,CACV,IAAI,EAAQ,SAAS,eAAe,mBAAmB,EACvD,EAAM,MAAM,QAAU,OAEtB,IAAI,EAAS,KAAK,KAAK,iBAAiB,SAAS,EACjD,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAO,OAAQ,IAAS,CAChD,IAAM,EAAU,EAAO,GACvB,EAAQ,YAAc,EAC1B,CAEA,IAAI,EAAS,KAAK,KAAK,iBAAiB,cAAc,EACtD,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAO,OAAQ,IAEvC,EADuB,EAChB,CAAC,UAAU,OAAO,aAAa,CAE9C,CAKA,eAAgB,CAAA,IAAA,GACZ,EAAA,KAAK,UAAA,MAAL,EAAc,QAAQ,GAAU,CAC5B,EAAO,SAAW,EACtB,CAAC,CACL,CAKA,cAAe,CAAA,IAAA,GACX,EAAA,KAAK,UAAA,MAAL,EAAc,QAAQ,GAAU,CAC5B,EAAO,SAAW,EACtB,CAAC,CACL,CAKA,sBAAuB,CACnB,SAAS,cAAc,iBAAiB,CAAC,CAAC,eAAe,CACrD,SAAU,QACd,CAAC,CACL,CAKA,oBAAqB,CACjB,SAAS,cAAc,cAAc,CAAC,CAAC,eAAe,CAClD,SAAU,QACd,CAAC,EAEG,KAAK,QAAQ,uBACb,SACK,cAAc,cAAc,CAAC,CAC7B,cAAc,yBAAyB,CAAC,CACxC,MAAM,CAEnB,CAOA,eAAe,EAAS,CACpB,IAAI,EAAQ,SAAS,eAAe,mBAAmB,EACvD,EAAM,MAAM,QAAU,QACtB,EAAM,cAAc,UAAU,CAAC,CAAC,UAAY,CAChD,CAkCJ,GAAA,SAAA,GAhW4B,CAAA,IAAA,EAKpB,SAJyB,iBACrB,sCAGE,CAAC,CAAC,QAAQ,GAAU,CACtB,EAAO,iBAAiB,QAAS,GAAK,CAE9B,EAAE,OAAO,UAAU,SACf,mCACJ,EAEA,EAAE,OAAO,UAAU,OACf,mCACJ,EAEA,EAAE,OAAO,UAAU,IAAI,mCAAmC,CAElE,CAAC,CACL,CAAC,GAMD,EAAA,SAJiC,cAC7B,4CAGU,IAAC,MAAf,EAAiB,iBAAiB,QAAS,GAAA,EACvC,KAAA,CAAA,CAAA,CAAA,EAAA,CAA8B,CAAC,CACnC,EAEA,KAAK,QAAU,KAAK,KAAK,iBAAiB,iBAAiB,EAE3D,KAAK,KAAK,iBAAiB,SAAU,GAAK,CACtC,KAAK,cAAc,EACnB,KAAK,SAAS,CAAC,CACnB,CAAC,EAED,IAAI,EAAO,KAAK,IAAI,EAEpB,EAAA,KAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CACJ,CAAA,SAAA,GAMkB,CAKd,GAJuB,SAAS,cAAc,oBAE7B,CAAC,EAAE,cAAc,sBAAsB,GAAK,KAGzD,OAGJ,IAAM,EAAoB,SAAS,cAC/B,yBACJ,EACM,EAAsB,SAAS,cACjC,wBACJ,EAEA,GAAI,CAAC,GAAqB,CAAC,EACvB,OAIJ,IAAM,EAAW,KAAK,MAAM,aAAa,QAAQ,UAAU,CAAC,GAAK,CAAA,EAE7D,EAAS,SAAW,GACpB,KAAK,eACD,sEACJ,EAIJ,IAAM,EAAc,GAAmB,QAAQ,aAC/C,GAAI,EACA,GAAI,CACA,IAAM,EAAW,KAAK,MAAM,CAAW,EACvC,IAAK,IAAM,KAAU,EAAU,CAC3B,IAAM,EAAU,KAAK,MAAM,EAAO,KAAK,CAAC,EAAE,OACtC,GAAW,EAAS,KACpB,EAAO,SAAW,EAAS,GAEnC,CACJ,MAAQ,CAER,CAIJ,IAAM,EAAa,EACf,EACA,CAAC,oBAAqB,uBAAuB,EAC7C,CACI,sBAAuB,EAC3B,CACJ,EAEA,GAAmB,YAAY,CAAU,EAEzC,IAAI,EAAiB,CAAA,EAErB,EAAS,QAAQ,GAAU,CACvB,EAAe,KAAK,KAAK,MAAM,EAAO,KAAK,CAAC,CAAC,MAAM,CACvD,CAAC,EAEG,IACA,EAAoB,MAAQ,EAAe,KAAK,GAAG,EAE3D,CAAA,SAAA,EAEyB,EAAG,CAAA,IAAA,EACxB,EAAE,eAAe,GAMjB,EAAA,SAJ8B,cAC1B,kCAGO,IAAC,MAAZ,EAAc,UAAU,OAAO,YAAY,CAC/C,CA0OJ,OAAO,KAAK,WAAa,OAAO,KAAK,YAAc,CAAC,EACpD,OAAO,KAAK,WAAa,IAAI,EAC7B,OAAO,KAAK,WAAW,KAAK"}