PageSourceSearch

https://meteo.hr/cookie-consent/cookieconsent.umd.js

js meteo.hr collected 2026-09-24 09:10:09 UTC 130,828 bytes, 3,948 lines download raw bytes

1/*!
2* CookieConsent 3.0.0
3* https://github.com/orestbida/cookieconsent/tree/v3
4* Author Orest Bida
5* Released under the MIT License
6*/
7
8(function (global, factory) {
9    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
10    typeof define === 'function' && define.amd ? define(['exports'], factory) :
11    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.CookieConsent = {}));
12})(this, (function (exports) { 'use strict';
13
14    const COOKIE_NAME = 'cc_cookie';
15
16    const OPT_IN_MODE = 'opt-in';
17    const OPT_OUT_MODE = 'opt-out';
18
19    const TOGGLE_CONSENT_MODAL_CLASS = 'show--consent';
20    const TOGGLE_PREFERENCES_MODAL_CLASS = 'show--preferences';
21    const TOGGLE_DISABLE_INTERACTION_CLASS = 'disable--interaction';
22
23    const SCRIPT_TAG_SELECTOR = 'data-category';
24
25    const DIV_TAG = 'div';
26    const BUTTON_TAG = 'button';
27
28    const ARIA_HIDDEN = 'aria-hidden';
29    const BTN_GROUP_CLASS = 'btn-group';
30    const CLICK_EVENT = 'click';
31    const DATA_ROLE = 'data-role';
32
33    const CONSENT_MODAL_NAME = 'consentModal';
34    const PREFERENCES_MODAL_NAME = 'preferencesModal';
35
36    /**
37     * @typedef {import('../../types')} CookieConsent
38     *
39     * @typedef {CookieConsent} Api
40     * @typedef {CookieConsent.CookieConsentConfig} UserConfig
41     * @typedef {CookieConsent.Category} Category
42     * @typedef {CookieConsent.Service} Service
43     * @typedef {Object.<string, Service>} Services
44     * @typedef {CookieConsent.AutoClear} AutoClear
45     * @typedef {CookieConsent.GuiOptions} GuiOptions
46     * @typedef {GuiOptions['consentModal']} GuiModalOption
47     * @typedef {CookieConsent.CookieConsentConfig['language']} Language
48     * @typedef {CookieConsent.Translation} Translation
49     * @typedef {CookieConsent.ConsentModalOptions} ConsentModalOptions
50     * @typedef {CookieConsent.PreferencesModalOptions} PreferencesModalOptions
51     * @typedef {CookieConsent.CookieTable} CookieTable
52     * @typedef {CookieConsent.Section} Section
53     * @typedef {CookieConsent.CookieValue} CookieValue
54     * @typedef {CookieConsent.UserPreferences} UserPreferences
55     */
56
57    /**
58     * Internal state for each script tag
59     * @typedef {Object} ScriptInfo
60     * @property {HTMLScriptElement} _script
61     * @property {string} _categoryName
62     * @property {string} [_serviceName]
63     * @property {boolean} _executed
64     * @property {boolean} _runOnDisable
65     */
66
67    /**
68     * Pointers to main dom elements
69     * @typedef {Object} DomElements
70     * @property {Document} _document
71     * @property {HTMLElement} _htmlDom
72     *
73     * @property {HTMLElement} _ccMain
74     * @property {HTMLElement} _cmContainer
75     * @property {HTMLElement} _pmContainer
76     *
77     * @property {HTMLElement} _cm
78     * @property {HTMLElement} _cmBody
79     * @property {HTMLElement} _cmTexts
80     * @property {HTMLElement} _cmTitle
81     * @property {HTMLElement} _cmDescription
82     * @property {HTMLElement} _cmBtns
83     * @property {HTMLElement} _cmBtnGroup
84     * @property {HTMLElement} _cmBtnGroup2
85     * @property {HTMLElement} _cmAcceptAllBtn
86     * @property {HTMLElement} _cmAcceptNecessaryBtn
87     * @property {HTMLElement} _cmShowPreferencesBtn
88     * @property {HTMLElement} _cmFooterLinksGroup
89     * @property {HTMLElement} _cmCloseIconBtn
90     *
91     * @property {HTMLElement} _pm
92     * @property {HTMLElement} _pmHeader
93     * @property {HTMLElement} _pmTitle
94     * @property {HTMLElement} _pmCloseBtn
95     * @property {HTMLElement} _pmBody
96     * @property {HTMLElement} _pmNewBody
97     * @property {HTMLElement} _pmSections
98     * @property {HTMLElement} _pmFooter
99     * @property {HTMLElement} _pmAcceptAllBtn
100     * @property {HTMLElement} _pmAcceptNecessaryBtn
101     * @property {HTMLElement} _pmSavePreferencesBtn
102     *
103     * @property {Object.<string, HTMLInputElement>} _categoryCheckboxInputs
104     * @property {Object.<string, ServiceToggle>} _serviceCheckboxInputs
105     *
106     * // Used to properly restore focus when modal is closed
107     * @property {HTMLSpanElement} _focusSpan
108     * @property {HTMLSpanElement} _pmFocusSpan
109     */
110
111    /**
112     * @typedef {Object} CustomCallbacks
113     * @property {Function} _onFirstConsent
114     * @property {Function} _onConsent
115     * @property {Function} _onChange
116     * @property {Function} _onModalShow
117     * @property {Function} _onModalHide
118     * @property {Function} _onModalReady
119     */
120
121    /**
122     * Pointers to all services toggles relative to a category
123     * @typedef {Object.<string, HTMLInputElement>} ServiceToggle
124     */
125
126    class GlobalState {
127        constructor() {
128
129            /**
130             * Default config. options
131             * @type {CookieConsent.CookieConsentConfig}
132             */
133            this._config = {
134                mode: OPT_IN_MODE,
135                revision: 0,
136
137                //{{START: GUI}}
138                autoShow: true,
139                lazyHtmlGeneration: true,
140                //{{END: GUI}}
141
142                autoClearCookies: true,
143                manageScriptTags: true,
144                hideFromBots: true,
145
146                cookie: {
147                    name: COOKIE_NAME,
148                    expiresAfterDays: 182,
149                    domain: '',
150                    path: '/',
151                    sameSite: 'Lax'
152                }
153            };
154
155            this._state = {
156                /**
157                * @type {UserConfig}
158                */
159                _userConfig: {},
160
161                _currentLanguageCode: '',
162
163                /**
164                * @type {Object.<string, Translation>}
165                */
166                _allTranslations: {},
167
168                /**
169                * @type {Translation}
170                */
171                _currentTranslation: {},
172
173                /**
174                * Internal state variables
175                * @type {CookieValue}
176                */
177                _savedCookieContent : {},
178
179                /**
180                 * Store all event data-cc event listeners
181                 * (so that they can be removed on .reset())
182                 *
183                 * @type {{
184                 *  _element: HTMLElement,
185                 *  _event: string,
186                 *  _listener: Function
187                 * }[]}
188                 */
189                _dataEventListeners: [],
190
191                _disablePageInteraction: false,
192
193                /**
194                * @type {any}
195                */
196                _cookieData : null,
197
198                /**
199                * @type {Date}
200                */
201                _consentTimestamp: null,
202
203                /**
204                * @type {Date}
205                */
206                _lastConsentTimestamp: null,
207
208                /**
209                * @type {string}
210                */
211                _consentId: '',
212
213                _invalidConsent : true,
214
215                //{{START: GUI}}
216                _consentModalExists : false,
217                _consentModalVisible : false,
218
219                _preferencesModalVisible : false,
220                _preferencesModalExists: false,
221
222                /**
223                * @type {HTMLElement[]}
224                */
225                _currentModalFocusableElements: [],
226                //{{END: GUI}}
227
228                _revisionEnabled : false,
229                _validRevision : true,
230
231                /**
232                * Array containing the last changed categories (enabled/disabled)
233                * @type {string[]}
234                */
235                _lastChangedCategoryNames : [],
236
237                _reloadPage : false,
238
239                /**
240                * @type {CookieConsent.AcceptType}
241                */
242                _acceptType: '',
243
244                /**
245                * Object containing all user's defined categories
246                * @type {Object.<string, Category>}
247                */
248                _allDefinedCategories: false,
249
250                /**
251                * Stores all available categories
252                * @type {string[]}
253                */
254                _allCategoryNames: [],
255
256                /**
257                * Contains all accepted categories
258                * @type {string[]}
259                */
260                _acceptedCategories : [],
261
262                /**
263                * Keep track of readonly toggles
264                * @type {string[]}
265                */
266                _readOnlyCategories : [],
267
268                /**
269                * Contains all categories enabled by default
270                * @type {string[]}
271                */
272                _defaultEnabledCategories : [],
273
274                /**
275                * Don't run plugin if bot detected
276                * (to avoid indexing its text content)
277                */
278                _botAgentDetected : false,
279
280                /**
281                * Save reference to the last focused element on the page
282                * (used later to restore focus when both modals are closed)
283                */
284
285                //{{START: GUI}}
286
287                /** @type {HTMLElement} **/_lastFocusedElemBeforeModal: false,
288                /** @type {HTMLElement} **/_lastFocusedModalElement: false,
289
290                /**
291                * Both of the arrays below have the same structure:
292                * [0]: first focusable element inside modal
293                * [1]: last focusable element inside modal
294                */
295
296                /** @type {HTMLElement[]}
296 **/ _cmFocusableElements : [],
297                /** @type {HTMLElement[]} **/ _pmFocusableElements : [],
298
299                /**
300                * Keep track of enabled/disabled categories
301                * @type {boolean[]}
302                */
303                _allToggleStates : [],
304
305                //{{END: GUI}}
306
307                /**
308                * @type {Object.<string, Services>}
309                */
310                _allDefinedServices: {},
311
312                /**
313                * @type {Object.<string, string[]>}
314                */
315                _acceptedServices: {},
316
317                /**
318                 * Keep track of the current state of the services
319                 * (may not be the same as enabledServices)
320                 *
321                 * @type {Object.<string, string[]>}
322                 */
323                _enabledServices: {},
324
325                /**
326                * @type {Object.<string, string[]>}
327                */
328                _lastChangedServices: {},
329
330                /**
331                * @type {Object.<string, string[]>}
332                */
333                _lastEnabledServices: {},
334
335                /**
336                * @type {ScriptInfo[]}
337                */
338                _allScriptTags: []
339            };
340
341            //{{START: GUI}}
342
343            /**
344             * Pointers to main dom elements
345             * @type {DomElements}
346             */
347            this._dom = {
348                _categoryCheckboxInputs: {},
349                _serviceCheckboxInputs: {}
350            };
351
352            //{{END: GUI}}
353
354            /**
355             * Callback functions
356             * @type {CustomCallbacks}
357             */
358            this._callbacks = {};
359
360            this._customEvents = {
361                _onFirstConsent: 'cc:onFirstConsent',
362                _onConsent: 'cc:onConsent',
363                _onChange: 'cc:onChange',
364                //{{START: GUI}}
365                _onModalShow: 'cc:onModalShow',
366                _onModalHide: 'cc:onModalHide',
367                _onModalReady: 'cc:onModalReady'
368                //{{END: GUI}}
369            };
370        }
371    }
372
373    const globalObj = new GlobalState();
374
375    /**
376     * Helper console.log function
377     * @param {Object} printMsg
378     * @param {Object} [optionalParam]
379     */
380    const _log = (printMsg, optionalParam) => {
381        console.log(printMsg, optionalParam !== undefined
382            ? optionalParam
383            : ' '
384        );
385    };
386
387    /**
388     * Helper indexOf
389     * @param {any[]|string} el
390     * @param {any} value
391     */
392    const indexOf = (el, value) => el.indexOf(value);
393
394    /**
395     * Returns true if el. (array or string) contains the specified value
396     * @param {any[]|string} el
397     */
398    const elContains = (el, value) => indexOf(el, value) !== -1;
399
400    const isArray = el => Array.isArray(el);
401
402    const isString = el => typeof el === 'string';
403
404    const isObject = el => !!el && typeof el === 'object' && !isArray(el);
405
406    const isFunction = el => typeof el === 'function';
407
408    const getKeys = obj => Object.keys(obj);
409
410    /**
411     * Return array without duplicates
412     * @param {any[]} arr
413     */
414    const unique = (arr) => Array.from(new Set(arr));
415
416    const getActiveElement = () => document.activeElement;
417
418    /**
419     * @param {Event} e
420     */
421    const preventDefault = (e) => e.preventDefault();
422
423    /**
424     * @param {Element} el
425     * @param {string} selector
426     */
427    const querySelectorAll = (el, selector) => el.querySelectorAll(selector);
428
429    /**
430     * @param {HTMLInputElement} input
431     */
432    const dispatchInputChangeEvent = (input) => input.dispatchEvent(new Event('change'));
433
434    /**
435     * @param {keyof HTMLElementTagNameMap} type
436     */
437    const createNode = (type) => {
438        const el = document.createElement(type);
439
440        if (type === BUTTON_TAG) {
441            el.type = type;
442        }
443
444        return el;
445    };
446
447    /**
448     * @param {HTMLElement} el
449     * @param {string} attribute
450     * @param {string} value
451     */
452    const setAttribute = (el, attribute, value) => el.setAttribute(attribute, value);
453
454    /**
455     * @param {HTMLElement} el
456     * @param {string} attribute
457     * @param {boolean}
457 [prependData]
458     */
459    const removeAttribute = (el, attribute, prependData) => {
460        el.removeAttribute(prependData
461            ? 'data-' + attribute
462            : attribute
463        );
464    };
465
466    /**
467     * @param {HTMLElement} el
468     * @param {string} attribute
469     * @param {boolean} [prependData]
470     * @returns {string}
471     */
472    const getAttribute = (el, attribute, prependData) => {
473        return el.getAttribute(prependData
474            ? 'data-' + attribute
475            : attribute
476        );
477    };
478
479    /**
480     * @param {Node} parent
481     * @param {Node} child
482     */
483    const appendChild = (parent, child) => parent.appendChild(child);
484
485    /**
486     * @param {HTMLElement} elem
487     * @param {string} className
488     */
489    const addClass = (elem, className) => elem.classList.add(className);
490
491    /**
492     * @param {HTMLElement} elem
493     * @param {string} className
494     */
495    const addClassCm = (elem, className) => addClass(elem, 'cm__' + className);
496    /**
497     * @param {HTMLElement} elem
498     * @param {string} className
499     */
500    const addClassPm = (elem, className) => addClass(elem, 'pm__' + className);
501
502    /**
503     * @param {HTMLElement} elem
504     * @param {string} className
505     */
506    const removeClass = (el, className) => el.classList.remove(className);
507
508    /**
509     * @param {HTMLElement} el
510     * @param {string} className
511     */
512    const hasClass = (el, className) => el.classList.contains(className);
513
514    const deepCopy = (el) => {
515        if (typeof el !== 'object' )
516            return el;
517
518        if (el instanceof Date)
519            return new Date(el.getTime());
520
521        let clone = Array.isArray(el) ? [] : {};
522
523        for (let key in el) {
524            let value = el[key];
525            clone[key] = deepCopy(value);
526        }
527
528        return clone;
529    };
530
531    /**
532     * Store categories and services' config. details
533     * @param {string[]} allCategoryNames
534     */
535    const fetchCategoriesAndServices = (allCategoryNames) => {
536        const {
537            _allDefinedCategories,
538            _allDefinedServices,
539            _acceptedServices,
540            _enabledServices,
541            _readOnlyCategories
542        } = globalObj._state;
543
544        for (let categoryName of allCategoryNames) {
545
546            const currCategory = _allDefinedCategories[categoryName];
547            const services = currCategory.services || {};
548            const serviceNames = isObject(services) && getKeys(services) || [];
549
550            _allDefinedServices[categoryName] = {};
551            _acceptedServices[categoryName] = [];
552            _enabledServices[categoryName] = [];
553
554            /**
555             * Keep track of readOnly categories
556             */
557            if (currCategory.readOnly) {
558                _readOnlyCategories.push(categoryName);
559                _acceptedServices[categoryName] = serviceNames;
560            }
561
562            globalObj._dom._serviceCheckboxInputs[categoryName] = {};
563
564            for (let serviceName of serviceNames) {
565                const service = services[serviceName];
566                service._enabled = false;
567                _allDefinedServices[categoryName][serviceName] = service;
568            }
569        }
570    };
571
572    /**
573     * Retrieves all script elements with 'data-category' attribute
574     * and save the following attributes: category-name and service
575     */
576    const retrieveScriptElements = () => {
577        if (!globalObj._config.manageScriptTags)
578            return;
579
580        const state = globalObj._state;
581
582        /**
583         * @type {NodeListOf<HTMLScriptElement>}
584         */
585        const scripts = querySelectorAll(document, 'script[' + SCRIPT_TAG_SELECTOR +']');
586
587        for (const scriptTag of scripts) {
588            let scriptCategoryName = getAttribute(scriptTag, SCRIPT_TAG_SELECTOR);
589            let scriptServiceName = scriptTag.dataset.service || '';
590            let runOnDisable = false;
591
592            /**
593             * Remove the '!' char if it is present
594             */
595            if (scriptCategoryName && scriptCategoryName.charAt(0) === '!') {
596                scriptCategoryName = scriptCategoryName.slice(1);
597                runOnDisable = true;
598            }
599
600            if (scriptServiceName.charAt(0) === '!') {
601                scriptServiceName = scriptServiceName.slice(1);
602                runOnDisable = true;
603            }
604
605            if (elContains(state._allCategoryNames, scriptCategoryName)) {
606                state._allScriptTags.push({
607                    _script: scriptTag,
608                    _executed: false,
609                    _runOnDisable: runOnDisable,
610                    _categoryName: scriptCategoryName,
611                    _serviceName: scriptServiceName
612                });
613
614                if (scriptServiceName) {
615                    const categoryServices = state._allDefinedServices[scriptCategoryName];
616                    if (!categoryServices[scriptServiceName]) {
617                        categoryServices[scriptServiceName] = {
618                            _enabled: false
619                        };
620                    }
621                }
622            }
623        }
624    };
625
626    /**
627     * Calculate rejected services (all services - enabled services)
628     * @returns {Object.<string, string[]>}
629     */
630    const retrieveRejectedServices = () => {
631        const rejectedServices = {};
632
633        const {
634            _allCategoryNames,
635            _allDefinedServices,
636            _acceptedServices
637        } = globalObj._state;
638
639        for (const categoryName of _allCategoryNames) {
640            rejectedServices[categoryName] = arrayDiff(
641                _acceptedServices[categoryName],
642                getKeys(_allDefinedServices[categoryName])
643            );
644        }
645
646        return rejectedServices;
647    };
648
649    const retrieveCategoriesFromModal = () => {
650        const toggles = globalObj._dom._categoryCheckboxInputs;
651
652        if (!toggles)
653            return [];
654
655        let enabledCategories = [];
656
657        for (let categoryName in toggles) {
658            if (toggles[categoryName].checked) {
659                enabledCategories.push(categoryName);
660            }
661        }
662
663        return enabledCategories;
664    };
665
666    /**
667     * @param {string[]|string} categories - Categories to accept
668     * @param {string[]} [excludedCategories]
669     */
670    const resolveEnabledCategories = (categories, excludedCategories) => {
671        const {
672            _allCategoryNames,
673            _acceptedCategories,
674            _readOnlyCategories,
675            _preferencesModalExists,
676            _enabledServices,
677            _allDefinedServices
678        } = globalObj._state;
679
680        /**
681         * @type {string[]}
682         */
683        let enabledCategories = [];
684
685        if (!categories) {
686            enabledCategories = _acceptedCategories;
687            //{{START: GUI}}
688            _preferencesModalExists && (enabledCategories = retrieveCategoriesFromModal());
689            //{{END: GUI}}
690        } else {
691
692            if (isArray(categories)) {
693                enabledCategories.push(...categories);
694            }else if (isString(categories)) {
695                enabledCategories = categories === 'all'
696                    ? _allCategoryNames
697                    : [categories];
698            }
699
700            /**
701             * If there are services, turn them all on or off
702             */
703            for (const categoryName of _allCategoryNames) {
704                _enabledServices[categoryName] = elContains(enabledCategories, categoryName)
705                    ? getKeys(_allDefinedServices[categoryName])
706                    : [];
707            }
708        }
709
710        // Remove invalid and excluded categories
711        enabledCategories = enabledCategories.filter(category =>
712            !elContains(_allCategoryNames, category) ||
713            !elContains(excludedCategories, category)
714        );
715
716        // Add back all the categories set as "readonly/required"
717        enabledCategories.push(..._readOnlyCategories);
718
719        setAcceptedCategories(enabledCategories);
720    };
721
722    /**
723     * @param {string} [relativeCategory]
724     */
725    const resolveEnabledServices = (relativeCategory) => {
726        const state = globalObj._state;
727
728        const {
729            _enabledServices,
730            _readOnlyCategories,
731            _acceptedServices,
732            _allDefinedServices,
733            _allCategoryNames
734        } = state;
735
736        const categoriesToConsider = relativeCategory
737            ? [relativeCategory]
738            : _allCategoryNames;
739
740        /**
741         * Save previously enabled services to calculate later on which of them was changed
742         */
743        state._lastEnabledServices = deepCopy(_acceptedServices);
744
745        for (const categoryName of categoriesToConsider) {
746            const services = _allDefinedServices[categoryName];
747            const serviceNames = getKeys(services);
748            const customServicesSelection = _enabledServices[categoryName] && _enabledServices[categoryName].length > 0;
749            const readOnlyCategory = elContains(_readOnlyCategories, categoryName);
750
751            /**
752             * Stop here if there are no services
753             */
754            if (serviceNames.length === 0)
755                continue;
756
757            // Empty (previously) enabled services
758            _acceptedServices[categoryName] = [];
759
760            // If category is marked as readOnly enable all its services
761            if (readOnlyCategory) {
762                _acceptedServices[categoryName].push(...serviceNames);
763            } else {
764                if (customServicesSelection) {
765                    const selectedServices = _enabledServices[categoryName];
766                    _acceptedServices[categoryName].push(...selectedServices);
767                } else {
768                    _acceptedServices[categoryName] = [];
769                }
770            }
771
772            /**
773             * Make sure there are no duplicates inside array
774             */
775            _acceptedServices[categoryName] = unique(_acceptedServices[categoryName]);
776        }
777    };
778
779    /**
780     * @param {string} eventName
781     */
782    const dispatchPluginEvent = (eventName, data) => dispatchEvent(new CustomEvent(eventName, {detail: data}));
783
784    /**
785     * Update services state internally and tick/untick checkboxes
786     * @param {string|string[]} service
787     * @param {string} category
788     */
789    const updateModalToggles = (service, category) => {
790        const state = globalObj._state;
791        const {
792            _allDefinedServices,
793            _enabledServices,
794            _preferencesModalExists
795        } = state;
796
797        const servicesInputs = globalObj._dom._serviceCheckboxInputs[category] || {};
798        const categoryInput = globalObj._dom._categoryCheckboxInputs[category] || {};
799        const allServiceNames = getKeys(_allDefinedServices[category]);
800
801        // Clear previously enabled services
802        _enabledServices[category] = [];
803
804        if (isString(service)) {
805            if (service === 'all') {
806
807                // Enable all services
808                _enabledServices[category].push(...allServiceNames);
809
810                if (_preferencesModalExists) {
811                    for (let serviceName in servicesInputs) {
812                        servicesInputs[serviceName].checked = true;
813                        dispatchInputChangeEvent(servicesInputs[serviceName]);
814                    }
815                }
816
817            } else {
818
819                // Enable only one service (if valid) and disable all the others
820                if (elContains(allServiceNames, service))
821                    _enabledServices[category].push(service);
822
823                if (_preferencesModalExists) {
824                    for (let serviceName in servicesInputs) {
825                        servicesInputs[serviceName].checked = service === serviceName;
826                        dispatchInputChangeEvent(servicesInputs[serviceName]);
827                    }
828                }
829            }
830        }else if (isArray(service)) {
831            for (let serviceName of allServiceNames) {
832                const validService = elContains(service, serviceName);
833                validService && _enabledServices[category].push(serviceName);
834
835                if (_preferencesModalExists) {
836                    servicesInputs[serviceName].checked = validService;
837                    dispatchInputChangeEvent(servicesInputs[serviceName]);
838                }
839            }
840        }
841
842        const uncheckCategory = _enabledServices[category].length === 0;
843
844        /**
845         * Remove/add the category from acceptedCategories
846         */
847        state._acceptedCategories = uncheckCategory
848            ? state._acceptedCategories.filter(cat => cat !== category)
849            : unique([...state._acceptedCategories, category]);
850
851        /**
852         * If there are no services enabled in the
853         * current category, uncheck the category
854         */
855        if (_preferencesModalExists) {
856            categoryInput.checked = !uncheckCategory;
857            dispatchInputChangeEvent(categoryInput);
858        }
859    };
860
861    /**
862     * Generate RFC4122-compliant UUIDs.
863     * https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid?page=1&tab=votes#tab-top
864     * @returns {string} unique uuid string
865     */
866    const uuidv4 = () => {
867        return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) => {
868            return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16);
869        });
870    };
871
872    /**
873     * Add event listener to dom object (cross browser function)
874     * @param {Element} elem
875     * @param {keyof WindowEventMap} event
876     * @param {EventListener} fn
877     * @param {boolean} [saveListener]
878     */
879    const addEvent = (elem, event, fn, saveListener) => {
880        elem.addEventListener(event, fn);
881
882        /**
883         * Keep track of specific event listeners
884         * that must be removed on `.reset()`
885         */
886        if (saveListener) {
887            globalObj._state._dataEventListeners.push({
888                _element: elem,
889                _event: event,
890                _listener: fn
891            });
892        }
893    };
894
895    /**
896     * Calculate the existing cookie's remaining time until expiration (in milliseconds)
897     */
898    const getRemainingExpirationTimeMS = () => {
899        const lastTimestamp = globalObj._state._lastConsentTimestamp;
900
901        const elapsedTimeMilliseconds = lastTimestamp
902            ? new Date() - lastTimestamp
903            : 0;
904
905        return getExpiresAfterDaysValue()*86400000 - elapsedTimeMilliseconds;
906    };
907
908    /**
909     * Used to fetch external language files (.json)
910     * @param {string} url
911     * @returns {Promise<import('../core/global').Translation | boolean>}
912     */
913    const fetchJson = async (url) => {
914        try {
915            const response = await fetch(url);
916            return await response.json();
917        } catch (e) {
918            console.error(e);
919            return false;
920        }
921    };
922
923    /**
924     * Helper function to retrieve cookie duration
925     * @returns {number}
926     */
927    const getExpiresAfterDaysValue = () => {
928        const expiresAfterDays = globalObj._config.cookie.expiresAfterDays;
929
930        return isFunction(expiresAfterDays)
931            ? expiresAfterDays(globalObj._state._acceptType)
932            : expiresAfterDays;
933    };
934
935    /**
936     * Symmetric difference between 2 arrays
937     * @param {any[]} arr1
938     * @param {any[]} arr2
939     */
940    const arrayDiff = (arr1, arr2) => {
941        const a = arr1 || [];
942        const b = arr2 || [];
943
944        return a
945            .filter(x => !elContains(b, x))
946            .concat(b.filter(x => !elContains(a, x)));
947    };
948
949    /**
950     * Calculate "accept type"
951     * @returns {'all'|'custom'|'necessary'} accept type
952     */
953    const resolveAcceptType = () => {
954        let type = 'custom';
955
956        const { _acceptedCategories, _allCategoryNames, _readOnlyCategories } = globalObj._state;
957        const nAcceptedCategories = _acceptedCategories.length;
958
959        if (nAcceptedCategories === _allCategoryNames.length)
960            type = 'all';
961        else if (nAcceptedCategories === _readOnlyCategories.length)
962            type = 'necessary';
963
964        return type;
965    };
966
967    /**
968     * Note: getUserPreferences() depends on "acceptType"
969     * @param {string[]} acceptedCategories
970     */
971    const setAcceptedCategories = (acceptedCategories) => {
972        globalObj._state._acceptedCategories = unique(acceptedCategories);
973        globalObj._state._acceptType = resolveAcceptType();
974    };
975
976    /**
977     * This callback type is called `requestCallback` and is displayed as a global symbol.
978     *
979     * @callback createModal
980     * @param {import('../core/global').Api} api
981     */
982
983    /**
984     * Add an onClick listeners to all html elements with data-cc attribute
985     * @param {HTMLElement} [elem]
986     * @param {import('../core/global').Api} api
987     * @param {createModal} [createPreferencesModal]
988     */
989    const addDataButtonListeners = (elem, api, createPreferencesModal, createMainContainer) => {
990        const ACCEPT_PREFIX = 'accept-';
991
992        const {
993            show,
994            showPreferences,
995            hide,
996            hidePreferences,
997            acceptCategory
998        } = api;
999
1000        const rootEl = elem || document;
1001        const getElements = dataRole => querySelectorAll(rootEl, `[data-cc="${dataRole}"]`);
1002
1003        /**
1004         * Helper function: accept and then hide modals
1005         * @param {Event} event source event
1006         * @param {string|string[]} [acceptType]
1007         */
1008        const acceptAction = (event, acceptType) => {
1009            preventDefault(event);
1010            acceptCategory(acceptType);
1011            hidePreferences();
1012            hide();
1013        };
1014
1015        const
1016            showPreferencesModalElements = getElements('show-preferencesModal'),
1017            showConsentModalElements = getElements('show-consentModal'),
1018            acceptAllElements = getElements(ACCEPT_PREFIX + 'all'),
1019            acceptNecessaryElements = getElements(ACCEPT_PREFIX + 'necessary'),
1020            acceptCustomElements = getElements(ACCEPT_PREFIX + 'custom'),
1021            createPreferencesModalOnHover = globalObj._config.lazyHtmlGeneration;
1022
1023        //{{START: GUI}}
1024        for (const el of showPreferencesModalElements) {
1025            setAttribute(el, 'aria-haspopup', 'dialog');
1026            addEvent(el, CLICK_EVENT, (event) => {
1027                preventDefault(event);
1028                showPreferences();
1029            });
1030
1031            if (createPreferencesModalOnHover) {
1032                addEvent(el, 'mouseenter', (event) => {
1033                    preventDefault(event);
1034                    if (!globalObj._state._preferencesModalExists)
1035                        createPreferencesModal(api, createMainContainer);
1036                }, true);
1037
1038                addEvent(el, 'focus', () => {
1039                    if (!globalObj._state._preferencesModalExists)
1040                        createPreferencesModal(api, createMainContainer);
1041                });
1042            }
1043        }
1044
1045        for (let el of showConsentModalElements) {
1046            setAttribute(el, 'aria-haspopup', 'dialog');
1047            addEvent(el, CLICK_EVENT, (event) => {
1048                preventDefault(event);
1049                show(true);
1050            }, true);
1051        }
1052        //{{END: GUI}}
1053
1054        for (let el of acceptAllElements) {
1055            addEvent(el, CLICK_EVENT, (event) => {
1056                acceptAction(event, 'all');
1057            }, true);
1058        }
1059
1060        for (let el of acceptCustomElements) {
1061            addEvent(el, CLICK_EVENT, (event) => {
1062                acceptAction(event);
1063            }, true);
1064        }
1065
1066        for (let el of acceptNecessaryElements) {
1067            addEvent(el, CLICK_EVENT, (event) => {
1068                acceptAction(event, []);
1069            }, true);
1070        }
1071    };
1072
1073    /**
1074     * @param {HTMLElement} el
1075     * @param {boolean} [toggleTabIndex]
1076     */
1077    const focus = (el, toggleTabIndex) => {
1078        if (!el) return;
1079
1080        /**
1081         * Momentarily add the `tabindex` attribute to fix
1082         * a bug with focus restoration in chrome
1083         */
1084        toggleTabIndex && (el.tabIndex = -1);
1085
1086        el.focus();
1087
1088        /**
1089         * Remove the `tabindex` attribute so
1090         * that the html markup is valid again
1091         */
1092        toggleTabIndex && el.removeAttribute('tabindex');
1093    };
1094
1095    /**
1096     * @param {HTMLDivElement} element
1097     * @param {1 | 2} modalId
1098     */
1099    const focusAfterTransition = (element, modalId) => {
1100        const getVisibleDiv = (modalId) => modalId === 1
1101            ? globalObj._dom._cmDivTabindex
1102            : globalObj._dom._pmDivTabindex;
1103
1104        const setFocus = (event) => {
1105            event.target.removeEventListener('transitionend', setFocus);
1106            if (event.propertyName === 'opacity' && getComputedStyle(element).opacity === '1') {
1107                focus(getVisibleDiv(modalId));
1108            }
1109        };
1110
1111        addEvent(element, 'transitionend', setFocus);
1112    };
1113
1114    /**
1115     * Obtain accepted and rejected categories
1116     * @returns {{accepted: string[], rejected: string[]}}
1117     */
1118    const getCurrentCategoriesState = () => {
1119        const {
1120            _invalidConsent,
1121            _acceptedCategories,
1122            _allCategoryNames
1123        } = globalObj._state;
1124
1125        return {
1126            accepted: _acceptedCategories,
1127            rejected: _invalidConsent
1128                ? []
1129                : _allCategoryNames.filter(category =>
1130                    !elContains(_acceptedCategories, category)
1131                )
1132        };
1133    };
1134
1135    let disableInteractionTimeout;
1136
1137    /**
1138     * @param {boolean} [enable]
1139     */
1140    const toggleDisableInteraction = (enable) => {
1141        clearTimeout(disableInteractionTimeout);
1142
1143        if (enable) {
1144            addClass(globalObj._dom._htmlDom, TOGGLE_DISABLE_INTERACTION_CLASS);
1145        }else {
1146            disableInteractionTimeout = setTimeout(() => {
1147                removeClass(globalObj._dom._htmlDom, TOGGLE_DISABLE_INTERACTION_CLASS);
1148            }, 500);
1149        }
1150    };
1151
1152    const iconStrokes = [
1153        'M 19.5 4.5 L 4.5 19.5 M 4.5 4.501 L 19.5 19.5',    // X
1154        'M 3.572 13.406 L 8.281 18.115 L 20.428 5.885',     // TICK
1155        'M 21.999 6.94 L 11.639 17.18 L 2.001 6.82 '        // ARROW
1156    ];
1157
1158    /**
1159     * [0: x, 1: tick, 2: arrow]
1160     * @param {0 | 1 | 2} [iconIndex]
1161     * @param {number} [strokeWidth]
1162     */
1163    const getSvgIcon = (iconIndex = 0, strokeWidth = 1.5) => {
1164        return `<svg viewBox="0 0 24 24" stroke-width="${strokeWidth}"><path d="${iconStrokes[iconIndex]}"/></svg>`;
1165    };
1166
1167    /**
1168     * Trap focus inside modal and focus the first
1169     * focusable element of current active modal
1170     * @param {HTMLDivElement} modal
1171     */
1172    const handleFocusTrap = (modal) => {
1173        const dom = globalObj._dom;
1174        const state = globalObj._state;
1175
1176        /**
1177         * @param {HTMLDivElement} modal
1178         * @param {HTMLElement[]} focusableElements
1179         */
1180        const trapFocus = (modal) => {
1181            const isConsentModal = modal === dom._cm;
1182
1183            const scope = state._userConfig.disablePageInteraction
1184                ? dom._htmlDom
1185                : isConsentModal
1186                    ? dom._ccMain
1187                    : dom._htmlDom;
1188
1189            const getFocusableElements = () => isConsentModal
1190                ? state._cmFocusableElements
1191                : state._pmFocusableElements;
1192
1193            const isModalVisible = () => isConsentModal
1194                ? state._consentModalVisible && !state._preferencesModalVisible
1195                : state._preferencesModalVisible;
1196
1197            addEvent(scope, 'keydown', (e) => {
1198                if (e.key !== 'Tab' || !isModalVisible())
1199                    return;
1200
1201                const currentActiveElement = getActiveElement();
1202                const focusableElements = getFocusableElements();
1203
1204                if (focusableElements.length === 0)
1205                    return;
1206
1207                /**
1208                 * If reached natural end of the tab sequence => restart
1209                 * If current focused element is not inside modal => focus modal
1210                 */
1211                if (e.shiftKey) {
1212                    if (currentActiveElement === focusableElements[0] || !modal.contains(currentActiveElement)) {
1213                        preventDefault(e);
1214                        focus(focusableElements[1]);
1215                    }
1216                } else {
1217                    if (currentActiveElement === focusableElements[1] || !modal.contains(currentActiveElement)) {
1218                        preventDefault(e);
1219                        focus(focusableElements[0]);
1220                    }
1221                }
1222            }, true);
1223        };
1224
1225        trapFocus(modal);
1226    };
1227
1228    /**
1229     * Note: any of the below focusable elements, which has the attribute tabindex="-1" AND is either
1230     * the first or last element of the modal, won't receive focus during "open/close" modal
1231     */
1232    const focusableTypesSelector = ['[href]', BUTTON_TAG, 'input', 'details', '[tabindex]']
1233        .map(selector => selector+':not([tabindex="-1"])').join(',');
1234
1235    const getFocusableElements = (root) => querySelectorAll(root, focusableTypesSelector);
1236
1237    /**
1238     * Save reference to first and last focusable elements inside each modal
1239     * to prevent losing focus while navigating with TAB
1240     * @param {1 | 2} [modalId]
1241     */
1242    const getModalFocusableData = (modalId) => {
1243        const { _state, _dom } = globalObj;
1244
1245        /**
1246         * Saves all focusable elements inside modal, into the array
1247         * @param {HTMLElement} modal
1248         * @param {Element[]} array
1249         */
1250        const saveAllFocusableElements = (modal, array) => {
1251            const focusableElements = getFocusableElements(modal);
1252
1253            /**
1254             * Save first and last elements (trap focus inside modal)
1255             */
1256            array[0] = focusableElements[0];
1257            array[1] = focusableElements[focusableElements.length - 1];
1258        };
1259
1260        if (modalId === 1 && _state._consentModalExists)
1261            saveAllFocusableElements(_dom._cm, _state._cmFocusableElements);
1262
1263        if (modalId === 2 && _state._preferencesModalExists)
1264            saveAllFocusableElements(_dom._pm, _state._pmFocusableElements);
1265    };
1266
1267    /**
1268     * Fire custom event
1269     * @param {string} eventName
1270     * @param {string} [modalName]
1271     * @param {HTMLElement} [modal]
1272     */
1273    const fireEvent = (eventName, modalName, modal) => {
1274        const {
1275            _onChange,
1276            _onConsent,
1277            _onFirstConsent,
1278            _onModalHide,
1279            _onModalReady,
1280            _onModalShow
1281        } = globalObj._callbacks;
1282
1283        const events = globalObj._customEvents;
1284
1285        //{{START: GUI}}
1286        if (modalName) {
1287            const modalParams = { modalName };
1288
1289            if (eventName === events._onModalShow) {
1290                isFunction(_onModalShow) && _onModalShow(modalParams);
1291            }else if (eventName === events._onModalHide) {
1292                isFunction(_onModalHide) && _onModalHide(modalParams);
1293            } else {
1294                modalParams.modal = modal;
1295                isFunction(_onModalReady) && _onModalReady(modalParams);
1296            }
1297
1298            return dispatchPluginEvent(eventName, modalParams);
1299        }
1300        //{{END: GUI}}
1301
1302        const params = {
1303            cookie: globalObj._state._savedCookieContent
1304        };
1305
1306        if (eventName === events._onFirstConsent) {
1307            isFunction(_onFirstConsent) && _onFirstConsent(deepCopy(params));
1308        }else if (eventName === events._onConsent) {
1309            isFunction(_onConsent) && _onConsent(deepCopy(params));
1310        }else {
1311            params.changedCategories = globalObj._state._lastChangedCategoryNames;
1312            params.changedServices = globalObj._state._lastChangedServices;
1313            isFunction(_onChange) && _onChange(deepCopy(params));
1314        }
1315
1316        dispatchPluginEvent(eventName, deepCopy(params));
1317    };
1318
1319    /**
1320     * @param {CallableFunction} fn
1321     */
1322    const safeRun = (fn, hideError) => {
1323        try {
1324            return fn();
1325        } catch (e) {
1326            !hideError && console.warn('CookieConsent:', e);
1327            return false;
1328        }
1329    };
1330
1331    /**
1332     * @param {string} type
1333     */
1334    const validMimeType = type => ['text/javascript', 'module'].includes(type);
1335
1336    /**
1337     * This function handles the loading/activation logic of the already
1338     * existing scripts based on the current accepted cookie categories
1339     *
1340     * @param {string[]} [defaultEnabledCategories]
1341     */
1342    const manageExistingScripts = (defaultEnabledCategories) => {
1343        const {
1344            _acceptedServices,
1345            _lastChangedServices,
1346            _allCategoryNames,
1347            _allDefinedServices,
1348            _allScriptTags,
1349            _savedCookieContent,
1350            _lastChangedCategoryNames,
1351        } = globalObj._state;
1352
1353        /**
1354         * Automatically Enable/Disable internal services
1355         */
1356        for (const categoryName of _allCategoryNames) {
1357            const lastChangedServices = _lastChangedServices[categoryName]
1358                || _acceptedServices[categoryName]
1359                || [];
1360
1361            for (const serviceName of lastChangedServices) {
1362                const service = _allDefinedServices[categoryName][serviceName];
1363
1364                if (!service)
1365                    continue;
1366
1367                const {onAccept, onReject} = service;
1368
1369                if (
1370                    !service._enabled
1371                    && elContains(_acceptedServices[categoryName], serviceName)
1372                    && isFunction(onAccept)
1373                ) {
1374                    service._enabled = true;
1375                    onAccept();
1376                }
1377
1378                else if (
1379                    service._enabled
1380                    && !elContains(_acceptedServices[categoryName], serviceName)
1381                    && isFunction(onReject)
1382                ) {
1383                    service._enabled = false;
1384                    onReject();
1385                }
1386            }
1387        }
1388
1389        if (!globalObj._config.manageScriptTags)
1390            return;
1391
1392        const scripts = _allScriptTags;
1393        const acceptedCategories = defaultEnabledCategories
1394            || _savedCookieContent.categories
1395            || [];
1396
1397        /**
1398         * Load scripts (sequentially), using a recursive function
1399         * which loops through the scripts array
1400         * @param {import('../core/global').ScriptInfo[]} scripts scripts to load
1401         * @param {number} index current script to load
1402         */
1403        const loadScriptsHelper = (scripts, index) => {
1404            if (index >= scripts.length)
1405                return;
1406
1407            const currScriptInfo = _allScriptTags[index];
1408
1409            /**
1410             * Skip script if it was already executed
1411             */
1412            if (currScriptInfo._executed)
1413                return loadScriptsHelper(scripts, index+1);
1414
1415            const currScript = currScriptInfo._script;
1416            const currScriptCategory = currScriptInfo._categoryName;
1417            const currScriptService = currScriptInfo._serviceName;
1418            const categoryAccepted = elContains(acceptedCategories, currScriptCategory);
1419            const serviceAccepted = currScriptService
1420                ? elContains(_acceptedServices[currScriptCategory], currScriptService)
1421                : false;
1422
1423            const categoryWasJustEnabled = () => !currScriptService
1424                && !currScriptInfo._runOnDisable
1425                && categoryAccepted;
1426
1427            const serviceWasJustEnabled = () => currScriptService
1428                && !currScriptInfo._runOnDisable
1429                && serviceAccepted;
1430
1431            const categoryWasJustDisabled = () => !currScriptService
1432                && currScriptInfo._runOnDisable
1433                && !categoryAccepted
1434                && elContains(_lastChangedCategoryNames, currScriptCategory);
1435
1436            const serviceWasJustDisabled = () => currScriptService
1437                && currScriptInfo._runOnDisable
1438                && !serviceAccepted
1439                && elContains(_lastChangedServices[currScriptCategory] || [], currScriptService);
1440
1441            const shouldRunScript =
1442                categoryWasJustEnabled()
1443                || categoryWasJustDisabled()
1444                || serviceWasJustEnabled()
1445                || serviceWasJustDisabled();
1446
1447            if (shouldRunScript) {
1448                currScriptInfo._executed = true;
1449                const dataType = getAttribute(currScript, 'type', true);
1450
1451                removeAttribute(currScript, 'type', !!dataType);
1452                removeAttribute(currScript, SCRIPT_TAG_SELECTOR);
1453
1454                // Get current script data-src (if there is one)
1455                let src = getAttribute(currScript, 'src', true);
1456
1457                // Some scripts (like ga) might throw warning if data-src is present
1458                src && removeAttribute(currScript, 'src', true);
1459
1460                /**
1461                 * Fresh script
1462                 * @type {HTMLScriptElement}
1463                 */
1464                const freshScript = createNode('script');
1465
1466                freshScript.textContent = currScript.innerHTML;
1467
1468                //Copy attributes over to the new "revived" script
1469                for (const {nodeName} of currScript.attributes) {
1470                    setAttribute(
1471                        freshScript,
1472                        nodeName,
1473                        currScript[nodeName] || getAttribute(currScript, nodeName)
1474                    );
1475                }
1476
1477                /**
1478                 * Set custom type
1479                 */
1480                dataType && (freshScript.type = dataType);
1481
1482                // Set src (if data-src found)
1483                src
1484                    ? (freshScript.src = src)
1485                    : (src = currScript.src);
1486
1487                const externalScript = !!src && (dataType ? validMimeType(dataType) : true);
1488
1489                // If script has valid "src" attribute
1490                // try loading it sequentially
1491                if (externalScript) {
1492                    // load script sequentially => the next script will not be loaded
1493                    // until the current's script onload event triggers
1494                    freshScript.onload = freshScript.onerror = () => {
1495                        loadScriptsHelper(scripts, ++index);
1496                    };
1497                }
1498
1499                // Replace current "sleeping" script with the new "revived" one
1500                currScript.replaceWith(freshScript);
1501
1502                /**
1503                 * If we managed to get here and src is still set, it means that
1504                 * the script is loading/loaded sequentially so don't go any further
1505                 */
1506                if (externalScript)
1507                    return;
1508            }
1509
1510
1511            // Go to next script right away
1512            loadScriptsHelper(scripts, ++index);
1513        };
1514
1515        loadScriptsHelper(scripts, 0);
1516    };
1517
1518    /**
1519     * Keep track of categories enabled by default (useful when mode==OPT_OUT_MODE)
1520     */
1521    const retrieveEnabledCategoriesAndServices = () => {
1522        const state = globalObj._state;
1523
1524        for (const categoryName of state._allCategoryNames) {
1525            const category = state._allDefinedCategories[categoryName];
1526
1527            if (category.readOnly || (category.enabled && state._userConfig.mode === OPT_OUT_MODE)) {
1528                state._defaultEnabledCategories.push(categoryName);
1529                const services = state._allDefinedServices[categoryName] || {};
1530
1531                for (let serviceName in services) {
1532                    state._acceptedServices[categoryName].push(serviceName);
1533                }
1534            }
1535        }
1536    };
1537
1538    /**
1539     * @typedef {Object} Layout
1540     * @property {string[]} _variants
1541     * @property {string[]} _alignV
1542     * @property {string[]} _alignH
1543     * @property {string} _defaultAlignV
1544     * @property {string} _defaultAlignH
1545     */
1546
1547    /**
1548     * @typedef {Object.<string, Layout>}
1548 Layouts
1549     */
1550
1551    const CLASS_CONSTANTS = {
1552        _top: 'top',
1553        _middle: 'middle',
1554        _bottom: 'bottom',
1555        _left: 'left',
1556        _center: 'center',
1557        _right: 'right',
1558        _inline: 'inline',
1559        _wide: 'wide',
1560        _pmPrefix: 'pm--',
1561        _cmPrefix: 'cm--',
1562        _box: 'box'
1563    };
1564
1565    const alignV = [
1566        CLASS_CONSTANTS._middle,
1567        CLASS_CONSTANTS._top,
1568        CLASS_CONSTANTS._bottom
1569    ];
1570
1571    const alignH = [
1572        CLASS_CONSTANTS._left,
1573        CLASS_CONSTANTS._center,
1574        CLASS_CONSTANTS._right
1575    ];
1576
1577    const ALL_CM_LAYOUTS = {
1578        box: {
1579            _variants: [CLASS_CONSTANTS._wide, CLASS_CONSTANTS._inline],
1580            _alignV: alignV,
1581            _alignH: alignH,
1582            _defaultAlignV: CLASS_CONSTANTS._bottom,
1583            _defaultAlignH: CLASS_CONSTANTS._right
1584        },
1585        cloud: {
1586            _variants: [CLASS_CONSTANTS._inline],
1587            _alignV: alignV,
1588            _alignH: alignH,
1589            _defaultAlignV: CLASS_CONSTANTS._bottom,
1590            _defaultAlignH: CLASS_CONSTANTS._center
1591        },
1592        bar: {
1593            _variants: [CLASS_CONSTANTS._inline],
1594            _alignV: alignV.slice(1),   //remove the first "middle" option
1595            _alignH: [],
1596            _defaultAlignV: CLASS_CONSTANTS._bottom,
1597            _defaultAlignH: ''
1598        }
1599    };
1600
1601    const ALL_PM_LAYOUTS = {
1602        box: {
1603            _variants: [],
1604            _alignV: [],
1605            _alignH: [],
1606            _defaultAlignV: '',
1607            _defaultAlignH: ''
1608        },
1609        bar: {
1610            _variants: [CLASS_CONSTANTS._wide],
1611            _alignV: [],
1612            _alignH: [CLASS_CONSTANTS._left, CLASS_CONSTANTS._right],
1613            _defaultAlignV: '',
1614            _defaultAlignH: CLASS_CONSTANTS._left
1615        }
1616    };
1617
1618    /**
1619     * Add appropriate classes to modals and buttons
1620     * @param {0 | 1} applyToModal
1621     */
1622    const guiManager = (applyToModal) => {
1623        const guiOptions = globalObj._state._userConfig.guiOptions;
1624        const consentModalOptions = guiOptions && guiOptions.consentModal;
1625        const preferencesModalOptions = guiOptions && guiOptions.preferencesModal;
1626
1627        if (applyToModal === 0) {
1628            setLayout(
1629                globalObj._dom._cm,
1630                ALL_CM_LAYOUTS,
1631                consentModalOptions,
1632                CLASS_CONSTANTS._cmPrefix,
1633                CLASS_CONSTANTS._box,
1634                'cm'
1635            );
1636        }
1637
1638        if (applyToModal === 1) {
1639            setLayout(
1640                globalObj._dom._pm,
1641                ALL_PM_LAYOUTS,
1642                preferencesModalOptions,
1643                CLASS_CONSTANTS._pmPrefix,
1644                CLASS_CONSTANTS._box,
1645                'pm'
1646            );
1647        }
1648    };
1649
1650    /**
1651     * Helper function to set the proper layout classes
1652     * @param {HTMLElement} modal
1653     * @param {Layouts} allowedLayoutsObj
1654     * @param {import("../core/global").GuiModalOption} userGuiOptions
1655     * @param {'cm--' | 'pm--'} modalClassPrefix
1656     * @param {string} defaultLayoutName
1657     * @param {'cm' | 'pm'} modalClassName
1658     */
1659    const setLayout = (modal, allowedLayoutsObj, userGuiOptions, modalClassPrefix, defaultLayoutName, modalClassName) => {
1660        /**
1661         * Reset modal classes to default
1662         */
1663        modal.className = modalClassName;
1664
1665        const layout = userGuiOptions && userGuiOptions.layout;
1666        const position = userGuiOptions && userGuiOptions.position;
1667        const flipButtons = userGuiOptions && userGuiOptions.flipButtons;
1668        const equalWeightButtons = !userGuiOptions || userGuiOptions.equalWeightButtons !== false;
1669
1670        const layoutSplit = layout && layout.split(' ') || [];
1671
1672        const layoutName = layoutSplit[0];
1673        const layoutVariant = layoutSplit[1];
1674
1675        const currentLayoutName = layoutName in allowedLayoutsObj
1676            ? layoutName
1677            : defaultLayoutName;
1678
1679        const currentLayout = allowedLayoutsObj[currentLayoutName];
1680        const currentLayoutVariant = elContains(currentLayout._variants, layoutVariant) && layoutVariant;
1681
1682        const positionSplit = position && position.split(' ') || [];
1683        const positionV = positionSplit[0];
1684
1685        const positionH = modalClassPrefix === CLASS_CONSTANTS._pmPrefix
1686            ? positionSplit[0]
1687            : positionSplit[1];
1688
1689        const currentPositionV = elContains(currentLayout._alignV, positionV)
1690            ? positionV
1691            : currentLayout._defaultAlignV;
1692
1693        const currentPositionH = elContains(currentLayout._alignH, positionH)
1694            ? positionH
1695            : currentLayout._defaultAlignH;
1696
1697        const addModalClass = className => {
1698            className && addClass(modal, modalClassPrefix + className);
1699        };
1700
1701        addModalClass(currentLayoutName);
1702        addModalClass(currentLayoutVariant);
1703        addModalClass(currentPositionV);
1704        addModalClass(currentPositionH);
1705        flipButtons && addModalClass('flip');
1706
1707        const secondaryBtnClass = 'btn--secondary';
1708        const btnClassPrefix = modalClassName + '__';
1709        const btnClass = btnClassPrefix + secondaryBtnClass;
1710
1711        /**
1712         * Add classes to buttons
1713         */
1714        if (modalClassName === 'cm') {
1715            const {_cmAcceptNecessaryBtn, _cmCloseIconBtn} = globalObj._dom;
1716
1717            if (_cmAcceptNecessaryBtn) {
1718                equalWeightButtons
1719                    ? removeClass(_cmAcceptNecessaryBtn, btnClass)
1720                    : addClass(_cmAcceptNecessaryBtn, btnClass);
1721            }
1722
1723            if (_cmCloseIconBtn) {
1724                equalWeightButtons
1725                    ? removeClass(_cmCloseIconBtn, btnClass)
1726                    : addClass(_cmCloseIconBtn, btnClass);
1727            }
1728        } else {
1729            const { _pmAcceptNecessaryBtn } =  globalObj._dom;
1730
1731            if (_pmAcceptNecessaryBtn) {
1732                equalWeightButtons
1733                    ? removeClass(_pmAcceptNecessaryBtn, btnClass)
1734                    : addClass(_pmAcceptNecessaryBtn, btnClass);
1735            }
1736        }
1737    };
1738
1739    /**
1740     * @callback CreateMainContainer
1741     */
1742
1743    /**
1744     * Generates preferences modal and appends it to "cc-main" el.
1745     * @param {import("../global").Api} api
1746     * @param {CreateMainContainer} createMainContainer
1747     */
1748    const createPreferencesModal = (api, createMainContainer) => {
1749        const state = globalObj._state;
1750        const dom = globalObj._dom;
1751        const {hide, hidePreferences, acceptCategory} = api;
1752
1753        /**
1754         * @param {string|string[]} [categories]
1755         */
1756        const acceptHelper = (categories) => {
1757            acceptCategory(categories);
1758            hidePreferences();
1759            hide();
1760        };
1761
1762        /**
1763         * @type {import("../global").PreferencesModalOptions}
1764         */
1765        const modalData = state._currentTranslation && state._currentTranslation.preferencesModal;
1766
1767        if (!modalData)
1768            return;
1769
1770        const
1771            titleData = modalData.title,
1772            closeIconLabelData = modalData.closeIconLabel,
1773            acceptAllBtnData = modalData.acceptAllBtn,
1774            acceptNecessaryBtnData = modalData.acceptNecessaryBtn,
1775            savePreferencesBtnData = modalData.savePreferencesBtn,
1776            sectionsData = modalData.sections || [],
1777            createFooter = acceptAllBtnData
1778                || acceptNecessaryBtnData
1779                || savePreferencesBtnData;
1780
1781        if (!dom._pmContainer) {
1782            dom._pmContainer = createNode(DIV_TAG);
1783            addClass(dom._pmContainer, 'pm-wrapper');
1784
1785            const pmOverlay = createNode('div');
1786            addClass(pmOverlay, 'pm-overlay');
1787            appendChild(dom._pmContainer, pmOverlay);
1788
1789            /**
1790             * Hide modal when overlay is clicked
1791             */
1792            addEvent(pmOverlay, CLICK_EVENT, hidePreferences);
1793
1794            // Preferences modal
1795            dom._pm = createNode(DIV_TAG);
1796
1797            addClass(dom._pm, 'pm');
1798            setAttribute(dom._pm, 'role', 'dialog');
1799            setAttribute(dom._pm, ARIA_HIDDEN, true);
1800            setAttribute(dom._pm, 'aria-modal', true);
1801            setAttribute(dom._pm, 'aria-labelledby', 'pm__title');
1802
1803            // Hide preferences on 'esc' key press
1804            addEvent(dom._htmlDom, 'keydown', (event) => {
1805                if (event.keyCode === 27)
1806                    hidePreferences();
1807            }, true);
1808
1809            dom._pmHeader = createNode(DIV_TAG);
1810            addClassPm(dom._pmHeader, 'header');
1811
1812            dom._pmTitle = createNode('h2');
1813            addClassPm(dom._pmTitle, 'title');
1814            dom._pmTitle.id = 'pm__title';
1815
1816            dom._pmCloseBtn = createNode(BUTTON_TAG);
1817            addClassPm(dom._pmCloseBtn, 'close-btn');
1818            setAttribute(dom._pmCloseBtn, 'aria-label', modalData.closeIconLabel || '');
1819            addEvent(dom._pmCloseBtn, CLICK_EVENT, hidePreferences);
1820
1821            dom._pmFocusSpan = createNode('span');
1822            dom._pmFocusSpan.innerHTML = getSvgIcon();
1823            appendChild(dom._pmCloseBtn, dom._pmFocusSpan);
1824
1825            dom._pmBody = createNode(DIV_TAG);
1826            addClassPm(dom._pmBody, 'body');
1827
1828            dom._pmFooter = createNode(DIV_TAG);
1829            addClassPm(dom._pmFooter, 'footer');
1830
1831            var _pmBtnContainer = createNode(DIV_TAG);
1832            addClass(_pmBtnContainer, 'btns');
1833
1834            var _pmBtnGroup1 = createNode(DIV_TAG);
1835            var _pmBtnGroup2 = createNode(DIV_TAG);
1836            addClassPm(_pmBtnGroup1, BTN_GROUP_CLASS);
1837            addClassPm(_pmBtnGroup2, BTN_GROUP_CLASS);
1838
1839            appendChild(dom._pmFooter, _pmBtnGroup1);
1840            appendChild(dom._pmFooter, _pmBtnGroup2);
1841
1842            appendChild(dom._pmHeader, dom._pmTitle);
1843            appendChild(dom._pmHeader, dom._pmCloseBtn);
1844
1845            dom._pmDivTabindex = createNode(DIV_TAG);
1846            setAttribute(dom._pmDivTabindex, 'tabIndex', -1);
1847            appendChild(dom._pm, dom._pmDivTabindex);
1848
1849            appendChild(dom._pm, dom._pmHeader);
1850            appendChild(dom._pm, dom._pmBody);
1851
1852            createFooter && appendChild(dom._pm, dom._pmFooter);
1853
1854            appendChild(dom._pmContainer, dom._pm);
1855        } else {
1856            dom._pmNewBody = createNode(DIV_TAG);
1857            addClassPm(dom._pmNewBody, 'body');
1858        }
1859
1860        if (titleData) {
1861            dom._pmTitle.innerHTML = titleData;
1862            closeIconLabelData && setAttribute(dom._pmCloseBtn, 'aria-label', closeIconLabelData);
1863        }
1864
1865        let sectionToggleContainer;
1866
1867        sectionsData.forEach((section, sectionIndex) => {
1868            const
1869                sTitleData = section.title,
1870                sDescriptionData = section.description,
1871                sLinkedCategory = section.linkedCategory,
1872                sCurrentCategoryObject = sLinkedCategory && state._allDefinedCategories[sLinkedCategory],
1873                sCookieTableData = section.cookieTable,
1874                sCookieTableBody = sCookieTableData && sCookieTableData.body,
1875                sCookieTableCaption = sCookieTableData && sCookieTableData.caption,
1876                sCreateCookieTable = sCookieTableBody && sCookieTableBody.length > 0,
1877                hasToggle = !!sCurrentCategoryObject,
1878
1879                /**
1880                 * @type {Object.<string, import('../global').Service>}
1881                 */
1882                sServices = hasToggle && state._allDefinedServices[sLinkedCategory],
1883                sServiceNames = isObject(sServices) && getKeys(sServices) || [],
1884                sIsExpandableToggle = hasToggle && (!!sDescriptionData || !!sCreateCookieTable || getKeys(sServices).length>0);
1885
1886
1887            // section
1888            var s = createNode(DIV_TAG);
1889            addClassPm(s, 'section');
1890
1891            if (sIsExpandableToggle || sDescriptionData) {
1892                var sDescContainer = createNode(DIV_TAG);
1893                addClassPm(sDescContainer, 'section-desc-wrapper');
1894            }
1895
1896            let nServices = sServiceNames.length;
1897
1898            if (sIsExpandableToggle) {
1899                if (nServices > 0) {
1900
1901                    const servicesContainer = createNode(DIV_TAG);
1902                    addClassPm(servicesContainer, 'section-services');
1903
1904                    for (const serviceName of sServiceNames) {
1905                        const service = sServices[serviceName];
1906                        const serviceLabel = service && service.label || serviceName;
1907                        const serviceDiv = createNode(DIV_TAG);
1908                        const serviceHeader = createNode(DIV_TAG);
1909                        const serviceIconContainer = createNode(DIV_TAG);
1910                        const serviceTitle = createNode(DIV_TAG);
1911
1912                        addClassPm(serviceDiv, 'service');
1913                        addClassPm(serviceTitle, 'service-title');
1914                        addClassPm(serviceHeader, 'service-header');
1915                        addClassPm(serviceIconContainer, 'service-icon');
1916
1917                        const toggleLabel = createToggleLabel(serviceLabel, serviceName, sCurrentCategoryObject, true, sLinkedCategory);
1918
1919                        serviceTitle.innerHTML = serviceLabel;
1920
1921                        appendChild(serviceHeader, serviceIconContainer);
1922                        appendChild(serviceHeader, serviceTitle);
1923                        appendChild(serviceDiv, serviceHeader);
1924                        appendChild(serviceDiv, toggleLabel);
1925                        appendChild(servicesContainer, serviceDiv);
1926                    }
1927
1928                    appendChild(sDescContainer, servicesContainer);
1929                }
1930            }
1931
1932            if (sTitleData) {
1933                var sTitleContainer = createNode(DIV_TAG);
1934
1935                var sTitle = hasToggle
1936                    ? createNode(BUTTON_TAG)
1937                    : createNode(DIV_TAG);
1938
1939                addClassPm(sTitleContainer, 'section-title-wrapper');
1940                addClassPm(sTitle, 'section-title');
1941
1942                sTitle.innerHTML = sTitleData;
1943                appendChild(sTitleContainer, sTitle);
1944
1945                if (hasToggle) {
1946
1947                    /**
1948                     * Arrow icon span
1949                     */
1950                    const sTitleIcon = createNode('span');
1951                    sTitleIcon.innerHTML = getSvgIcon(2, 3.5);
1952                    addClassPm(sTitleIcon, 'section-arrow');
1953                    appendChild(sTitleContainer, sTitleIcon);
1954
1955                    s.className += '--toggle';
1956
1957                    const toggleLabel = createToggleLabel(sTitleData, sLinkedCategory, sCurrentCategoryObject);
1958
1959                    let serviceCounterLabel = modalData.serviceCounterLabel;
1960
1961                    if (nServices > 0 && isString(serviceCounterLabel)) {
1962                        let serviceCounter = createNode('span');
1963
1964                        addClassPm(serviceCounter, 'badge');
1965                        addClassPm(serviceCounter, 'service-counter');
1966                        setAttribute(serviceCounter, ARIA_HIDDEN, true);
1967                        setAttribute(serviceCounter, 'data-servicecounter', nServices);
1968
1969                        if (serviceCounterLabel) {
1970                            serviceCounterLabel = serviceCounterLabel.split('|');
1971
1972                            if (serviceCounterLabel.length > 1 && nServices > 1)
1973                                serviceCounterLabel = serviceCounterLabel[1];
1974                            else
1975                                serviceCounterLabel = serviceCounterLabel[0];
1976
1977                            setAttribute(serviceCounter, 'data-counterlabel', serviceCounterLabel);
1978                        }
1979
1980                        serviceCounter.innerHTML = nServices + (serviceCounterLabel
1981                            ? ' ' + serviceCounterLabel
1982                            : '');
1983
1984                        appendChild(sTitle, serviceCounter);
1985                    }
1986
1987                    if (sIsExpandableToggle) {
1988                        addClassPm(s, 'section--expandable');
1989                        var expandableDivId = sLinkedCategory + '-desc';
1990                        setAttribute(sTitle, 'aria-expanded', false);
1991                        setAttribute(sTitle, 'aria-controls', expandableDivId);
1992                    }
1993
1994                    appendChild(sTitleContainer, toggleLabel);
1995
1996                } else {
1997                    setAttribute(sTitle, 'role', 'heading');
1998                    setAttribute(sTitle, 'aria-level', '3');
1999                }
2000
2001                appendChild(s, sTitleContainer);
2002            }
2003
2004            if (sDescriptionData) {
2005                var sDesc = createNode('p');
2006                addClassPm(sDesc, 'section-desc');
2007                sDesc.innerHTML = sDescriptionData;
2008                appendChild(sDescContainer, sDesc);
2009            }
2010
2011            if (sIsExpandableToggle) {
2012                setAttribute(sDescContainer, ARIA_HIDDEN, 'true');
2013                sDescContainer.id = expandableDivId;
2014
2015                /**
2016                 * On button click handle the following :=> aria-expanded, aria-hidden and act class for current section
2017                 */
2018                ((accordion, section, btn) => {
2019                    addEvent(sTitle, CLICK_EVENT, () => {
2020                        if (!hasClass(section, 'is-expanded')) {
2021                            addClass(section, 'is-expanded');
2022                            setAttribute(btn, 'aria-expanded', 'true');
2023                            setAttribute(accordion, ARIA_HIDDEN, 'false');
2024                        } else {
2025                            removeClass(section, 'is-expanded');
2026                            setAttribute(btn, 'aria-expanded', 'false');
2027                            setAttribute(accordion, ARIA_HIDDEN, 'true');
2028                        }
2029                    });
2030                })(sDescContainer, s, sTitle);
2031
2032
2033                if (sCreateCookieTable) {
2034                    const table = createNode('table');
2035                    const thead = createNode('thead');
2036                    const tbody = createNode('tbody');
2037
2038                    if (sCookieTableCaption) {
2039                        const caption = createNode('caption');
2040                        addClassPm(caption, 'table-caption');
2041                        caption.innerHTML = sCookieTableCaption;
2042                        table.appendChild(caption);
2043                    }
2044
2045                    addClassPm(table, 'section-table');
2046                    addClassPm(thead, 'table-head');
2047                    addClassPm(tbody, 'table-body');
2048
2049                    const headerData = sCookieTableData.headers;
2050                    const tableHeadersKeys = getKeys(headerData);
2051
2052                    /**
2053                     * Create table headers
2054                     */
2055                    const trHeadFragment = dom._document.createDocumentFragment();
2056                    const trHead = createNode('tr');
2057
2058                    for (const headerKey of tableHeadersKeys) {
2059                        const headerValue = headerData[headerKey];
2060                        const th = createNode('th');
2061
2062                        th.id = 'cc__row-' + headerValue + sectionIndex;
2063                        setAttribute(th, 'scope', 'col');
2064                        addClassPm(th, 'table-th');
2065
2066                        th.innerHTML = headerValue;
2067                        appendChild(trHeadFragment, th);
2068                    }
2069
2070                    appendChild(trHead, trHeadFragment);
2071                    appendChild(thead, trHead);
2072
2073                    /**
2074                     * Create table body
2075                     */
2076                    const bodyFragment = dom._document.createDocumentFragment();
2077
2078                    for (const bodyItem of sCookieTableBody) {
2079                        const tr = createNode('tr');
2080                        addClassPm(tr, 'table-tr');
2081
2082                        for (const tdKey of tableHeadersKeys) {
2083                            const tdHeader = headerData[tdKey];
2084                            const tdValue = bodyItem[tdKey];
2085
2086                            const td = createNode('td');
2087                            const tdInner = createNode(DIV_TAG);
2088
2089                            addClassPm(td, 'table-td');
2090                            setAttribute(td, 'data-column', tdHeader);
2091                            setAttribute(td, 'headers', 'cc__row-' + tdHeader + sectionIndex);
2092
2093                            tdInner.insertAdjacentHTML('beforeend', tdValue);
2094
2095                            appendChild(td, tdInner);
2096                            appendChild(tr, td);
2097                        }
2098
2099                        appendChild(bodyFragment, tr);
2100                    }
2101
2102                    appendChild(tbody, bodyFragment);
2103                    appendChild(table, thead);
2104                    appendChild(table, tbody);
2105                    appendChild(sDescContainer, table);
2106                }
2107            }
2108
2109            if (sIsExpandableToggle || sDescriptionData)
2110                appendChild(s, sDescContainer);
2111
2112            const currentBody = dom._pmNewBody || dom._pmBody;
2113
2114            if (hasToggle) {
2115                if (!sectionToggleContainer) {
2116                    sectionToggleContainer = createNode(DIV_TAG);
2117                    addClassPm(sectionToggleContainer, 'section-toggles');
2118                }
2119                sectionToggleContainer.appendChild(s);
2120            } else {
2121                sectionToggleContainer = null;
2122            }
2123
2124            appendChild(currentBody, sectionToggleContainer || s);
2125
2126        });
2127
2128        if (acceptAllBtnData) {
2129            if (!dom._pmAcceptAllBtn) {
2130                dom._pmAcceptAllBtn = createNode(BUTTON_TAG);
2131                addClassPm(dom._pmAcceptAllBtn, 'btn');
2132                setAttribute(dom._pmAcceptAllBtn, DATA_ROLE, 'all');
2133                appendChild(_pmBtnGroup1, dom._pmAcceptAllBtn);
2134                addEvent(dom._pmAcceptAllBtn, CLICK_EVENT, () =>
2135                    acceptHelper('all')
2136                );
2137            }
2138
2139            dom._pmAcceptAllBtn.innerHTML = acceptAllBtnData;
2140        }
2141
2142        if (acceptNecessaryBtnData) {
2143            if (!dom._pmAcceptNecessaryBtn) {
2144                dom._pmAcceptNecessaryBtn = createNode(BUTTON_TAG);
2145                addClassPm(dom._pmAcceptNecessaryBtn, 'btn');
2146                setAttribute(dom._pmAcceptNecessaryBtn, DATA_ROLE, 'necessary');
2147                appendChild(_pmBtnGroup1, dom._pmAcceptNecessaryBtn);
2148                addEvent(dom._pmAcceptNecessaryBtn, CLICK_EVENT, () =>
2149                    acceptHelper([])
2150                );
2151            }
2152
2153            dom._pmAcceptNecessaryBtn.innerHTML = acceptNecessaryBtnData;
2154        }
2155
2156        if (savePreferencesBtnData) {
2157            if (!dom._pmSavePreferencesBtn) {
2158                dom._pmSavePreferencesBtn = createNode(BUTTON_TAG);
2159                addClassPm(dom._pmSavePreferencesBtn, 'btn');
2160                addClassPm(dom._pmSavePreferencesBtn, 'btn--secondary');
2161                setAttribute(dom._pmSavePreferencesBtn, DATA_ROLE, 'save');
2162                appendChild(_pmBtnGroup2, dom._pmSavePreferencesBtn);
2163
2164                addEvent(dom._pmSavePreferencesBtn, CLICK_EVENT, () =>
2165                    acceptHelper()
2166                );
2167            }
2168
2169            dom._pmSavePreferencesBtn.innerHTML = savePreferencesBtnData;
2170        }
2171
2172        if (dom._pmNewBody) {
2173            dom._pm.replaceChild(dom._pmNewBody, dom._pmBody);
2174            dom._pmBody = dom._pmNewBody;
2175        }
2176
2177        guiManager(1);
2178
2179        if (!state._preferencesModalExists) {
2180            state._preferencesModalExists = true;
2181
2182            _log('CookieConsent [HTML] created', PREFERENCES_MODAL_NAME);
2183
2184            fireEvent(globalObj._customEvents._onModalReady, PREFERENCES_MODAL_NAME, dom._pm);
2185            createMainContainer(api);
2186            appendChild(dom._ccMain, dom._pmContainer);
2187            handleFocusTrap(dom._pm);
2188
2189            /**
2190             * Enable transition
2191             */
2192            setTimeout(() => addClass(dom._pmContainer, 'cc--anim'), 100);
2193        }
2194
2195        getModalFocusableData(2);
2196    };
2197
2198    /**
2199     * Generate toggle
2200     * @param {string} label block title
2201     * @param {string} value category/service
2202     * @param {import('../global').Category} sCurrentCategoryObject
2203     * @param {boolean} [isService]
2204     * @param {string} categoryName
2205     */
2206    function createToggleLabel(label, value, sCurrentCategoryObject, isService, categoryName) {
2207        const state = globalObj._state;
2208        const dom = globalObj._dom;
2209
2210        /** @type {HTMLLabelElement} */ const toggleLabel = createNode('label');
2211        /** @type {HTMLInputElement} */ const toggle = createNode('input');
2212        /** @type {HTMLSpanElement} */  const toggleIcon = createNode('span');
2213        /** @type {HTMLSpanElement} */  const toggleIconCircle = createNode('span');
2214        /** @type {HTMLSpanElement} */  const toggleLabelSpan = createNode('span');
2215
2216        // each will contain 2 pseudo-elements to generate 'tick' and 'x' icons
2217        /** @type {HTMLSpanElement} */  const toggleOnIcon = createNode('span');
2218        /** @type {HTMLSpanElement} */  const toggleOffIcon = createNode('span');
2219
2220        toggleOnIcon.innerHTML = getSvgIcon(1, 3);
2221        toggleOffIcon.innerHTML = getSvgIcon(0, 3);
2222
2223        toggle.type = 'checkbox';
2224
2225        addClass(toggleLabel, 'section__toggle-wrapper');
2226        addClass(toggle, 'section__toggle');
2227        addClass(toggleOnIcon, 'toggle__icon-on');
2228        addClass(toggleOffIcon, 'toggle__icon-off');
2229        addClass(toggleIcon, 'toggle__icon');
2230        addClass(toggleIconCircle, 'toggle__icon-circle');
2231        addClass(toggleLabelSpan, 'toggle__label');
2232
2233        setAttribute(toggleIcon, ARIA_HIDDEN, 'true');
2234
2235        if (isService) {
2236            addClass(toggleLabel, 'toggle-service');
2237            setAttribute(toggle, SCRIPT_TAG_SELECTOR, categoryName);
2238
2239            // Save reference to toggles to avoid using document.querySelector later on
2240            dom._serviceCheckboxInputs[categoryName][value] = toggle;
2241        } else {
2242            dom._categoryCheckboxInputs[value] = toggle;
2243        }
2244
2245        if (!isService) {
2246            ((value)=> {
2247                addEvent(toggle, CLICK_EVENT, () => {
2248                    const categoryServicesToggles = dom._serviceCheckboxInputs[value];
2249                    const checked = toggle.checked;
2250                    state._enabledServices[value] = [];
2251
2252                    /**
2253                     * Enable/disable all services
2254                     */
2255                    for (let serviceName in categoryServicesToggles) {
2256                        categoryServicesToggles[serviceName].checked = checked;
2257                        checked && state._enabledServices[value].push(serviceName);
2258                    }
2259                });
2260            })(value);
2261        } else {
2262            ((categoryName) => {
2263                addEvent(toggle, 'change', () => {
2264                    const categoryServicesToggles = dom._serviceCheckboxInputs[categoryName];
2265                    const categoryToggle = dom._categoryCheckboxInputs[categoryName];
2266
2267                    state._enabledServices[categoryName] = [];
2268
2269                    for (let serviceName in categoryServicesToggles) {
2270                        const serviceInput = categoryServicesToggles[serviceName];
2271
2272                        if (serviceInput.checked) {
2273                            state._enabledServices[categoryName].push(serviceInput.value);
2274                        }
2275                    }
2276
2277                    categoryToggle.checked = state._enabledServices[categoryName].length > 0;
2278                });
2279            })(categoryName);
2280
2281        }
2282
2283        toggle.value = value;
2284        toggleLabelSpan.textContent = label.replace(/<.*>.*<\/.*>/gm, '');
2285
2286        appendChild(toggleIconCircle, toggleOffIcon);
2287        appendChild(toggleIconCircle, toggleOnIcon);
2288        appendChild(toggleIcon, toggleIconCircle);
2289
2290        /**
2291         * If consent is valid => retrieve category states from cookie
2292         * Otherwise use states defined in the userConfig. object
2293         */
2294        if (!state._invalidConsent) {
2295            if (isService) {
2296                const enabledServices = state._acceptedServices[categoryName];
2297                toggle.checked = sCurrentCategoryObject.readOnly || elContains(enabledServices, value);
2298            }else if (elContains(state._acceptedCategories, value)) {
2299                toggle.checked = true;
2300            }
2301        }else if (sCurrentCategoryObject.readOnly || sCurrentCategoryObject.enabled) {
2302            toggle.checked = true;
2303        }
2304
2305        /**
2306         * Set toggle as readonly if true (disable checkbox)
2307         */
2308        if (sCurrentCategoryObject.readOnly) {
2309            toggle.disabled = true;
2310        }
2311
2312        appendChild(toggleLabel, toggle);
2313        appendChild(toggleLabel, toggleIcon);
2314        appendChild(toggleLabel, toggleLabelSpan);
2315
2316        return toggleLabel;
2317    }
2318
2319    /**
2320     * @callback CreateMainContainer
2321     */
2322
2323    /**
2324     * @returns {HTMLSpanElement}
2325     */
2326    const createFocusSpan = () => {
2327        const span = createNode('span');
2328
2329        if (!globalObj._dom._focusSpan)
2330            globalObj._dom._focusSpan = span;
2331
2332        return span;
2333    };
2334
2335    /**
2336     * Create consent modal and append it to "cc-main" el.
2337     * @param {import("../global").Api} api
2338     * @param {CreateMainContainer} createMainContainer
2339     */
2340    const createConsentModal = (api, createMainContainer) => {
2341        const state = globalObj._state;
2342        const dom = globalObj._dom;
2343        const {hide, showPreferences, acceptCategory} = api;
2344
2345        /**
2346         * @type {import("../global").ConsentModalOptions}
2347         */
2348        const consentModalData = state._currentTranslation && state._currentTranslation.consentModal;
2349
2350        if (!consentModalData)
2351            return;
2352
2353        const acceptAllBtnData = consentModalData.acceptAllBtn,
2354            acceptNecessaryBtnData = consentModalData.acceptNecessaryBtn,
2355            showPreferencesBtnData = consentModalData.showPreferencesBtn,
2356            closeIconLabelData = consentModalData.closeIconLabel,
2357            footerData = consentModalData.footer,
2358            consentModalLabelValue = consentModalData.label,
2359            consentModalTitleValue = consentModalData.title;
2360
2361        /**
2362         * @param {string|string[]} [categories]
2363         */
2364        const acceptAndHide = (categories) => {
2365            hide();
2366            acceptCategory(categories);
2367        };
2368
2369        // Create modal if it doesn't exist
2370        if (!dom._cmContainer) {
2371            dom._cmContainer = createNode(DIV_TAG);
2372            dom._cm = createNode(DIV_TAG);
2373            dom._cmBody = createNode(DIV_TAG);
2374            dom._cmTexts = createNode(DIV_TAG);
2375            dom._cmBtns = createNode(DIV_TAG);
2376
2377            addClass(dom._cmContainer, 'cm-wrapper');
2378            addClass(dom._cm, 'cm');
2379            addClassCm(dom._cmBody, 'body');
2380            addClassCm(dom._cmTexts, 'texts');
2381            addClassCm(dom._cmBtns, 'btns');
2382
2383            setAttribute(dom._cm, 'role', 'dialog');
2384            setAttribute(dom._cm, 'aria-modal', 'true');
2385            setAttribute(dom._cm, ARIA_HIDDEN, 'false');
2386            setAttribute(dom._cm, 'aria-describedby', 'cm__desc');
2387
2388            if (consentModalLabelValue)
2389                setAttribute(dom._cm, 'aria-label', consentModalLabelValue);
2390            else if (consentModalTitleValue)
2391                setAttribute(dom._cm, 'aria-labelledby', 'cm__title');
2392
2393            const
2394                boxLayout = 'box',
2395                guiOptions = state._userConfig.guiOptions,
2396                consentModalOptions = guiOptions && guiOptions.consentModal,
2397                consentModalLayout = consentModalOptions && consentModalOptions.layout || boxLayout,
2398                isBoxLayout = consentModalLayout.split(' ')[0] === boxLayout;
2399
2400            /**
2401             * Close icon-button (visible only in the 'box' layout)
2402             */
2403            if (consentModalTitleValue && closeIconLabelData && isBoxLayout) {
2404                if (!dom._cmCloseIconBtn) {
2405                    dom._cmCloseIconBtn = createNode(BUTTON_TAG);
2406                    dom._cmCloseIconBtn.innerHTML = getSvgIcon();
2407                    addClassCm(dom._cmCloseIconBtn, 'btn');
2408                    addClassCm(dom._cmCloseIconBtn, 'btn--close');
2409                    addEvent(dom._cmCloseIconBtn, CLICK_EVENT, () => {
2410                        _log('CookieConsent [ACCEPT]: necessary');
2411                        acceptAndHide([]);
2412                    });
2413                    appendChild(dom._cmBody, dom._cmCloseIconBtn);
2414                }
2415
2416                setAttribute(dom._cmCloseIconBtn, 'aria-label', closeIconLabelData);
2417            }
2418
2419            appendChild(dom._cmBody, dom._cmTexts);
2420
2421            if (acceptAllBtnData || acceptNecessaryBtnData || showPreferencesBtnData)
2422                appendChild(dom._cmBody, dom._cmBtns);
2423
2424            dom._cmDivTabindex = createNode(DIV_TAG);
2425            setAttribute(dom._cmDivTabindex, 'tabIndex', -1);
2426            appendChild(dom._cm, dom._cmDivTabindex);
2427
2428            appendChild(dom._cm, dom._cmBody);
2429            appendChild(dom._cmContainer, dom._cm);
2430        }
2431
2432        if (consentModalTitleValue) {
2433            if (!dom._cmTitle) {
2434                dom._cmTitle = createNode('h2');
2435                dom._cmTitle.className = dom._cmTitle.id = 'cm__title';
2436                appendChild(dom._cmTexts, dom._cmTitle);
2437            }
2438
2439            dom._cmTitle.innerHTML = consentModalTitleValue;
2440        }
2441
2442        let description = consentModalData.description;
2443
2444        if (description) {
2445            if (state._revisionEnabled) {
2446                description = description.replace(
2447                    '{{revisionMessage}}',
2448                    state._validRevision
2449                        ? ''
2450                        : consentModalData.revisionMessage || ''
2451                );
2452            }
2453
2454            if (!dom._cmDescription) {
2455                dom._cmDescription = createNode('p');
2456                dom._cmDescription.className = dom._cmDescription.id = 'cm__desc';
2457                appendChild(dom._cmTexts, dom._cmDescription);
2458            }
2459
2460            dom._cmDescription.innerHTML = description;
2461        }
2462
2463        if (acceptAllBtnData) {
2464            if (!dom._cmAcceptAllBtn) {
2465                dom._cmAcceptAllBtn = createNode(BUTTON_TAG);
2466                appendChild(dom._cmAcceptAllBtn, createFocusSpan());
2467                addClassCm(dom._cmAcceptAllBtn, 'btn');
2468                setAttribute(dom._cmAcceptAllBtn, DATA_ROLE, 'all');
2469
2470                addEvent(dom._cmAcceptAllBtn, CLICK_EVENT, () => {
2471                    _log('CookieConsent [ACCEPT]: all');
2472                    acceptAndHide('all');
2473                });
2474            }
2475
2476            dom._cmAcceptAllBtn.firstElementChild.innerHTML = acceptAllBtnData;
2477        }
2478
2479        if (acceptNecessaryBtnData) {
2480            if (!dom._cmAcceptNecessaryBtn) {
2481                dom._cmAcceptNecessaryBtn = createNode(BUTTON_TAG);
2482                appendChild(dom._cmAcceptNecessaryBtn, createFocusSpan());
2483                addClassCm(dom._cmAcceptNecessaryBtn, 'btn');
2484                setAttribute(dom._cmAcceptNecessaryBtn, DATA_ROLE, 'necessary');
2485
2486                addEvent(dom._cmAcceptNecessaryBtn, CLICK_EVENT, () => {
2487                    _log('CookieConsent [ACCEPT]: necessary');
2488                    acceptAndHide([]);
2489                });
2490            }
2491
2492            dom._cmAcceptNecessaryBtn.firstElementChild.innerHTML = acceptNecessaryBtnData;
2493        }
2494
2495        if (showPreferencesBtnData) {
2496            if (!dom._cmShowPreferencesBtn) {
2497                dom._cmShowPreferencesBtn = createNode(BUTTON_TAG);
2498                appendChild(dom._cmShowPreferencesBtn, createFocusSpan());
2499                addClassCm(dom._cmShowPreferencesBtn, 'btn');
2500                addClassCm(dom._cmShowPreferencesBtn, 'btn--secondary');
2501                setAttribute(dom._cmShowPreferencesBtn, DATA_ROLE, 'show');
2502
2503                addEvent(dom._cmShowPreferencesBtn, 'mouseenter', () => {
2504                    if (!state._preferencesModalExists)
2505                        createPreferencesModal(api, createMainContainer);
2506                });
2507                addEvent(dom._cmShowPreferencesBtn, CLICK_EVENT, showPreferences);
2508            }
2509
2510            dom._cmShowPreferencesBtn.firstElementChild.innerHTML = showPreferencesBtnData;
2511        }
2512
2513        if (!dom._cmBtnGroup) {
2514            dom._cmBtnGroup = createNode(DIV_TAG);
2515            addClassCm(dom._cmBtnGroup, BTN_GROUP_CLASS);
2516
2517            acceptAllBtnData && appendChild(dom._cmBtnGroup, dom._cmAcceptAllBtn);
2518            acceptNecessaryBtnData && appendChild(dom._cmBtnGroup, dom._cmAcceptNecessaryBtn);
2519
2520            (acceptAllBtnData || acceptNecessaryBtnData) && appendChild(dom._cmBody, dom._cmBtnGroup);
2521            appendChild(dom._cmBtns, dom._cmBtnGroup);
2522        }
2523
2524        if (dom._cmShowPreferencesBtn && !dom._cmBtnGroup2) {
2525            dom._cmBtnGroup2 = createNode(DIV_TAG);
2526
2527            if ((!dom._cmAcceptNecessaryBtn || !dom._cmAcceptAllBtn)) {
2528                appendChild(dom._cmBtnGroup, dom._cmShowPreferencesBtn);
2529                addClassCm(dom._cmBtnGroup, BTN_GROUP_CLASS + '--uneven');
2530            }else {
2531                addClassCm(dom._cmBtnGroup2, BTN_GROUP_CLASS);
2532                appendChild(dom._cmBtnGroup2, dom._cmShowPreferencesBtn);
2533                appendChild(dom._cmBtns, dom._cmBtnGroup2);
2534            }
2535        }
2536
2537        if (footerData) {
2538            if (!dom._cmFooterLinksGroup) {
2539                let _consentModalFooter = createNode(DIV_TAG);
2540                let _consentModalFooterLinks = createNode(DIV_TAG);
2541                dom._cmFooterLinksGroup = createNode(DIV_TAG);
2542
2543                addClassCm(_consentModalFooter, 'footer');
2544                addClassCm(_consentModalFooterLinks, 'links');
2545                addClassCm(dom._cmFooterLinksGroup, 'link-group');
2546
2547                appendChild(_consentModalFooterLinks, dom._cmFooterLinksGroup);
2548                appendChild(_consentModalFooter, _consentModalFooterLinks);
2549                appendChild(dom._cm, _consentModalFooter);
2550            }
2551
2552            dom._cmFooterLinksGroup.innerHTML = footerData;
2553        }
2554
2555        guiManager(0);
2556
2557        if (!state._consentModalExists) {
2558            state._consentModalExists = true;
2559
2560            _log('CookieConsent [HTML] created', CONSENT_MODAL_NAME);
2561
2562            fireEvent(globalObj._customEvents._onModalReady, CONSENT_MODAL_NAME, dom._cm);
2563            createMainContainer(api);
2564            appendChild(dom._ccMain, dom._cmContainer);
2565            handleFocusTrap(dom._cm);
2566
2567            /**
2568             * Enable transition
2569             */
2570            setTimeout(() => addClass(dom._cmContainer, 'cc--anim'), 100);
2571        }
2572
2573        getModalFocusableData(1);
2574
2575        addDataButtonListeners(dom._cmBody, api, createPreferencesModal, createMainContainer);
2576    };
2577
2578    /**
2579     * Detect the available language. The language autodetection process prioritizes finding translations
2580     * for the complete language code. If translations for the complete code are unavailable, the detection
2581     * mechanism then resorts to searching for the language-only version.
2582     * Works with 'en', 'en_US' and 'en-US'.
2583     *
2584     * @param {string} languageCode - The language code to be detected.
2585     * @returns {?string} The detected language code, or null if not detected.
2586     */
2587    const getAvailableLanguage = (languageCode) => {
2588        if (!isString(languageCode))
2589            return null;
2590
2591        if (languageCode in globalObj._state._allTranslations)
2592            return languageCode;
2593
2594        /**
2595         * @type {string}
2596         */
2597        let language = languageCode.slice(0, 2);
2598
2599        if (language in globalObj._state._allTranslations)
2600            return language;
2601
2602        return null;
2603    };
2604
2605    /**
2606     * Returns the current language code
2607     * @returns {string}
2608     */
2609    const getCurrentLanguageCode = () => {
2610        return globalObj._state._currentLanguageCode || globalObj._state._userConfig.language.default;
2611    };
2612
2613    /**
2614     * Set language code
2615     * @param {string} newLanguageCode
2616     */
2617    const setCurrentLanguageCode = (newLanguageCode) => {
2618        newLanguageCode && (globalObj._state._currentLanguageCode = newLanguageCode);
2619    };
2620
2621    /**
2622     * Get current client's browser language
2623     * returns only the first 2 chars: en-US => en
2624     * @returns {string} language
2625     */
2626    const getBrowserLanguageCode = () => navigator.language;
2627
2628    /**
2629     * Get the lang attribute
2630     * @returns lang attribute
2631     */
2632    const getDocumentLanguageCode = () => document.documentElement.lang;
2633
2634    /**
2635     * Resolve the language to use.
2636     * @returns {string} language code
2637     */
2638    const resolveCurrentLanguageCode = () =>  {
2639        const autoDetect = globalObj._state._userConfig.language.autoDetect;
2640
2641        if (autoDetect) {
2642            _log('CookieConsent [LANG]: autoDetect strategy: "' + autoDetect + '"');
2643
2644            const detectionStrategies = {
2645                browser: getBrowserLanguageCode(),
2646                document: getDocumentLanguageCode()
2647            };
2648
2649            /**
2650             * @type {string}
2651             */
2652            const newLanguageCode = getAvailableLanguage(detectionStrategies[autoDetect]);
2653
2654            if (newLanguageCode)
2655                return newLanguageCode;
2656        }
2657
2658        /**
2659         * Use current language
2660         */
2661        return getCurrentLanguageCode();
2662    };
2663
2664    /**
2665     * Load translation
2666     * @param {string | null} [desiredLanguageCode]
2667     */
2668    const loadTranslationData = async (desiredLanguageCode) => {
2669        const state = globalObj._state;
2670
2671        /**
2672         * @type {string}
2673         */
2674        let currentLanguageCode = getAvailableLanguage(desiredLanguageCode)
2675            ? desiredLanguageCode
2676            : getCurrentLanguageCode();
2677
2678        let currentTranslation = state._allTranslations[currentLanguageCode];
2679
2680        if (!currentTranslation)
2681            return false;
2682
2683        /**
2684         * If translation is a string, fetch the external json file and replace
2685         * the string (path to json file) with the parsed object
2686         */
2687        if (isString(currentTranslation)) {
2688            const fetchedTranslation = await fetchJson(currentTranslation);
2689
2690            if (!fetchedTranslation)
2691                return false;
2692
2693            currentTranslation = fetchedTranslation;
2694        }
2695
2696        state._currentTranslation = currentTranslation;
2697        setCurrentLanguageCode(currentLanguageCode);
2698
2699        _log('CookieConsent [LANG]: set language: "' + currentLanguageCode + '"');
2700
2701        return true;
2702    };
2703
2704    /**
2705     * Toggle RTL class on/off based on current language
2706     */
2707    const handleRtlLanguage = () => {
2708        let rtlLanguages = globalObj._state._userConfig.language.rtl;
2709        let ccMain = globalObj._dom._ccMain;
2710
2711        if (rtlLanguages && ccMain) {
2712            if (!isArray(rtlLanguages))
2713                rtlLanguages = [rtlLanguages];
2714
2715            elContains(rtlLanguages, globalObj._state._currentLanguageCode)
2716                ? addClass(ccMain, 'cc--rtl')
2717                : removeClass(ccMain, 'cc--rtl');
2718        }
2719    };
2720
2721    const createMainContainer = () => {
2722        const dom = globalObj._dom;
2723
2724        if (dom._ccMain) return;
2725
2726        dom._ccMain = createNode(DIV_TAG);
2727        dom._ccMain.id = 'cc-main';
2728        dom._ccMain.setAttribute('data-nosnippet', '');
2729
2730        handleRtlLanguage();
2731
2732        let root = globalObj._state._userConfig.root;
2733
2734        if (root && isString(root))
2735            root = document.querySelector(root);
2736
2737        // Append main container to dom
2738        (root || dom._document.body).appendChild(dom._ccMain);
2739    };
2740
2741    /**
2742     * @param {import('../global').Api} api
2743     */
2744    const generateHtml = (api) => {
2745        addDataButtonListeners(null, api, createPreferencesModal, createMainContainer);
2746
2747        if (globalObj._state._invalidConsent)
2748            createConsentModal(api, createMainContainer);
2749
2750        if (!globalObj._config.lazyHtmlGeneration)
2751            createPreferencesModal(api, createMainContainer);
2752    };
2753
2754    const localStorageManager = {
2755        /**
2756         * @param {string} key
2757         * @param {string} value
2758         */
2759        _setItem: (key, value) => {
2760            safeRun(() => localStorage.setItem(key, value));
2761        },
2762
2763        /**
2764         * @param {string} key
2765         */
2766        _getItem: (key) => safeRun(() => localStorage.getItem(key)) || '',
2767
2768        /**
2769         * @param {string} key
2770         */
2771        _removeItem: (key) => safeRun(() => localStorage.removeItem(key))
2772    };
2773
2774    /**
2775     * @param {boolean} [isFirstConsent]
2776     */
2777    const getCategoriesWithCookies = (isFirstConsent) => {
2778        const state = globalObj._state;
2779
2780        const categoriesToFilter = isFirstConsent
2781            ? state._allCategoryNames
2782            : state._lastChangedCategoryNames;
2783
2784        /**
2785         * Filter out categories with readOnly=true or don't have an autoClear object
2786         */
2787        return categoriesToFilter.filter(categoryName => {
2788            const currentCategoryObject = state._allDefinedCategories[categoryName];
2789
2790            return !!currentCategoryObject
2791                && !currentCategoryObject.readOnly
2792                && !!currentCategoryObject.autoClear;
2793        });
2794    };
2795
2796    /**
2797     * @param {string[]} allCookies
2798     * @param {string} cookieName
2799     */
2800    const findMatchingCookies = (allCookies, cookieName) => {
2801        if (cookieName instanceof RegExp) {
2802            return allCookies.filter(cookie => cookieName.test(cookie));
2803        } else {
2804            const cookieIndex = indexOf(allCookies, cookieName);
2805            return cookieIndex > -1
2806                ? [allCookies[cookieIndex]]
2807                : [];
2808        }
2809    };
2810
2811    /**
2812     * Delete all unused cookies
2813     * @param {boolean}
2813 [isFirstConsent]
2814     */
2815    const autoclearCookiesHelper = (isFirstConsent) => {
2816        const state = globalObj._state;
2817        const allCookiesArray = getAllCookies();
2818        const categoriesToClear = getCategoriesWithCookies(isFirstConsent);
2819
2820        /**
2821         * Clear cookies for each disabled service
2822         */
2823        for (const categoryName in state._lastChangedServices) {
2824            for (const serviceName of state._lastChangedServices[categoryName]) {
2825                const serviceCookies = state._allDefinedServices[categoryName][serviceName].cookies;
2826                const serviceIsDisabled = !elContains(state._acceptedServices[categoryName], serviceName);
2827
2828                if (!serviceIsDisabled || !serviceCookies)
2829                    continue;
2830
2831                for (const cookieItem of serviceCookies) {
2832                    const foundCookies = findMatchingCookies(allCookiesArray, cookieItem.name);
2833                    eraseCookiesHelper(foundCookies, cookieItem.path, cookieItem.domain);
2834                }
2835            }
2836        }
2837
2838        for (const currentCategoryName of categoriesToClear) {
2839            const category = state._allDefinedCategories[currentCategoryName];
2840            const autoClear = category.autoClear;
2841            const autoClearCookies = autoClear && autoClear.cookies || [];
2842
2843            const categoryWasJustChanged = elContains(state._lastChangedCategoryNames, currentCategoryName);
2844            const categoryIsDisabled = !elContains(state._acceptedCategories, currentCategoryName);
2845            const categoryWasJustDisabled = categoryWasJustChanged && categoryIsDisabled;
2846
2847            const shouldClearCookies = isFirstConsent
2848                ? categoryIsDisabled
2849                : categoryWasJustDisabled;
2850
2851            if (!shouldClearCookies)
2852                continue;
2853
2854            if (autoClear.reloadPage && categoryWasJustDisabled)
2855                state._reloadPage = true;
2856
2857            for (const cookieItem of autoClearCookies) {
2858                const foundCookies = findMatchingCookies(allCookiesArray, cookieItem.name);
2859                eraseCookiesHelper(foundCookies, cookieItem.path, cookieItem.domain);
2860            }
2861        }
2862    };
2863
2864    const saveCookiePreferences = () => {
2865        const state = globalObj._state;
2866
2867        /**
2868         * Determine if categories were changed from last state (saved in the cookie)
2869         */
2870        state._lastChangedCategoryNames = globalObj._config.mode === OPT_OUT_MODE && state._invalidConsent
2871            ? arrayDiff(state._defaultEnabledCategories, state._acceptedCategories)
2872            : arrayDiff(state._acceptedCategories, state._savedCookieContent.categories);
2873
2874        let categoriesWereChanged = state._lastChangedCategoryNames.length > 0;
2875        let servicesWereChanged = false;
2876
2877        /**
2878         * Determine if services were changed from last state
2879         */
2880        for (const categoryName of state._allCategoryNames) {
2881            state._lastChangedServices[categoryName] = arrayDiff(
2882                state._acceptedServices[categoryName],
2883                state._lastEnabledServices[categoryName]
2884            );
2885
2886            if (state._lastChangedServices[categoryName].length > 0)
2887                servicesWereChanged = true;
2888        }
2889
2890        //{{START: GUI}}
2891        const categoryToggles = globalObj._dom._categoryCheckboxInputs;
2892
2893        /**
2894         * If the category is accepted check checkbox,
2895         * otherwise uncheck it
2896         */
2897        for (const categoryName in categoryToggles) {
2898            categoryToggles[categoryName].checked = elContains(state._acceptedCategories, categoryName);
2899        }
2900
2901        for (const categoryName of state._allCategoryNames) {
2902            const servicesToggles = globalObj._dom._serviceCheckboxInputs[categoryName];
2903            const enabledServices = state._acceptedServices[categoryName];
2904
2905            for (const serviceName in servicesToggles) {
2906                const serviceInput = servicesToggles[serviceName];
2907                serviceInput.checked = elContains(enabledServices, serviceName);
2908            }
2909        }
2910        //{{END: GUI}}
2911
2912        if (!state._consentTimestamp)
2913            state._consentTimestamp = new Date();
2914
2915        if (!state._consentId)
2916            state._consentId = uuidv4();
2917
2918        state._savedCookieContent = {
2919            categories: deepCopy(state._acceptedCategories),
2920            revision: globalObj._config.revision,
2921            data: state._cookieData,
2922            consentTimestamp: state._consentTimestamp.toISOString(),
2923            consentId: state._consentId,
2924            services: deepCopy(state._acceptedServices)
2925        };
2926
2927        let isFirstConsent = false;
2928        const stateChanged = categoriesWereChanged || servicesWereChanged;
2929
2930        if (state._invalidConsent || stateChanged) {
2931            /**
2932             * Set consent as valid
2933             */
2934            if (state._invalidConsent) {
2935                state._invalidConsent = false;
2936                isFirstConsent = true;
2937            }
2938
2939            state._lastConsentTimestamp = !state._lastConsentTimestamp
2940                ? state._consentTimestamp
2941                : new Date();
2942
2943            state._savedCookieContent.lastConsentTimestamp = state._lastConsentTimestamp.toISOString();
2944
2945            setCookie();
2946
2947            const isAutoClearEnabled = globalObj._config.autoClearCookies;
2948            const shouldClearCookies = isFirstConsent || stateChanged;
2949
2950            if (isAutoClearEnabled && shouldClearCookies)
2951                autoclearCookiesHelper(isFirstConsent);
2952
2953            manageExistingScripts();
2954        }
2955
2956        if (isFirstConsent) {
2957            fireEvent(globalObj._customEvents._onFirstConsent);
2958            fireEvent(globalObj._customEvents._onConsent);
2959
2960            if (globalObj._config.mode === OPT_IN_MODE)
2961                return;
2962        }
2963
2964        if (stateChanged)
2965            fireEvent(globalObj._customEvents._onChange);
2966
2967        /**
2968         * Reload page if needed
2969         */
2970        if (state._reloadPage) {
2971            state._reloadPage = false;
2972            location.reload();
2973        }
2974    };
2975
2976    /**
2977     * Set plugin's cookie
2978     * @param {boolean} [useRemainingExpirationTime]
2979     */
2980    const setCookie = (useRemainingExpirationTime) => {
2981        const { hostname, protocol } = location;
2982        const { name, path, domain, sameSite, useLocalStorage } = globalObj._config.cookie;
2983
2984        const expiresAfterMs = useRemainingExpirationTime
2985            ? getRemainingExpirationTimeMS()
2986            : getExpiresAfterDaysValue()*86400000;
2987
2988        /**
2989         * Expiration date
2990         */
2991        const date = new Date();
2992        date.setTime(date.getTime() + expiresAfterMs);
2993
2994        /**
2995         * Store the expiration date in the cookie (in case localstorage is used)
2996         */
2997
2998        globalObj._state._savedCookieContent.expirationTime = date.getTime();
2999
3000        const value = JSON.stringify(globalObj._state._savedCookieContent);
3001
3002        /**
3003         * Encode value (RFC compliant)
3004         */
3005        const cookieValue = encodeURIComponent(value);
3006
3007        let cookieStr = name + '='
3008            + cookieValue
3009            + (expiresAfterMs !== 0 ? '; expires=' + date.toUTCString() : '')
3010            + '; Path=' + path
3011            + '; SameSite=' + sameSite;
3012
3013        /**
3014         * Set "domain" only if hostname contains a dot (e.g domain.com)
3015         * to ensure that cookie works with 'localhost'
3016         */
3017        if (elContains(hostname, '.'))
3018            cookieStr += '; Domain=' + domain;
3019
3020        if (protocol === 'https:')
3021            cookieStr += '; Secure';
3022
3023        useLocalStorage
3024            ? localStorageManager._setItem(name, value)
3025            : document.cookie = cookieStr;
3026
3027        _log('CookieConsent [SET_COOKIE]: ' + name + ':', globalObj._state._savedCookieContent);
3028    };
3029
3030    /**
3031     * Parse cookie value using JSON.parse
3032     * @param {string} value
3033     */
3034    const parseCookie = (value, skipDecode) => {
3035        /**
3036         * @type {import('../../types').CookieValue}
3037         */
3038        let parsedValue;
3039
3040        parsedValue = safeRun(() => JSON.parse(skipDecode
3041            ? value
3042            : decodeURIComponent(value)
3043        ), true) || {};
3044
3045        return parsedValue;
3046    };
3047
3048    /**
3049     * Delete cookie by name & path
3050     * @param {string[]} cookies Array of cookie names
3051     * @param {string} [customPath]
3052     * @param {string} [customDomain]
3053     */
3054    const eraseCookiesHelper = (cookies, customPath, customDomain) => {
3055        if (cookies.length === 0)
3056            return;
3057
3058        const domain = customDomain || globalObj._config.cookie.domain;
3059        const path = customPath || globalObj._config.cookie.path;
3060        const isWwwSubdomain = domain.slice(0, 4) === 'www.';
3061        const mainDomain = isWwwSubdomain && domain.substring(4);
3062
3063        /**
3064         * Helper function to erase cookie
3065         * @param {string} cookie
3066         * @param {string} [domain]
3067         */
3068        const erase = (cookie, domain) => {
3069            document.cookie = cookie + '='
3070                + '; path=' + path
3071                + (domain ? '; domain=.' + domain : '')
3072                + '; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
3073        };
3074
3075        for (const cookieName of cookies) {
3076
3077            /**
3078             * 2 attempts to erase the cookie:
3079             * - without domain
3080             * - with domain
3081             */
3082            erase(cookieName);
3083            erase(cookieName, domain);
3084
3085            /**
3086             * If domain starts with 'www.',
3087             * also erase the cookie for the
3088             * main domain (without www)
3089             */
3090            if (isWwwSubdomain)
3091                erase(cookieName, mainDomain);
3092
3093            _log('CookieConsent [AUTOCLEAR]: deleting cookie: "' + cookieName + '" path: "' + path + '" domain:', domain);
3094        }
3095    };
3096
3097    /**
3098     * Get plugin cookie
3099     * @param {string} [customName]
3100     */
3101    const getPluginCookie = (customName) => {
3102        const name = customName || globalObj._config.cookie.name;
3103        const useLocalStorage = globalObj._config.cookie.useLocalStorage;
3104        const valueStr = useLocalStorage
3105            ? localStorageManager._getItem(name)
3106            : getSingleCookie(name, true);
3107        return parseCookie(valueStr, useLocalStorage);
3108    };
3109
3110    /**
3111     * Returns the cookie name/value, if it exists
3112     * @param {string} name
3113     * @param {boolean} getValue
3114     * @returns {string}
3115     */
3116    const getSingleCookie = (name, getValue) => {
3117        const found = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
3118
3119        return found
3120            ? (getValue ? found.pop() : name)
3121            : '';
3122    };
3123
3124    /**
3125     * Returns array with all the cookie names
3126     * @param {RegExp} regex
3127     * @returns {string[]}
3128     */
3129    const getAllCookies = (regex) => {
3130        const allCookies = document.cookie.split(/;\s*/);
3131
3132        /**
3133         * @type {string[]}
3134         */
3135        const cookieNames = [];
3136
3137        /**
3138         * Save only the cookie names
3139         */
3140        for (const cookie of allCookies) {
3141            let name = cookie.split('=')[0];
3142
3143            if (regex) {
3144                safeRun(() => {
3145                    regex.test(name) && cookieNames.push(name);
3146                });
3147            } else {
3148                cookieNames.push(name);
3149            }
3150        }
3151
3152        return cookieNames;
3153    };
3154
3155    /**
3156     * Configure CookieConsent
3157     * @param {import("./global").UserConfig} userConfig
3158     */
3159    const setConfig = (userConfig) => {
3160        const { _dom, _config, _state } = globalObj;
3161
3162        const
3163            config = _config,
3164            state = _state,
3165            { cookie } = config,
3166            callbacks = globalObj._callbacks,
3167            userCookieConfig = userConfig.cookie,
3168            userCategories = userConfig.categories,
3169            allCategoryNames = getKeys(userCategories) || [],
3170            nav = navigator,
3171            doc = document;
3172
3173        /**
3174         * Access the 'window' and 'document' objects
3175         * during execution, rather than on import
3176         * (avoid window/document is not defined error)
3177         */
3178        _dom._document = doc;
3179        //{{START: GUI}}
3180        _dom._htmlDom = doc.documentElement;
3181        //{{END: GUI}}
3182        cookie.domain = location.hostname;
3183
3184        /**
3185         * Make user configuration globally available
3186         */
3187        state._userConfig = userConfig;
3188        state._allDefinedCategories = userCategories;
3189        state._allCategoryNames = allCategoryNames;
3190
3191        //{{START: GUI}}
3192        state._allTranslations = userConfig.language.translations;
3193        state._disablePageInteraction = !!userConfig.disablePageInteraction;
3194        //{{END: GUI}}
3195
3196        /**
3197         * Save references to callback functions
3198         */
3199        callbacks._onFirstConsent = userConfig.onFirstConsent;
3200        callbacks._onConsent = userConfig.onConsent;
3201        callbacks._onChange = userConfig.onChange;
3202
3203        //{{START: GUI}}
3204        callbacks._onModalHide = userConfig.onModalHide;
3205        callbacks._onModalShow = userConfig.onModalShow;
3206        callbacks._onModalReady = userConfig.onModalReady;
3207        //{{END: GUI}}
3208
3209        const {
3210            mode,
3211            //{{START: GUI}}
3212            autoShow,
3213            lazyHtmlGeneration,
3214            //{{END: GUI}}
3215            autoClearCookies,
3216            revision,
3217            manageScriptTags,
3218            hideFromBots,
3219        } = userConfig;
3220
3221        if (mode === OPT_OUT_MODE)
3222            config.mode = mode;
3223
3224        if (typeof autoClearCookies === 'boolean')
3225            config.autoClearCookies = autoClearCookies;
3226
3227        if (typeof manageScriptTags === 'boolean')
3228            config.manageScriptTags = manageScriptTags;
3229
3230        if (typeof revision === 'number' && revision >= 0) {
3231            config.revision = revision;
3232            state._revisionEnabled = true;
3233        }
3234
3235        //{{START: GUI}}
3236
3237        if (typeof autoShow === 'boolean')
3238            config.autoShow = autoShow;
3239
3240        if (typeof lazyHtmlGeneration === 'boolean')
3241            config.lazyHtmlGeneration = lazyHtmlGeneration;
3242
3243        //{{END: GUI}}
3244
3245        if (hideFromBots === false)
3246            config.hideFromBots = false;
3247
3248        if (config.hideFromBots === true && nav)
3249            state._botAgentDetected = (nav.userAgent && /bot|crawl|spider|slurp|teoma/i.test(nav.userAgent)) || nav.webdriver;
3250
3251        if (isObject(userCookieConfig))
3252            config.cookie = {...cookie, ...userCookieConfig};
3253
3254        _log('CookieConsent [CONFIG]: configuration:', userConfig);
3255        _log('CookieConsent [CONFIG]: autoClearCookies:', config.autoClearCookies);
3256        _log('CookieConsent [CONFIG]: revision enabled:', state._revisionEnabled);
3257        _log('CookieConsent [CONFIG]: manageScriptTags:', config.manageScriptTags);
3258
3259        fetchCategoriesAndServices(allCategoryNames);
3260        retrieveScriptElements();
3261
3262        //{{START: GUI}}
3263        setCurrentLanguageCode(resolveCurrentLanguageCode());
3264        //{{END: GUI}}
3265    };
3266
3267    /**
3268     * Accept API
3269     * @param {string[]|string} categories - Categories to accept
3270     * @param {string[]} [excludedCategories]
3271     */
3272    const acceptCategory = (categories, excludedCategories = []) => {
3273        resolveEnabledCategories(categories, excludedCategories);
3274        resolveEnabledServices();
3275        saveCookiePreferences();
3276    };
3277
3278    /**
3279     * Returns true if cookie category is accepted
3280     * @param {string} category
3281     */
3282    const acceptedCategory = (category) => {
3283
3284        const acceptedCategories = !globalObj._state._invalidConsent
3285            ? globalObj._state._acceptedCategories
3286            : [];
3287
3288        return elContains(acceptedCategories, category);
3289    };
3290
3291    /**
3292     * Accept one or multiple services under a specific category
3293     * @param {string|string[]} service
3294     * @param {string} category
3295     */
3296    const acceptService = (service, category) => {
3297        const { _allCategoryNames, _allDefinedServices,  } = globalObj._state;
3298
3299        if (
3300            !service
3301            || !category
3302            || !isString(category)
3303            || !elContains(_allCategoryNames, category)
3304            || getKeys(_allDefinedServices[category]).length === 0
3305        ) {
3306            return false;
3307        }
3308
3309        //{{START: GUI}}
3310        updateModalToggles(service, category);
3311        //{{END: GUI}}
3312
3313        acceptCategory();
3314    };
3315
3316    /**
3317     * Returns true if the service in the specified
3318     * category is accepted/enabled
3319     * @param {string} service
3320     * @param {string} category
3321     */
3322    const acceptedService = (service, category) => {
3323        const acceptedServices = !globalObj._state._invalidConsent
3324            ? globalObj._state._acceptedServices[category]
3325            : [];
3326
3327        return elContains(acceptedServices, service);
3328    };
3329
3330    /**
3331     * Returns true if cookie was found and has valid value (not an empty string)
3332     * @param {string} cookieName
3333     */
3334    const validCookie = (cookieName) => getSingleCookie(cookieName, true) !== '';
3335
3336    /**
3337     * Erase cookies API
3338     * @param {(string|RegExp|(string|RegExp)[])} cookies
3339     * @param {string} [path]
3340     * @param {string} [domain]
3341     */
3342    const eraseCookies = (cookies, path, domain) => {
3343        let allCookies = [];
3344
3345        /**
3346         * Add cookie to allCookies array if it exists
3347         * @param {string | RegExp} cookieName
3348         */
3349        const addCookieIfExists = (cookieName) => {
3350            if (isString(cookieName)) {
3351                let name = getSingleCookie(cookieName);
3352                name !== '' && allCookies.push(name);
3353            } else {
3354                allCookies.push(...getAllCookies(cookieName));
3355            }
3356        };
3357
3358        if (isArray(cookies)) {
3359            for (let cookie of cookies) {
3360                addCookieIfExists(cookie);
3361            }
3362        } else {
3363            addCookieIfExists(cookies);
3364        }
3365
3366        eraseCookiesHelper(allCookies, path, domain);
3367    };
3368
3369    //{{START: GUI}}
3370
3371    /**
3372     * Show cookie consent modal
3373     * @param {boolean} [createModal] create modal if it doesn't exist
3374     */
3375    const show = (createModal) => {
3376        const { _dom, _state } = globalObj;
3377
3378        if (_state._consentModalVisible)
3379            return;
3380
3381        if (!_state._consentModalExists) {
3382            if (createModal) {
3383                createConsentModal(miniAPI, createMainContainer);
3384            } else {
3385                return;
3386            }
3387        }
3388
3389        _state._consentModalVisible = true;
3390        _state._lastFocusedElemBeforeModal = getActiveElement();
3391
3392        if (_state._disablePageInteraction)
3393            toggleDisableInteraction(true);
3394
3395        focusAfterTransition(_dom._cm, 1);
3396
3397        addClass(_dom._htmlDom, TOGGLE_CONSENT_MODAL_CLASS);
3398        setAttribute(_dom._cm, ARIA_HIDDEN, 'false');
3399
3400        /**
3401         * Set focus to consentModal
3402         */
3403        setTimeout(() => {
3404            focus(globalObj._dom._cmDivTabindex);
3405        }, 100);
3406
3407        _log('CookieConsent [TOGGLE]: show consentModal');
3408
3409        fireEvent(globalObj._customEvents._onModalShow, CONSENT_MODAL_NAME);
3410    };
3411
3412    /**
3413     * Hide consent modal
3414     */
3415    const hide = () => {
3416        const { _dom, _state, _customEvents } = globalObj;
3417
3418        if (!_state._consentModalVisible)
3419            return;
3420
3421        _state._consentModalVisible = false;
3422
3423        if (_state._disablePageInteraction)
3424            toggleDisableInteraction();
3425
3426        /**
3427         * Fix focus restoration to body with Chrome
3428         */
3429        focus(_dom._focusSpan, true);
3430
3431        removeClass(_dom._htmlDom, TOGGLE_CONSENT_MODAL_CLASS);
3432        setAttribute(_dom._cm, ARIA_HIDDEN, 'true');
3433
3434        /**
3435         * Restore focus to last focused element
3436         */
3437        focus(_state._lastFocusedElemBeforeModal);
3438        _state._lastFocusedElemBeforeModal = null;
3439
3440        _log('CookieConsent [TOGGLE]: hide consentModal');
3441
3442        fireEvent(_customEvents._onModalHide, CONSENT_MODAL_NAME);
3443    };
3444
3445    /**
3446     * Show preferences modal
3447     */
3448    const showPreferences = () => {
3449        const state = globalObj._state;
3450
3451        if (state._preferencesModalVisible)
3452            return;
3453
3454        if (!state._preferencesModalExists)
3455            createPreferencesModal(miniAPI, createMainContainer);
3456
3457        state._preferencesModalVisible = true;
3458
3459        // If there is no consent-modal, keep track of the last focused elem.
3460        if (!state._consentModalVisible) {
3461            state._lastFocusedElemBeforeModal = getActiveElement();
3462        } else {
3463            state._lastFocusedModalElement = getActiveElement();
3464        }
3465
3466        focusAfterTransition(globalObj._dom._pm, 2);
3467
3468        addClass(globalObj._dom._htmlDom, TOGGLE_PREFERENCES_MODAL_CLASS);
3469        setAttribute(globalObj._dom._pm, ARIA_HIDDEN, 'false');
3470
3471        /**
3472         * Set focus to preferencesModal
3473         */
3474        setTimeout(() => {
3475            focus(globalObj._dom._pmDivTabindex);
3476        }, 100);
3477
3478        _log('CookieConsent [TOGGLE]: show preferencesModal');
3479
3480        fireEvent(globalObj._customEvents._onModalShow, PREFERENCES_MODAL_NAME);
3481    };
3482
3483    /**
3484     * https://github.com/orestbida/cookieconsent/issues/481
3485     */
3486    const discardUnsavedPreferences = () => {
3487        const consentIsValid = validConsent();
3488        const allDefinedCategories = globalObj._state._allDefinedCategories;
3489        const categoryInputs = globalObj._dom._categoryCheckboxInputs;
3490        const serviceInputs = globalObj._dom._serviceCheckboxInputs;
3491
3492        /**
3493         * @param {string} category
3494         */
3495        const categoryEnabledByDefault = (category) => elContains(globalObj._state._defaultEnabledCategories, category);
3496
3497        for (const category in categoryInputs) {
3498            const isReadOnly = !!allDefinedCategories[category].readOnly;
3499
3500            categoryInputs[category].checked = isReadOnly || (consentIsValid
3501                ? acceptedCategory(category)
3502                : categoryEnabledByDefault(category)
3503            );
3504
3505            for (const service in serviceInputs[category]) {
3506                serviceInputs[category][service].checked = isReadOnly || (consentIsValid
3507                    ? acceptedService(service, category)
3508                    : categoryEnabledByDefault(category)
3509                );
3510            }
3511        }
3512    };
3513
3514    /**
3515     * Hide preferences modal
3516     */
3517    const hidePreferences = () => {
3518        const state = globalObj._state;
3519
3520        if (!state._preferencesModalVisible)
3521            return;
3522
3523        state._preferencesModalVisible = false;
3524
3525        discardUnsavedPreferences();
3526
3527        /**
3528         * Fix focus restoration to body with Chrome
3529         */
3530        focus(globalObj._dom._pmFocusSpan, true);
3531
3532        removeClass(globalObj._dom._htmlDom, TOGGLE_PREFERENCES_MODAL_CLASS);
3533        setAttribute(globalObj._dom._pm, ARIA_HIDDEN, 'true');
3534
3535        /**
3536         * If consent modal is visible, focus him (instead of page document)
3537         */
3538        if (state._consentModalVisible) {
3539            focus(state._lastFocusedModalElement);
3540            state._lastFocusedModalElement = null;
3541        } else {
3542            /**
3543             * Restore focus to last page element which had focu
3543s before modal opening
3544             */
3545            focus(state._lastFocusedElemBeforeModal);
3546            state._lastFocusedElemBeforeModal = null;
3547        }
3548
3549        _log('CookieConsent [TOGGLE]: hide preferencesModal');
3550
3551        fireEvent(globalObj._customEvents._onModalHide, PREFERENCES_MODAL_NAME);
3552    };
3553
3554    var miniAPI = {
3555        show,
3556        hide,
3557        showPreferences,
3558        hidePreferences,
3559        acceptCategory
3560    };
3561
3562    /**
3563     * Update/change modal's language
3564     * @param {string} lang new language
3565     * @param {boolean} [forceUpdate] update language fields forcefully
3566     * @returns {Promise<boolean>}
3567     */
3568    const setLanguage = async (newLanguageCode, forceUpdate) => {
3569        if (!getAvailableLanguage(newLanguageCode))
3570            return false;
3571
3572        const state = globalObj._state;
3573
3574        /**
3575         * Set language only if it differs from current
3576         */
3577        if (newLanguageCode !== getCurrentLanguageCode() || forceUpdate === true) {
3578
3579            const loaded = await loadTranslationData(newLanguageCode);
3580
3581            if (!loaded)
3582                return false;
3583
3584            setCurrentLanguageCode(newLanguageCode);
3585
3586            if (state._consentModalExists)
3587                createConsentModal(miniAPI, createMainContainer);
3588
3589            if (state._preferencesModalExists)
3590                createPreferencesModal(miniAPI, createMainContainer);
3591
3592            handleRtlLanguage();
3593
3594            return true;
3595        }
3596
3597        return false;
3598    };
3599
3600    //{{END: GUI}}
3601
3602    /**
3603     * Retrieve current user preferences (summary)
3604     * @returns {import("./global").UserPreferences}
3605     */
3606    const getUserPreferences = () => {
3607        const { _acceptType, _acceptedServices } = globalObj._state;
3608        const { accepted, rejected } = getCurrentCategoriesState();
3609
3610        return deepCopy({
3611            acceptType: _acceptType,
3612            acceptedCategories: accepted,
3613            rejectedCategories: rejected,
3614            acceptedServices: _acceptedServices,
3615            rejectedServices: retrieveRejectedServices()
3616        });
3617    };
3618
3619    /**
3620     * Dynamically load script (append to head)
3621     * @param {string} src
3622     * @param {{[key: string]: string}} [attrs] Custom attributes
3623     * @returns {Promise<boolean>} promise
3624     */
3625    const loadScript = (src, attrs) => {
3626        /**
3627         * @type {HTMLScriptElement}
3628         */
3629        let script = document.querySelector('script[src="' + src + '"]');
3630
3631        return new Promise((resolve) => {
3632            if (script)
3633                return resolve(true);
3634
3635            script = createNode('script');
3636
3637            /**
3638             * Add custom attributes
3639             */
3640            if (isObject(attrs)) {
3641                for (const key in attrs) {
3642                    setAttribute(script, key, attrs[key]);
3643                }
3644            }
3645
3646            script.onload = () => resolve(true);
3647            script.onerror = () => {
3648                /**
3649                 * Remove script from dom if error is thrown
3650                 */
3651                script.remove();
3652                resolve(false);
3653            };
3654
3655            script.src = src;
3656
3657            appendChild(document.head, script);
3658        });
3659    };
3660
3661    /**
3662     * Save custom data inside cookie
3663     * @param {{
3664     *  value: any,
3665     *  mode: string
3666     * }} props
3667     * @returns {boolean}
3668     */
3669    const setCookieData = (props) => {
3670        let newData = props.value,
3671            mode = props.mode,
3672            set = false,
3673            cookieData;
3674
3675        const state = globalObj._state;
3676
3677        /**
3678         * If mode is 'update':
3679         * add/update only the specified props.
3680         */
3681        if (mode === 'update') {
3682            state._cookieData = cookieData = getCookie('data');
3683            const sameType = typeof cookieData === typeof newData;
3684
3685            if (sameType && typeof cookieData === 'object') {
3686                !cookieData && (cookieData = {});
3687
3688                for (let prop in newData) {
3689                    if (cookieData[prop] !== newData[prop]) {
3690                        cookieData[prop] = newData[prop];
3691                        set = true;
3692                    }
3693                }
3694            }else if ((sameType || !cookieData) && cookieData !== newData) {
3695                cookieData = newData;
3696                set = true;
3697            }
3698        } else {
3699            cookieData = newData;
3700            set = true;
3701        }
3702
3703        if (set) {
3704            state._cookieData = cookieData;
3705            state._savedCookieContent.data = cookieData;
3706            setCookie(true);
3707        }
3708
3709        return set;
3710    };
3711
3712    /**
3713     * Retrieve data from existing cookie
3714     * @param {string} field
3715     * @param {string} [cookieName]
3716     * @returns {any}
3717     */
3718    const getCookie = (field, cookieName) => {
3719        const cookie = getPluginCookie(cookieName);
3720
3721        return field
3722            ? cookie[field]
3723            : cookie;
3724    };
3725
3726    /**
3727     * Return configuration object or just one of its fields.
3728     * @param {string} field
3729     * @returns {any}
3730     */
3731    const getConfig = (field) => {
3732        const config = globalObj._config;
3733        const userConfig = globalObj._state._userConfig;
3734
3735        return field
3736            ? config[field] || userConfig[field]
3737            : {...config, ...userConfig, cookie:{...config.cookie}};
3738    };
3739
3740    /**
3741     * Returns true if consent is valid
3742     * @returns {boolean}
3743     */
3744    const validConsent = () => !globalObj._state._invalidConsent;
3745
3746    const retrieveState = () => {
3747        const state = globalObj._state;
3748        const config = globalObj._config;
3749
3750        const cookieValue = getPluginCookie();
3751
3752        const {
3753            categories,
3754            services,
3755            consentId,
3756            consentTimestamp,
3757            lastConsentTimestamp,
3758            data,
3759            revision
3760        } = cookieValue;
3761
3762        const validCategories = isArray(categories);
3763
3764        state._savedCookieContent = cookieValue;
3765        state._consentId = consentId;
3766
3767        // If "_consentId" is present => assume that consent was previously given
3768        const validConsentId = !!consentId && isString(consentId);
3769
3770        // Retrieve "_consentTimestamp"
3771        state._consentTimestamp = consentTimestamp;
3772        state._consentTimestamp && (state._consentTimestamp = new Date(consentTimestamp));
3773
3774        // Retrieve "_lastConsentTimestamp"
3775        state._lastConsentTimestamp = lastConsentTimestamp;
3776        state._lastConsentTimestamp && (state._lastConsentTimestamp = new Date(lastConsentTimestamp));
3777
3778        // Retrieve "data"
3779        state._cookieData = typeof data !== 'undefined'
3780            ? data
3781            : null;
3782
3783        // If revision is enabled and current value !== saved value inside the cookie => revision is not valid
3784        if (state._revisionEnabled && validConsentId && revision !== config.revision)
3785            state._validRevision = false;
3786
3787        state._invalidConsent = !validConsentId
3788            || !state._validRevision
3789            || !state._consentTimestamp
3790            || !state._lastConsentTimestamp
3791            || !validCategories;
3792
3793        /**
3794         * If localStorage is enabled, also check the stored `expirationTime`
3795         */
3796        if (config.cookie.useLocalStorage && !state._invalidConsent) {
3797            state._invalidConsent = new Date().getTime() > (cookieValue.expirationTime || 0);
3798            state._invalidConsent && (localStorageManager._removeItem(config.cookie.name));
3799        }
3800
3801        _log('CookieConsent [STATUS] valid consent:', !state._invalidConsent);
3802        retrieveEnabledCategoriesAndServices();
3803
3804        /**
3805         * Retrieve last accepted categories from cookie
3806         * and calculate acceptType
3807         */
3808        if (!state._invalidConsent) {
3809            state._acceptedServices = {
3810                ...state._acceptedServices,
3811                ...services
3812            };
3813
3814            setAcceptedCategories([
3815                ...state._readOnlyCategories,
3816                ...categories
3817            ]);
3818        } else {
3819            if (config.mode === OPT_OUT_MODE) {
3820                state._acceptedCategories = [
3821                    ...state._defaultEnabledCategories
3822                ];
3823            }
3824        }
3825
3826        state._enabledServices = {...state._acceptedServices};
3827    };
3828
3829    /**
3830     * Will run once and only if modals do not exist.
3831     * @param {import("./global").UserConfig} userConfig
3832     */
3833    const run = async (userConfig) => {
3834        const {
3835            _state,
3836            _config,
3837            _customEvents
3838        } = globalObj;
3839
3840        const win = window;
3841
3842        if (!win._ccRun) {
3843            win._ccRun = true;
3844
3845            setConfig(userConfig);
3846
3847            if (_state._botAgentDetected)
3848                return;
3849
3850            retrieveState();
3851
3852            const consentIsValid = validConsent();
3853
3854            //{{START: GUI}}
3855            const translationLoaded = await loadTranslationData();
3856
3857            if (!translationLoaded)
3858                return false;
3859
3860            generateHtml(miniAPI);
3861
3862            if (_config.autoShow && !consentIsValid)
3863                show(true);
3864            //{{END: GUI}}
3865
3866            if (consentIsValid) {
3867                manageExistingScripts();
3868                return fireEvent(_customEvents._onConsent);
3869            }
3870
3871            if (_config.mode === OPT_OUT_MODE)
3872                manageExistingScripts(_state._defaultEnabledCategories);
3873        }
3874    };
3875
3876    /**
3877     * Reset cookieconsent.
3878     * @param {boolean} [deleteCookie] Delete plugin's cookie
3879     */
3880    const reset = (deleteCookie) => {
3881        //{{START: GUI}}
3882        const { _ccMain, _htmlDom } = globalObj._dom;
3883        //{{END: GUI}}
3884
3885        const { name, path, domain, useLocalStorage } = globalObj._config.cookie;
3886
3887        if (deleteCookie) {
3888            useLocalStorage
3889                ? localStorageManager._removeItem(name)
3890                : eraseCookies(name, path, domain);
3891        }
3892
3893        /**
3894         * Remove data-cc event listeners
3895         */
3896        for (const {_element, _event, _listener} of globalObj._state._dataEventListeners) {
3897            _element.removeEventListener(_event, _listener);
3898        }
3899
3900        //{{START: GUI}}
3901        /**
3902         * Remove main container from DOM
3903         */
3904        _ccMain && _ccMain.remove();
3905
3906        /**
3907         * Remove any remaining classes
3908         */
3909        _htmlDom && _htmlDom.classList.remove(
3910            TOGGLE_DISABLE_INTERACTION_CLASS,
3911            TOGGLE_PREFERENCES_MODAL_CLASS,
3912            TOGGLE_CONSENT_MODAL_CLASS
3913        );
3914        //{{END: GUI}}
3915
3916        const newGlobal = new GlobalState();
3917
3918        /**
3919         * Reset all global state props.
3920         */
3921        for (const key in globalObj) {
3922            globalObj[key] = newGlobal[key];
3923        }
3924
3925        window._ccRun = false;
3926    };
3927
3928    exports.acceptCategory = acceptCategory;
3929    exports.acceptService = acceptService;
3930    exports.acceptedCategory = acceptedCategory;
3931    exports.acceptedService = acceptedService;
3932    exports.eraseCookies = eraseCookies;
3933    exports.getConfig = getConfig;
3934    exports.getCookie = getCookie;
3935    exports.getUserPreferences = getUserPreferences;
3936    exports.hide = hide;
3937    exports.hidePreferences = hidePreferences;
3938    exports.loadScript = loadScript;
3939    exports.reset = reset;
3940    exports.run = run;
3941    exports.setCookieData = setCookieData;
3942    exports.setLanguage = setLanguage;
3943    exports.show = show;
3944    exports.showPreferences = showPreferences;
3945    exports.validConsent = validConsent;
3946    exports.validCookie = validCookie;
3947
3948}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.