{"version":3,"file":"all.min.js","sources":["../../node_modules/@progress-wad/data-attr-parser/index.mjs","../../src/js/modules/dataloader.js","../../src/js/modules/support-css-property.js","../../src/js/modules/section-banner.js","../../src/js/modules/section-patch.js","../../src/js/modules/jquery.tabs.js","../../src/js/modules/jquery.accordion.js","../../src/js/modules/site-specific.js","../../src/js/modules/contextual-nav.js","../../src/js/modules/fragments.js","../../src/js/modules/toggle-self.js","../../src/js/modules/toggle.js","../../node_modules/@progress-wad/social-share/src/social-share.js","../../src/js/modules/interactive.js","../../src/js/modules/social-share.js","../../src/js/modules/outline-nav.js","../../src/js/modules/forms-floating-labels.js","../../src/js/modules/replacement-query-parameter.js","../../node_modules/@progress-wad/litebox/src/litebox.js","../../node_modules/@progress-wad/siema-slider/src/siema.js","../../node_modules/@progress-wad/sticky-element/index.js","../../node_modules/@progress-wad/site-search/build/index.es6.js","../../node_modules/@progress-wad/site-search/build/init.es6.js","../../node_modules/@progress-wad/youtube-lite-embed/dist/youtube-picture-in-picture.mjs","../../node_modules/@progress-wad/youtube-lite-embed/dist/youtube-lite-embed.mjs","../../node_modules/@progress-wad/youtube-lite-embed/dist/index.mjs","../../node_modules/@progress-wad/select-ui/src/select.js","../../node_modules/@progress-wad/multi-select/src/multi-select-item.js","../../node_modules/@progress-wad/multi-select/src/multi-select.js","../../src/js/all.mjs"],"sourcesContent":["const rejson = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/;\n\nexport function isNumber(num) {\n var number = +num;\n\n if ((number - number) !== 0) {\n // Discard Infinity and NaN\n return false;\n }\n\n if (number === num) {\n return true;\n }\n\n if (typeof num === 'string') {\n // String parsed, both a non-empty whitespace string and an empty string\n // will have been coerced to 0. If 0 trim the string and see if its empty.\n if (number === 0 && num.trim() === '') {\n return false;\n }\n return true;\n }\n return false;\n};\n\nexport function hyphenToCamelCase(hyphen) {\n return hyphen.replace(/-([a-z])/g, function(match) {\n return match[1].toUpperCase();\n });\n}\n\nexport function camelCaseToHyphen(camelCase) {\n return camelCase.replace(/[A-Z]/g, '-$1').toLowerCase();\n}\n\nexport function attributeToCamelCase(attribute) {\n return hyphenToCamelCase(attribute.replace(/^(data-)?(.*)/, '$2'));\n}\n\nexport function camelCaseToAttribute(camelCase) {\n return 'data-' + camelCaseToHyphen(camelCase);\n}\n\nexport function stringToCamelCase(string) {\n return string.replace(/(?:^\\w|[A-Z]|\\b\\w)/g, function(letter, index) {\n return index == 0 ? letter.toLowerCase() : letter.toUpperCase();\n }).replace(/\\s+/g, '');\n}\n\nexport class DataAttrParser {\n /**\n * @param {DOMStringMap} dataset\n * @param {Array} _prefixes\n */\n static parse(dataset, _prefixes = []) {\n let result = {};\n const keys = Object.keys(dataset);\n const grouping = _prefixes.length;\n const prefixes = _prefixes.map(hyphenToCamelCase);\n\n for (let i = 0; i < keys.length; i++) {\n let key = keys[i];\n let value = dataset[key];\n\n if (value === 'true') {\n result[key] = true;\n continue;\n }\n\n if (value === 'false') {\n result[key] = false;\n continue;\n }\n\n if (isNumber(value)) {\n result[key] = Number(value);\n continue;\n }\n\n if (value === 'null') {\n result[key] = null;\n continue;\n }\n\n if (rejson.test(value)) {\n result[key] = JSON.parse(value);\n continue;\n }\n\n // @String\n result[key] = value;\n }\n\n // Group result object\n prefixes.forEach(prefix => {\n let subResult = {};\n\n Object.keys(result)\n .filter(key => key.startsWith(prefix))\n .forEach(key => {\n let slicedKey = stringToCamelCase(key.replace(prefix, ''));\n if (slicedKey === '') slicedKey = prefix;\n subResult[slicedKey] = result[key];\n delete result[key];\n });\n\n result[prefix] = subResult;\n });\n\n return result;\n }\n}\n","import $ from 'jquery';\nimport { DataAttrParser } from '@progress-wad/data-attr-parser';\n\n/**\n * Init prgs/tlrk jquery plugins from data attrs\n *\n * @param {String} selector - data attr name - example \"data-tlrk-plugin\" or \"data-prgs-plugin\"\n */\nfunction init(selector) {\n const elements = Array.from(document.querySelectorAll(`[${selector}]`));\n\n elements.forEach(element => {\n const $element = $(element);\n const plugins = element.getAttribute(selector).split(' ');\n\n plugins.forEach(plugin => {\n if ($.isFunction($.fn[plugin])) {\n const group = plugin.toLowerCase();\n const options = DataAttrParser.parse(element.dataset, [group]);\n $element[plugin](options[group]);\n }\n });\n });\n}\n\n$(document).ready(() => {\n init('data-tlrk-plugin');\n init('data-prgs-plugin');\n init('data-ipswitch-plugin');\n});\n","import $ from 'jquery';\n\n/* eslint-disable */\n$.support.cssProperty = (function supportCssPropertyFactory() {\n return function supportCssProperty(a, e) {\n const d = (document.body || document.documentElement).style;\n let c;\n\n if (typeof d === 'undefined') {\n return !1;\n }\n\n if (typeof d[a] === 'string') {\n return e ? a : !0;\n }\n\n const b = 'Moz Webkit Khtml O ms Icab'.split(' ');\n\n for (a = a.charAt(0).toUpperCase() + a.substr(1), c = 0; c < b.length; c++) {\n if (typeof d[b[c] + a] === 'string') {\n return e ? b[c] + a : !0;\n }\n }\n };\n})();\n/* eslint-enable */\n","import $ from 'jquery';\n\n(() => {\n if (!$.support.cssProperty('object-fit')) {\n $('.Section-bg img, img.-contain').each(function each() {\n var $this = $(this);\n const $container = $this.parent();\n const imgUrl = $this.prop('src');\n const imgClasses = $this.attr('class');\n if (imgUrl) {\n if ($this.hasClass('-contain')) {\n $container\n .css('backgroundImage', 'url(' + imgUrl + ')')\n .addClass('compat-object-contain');\n } else {\n $container\n .css('backgroundImage', 'url(' + imgUrl + ')')\n .addClass('compat-object-fit')\n .addClass(imgClasses);\n // fix for ie's missing background color behind the banner image\n if ($container.hasClass('-bg-black')) {\n $container.parent().addClass('-tint-black');\n }\n }\n }\n });\n }\n})();\n","(() => {\n const customPaddingClass = 'has-custom-padding';\n\n function findCousin(elements, nextOrPrev, selector) {\n // Reference the last of the selected elements\n const reference = elements[elements.length - 1];\n\n // Set of all elements matching the selector\n const set = Array.from(document.querySelectorAll(selector));\n\n // Finds the position of the reference element in the set of all elements\n const pos = set.indexOf(reference);\n\n // If the element does not match the selector for cousin elements return null\n if (set.length === (pos - 1)) return null;\n\n // Return the next or previous cousin to the reference\n return set[pos + (nextOrPrev === 'next' ? 1 : (-1))];\n }\n\n // do not execute in edit mode\n const url = window.location.href.toLowerCase();\n\n if (url.indexOf('/action/edit') > 0 || url.indexOf('/action/inedit') > 0) {\n return;\n }\n\n let resizing = false;\n\n function calculateOffsets() {\n document.querySelectorAll('.Section--patch').forEach(patch => {\n const patchHalfHeight = `${Math.ceil(patch.offsetHeight / 2)}px`;\n\n if (document.body.offsetWidth > 1460) {\n const containers = patch.querySelectorAll('.container');\n const prevCousin = findCousin(containers, 'prev', '.Section .container, .IP-container, .WUG-container');\n const nextCousin = findCousin(containers, 'next', '.Section .container, .IP-container, .WUG-container');\n\n patch.classList.add('is-active');\n\n patch.setAttribute('style', `margin-top: -${patchHalfHeight}; margin-bottom: -${patchHalfHeight}`);\n\n if (prevCousin) {\n prevCousin.classList.add(customPaddingClass);\n prevCousin.style.paddingBottom = patchHalfHeight;\n }\n\n if (nextCousin) {\n nextCousin.classList.add(customPaddingClass);\n nextCousin.style.paddingTop = patchHalfHeight;\n }\n }\n });\n }\n\n if (!document.querySelector('.sfPageEditor') && document.querySelector('.Section--patch')) {\n window.addEventListener('resize', () => {\n resizing = true;\n });\n\n window.setInterval(() => {\n if (resizing) {\n calculateOffsets();\n resizing = false;\n }\n }, 500);\n\n calculateOffsets();\n }\n})();\n","import $ from 'jquery';\n\nconst pluginName = 'tabs';\n\nconst defaults = {\n wrapperSelector: '.ipswitch-tabs', // jquery selector for the wrapper of all tabs\n activeTab: 1, // which tab is active by default\n scrollToTabs: true, // whether to scroll page to the tab-area on click of any tab-link\n automated: false, // auto rotation of tabs (example usage: testimonials)\n automatedTimer: 5000, // auto rotation delay between each tab\n automatedFreeze: true, // freeze auto rotation when mouseover a tab\n setAnchors: true, // set hash, on click of tabs navigation (you can disable for cases like testimonials)\n animateHeight: false, // dynamically animate the height of the wrapper while switching between tabs\n enableSwipe: true,\n\n // how to treat the event\n preventDefault: true,\n stopPropagation: false\n};\n\nfunction Plugin(element, options) {\n this.$element = $(element);\n this.tabLinks = this.$element.find('a');\n this.options = $.extend({}, defaults, options);\n this.innerWrap = $(this.options.wrapperSelector);\n this.tabs = this.innerWrap.children();\n this.tabsCount = this.tabs.length;\n this.activeTab = parseInt(this.options.activeTab, 10) || 1;\n this.automation = false;\n\n // change tabs on click\n const $this = this;\n this.tabLinks.on('click', e => {\n if (typeof $this.automation !== 'undefined' && $this.automation !== false) {\n $this._AutomationPause();\n }\n $this.show(e, null);\n });\n\n // check URL hash\n this._hashcheck();\n\n // execute initial tab to be shown\n if (this.activeTab && this.activeTab > 0) {\n this.show(null, this.activeTab - 1);\n }\n\n // automate rotation of tabs if needed\n if (this.options.automated) {\n this._autoRotate();\n }\n\n // swipe support\n if (this.options.enableSwipe && typeof window.ontouchstart !== 'undefined') {\n this.$element.closest('.Section').css('overflow-x', 'hidden');\n this.innerWrap[0].addEventListener('touchstart', $.proxy(this._touchstart, this));\n this.innerWrap[0].addEventListener('touchend', $.proxy(this._touchend, this));\n this.innerWrap[0].addEventListener('touchcancel', $.proxy(this._touchend, this));\n this.innerWrap[0].addEventListener('touchmove', $.proxy(this._touchmove, this));\n }\n\n let resizing = false;\n $(window).on('resize', () => {\n resizing = true;\n });\n\n window.setInterval(() => {\n if (resizing) {\n resizing = false;\n $this.innerWrap.height($this.tabs.filter('.is-active').height());\n }\n }, 800);\n}\n\nPlugin.prototype = {\n _translateX(x) {\n this.innerWrap[0].style.transform = `translateX(${-x}%)`;\n // stop automation once swiping has occured\n this._AutomationPause();\n },\n _resetPosition() {\n this._translateX(0);\n },\n _AutomationPause() {\n if (this.automation) {\n window.clearInterval(this.automation);\n this.automation = false;\n }\n },\n _AutomationPlay() {\n if (!this.automation) {\n this._initAutomation();\n }\n },\n _initAutomation() {\n if (this.automation) {\n window.clearInterval(this.automation);\n }\n const $plugin = this;\n this.automation = window.setInterval(() => {\n $plugin._showNextTab();\n }, this.options.automatedTimer);\n },\n _showNextTab() {\n const selected = this.tabLinks.filter('.is-active').eq(0).index();\n const next = (selected === this.tabLinks.length - 1) ? 0 : selected + 1;\n this.show(null, next);\n },\n _showPrevTab() {\n const selected = this.tabLinks.filter('.is-active').eq(0).index();\n const prev = (selected === 0) ? this.tabLinks.length - 1 : selected - 1;\n this.show(null, prev);\n },\n _autoRotate() {\n // start auto animation\n this._initAutomation();\n\n const $plugin = this;\n\n // set mouse hover events if needed\n if (this.options.automatedFreeze) {\n const $section = this.innerWrap.closest('.Section');\n const $hoverArea = $section.length ? $section : this.innerWrap;\n\n $hoverArea.on('mouseenter', () => {\n $plugin._AutomationPause();\n });\n $hoverArea.on('mouseleave', () => {\n $plugin._AutomationPlay();\n });\n }\n\n // pause automation when plugin is not into view\n let scrolling = false;\n function scrollHandler() {\n const tabsData = $plugin._calcPosition();\n const tabsTop = tabsData[0];\n const tabsBottom = tabsData[1];\n const vTop = $(window).scrollTop();\n const vBottom = vTop + $(window).height();\n if (tabsBottom > vTop && tabsTop < vBottom) {\n // into view\n $plugin._AutomationPlay();\n }\n else {\n // not into view\n $plugin._AutomationPause();\n }\n }\n\n $(window).on('scroll', () => {\n scrolling = true;\n });\n\n window.setInterval(() => {\n if (scrolling) {\n scrolling = false;\n scrollHandler();\n }\n }, 250);\n },\n _hashcheck() {\n // check for URL hash, to override the plugin option for a default starting tab\n let { hash } = document.location;\n const plugin = this;\n\n if (hash) {\n // if the hash provided matches a tab - set it as active\n hash = plugin.tabLinks.filter(`a[href='${hash}']`).parent().index() + 1;\n plugin.activeTab = (hash >= 1) ? hash : plugin.activeTab;\n\n window.setTimeout(() => {\n // scroll to tab if there is a hash in the url:\n if (hash >= 1) {\n plugin._scrollToItself();\n }\n }, 250);\n }\n },\n _historyEdit(newHash) {\n if (typeof newHash === 'undefined' || newHash === null) {\n // eslint-disable-next-line\n newHash = '';\n }\n\n if (newHash.length < 1) {\n if (window.history.pushState) {\n window.history.pushState('', document.title, ' ');\n }\n else {\n window.location.hash = '';\n }\n return;\n }\n\n // eslint-disable-next-line\n newHash = new RegExp('#').test(newHash) ? newHash : `#${newHash}`;\n if (window.history.replaceState) {\n window.history.replaceState('', document.title, newHash);\n }\n else {\n window.location.replace(newHash);\n }\n },\n _calcPosition() {\n const tabsPosition = (this.innerWrap.closest('.Section').length > 0) ? this.innerWrap.closest('.Section') : this.innerWrap;\n const\n tabsTop = tabsPosition.offset().top;\n return [tabsTop, tabsTop + tabsPosition.height()];\n },\n _scrollToItself() {\n const tabsData = this._calcPosition();\n const tabsPosition = tabsData[0];\n $('html,body').animate({ scrollTop: tabsPosition }, 400);\n },\n _touchstart(e) {\n this.scrolling = undefined;\n this.deltaX = 0;\n this.startX = e.touches[0].pageX;\n this.startY = e.touches[0].pageY;\n this.stripeWidth = this.innerWrap[0].scrollWidth;\n e.stopPropagation();\n },\n _touchend() {\n if (this.scrolling) return;\n\n if (Math.abs(this.deltaX) > this.stripeWidth / 100) {\n if (this.deltaX < 0) {\n this._showPrevTab();\n } else if (this.deltaX > 0) {\n this._showNextTab();\n } else {\n this._resetPosition();\n }\n } else {\n this._resetPosition();\n }\n\n this.deltaX = null;\n this.startX = null;\n this.startY = null;\n },\n _touchmove(e) {\n const changeX = this.startX - e.touches[0].pageX;\n const changeY = e.touches[0].pageY - this.startY;\n\n // if pinch abort\n if (e.touches.length > 1 || (e.scale && e.scale !== 1)) return;\n\n this.deltaX = (changeX / this.stripeWidth) * 100;\n\n if (typeof this.scrolling === 'undefined') {\n this.scrolling = Math.abs(changeX) < Math.abs(changeY);\n }\n\n if (this.scrolling) return;\n this._translateX(this.deltaX);\n\n e.preventDefault();\n },\n show(e, eq) {\n this.tabLinks.removeClass('is-active').removeAttr('aria-label');\n\n const { options } = this;\n const tabClicked = (e) ? $(e.target).addClass('is-active').attr('aria-label', 'selected') : this.tabLinks.eq(eq).addClass('is-active').attr('aria-label', 'selected');\n const tabIndex = eq || this.tabLinks.index(tabClicked);\n if (e) {\n // this is a click event\n if (options.preventDefault) {\n e.preventDefault();\n }\n if (options.stopPropagation) {\n e.stopPropagation();\n }\n\n if (options.scrollToTabs) {\n this._scrollToItself();\n }\n if (options.setAnchors) {\n this._historyEdit(tabClicked.attr('href'));\n }\n }\n\n this.tabs.removeClass('is-active').eq(tabIndex).addClass('is-active');\n\n if (options.animateHeight) {\n window.setTimeout(() => {\n this.innerWrap.animate({ height: this.tabs.filter('.is-active').height() }, 350);\n }, 4);\n }\n this._resetPosition();\n } // end of show\n};\n\n$.fn[pluginName] = function tabsPlugin(options) {\n return this.each(function each() {\n if (!$.data(this, `plugin_${pluginName}`)) {\n $.data(this, `plugin_${pluginName}`, new Plugin(this, options));\n }\n });\n};\n","import $ from 'jquery';\n\n$(() => {\n const $stickyNav = $('#js-wug-nav');\n const $contextualNav = $('.ContextualNav')\n const $page = $('html, body');\n const isFAQ = $('.Accordion').hasClass('Accordion--FAQ');\n const toggles = $('.Accordion-toggle');\n const contents = $('.Accordion-inner');\n\n\n contents.each((i, el) => {\n $(el).not(':visible').attr('aria-hidden', 'true');\n });\n toggles.each((i, el) => {\n if ( $(el).hasClass('is-active') || $(el).parent().hasClass('is-active') ) {\n $(el).attr({\n 'aria-expanded': 'true',\n 'aria-label': 'Expanded'\n });\n }\n else {\n $(el).attr({\n 'aria-expanded': 'false',\n 'aria-label': 'Collapsed'\n });\n }\n });\n\n toggles.on('click', function onClick(e) {\n e.preventDefault();\n\n const $this = $(this);\n const $parent = $this.parent();\n const $switchContent = $this.closest('.Accordion-wrap').find('.Accordion-switch');\n\n // FAQs scroll to item\n // this behavior is not needed on Resources page\n if ( isFAQ ) {\n if ( !$parent.hasClass('is-active') ) {\n $parent\n .addClass('is-active')\n .siblings()\n .removeClass('is-active')\n .find('.Accordion-inner')\n .stop()\n .slideUp('fast');\n\n $parent.siblings().find('.Accordion-toggle').attr({\n 'aria-expanded': 'false',\n 'aria-label': 'Collapsed'\n });\n $this.attr({\n 'aria-expanded': 'true',\n 'aria-label': 'Expanded'\n });\n\n $parent.siblings().find('.Accordion-inner').attr('aria-hidden', 'true');\n $this.next().attr('aria-hidden', 'false');\n\n } else {\n $parent.removeClass('is-active');\n $this.attr({\n 'aria-expanded': 'false',\n 'aria-label': 'Collapsed'\n });\n $this.next().attr('aria-hidden', 'true');\n }\n $this.next().slideToggle('fast');\n\n const stickyNavHeight = ($stickyNav.height()) ? ($stickyNav.height()) : 0;\n const contextualNavHeight = ($contextualNav.height()) ? ($contextualNav.height()) : 0;\n const scrollPosition = $parent.offset().top - stickyNavHeight - contextualNavHeight;\n\n $page.not(':animated').stop().animate({\n scrollTop: scrollPosition\n }, 350);\n }\n // default behavior, set on Resources page\n else {\n $parent\n .toggleClass('is-active')\n .siblings()\n .removeClass('is-active')\n .find('.Accordion-inner')\n .stop()\n .slideUp(300);\n\n $this.next().slideToggle(300);\n\n $switchContent.removeClass('is-active').eq($this.parent().index()).toggleClass('is-active');\n }\n\n });\n});\n","import $ from 'jquery';\n\n// litebox fix\nconst liteboxImageLinks = Array.from(document.querySelectorAll('a[href*=\".jpg\"], a[href*=\".jpeg\"], a[href*=\".png\"], a[href*=\".webp\"], .litebox-button'));\nliteboxImageLinks.forEach((link) => link.classList.contains('litebox-button') ? link.classList.add('litebox') : link.classList.add('litebox', '-l-pen'));\n\n\n// required class for forms\n$('input[required], select[required], textarea[required]').prevAll('label').addClass('required');\n\n// detect ie\n(function detectIE() {\n const ua = window.navigator.userAgent;\n const msie = ua.indexOf('MSIE ');\n const trident = ua.indexOf('Trident/');\n\n if (msie > 0 || trident > 0) {\n document.body.classList.add('t-ie');\n }\n})();\n\n// add aria-labels to all kinds of pagination links\nconst setAriaLabelsOnPager = () => {\n let activePageLink = document.querySelector('.TK-Pager-Links a.is-active');\n let prevPageLink = document.querySelector('.TK-Pager-Prev a');\n let nextPageLink = document.querySelector('.TK-Pager-Next a');\n if(activePageLink) activePageLink.setAttribute('aria-label', 'Selected page');\n if(prevPageLink) prevPageLink.setAttribute('aria-label', 'Previous');\n if(nextPageLink) nextPageLink.setAttribute('aria-label', 'Next');\n}\nwindow.addEventListener(\"load\", (e) => {\n setTimeout(setAriaLabelsOnPager, 500);\n});\n","(() => {\n const nav = document.querySelector('.ContextualNav');\n\n if (nav === null) return;\n\n const trigger = document.querySelector('.js-nav-trigger');\n const dropdownMenus = Array.from(document.querySelectorAll('.ContextualNav ul:not(:only-child)'));\n\n const expand = el => el.classList.toggle('is-expanded');\n\n const closeDropdown = () => {\n const openDropdown = document.querySelector('.ContextualNav-content li.is-expanded');\n if (openDropdown) openDropdown.classList.remove('is-expanded');\n };\n\n const outsideClick = (event) => {\n if (!nav.contains(event.target)) {\n closeDropdown();\n document.removeEventListener('click', outsideClick);\n }\n };\n\n dropdownMenus.map(menu => {\n const trigger = menu.parentElement;\n trigger.addEventListener('click', () => {\n if (!trigger.classList.contains('is-expanded')) closeDropdown();\n expand(trigger);\n document.addEventListener('click', outsideClick);\n });\n });\n\n trigger.addEventListener('click', () => expand(nav));\n})();\n","import $ from 'jquery';\n\n/**\n * Fragments (when a .js-fragment element comes into view it gets a new classname - is-intoview)\n *\n * Add modifier .js-fragment--once to execute animation only on the first appearance\n * Add data-offset=\"200\" to the .js-fragment element, to trigger the animation, after offset of 200px from top/bottom viewport\n * Add a specific appearing animation to the element using classes from _fragments.styl\n */\n\n(() => {\n const $fragments = $('.js-fragment');\n let topOffset = 0; // set data-offset=\"xxx\" on the js-fragment element to control each one\n let scrolling = false;\n\n if (!$fragments.length) return;\n\n function isIntoView($fragment) {\n const fragmentTop = $fragment.offset().top;\n const fragmentBottom = fragmentTop + $fragment.outerHeight();\n const viewportTop = $(window).scrollTop();\n const viewportBottom = viewportTop + $(window).height();\n topOffset = $fragment.data('offset') || topOffset;\n return (fragmentBottom - topOffset) > viewportTop && fragmentTop < (viewportBottom - topOffset);\n }\n\n function checkFragments() {\n if (!scrolling) {\n return;\n }\n scrolling = false;\n\n $fragments.each((i, fragment) => {\n const $fragment = $(fragment);\n if (isIntoView($fragment)) $fragment.addClass('is-intoview');\n else if (!$fragment.hasClass('js-fragment--once')) $fragment.removeClass('is-intoview');\n });\n }\n\n function throttleScroll() {\n scrolling = true;\n }\n\n $(window).on('scroll', throttleScroll);\n window.setInterval(checkFragments, 100);\n scrolling = true;\n checkFragments();\n})();\n","(() => {\n function listenForClicks(e) {\n e.addEventListener(\"click\", function(e) {\n let el = e.currentTarget;\n let newClass = el.getAttribute(\"class\").split(\"js-tglslf-\")[1].split(\" \")[0];\n el.classList.toggle(newClass);\n })\n }\n document.querySelectorAll(\"[class*='js-tglslf-']\").forEach(function(e) {\n listenForClicks(e)\n });\n})();\n","const pluginName = 'toggle';\n\nconst defaultOptions = {\n className: '-dn'\n};\n\n/**\n * Toggle plugin that applies side effects to elements (hides, shows, toggles)\n * Can be extended to support multiple effects and does not cache elements on DOM load\n */\nexport class Toggle {\n constructor(element, options) {\n this.element = element;\n this.options = {\n ...defaultOptions,\n ...options\n };\n this.attachEventListeners();\n\n this.hashChangeHandler();\n if (this.options.hash && this.hashArgv !== null && this.hashArgv.includes(this.options.hash)) {\n this.currentHash = this.options.hash;\n }\n }\n\n attachEventListeners() {\n this.element.addEventListener('click', this.effects.bind(this));\n window.addEventListener('hashchange', this.hashChangeHandler.bind(this));\n }\n\n static parseHash() {\n let windowHash = window.location.hash;\n if (!windowHash) return null;\n [, windowHash] = windowHash.split('#');\n if (windowHash.includes(',')) {\n return windowHash.split(',');\n }\n return [windowHash];\n }\n\n hashChangeHandler() {\n if (this.skipHashHandler) {\n this.skipHashHandler = false;\n return;\n }\n\n this.hashArgv = Toggle.parseHash();\n if (this.hashArgv === null) return;\n this.hashArgv.forEach(hash => {\n if (hash === this.options.hash) {\n this.effects(null, true);\n }\n });\n }\n\n updateScopes() {\n const scopeElements = document.querySelectorAll(this.element.dataset.scope);\n if (scopeElements.length === 0) {\n this.scopes = [document];\n return;\n }\n this.scopes = scopeElements;\n }\n\n updateTargets() {\n let hideTargets = [];\n let showTargets = [];\n let toggleTargets = [];\n\n this.scopes.forEach(scope => {\n hideTargets = [\n ...hideTargets,\n ...Array.from(scope.querySelectorAll(this.element.dataset.hide))\n ];\n\n showTargets = [\n ...showTargets,\n ...Array.from(scope.querySelectorAll(this.element.dataset.show))\n ];\n\n toggleTargets = [\n ...toggleTargets,\n ...Array.from(scope.querySelectorAll(this.element.dataset.toggle))\n ];\n });\n\n this.hideTargets = hideTargets;\n this.showTargets = showTargets;\n this.toggleTargets = toggleTargets;\n }\n\n toggleHash() {\n if (!this.currentHash || this.currentHash !== this.options.hash) {\n this.skipHashHandler = true;\n window.location.hash = this.options.hash;\n this.currentHash = this.options.hash;\n return;\n }\n\n if (this.toggleTargets.length) {\n window.history.pushState(null, null, ' ');\n this.currentHash = null;\n }\n }\n\n effects(event, hashChange) {\n this.updateScopes();\n this.updateTargets();\n this.hideTargets.forEach(element => (element.classList.add(this.options.className)));\n this.showTargets.forEach(element => (element.classList.remove(this.options.className)));\n this.toggleTargets.forEach(element => (element.classList.toggle(this.options.className)));\n\n // the hash should be toggled only if there are toggle targets as the show/hide operations are\n // only executed once and if a hash is provided, they are already executed\n if (!hashChange && this.options.hash) {\n this.toggleHash();\n }\n\n const changeEvent = new Event('change');\n changeEvent.hideTargets = this.hideTargets;\n changeEvent.showTargets = this.showTargets;\n changeEvent.toggleTargets = this.toggleTargets;\n\n this.element.dispatchEvent(changeEvent);\n\n // The synthetic event below is useful in some cases when working with other plugins\n window.dispatchEvent(new Event('resize'));\n }\n}\n\n$.fn[pluginName] = function(options) {\n return this.each(function() {\n if (!$.data(this, `plugin_${pluginName}`)) {\n new Toggle(this, options);\n }\n });\n};\n\n","export const defaultOptions = {\n width: 560,\n height: 400,\n media: 'twitter',\n url: window.location.href,\n text: document.title,\n};\n\nclass SocialShare {\n /**\n * @constructor\n */\n constructor(options) {\n this.options = { ...defaultOptions, ...options };\n\n this.element = this.options.element;\n this.media = this.options.media;\n this.url = this.options.url || window.location.href;\n this.text = this.options.text || document.title;\n\n if (!this.element) {\n throw new Error('Social share anchor element is required');\n }\n\n this.populateLink();\n\n this.element.addEventListener('click', this.onLinkClick.bind(this));\n }\n\n /**\n * Populate the link href\n */\n populateLink() {\n const hashtag = this.options.hashtag ? `&hashtags=${this.options.hashtag}` : '';\n const encodedUrl = encodeURIComponent(this.url);\n const encodedText = encodeURIComponent(this.text);\n\n switch (this.media) {\n case 'facebook':\n this.element.href = `https://www.facebook.com/sharer.php?u=${encodedUrl}`;\n break;\n case 'twitter':\n this.element.href = `https://twitter.com/share?url=${encodedUrl}&text=${encodedText}${hashtag}`;\n break;\n case 'linkedin':\n this.element.href = `https://www.linkedin.com/shareArticle?mini=true&url=${encodedUrl}&title=${encodedText}`;\n break;\n case 'reddit':\n this.element.href = `https://reddit.com/submit?url=${encodedUrl}&title=${encodedText}`;\n break;\n case 'mail':\n case 'email':\n this.element.href = `mailto:?subject=${encodedText}&body=${encodedUrl}`;\n }\n }\n\n /**\n * @param {Event} - click event\n */\n onLinkClick(event) {\n const medias = ['facebook', 'twitter', 'linkedin', 'reddit'];\n\n if (medias.includes(this.media)) {\n event.preventDefault();\n this.openPopup(event.currentTarget.href);\n }\n }\n\n /**\n * open popup window\n */\n openPopup(url, title = 'Share') {\n const top = (window.innerHeight - this.options.height) / 2;\n const left = (window.innerWidth - this.options.width) / 2;\n const status = 1;\n const windowOptions = `status=${status},width=${this.options.width},height=${this.options.height},top=${top},left=${left}`;\n\n window.open(url, title, windowOptions);\n }\n}\n\nexport default SocialShare;\n","/**\n * DOM Ready alternative.\n * Works in/with async scripts!\n */\n\nexport const interactive = () => {\n return new Promise((resolve) => {\n if (document.readyState !== 'loading') {\n resolve();\n }\n else {\n document.addEventListener('readystatechange', () => {\n if (document.readyState === 'interactive') {\n resolve();\n }\n });\n }\n });\n};\n","import SocialShare from '@progress-wad/social-share';\nimport { interactive } from './interactive.js';\n\ninteractive().then(() => {\n const elements = Array.from(document.querySelectorAll('#js-social-share a, .js-social-share a'));\n elements.forEach(element => new SocialShare({\n element,\n media: element.dataset.network\n }));\n});\n","(() => {\n const pluginName = 'OutlineNav';\n\n const defaults = {\n headingSelector: 'h2'\n };\n\n class OutlineNav {\n constructor(container, options) {\n this.container = container;\n this.options = Object.assign({}, defaults, options);\n this.headingSelector = this.options.headingSelector;\n \n this.headingElements = Array.from(document.querySelectorAll(this.headingSelector));\n \n this.generateHeadingIdentifiers();\n this.buildNavigation();\n }\n\n generateHeadingIdentifiers() {\n this.headingElements.forEach(heading => {\n if (heading.getAttribute('id')) return;\n\n // Generate sanitized ID without whitespace or special symbols https://regex101.com/r/5JWswv/1\n const generatedId = heading.innerText.trim().toLowerCase().replace(/#|\\s/g, '-');\n\n heading.setAttribute('id', generatedId);\n });\n }\n\n buildNavigation() {\n const listItems = this.headingElements.map(heading => {\n const anchor = document.createElement('a');\n anchor.setAttribute('href', `#${heading.getAttribute('id')}`);\n anchor.innerText = heading.innerText.trim();\n \n const listItem = document.createElement('li');\n listItem.append(anchor);\n\n return listItem;\n });\n\n this.container.append(...listItems);\n }\n }\n\n if (window.jQuery) {\n $.fn[pluginName] = function(options) {\n return this.each(function() {\n if (!$.data(this, pluginName)) {\n $.data(this, pluginName, new OutlineNav(this, options));\n }\n });\n };\n }\n})();\n","\n(() => {\n const forms = document.querySelectorAll('.js-floating-labels');\n\n forms.forEach((form) => {\n const allFields = form.querySelectorAll(\"input:not(.-dn):not([type=hidden]):not([type=checkbox]):not([type=radio]), select, textarea\")\n\n allFields.forEach((field) => {\n const wrap = field.closest('.sf-fieldWrp, .form-group');\n const error = wrap.querySelector('[data-sf-role=\"error-message\"]');\n\n const isEmpty = (el) => {\n return (el.tagName.toLowerCase() === 'select') ? false : !(el.value.length)\n };\n\n field.addEventListener('focus', () => {\n wrap.classList.remove('is-empty');\n wrap.classList.add('has-focus');\n }, true);\n\n field.addEventListener('blur', () => {\n wrap.classList.remove('has-focus');\n if (isEmpty(field))\n wrap.classList.add('is-empty');\n }, true);\n\n field.addEventListener('change', () => {\n (isEmpty(field))\n ? wrap.classList.add('is-empty')\n : wrap.classList.remove('is-empty');\n }, true);\n\n const errorHandle = (error) => {\n if (error.classList.contains('sfError') && error.style.display === 'block') {\n wrap.classList.add('has-error');\n }\n else {\n wrap.classList.remove('has-error');\n }\n };\n\n // Callback function to execute when mutations are observed\n const callback = (mutations) => {\n for (const mutation of mutations) {\n if (mutation.type === 'attributes')\n errorHandle(error);\n }\n };\n\n const observer = new MutationObserver(callback);\n observer.observe(error, { attributes: true, childList: false, subtree: false });\n\n const initField = (field) => {\n // Detect browser autofill and mark empty fields via event dispatch\n const event = new Event('change');\n field.dispatchEvent(event);\n }\n\n initField(field);\n });\n });\n})();\n","function getParameterByName(name, url) {\n let currentURL = url;\n if (!currentURL) currentURL = window.location.href;\n let cleanedName = name.replace(/[\\[\\]]/g, '\\\\$&');\n const regex = new RegExp('[?&]' + cleanedName + '(=([^&#]*)|&|#|$)'),\n results = regex.exec(currentURL);\n if (!results) return null;\n if (!results[2]) return '';\n return decodeURIComponent(results[2].replace(/\\+/g, ' '));\n}\n\nconst queryParameterValue = getParameterByName(\"utm_term\");\nif (queryParameterValue) {\n let toBeReplaced = document.querySelector(\".js-to-be-replaced\");\n if (toBeReplaced) {\n toBeReplaced.innerText = queryParameterValue;\n }\n}\n","const ANIMATIONEND = 'animationend';\nconst $window = $(window);\nconst $document = $(document);\nlet $overlay; let $wrapper; let $content; let $loader; let $close;\nconst DATA_ATTRIBS_MAP = {\n 'lite-info': 'info',\n 'lite-width': 'width',\n 'lite-height': 'height',\n 'lite-resize': 'resize',\n 'lite-noscroll': 'noscroll',\n 'lite-root': 'root'\n};\nconst idhash = {};\nlet liteboxLoaded = false;\n\nfunction resizeImage(obj) {\n const ww = $window.width(); const wh = $window.height(); const\n $obj = $(obj);\n\n if (obj.originalWidth > ww) {\n $obj.css('max-width', 0.84 * ww);\n }\n else {\n $obj.css('max-width', 'none');\n }\n\n if (obj.originalHeight > wh - 60) {\n $obj.css('max-height', 0.84 * wh);\n }\n else {\n $obj.css('max-height', 'none');\n }\n}\n\nfunction setWidthHeight($elem, elem) { // from data attributes\n $elem.css({\n width: elem.options.width,\n height: elem.options.height\n });\n}\n\nfunction buildImage(obj) {\n const dfd = $.Deferred();\n const $img = $('', { src: obj.elem.getAttribute('href') });\n\n $img.on('load', function() {\n if (obj.options.resize === true || Number(obj.options.resize) === 1 || obj.options.resize === 'yes') {\n this.originalWidth = this.width;\n this.originalHeight = this.height;\n resizeImage(this);\n }\n dfd.resolve(this);\n });\n\n setWidthHeight($img, obj);\n\n return dfd.promise();\n}\n\nfunction buildVideo(obj) {\n let href = obj.elem.getAttribute('href');\n\n if (href.indexOf('?') !== -1 && href.indexOf('wmode=transparent') === -1) href += '&wmode=transparent';\n if (href.indexOf('?') === -1 && href.indexOf('&') === -1 && href.indexOf('wmode=transparent') === -1) href += '?wmode=transparent';\n\n const $iframe = $('', {\n src: href,\n width: obj.options.width,\n height: obj.options.height,\n frameborder: 0,\n allowfullscreen: true,\n mozallowfullscreen: true,\n webkitallowfullscreen: true\n });\n\n return $iframe;\n}\n\nfunction buildContent(obj) {\n const id = obj.elem.getAttribute('href').split('#')[1] || obj.elem.dataset.liteId;\n\n if (!id) return;\n\n let $obj = $(`#${id}`);\n const $iframes = $obj.find('iframe');\n\n if ($obj[0]) {\n idhash[id] = obj;\n }\n else {\n $obj = idhash[id].cachedContent;\n }\n\n if ($iframes.length > 0) {\n $iframes.each(function each() {\n let source = this.src;\n if (source.indexOf('?') !== -1 && source.indexOf('wmode=transparent') === -1) source += '&wmode=transparent';\n if (source.indexOf('?') === -1 && source.indexOf('&') === -1 && source.indexOf('wmode=transparent') === -1) source += '?wmode=transparent';\n if (this.src !== source) this.src = source;\n });\n }\n\n $obj.removeClass(obj.options.csshide);\n setWidthHeight($obj, obj);\n\n return $obj;\n}\n\nfunction Litebox(elem, options) {\n this.elem = elem;\n this.$elem = $(elem);\n this.timer = 0;\n\n let attrib;\n const dataAttribs = {};\n\n for (attrib in DATA_ATTRIBS_MAP) {\n if (typeof this.$elem.data(attrib) !== 'undefined') dataAttribs[DATA_ATTRIBS_MAP[attrib]] = this.$elem.data(attrib);\n }\n\n this.options = $.extend({}, $.fn.litebox.defaults, options, dataAttribs);\n this.init();\n}\n\nLitebox.prototype = {\n init() {\n const self = this;\n self.$root = $(self.options.root).length ? $(self.options.root) : $('body');\n if (!$overlay) self.stage();\n self.$elem.on('click.litebox-click', $.proxy(self.show, self));\n },\n\n enable() {\n this.$elem.on('click.litebox-click', $.proxy(this.show, this));\n },\n\n disable() {\n this.$elem.off('click.litebox-click');\n },\n\n stage() {\n const self = this;\n\n $overlay = $('
', { id: 'tlrk-overlay', class: self.options.csshide });\n $wrapper = $('
', { id: 'tlrk-litebox', class: self.options.csshide });\n $content = $('
', { class: (`litebox-content ${self.options.csshide}`) });\n $loader = $('
', { class: 'litebox-loader' }).html('Loading ...'); // html() is faster on empty div, than text()\n $close = $('', { href: '#', class: 'litebox-close' }).html('×');\n\n // clicking on an element with .litebox-close-btn in the content will close the litebox\n $content.on('click', '.litebox-close-btn', e => {\n e.preventDefault();\n self.hide();\n });\n\n self.$root.append($overlay, $wrapper);\n $wrapper.append($content, $close);\n },\n\n populate() {\n const self = this;\n\n if (self.cachedContent === undefined) {\n $content.append($loader);\n\n if (self.options.info === 'image') {\n buildImage(self).done(img => {\n self.cachedContent = img;\n if (!self.opened) return;\n $content.html(self.cachedContent);\n self.appendTitle();\n if (self.options.center) self.center();\n });\n }\n else if (self.options.info === 'video') {\n self.cachedContent = buildVideo(self);\n $loader.remove();\n $content.html(self.cachedContent);\n self.appendTitle();\n }\n else if (self.options.info === 'content') {\n self.cachedContent = buildContent(self);\n $loader.remove();\n $content.html(self.cachedContent);\n self.appendTitle();\n }\n }\n else {\n if (self.options.info === 'image') resizeImage(self.cachedContent);\n $content.html(self.cachedContent);\n self.appendTitle();\n }\n if (self.options.center) self.center();\n },\n\n appendTitle() {\n const self = this;\n const title = self.elem.dataset.title;\n if (title) {\n const $p = $('

', { class: 'litebox-title' });\n $p.text(title);\n $content.append($p);\n }\n },\n\n destroy() {\n $content.empty();\n },\n\n show() {\n const self = this;\n self.opened = true;\n\n if (self.options.noscroll) self.$root.addClass('tlrk-litebox-noscroll');\n\n self.populate();\n\n $wrapper.add($close).removeClass(self.options.csshide);\n\n $overlay.removeClass(self.options.csshide).addClass(self.options.animIn);\n $content.removeClass(self.options.csshide).addClass(self.options.animInSec);\n\n if (self.options.center) self.center();\n\n // close\n $overlay.add($close).add($wrapper).one('click', $.proxy(self.hide, self));\n $content.on('click', e => {\n e.stopPropagation();\n });\n\n $document.on('keydown.litebox-keydown', e => {\n if (e.keyCode === 27 || (e.keyCode === 8 && !($(e.target).is('input') || $(e.target).is('textarea')))) {\n self.hide();\n e.preventDefault(); // ff and ie prevent .gif animation stop\n }\n });\n\n $window.on('resize.litebox-resize', () => {\n if (self.timer) clearTimeout(self.timer);\n self.timer = setTimeout($.proxy(self.resize, self), 150);\n });\n\n if (liteboxLoaded) {\n return false;\n }\n },\n\n hide() {\n const self = this;\n self.opened = false;\n $close.addClass(self.options.csshide);\n\n $overlay.removeClass(self.options.animIn).addClass(self.options.animOut);\n $content.removeClass(self.options.animInSec).addClass(self.options.animOutSec);\n\n $content.on(ANIMATIONEND, () => {\n if (self.options.noscroll) self.$root.removeClass('tlrk-litebox-noscroll');\n $overlay.removeClass(self.options.animOut).addClass(self.options.csshide);\n $content.removeClass(self.options.animOutSec).addClass(self.options.csshide);\n $wrapper.addClass(self.options.csshide);\n self.destroy();\n $content.off(ANIMATIONEND);\n });\n\n $document.off('keydown.litebox-keydown');\n $window.off('resize.litebox-resize');\n\n return false;\n },\n\n center() {\n $content.css({\n 'margin-top': -($content.height() / 2),\n 'margin-left': -($content.width() / 2),\n 'margin-right': ($content.width() / 2)\n });\n },\n\n resize() {\n const self = this;\n resizeImage(self.cachedContent);\n if (self.options.center) self.center\n }\n};\n\n$.fn.litebox = function liteboxPlugin(options) {\n return this.each(function each() {\n if (!$.data(this, 'litebox')) $.data(this, 'litebox', new Litebox(this, options));\n });\n};\n\n$.fn.litebox.defaults = {\n info: 'image',\n animIn: 'fadeIn',\n animOut: 'fadeOut',\n animInSec: 'fadeInScale',\n animOutSec: 'fadeOutScale',\n csshide: '-dn',\n resize: true,\n noscroll: false,\n root: 'body',\n center: false\n};\n\nfunction bindEvent(el, eventName, handler) {\n if (el.addEventListener) {\n el.addEventListener(eventName, handler);\n }\n else if (el.attachEvent) {\n el.attachEvent(`on${eventName}`, () => {\n handler.call(el);\n });\n }\n}\n\nlet timer = false;\nconst minDeviceWidth = 990;\n\nfunction createInstances(options) {\n if (timer) {\n window.clearTimeout(timer);\n timer = false;\n }\n if ($window.width() >= minDeviceWidth && !liteboxLoaded) {\n $('.litebox').litebox(options);\n liteboxLoaded = true;\n }\n if ($window.width() < minDeviceWidth) {\n $(\".litebox[data-lite-info='content']\").litebox(options);\n liteboxLoaded = false;\n }\n}\n\nexport function initialize(options) {\n createInstances(options);\n\n $.fn.litebox.defaults = {\n ...$.fn.litebox.defaults,\n ...options\n };\n\n bindEvent(window, 'resize', () => {\n if (!timer) {\n timer = window.setTimeout(() => createInstances(options), 1000);\n }\n });\n\n bindEvent(window, 'orientationchange', () => createInstances(options));\n}\n","export const defaultOptions = {\n selector: '.siema',\n duration: 200,\n easing: 'ease-out',\n perPage: 1,\n startIndex: 0,\n draggable: true,\n multipleDrag: true,\n slidePage: false,\n threshold: 20,\n loop: false,\n rtl: false,\n dotNav: false, // generate dots navigation, it is false by default for backward compatibility\n dotNavClass: 'siema-dot-nav',\n dotClass: 'siema-dot',\n automatedTimer: 10000,\n onInit: () => {},\n onChange: () => {},\n};\n\nexport default class Siema {\n /**\n * @param {Object} options\n */\n constructor(options) {\n this.config = { ...defaultOptions, ...options };\n\n // THIS IS BAD, because it is the actual element and not the selector itself, be careful\n // the provided \"selector\" should be always a string\n this.element = this.selector = typeof this.config.selector === 'string' ? document.querySelector(this.config.selector) : this.config.selector;\n\n if (this.element === null) {\n throw new Error('Something wrong with your selector');\n }\n\n // Update perPage attribute depending on the options and viewport\n this.resolveSlidesNumber();\n\n this.elementWidth = this.element.offsetWidth;\n this.innerElements = Array.from(this.element.children);\n this.currentSlide = this.config.loop ?\n this.config.startIndex % this.innerElements.length :\n Math.max(0, Math.min(this.config.startIndex, this.innerElements.length - this.perPage));\n if (this.config.loop) {\n this.loopSlidesPrepended = [];\n this.loopSlidesAppended = [];\n }\n this.focusableElements = 'a, input, select, textarea, button, iframe, [tabindex], [contentEditable]';\n this.hideA11y = (wrapper) => {\n wrapper.setAttribute('aria-hidden', 'true');\n wrapper.querySelectorAll(this.focusableElements).forEach(focusable => focusable.setAttribute('tabindex', '-1'));\n };\n this.showA11y = (wrapper) => {\n wrapper.removeAttribute('aria-hidden');\n wrapper.querySelectorAll(this.focusableElements).forEach(focusable => focusable.removeAttribute('tabindex'));\n };\n\n // Bind all event handlers for referencability\n [\n 'resizeHandler',\n 'touchstartHandler',\n 'touchendHandler',\n 'touchmoveHandler',\n 'mousedownHandler',\n 'mouseupHandler',\n 'mouseleaveHandler',\n 'mousemoveHandler',\n 'clickHandler',\n 'btnNextHandler',\n 'btnPrevHandler',\n 'dotClickHandler'\n ].forEach(method => {\n this[method] = this[method].bind(this);\n });\n\n if (!options.dotNav) {\n this.dots = options.dots ? Array.from(document.querySelectorAll(options.dots)) : null;\n }\n\n this.btnNext = options.btnNext ? document.querySelector(options.btnNext) : null;\n this.btnPrev = options.btnPrev ? document.querySelector(options.btnPrev) : null;\n\n // Build markup and apply required styling to elements\n this.init();\n }\n\n /**\n * Attaches listeners to required events.\n */\n attachEvents() {\n // Resize element on window resize\n window.addEventListener('resize', this.resizeHandler);\n\n // prev & next button support\n if (this.btnNext) this.btnNext.addEventListener('click', this.btnNextHandler);\n if (this.btnPrev) this.btnPrev.addEventListener('click', this.btnPrevHandler);\n\n // tab controls (dots)\n if (this.dots) {\n this.dots.forEach((dot, index) => dot.addEventListener('click', this.dotClickHandler(index)));\n }\n\n // If element is draggable / swipable, add event handlers\n if (this.config.draggable) {\n // Keep track pointer hold and dragging distance\n this.pointerDown = false;\n this.drag = {\n startX: 0,\n endX: 0,\n startY: 0,\n letItGo: null,\n preventClick: false,\n };\n\n // Touch events\n this.element.addEventListener('touchstart', this.touchstartHandler);\n this.element.addEventListener('touchend', this.touchendHandler);\n this.element.addEventListener('touchmove', this.touchmoveHandler);\n\n // Mouse events\n this.element.addEventListener('mousedown', this.mousedownHandler);\n this.element.addEventListener('mouseup', this.mouseupHandler);\n this.element.addEventListener('mouseleave', this.mouseleaveHandler);\n this.element.addEventListener('mousemove', this.mousemoveHandler);\n\n // Click\n this.element.addEventListener('click', this.clickHandler);\n }\n }\n\n /**\n * Detaches listeners from required events.\n */\n detachEvents() {\n window.removeEventListener('resize', this.resizeHandler);\n if (this.btnNext) this.btnNext.removeEventListener('click', this.btnNextHandler);\n if (this.btnPrev) this.btnPrev.removeEventListener('click', this.btnPrevHandler);\n this.element.removeEventListener('touchstart', this.touchstartHandler);\n this.element.removeEventListener('touchend', this.touchendHandler);\n this.element.removeEventListener('touchmove', this.touchmoveHandler);\n this.element.removeEventListener('mousedown', this.mousedownHandler);\n this.element.removeEventListener('mouseup', this.mouseupHandler);\n this.element.removeEventListener('mouseleave', this.mouseleaveHandler);\n this.element.removeEventListener('mousemove', this.mousemoveHandler);\n this.element.removeEventListener('click', this.clickHandler);\n }\n\n /**\n * Builds the markup and attaches listeners to required events.\n */\n init() {\n // hide everything out of selector's boundaries\n this.element.style.overflow = 'hidden';\n\n // rtl or ltr\n this.element.style.direction = this.config.rtl ? 'rtl' : 'ltr';\n\n // build a frame and slide to a currentSlide\n this.buildSliderFrame();\n\n // initialize automation\n if (this.config.automated) this.initAutomation();\n\n this.config.onInit.call(this);\n }\n\n btnNextHandler() {\n this.next();\n if (this.autoplay) this.autoplay = window.clearInterval(this.autoplay);\n }\n\n btnPrevHandler() {\n this.prev();\n if (this.autoplay) this.autoplay = window.clearInterval(this.autoplay);\n }\n\n dotClickHandler(index) {\n return (event) => {\n event.preventDefault();\n\n if (this.config.automated) {\n this.autoplay = window.clearInterval(this.autoplay);\n }\n\n if (this.config.slidePage) {\n this.goTo(index * this.perPage);\n return;\n }\n\n this.goTo(index);\n }\n }\n\n /**\n * Build a dot navigation dynamically\n */\n buildDotNavigation() {\n this.dotNav = document.createElement('div');\n\n this.dots = this.innerElements.flatMap((element, index) => {\n // If slidePage option is enabled create a single dot per page\n if (this.config.slidePage && this.perPage > 1 && (index + 1) % this.perPage !== 1) return [];\n \n if (!this.config.slidePage && index > this.innerElements.length - this.perPage) return [];\n\n const dot = document.createElement('a');\n dot.href = `#slide-${index + 1}`;\n dot.dataset.slide = index;\n dot.setAttribute('aria-label', `Go to slide ${index + 1}`);\n dot.classList.add(this.config.dotClass);\n return [dot];\n });\n\n this.dotNav.append(...this.dots);\n\n this.dotNav.classList.add(...[this.config.dotNavClass].flat());\n this.element.appendChild(this.dotNav);\n }\n\n /**\n * Build a sliderFrame and slide to a current item.\n */\n buildSliderFrame() {\n const widthItem = this.elementWidth / this.perPage;\n const itemsToBuild = this.config.loop ? this.innerElements.length + (2 * this.perPage) : this.innerElements.length;\n\n // Create frame and apply styling\n this.sliderFrame = document.createElement('div');\n this.sliderFrame.style.width = `${widthItem * itemsToBuild}px`;\n this.enableTransition();\n\n if (this.config.draggable) {\n this.element.style.cursor = 'grab';\n }\n\n // Create a document fragment to put slides into it\n const docFragment = document.createDocumentFragment();\n const createLoopSlide = (slide, loopSlides) => {\n const el = this.buildSliderFrameItem(slide.cloneNode(true));\n this.hideA11y(el);\n docFragment.appendChild(el);\n loopSlides.push(el);\n };\n\n // Loop through the slides, add styling and add them to document fragment\n if (this.config.loop) {\n for (let i = this.innerElements.length - this.perPage; i < this.innerElements.length; i++) {\n createLoopSlide(this.innerElements[i], this.loopSlidesPrepended);\n }\n }\n for (let i = 0; i < this.innerElements.length; i++) {\n const element = this.buildSliderFrameItem(this.innerElements[i]);\n docFragment.appendChild(element);\n }\n if (this.config.loop) {\n for (let i = 0; i < this.perPage; i++) {\n createLoopSlide(this.innerElements[i], this.loopSlidesAppended);\n }\n }\n\n // Add fragment to the frame\n this.sliderFrame.appendChild(docFragment);\n\n // Clear selector (just in case something is there) and insert a frame\n this.element.innerHTML = '';\n this.element.appendChild(this.sliderFrame);\n\n // build dots navigation\n if (this.config.dotNav) {\n this.buildDotNavigation();\n }\n\n this.detachEvents();\n this.attachEvents();\n\n // Go to currently active slide after initial build\n this.slideToCurrent();\n\n this.change();\n }\n\n buildSliderFrameItem(elm) {\n const elementContainer = document.createElement('div');\n elementContainer.style.cssFloat = this.config.rtl ? 'right' : 'left';\n elementContainer.style.float = this.config.rtl ? 'right' : 'left';\n elementContainer.style.width = `${this.config.loop ? 100 / (this.innerElements.length + (this.perPage * 2)) : 100 / (this.innerElements.length)}%`;\n elementContainer.appendChild(elm);\n return elementContainer;\n }\n\n /**\n * Determinates slides number accordingly to clients viewport.\n */\n resolveSlidesNumber() {\n if (typeof this.config.perPage === 'number') {\n this.perPage = this.config.perPage;\n }\n else if (typeof this.config.perPage === 'object') {\n this.perPage = 1;\n for (const viewport in this.config.perPage) {\n if (window.innerWidth >= viewport) {\n this.perPage = this.config.perPage[viewport];\n }\n }\n }\n }\n\n /**\n * Go to previous slide.\n * @param {number} howManySlides - How many items to slide backward.\n * @param {function} callback - Optional callback function.\n */\n prev(howManySlides, callback) {\n // early return when there is nothing to slide\n if (this.innerElements.length <= this.perPage) {\n return;\n }\n\n if (!howManySlides) {\n howManySlides = this.config.slidePage ? this.perPage : 1;\n }\n\n const beforeChange = this.currentSlide;\n\n if (this.config.loop) {\n const isNewIndexClone = this.currentSlide - howManySlides < 0;\n if (isNewIndexClone) {\n this.disableTransition();\n\n const mirrorSlideIndex = this.currentSlide + this.innerElements.length;\n const mirrorSlideIndexOffset = this.perPage;\n const moveTo = mirrorSlideIndex + mirrorSlideIndexOffset;\n const offset = (this.config.rtl ? 1 : -1) * moveTo * (this.elementWidth / this.perPage);\n const dragDistance = this.config.draggable ? this.drag.endX - this.drag.startX : 0;\n\n this.sliderFrame.style.transform = `translate3d(${offset + dragDistance}px, 0, 0)`;\n this.currentSlide = mirrorSlideIndex - howManySlides;\n }\n else {\n this.currentSlide = this.currentSlide - howManySlides;\n }\n }\n else {\n this.currentSlide = Math.max(this.currentSlide - howManySlides, 0);\n }\n\n if (beforeChange !== this.currentSlide) {\n this.slideToCurrent(this.config.loop);\n this.config.onChange.call(this);\n this.change();\n if (callback) {\n callback.call(this);\n }\n }\n }\n\n /**\n * Go to next slide.\n * @param {number} howManySlides - How many items to slide forward.\n * @param {function} callback - Optional callback function.\n */\n next(howManySlides, callback) {\n // early return when there is nothing to slide\n if (this.innerElements.length <= this.perPage) {\n return;\n }\n\n if (!howManySlides) {\n howManySlides = this.config.slidePage ? this.perPage : 1;\n }\n\n const beforeChange = this.currentSlide;\n\n if (this.config.loop) {\n const isNewIndexClone = this.currentSlide + howManySlides > this.innerElements.length - this.perPage;\n if (isNewIndexClone) {\n this.disableTransition();\n\n const mirrorSlideIndex = this.currentSlide - this.innerElements.length;\n const mirrorSlideIndexOffset = this.perPage;\n const moveTo = mirrorSlideIndex + mirrorSlideIndexOffset;\n const offset = (this.config.rtl ? 1 : -1) * moveTo * (this.elementWidth / this.perPage);\n const dragDistance = this.config.draggable ? this.drag.endX - this.drag.startX : 0;\n\n this.sliderFrame.style.transform = `translate3d(${offset + dragDistance}px, 0, 0)`;\n this.currentSlide = mirrorSlideIndex + howManySlides;\n }\n else {\n this.currentSlide = this.currentSlide + howManySlides;\n }\n }\n else {\n this.currentSlide = Math.min(this.currentSlide + howManySlides, this.innerElements.length - this.perPage);\n }\n if (beforeChange !== this.currentSlide) {\n this.slideToCurrent(this.config.loop);\n this.config.onChange.call(this);\n this.change();\n if (callback) {\n callback.call(this);\n }\n }\n }\n\n /**\n * Disable transition on sliderFrame.\n */\n disableTransition() {\n this.sliderFrame.style.transition = `all 0ms ${this.config.easing}`;\n }\n\n /**\n * Enable transition on sliderFrame.\n */\n enableTransition() {\n this.sliderFrame.style.transition = `all ${this.config.duration}ms ${this.config.easing}`;\n }\n\n /**\n * Go to slide with particular index\n * @param {number} index - Item index to slide to.\n * @param {function} callback - Optional callback function.\n */\n goTo(index, callback) {\n if (this.innerElements.length <= this.perPage) {\n return;\n }\n const beforeChange = this.currentSlide;\n this.currentSlide = this.config.loop ?\n index % this.innerElements.length :\n Math.min(Math.max(index, 0), this.innerElements.length - this.perPage);\n if (beforeChange !== this.currentSlide) {\n this.slideToCurrent();\n this.config.onChange.call(this);\n this.change();\n if (callback) {\n callback.call(this);\n }\n }\n }\n\n /**\n * Moves sliders frame to position of currently active slide\n */\n slideToCurrent(enableTransition) {\n const currentSlide = this.config.loop ? this.currentSlide + this.perPage : this.currentSlide;\n const offset = (this.config.rtl ? 1 : -1) * currentSlide * (this.elementWidth / this.perPage);\n\n if (enableTransition) {\n // This one is tricky, I know but this is a perfect explanation:\n // https://youtu.be/cCOL7MC4Pl0\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n this.enableTransition();\n this.sliderFrame.style.transform = `translate3d(${offset}px, 0, 0)`;\n });\n });\n }\n else {\n this.sliderFrame.style.transform = `translate3d(${offset}px, 0, 0)`;\n }\n }\n\n /**\n * Recalculate drag /swipe event and reposition the frame of a slider\n */\n updateAfterDrag() {\n const movement = (this.config.rtl ? -1 : 1) * (this.drag.endX - this.drag.startX);\n const movementDistance = Math.abs(movement);\n let howManySliderToSlide = this.config.multipleDrag ? Math.ceil(movementDistance / (this.elementWidth / this.perPage)) : 1;\n\n if (this.config.slidePage) {\n howManySliderToSlide = Math.floor(howManySliderToSlide / 3) * 3;\n }\n\n const slideToNegativeClone = movement > 0 && this.currentSlide - howManySliderToSlide < 0;\n const slideToPositiveClone = movement < 0 && this.currentSlide + howManySliderToSlide > this.innerElements.length - this.perPage;\n\n if (movement > 0 && movementDistance > this.config.threshold && this.innerElements.length > this.perPage) {\n this.prev(howManySliderToSlide);\n }\n else if (movement < 0 && movementDistance > this.config.threshold && this.innerElements.length > this.perPage) {\n this.next(howManySliderToSlide);\n }\n this.slideToCurrent(slideToNegativeClone || slideToPositiveClone);\n\n this.autoplay = window.clearInterval(this.autoplay);\n }\n\n /**\n * When window resizes, resize slider components as well\n */\n resizeHandler() {\n // update perPage number dependable of user value\n this.resolveSlidesNumber();\n\n // relcalculate currentSlide\n // prevent hiding items when browser width increases\n if (this.currentSlide + this.perPage > this.innerElements.length) {\n this.currentSlide = this.innerElements.length <= this.perPage ? 0 : this.innerElements.length - this.perPage;\n }\n\n this.elementWidth = this.element.offsetWidth;\n\n this.buildSliderFrame();\n }\n\n /**\n * Clear drag after touchend and mouseup event\n */\n clearDrag() {\n this.drag = {\n startX: 0,\n endX: 0,\n startY: 0,\n letItGo: null,\n preventClick: this.drag.preventClick\n };\n }\n\n touchstartHandler(e) {\n // Prevent dragging / swiping on inputs, selects and textareas\n const ignoreSiema = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].indexOf(e.target.nodeName) !== -1;\n if (ignoreSiema) {\n return;\n }\n\n e.stopPropagation();\n this.pointerDown = true;\n this.drag.startX = e.touches[0].pageX;\n this.drag.startY = e.touches[0].pageY;\n }\n\n touchendHandler(e) {\n e.stopPropagation();\n this.pointerDown = false;\n this.enableTransition();\n if (this.drag.endX) {\n this.updateAfterDrag();\n }\n this.clearDrag();\n }\n\n touchmoveHandler(e) {\n e.stopPropagation();\n\n if (this.drag.letItGo === null) {\n this.drag.letItGo = Math.abs(this.drag.startY - e.touches[0].pageY) < Math.abs(this.drag.startX - e.touches[0].pageX);\n }\n\n if (this.pointerDown && this.drag.letItGo) {\n e.preventDefault();\n this.drag.endX = e.touches[0].pageX;\n this.sliderFrame.style.transition = `all 0ms ${this.config.easing}`;\n\n const currentSlide = this.config.loop ? this.currentSlide + this.perPage : this.currentSlide;\n const currentOffset = currentSlide * (this.elementWidth / this.perPage);\n const dragOffset = (this.drag.endX - this.drag.startX);\n const offset = this.config.rtl ? currentOffset + dragOffset : currentOffset - dragOffset;\n this.sliderFrame.style.transform = `translate3d(${(this.config.rtl ? 1 : -1) * offset}px, 0, 0)`;\n }\n }\n\n mousedownHandler(e) {\n // Prevent dragging / swiping on inputs, selects and textareas\n const ignoreSiema = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].indexOf(e.target.nodeName) !== -1;\n if (ignoreSiema) {\n return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n this.pointerDown = true;\n this.drag.startX = e.pageX;\n }\n\n mouseupHandler(e) {\n e.stopPropagation();\n this.pointerDown = false;\n this.element.style.cursor = 'grab';\n this.enableTransition();\n if (this.drag.endX) {\n this.updateAfterDrag();\n }\n this.clearDrag();\n }\n\n mousemoveHandler(e) {\n e.preventDefault();\n if (this.pointerDown) {\n // if dragged element is a link\n // mark preventClick prop as a true\n // to detemine about browser redirection later on\n if (e.target.nodeName === 'A') {\n this.drag.preventClick = true;\n }\n\n this.drag.endX = e.pageX;\n this.element.style.cursor = 'grabbing';\n this.sliderFrame.style.transition = `all 0ms ${this.config.easing}`;\n\n const currentSlide = this.config.loop ? this.currentSlide + this.perPage : this.currentSlide;\n const currentOffset = currentSlide * (this.elementWidth / this.perPage);\n const dragOffset = (this.drag.endX - this.drag.startX);\n const offset = this.config.rtl ? currentOffset + dragOffset : currentOffset - dragOffset;\n this.sliderFrame.style.transform = `translate3d(${(this.config.rtl ? 1 : -1) * offset}px, 0, 0)`;\n }\n }\n\n mouseleaveHandler(e) {\n if (this.pointerDown) {\n this.pointerDown = false;\n this.element.style.cursor = 'grab';\n this.drag.endX = e.pageX;\n this.drag.preventClick = false;\n this.enableTransition();\n this.updateAfterDrag();\n this.clearDrag();\n }\n }\n\n clickHandler(e) {\n // if the dragged element is a link\n // prevent browsers from folowing the link\n if (this.drag.preventClick) {\n e.preventDefault();\n }\n this.drag.preventClick = false;\n }\n\n /**\n * Remove item from carousel.\n * @param {number} index - Item index to remove.\n * @param {function} callback - Optional callback to call after remove.\n */\n remove(index, callback) {\n if (index < 0 || index >= this.innerElements.length) {\n throw new Error('Item to remove doesn\\'t exist');\n }\n\n // Shift sliderFrame back by one item when:\n // 1. Item with lower index than currenSlide is removed.\n // 2. Last item is removed.\n const lowerIndex = index < this.currentSlide;\n const lastItem = this.currentSlide + this.perPage - 1 === index;\n\n if (lowerIndex || lastItem) {\n this.currentSlide--;\n }\n\n this.innerElements.splice(index, 1);\n\n // build a frame and slide to a currentSlide\n this.buildSliderFrame();\n\n if (callback) {\n callback.call(this);\n }\n }\n\n /**\n * Insert item to carousel at particular index.\n * @param {HTMLElement} item - Item to insert.\n * @param {number} index - Index of new new item insertion.\n * @param {function} callback - Optional callback to call after insert.\n */\n insert(item, index, callback) {\n if (index < 0 || index > this.innerElements.length + 1) {\n throw new Error('Unable to inset it at this index');\n }\n if (this.innerElements.indexOf(item) !== -1) {\n throw new Error('The same item in a carousel? Really? Nope');\n }\n\n // Avoid shifting content\n const shouldItShift = index <= this.currentSlide > 0 && this.innerElements.length;\n this.currentSlide = shouldItShift ? this.currentSlide + 1 : this.currentSlide;\n\n this.innerElements.splice(index, 0, item);\n\n // build a frame and slide to a currentSlide\n this.buildSliderFrame();\n\n if (callback) {\n callback.call(this);\n }\n }\n\n /**\n * Prepernd item to carousel.\n * @param {HTMLElement} item - Item to prepend.\n * @param {function} callback - Optional callback to call after prepend.\n */\n prepend(item, callback) {\n this.insert(item, 0);\n if (callback) {\n callback.call(this);\n }\n }\n\n /**\n * Append item to carousel.\n * @param {HTMLElement} item - Item to append.\n * @param {function} callback - Optional callback to call after append.\n */\n append(item, callback) {\n this.insert(item, this.innerElements.length + 1);\n if (callback) {\n callback.call(this);\n }\n }\n\n /**\n * Removes listeners and optionally restores to initial markup\n * @param {boolean} restoreMarkup - Determinants about restoring an initial markup.\n * @param {function} callback - Optional callback function.\n */\n destroy(restoreMarkup = false, callback) {\n this.detachEvents();\n\n this.element.style.cursor = 'auto';\n\n if (restoreMarkup) {\n const slides = document.createDocumentFragment();\n for (let i = 0; i < this.innerElements.length; i++) {\n slides.appendChild(this.innerElements[i]);\n }\n this.element.innerHTML = '';\n this.element.appendChild(slides);\n this.element.removeAttribute('style');\n }\n\n if (callback) {\n callback.call(this);\n }\n }\n\n /**\n * Creates the automated interval\n */\n initAutomation() {\n this.autoplay = window.setInterval(() => {\n if (this.currentSlide === this.innerElements.length - 1) return this.goTo(0);\n this.next();\n }, this.config.automatedTimer);\n\n this.element.addEventListener('touchstart', () => {\n this.autoplay = window.clearInterval(this.autoplay);\n });\n }\n\n /**\n * Sets the active class on the currently active dot\n */\n setActiveDot() {\n if (!this.dots) return;\n\n const activeDot = this.dots.find(el => el.classList.contains('is-active'));\n if (activeDot) activeDot.classList.remove('is-active');\n\n let activeDotIndex = this.currentSlide;\n\n if (this.config.slidePage && this.perPage > 1) {\n activeDotIndex = Math.ceil((this.currentSlide + 1) / this.perPage) - 1;\n if ((this.currentSlide + 1) % this.perPage !== 1) activeDotIndex += 1;\n }\n\n if (this.perPage > 1) {\n if (activeDotIndex < 0) activeDotIndex = 0;\n if (activeDotIndex > this.dots.length - 1) activeDotIndex = this.dots.length - 1;\n }\n\n this.dots[activeDotIndex].classList.add('is-active');\n }\n\n /**\n * Handles side-effects on change\n */\n change() {\n this.setActiveDot();\n\n // Disable buttons\n if (this.btnNext && !this.config.loop) {\n this.btnNext.disabled = false;\n if (this.currentSlide + this.perPage === this.innerElements.length) this.btnNext.disabled = true;\n }\n\n if (this.btnPrev && !this.config.loop) {\n this.btnPrev.disabled = false;\n if (this.currentSlide === 0) this.btnPrev.disabled = true;\n }\n\n // accessibility \n this.innerElements.forEach((slide, i) => {\n if (i < this.currentSlide || i > this.currentSlide + this.perPage - 1) {\n this.hideA11y(slide.parentElement);\n } \n else {\n this.showA11y(slide.parentElement);\n }\n if (this.config.loop) {\n this.loopSlidesPrepended.concat(this.loopSlidesAppended).forEach(loopSlide => this.hideA11y(loopSlide));\n if (this.currentSlide < 0) { \n for (let i = this.loopSlidesPrepended.length + this.currentSlide; i < this.loopSlidesPrepended.length; i++) {\n this.showA11y(this.loopSlidesPrepended[i]);\n }\n }\n else if (this.currentSlide > this.innerElements.length - this.perPage) {\n for (let i = 0; i <= this.perPage - (this.innerElements.length - this.currentSlide) - 1; i++) {\n this.showA11y(this.loopSlidesAppended[i]);\n }\n }\n }\n });\n }\n}\n\n// Attach to jquery if present\nif (window.jQuery) {\n $.fn.siema = function(options) {\n return this.each(function() {\n if (!$.data(this, 'siema')) {\n $.data(this, 'siema', new Siema({\n ...options,\n selector: this\n }));\n }\n });\n };\n}\n","'use strict'\n\nfunction noop() {\n\n}\n\nfunction offset(element, from) {\n return element.getBoundingClientRect()[from] + (window.scrollY || window.pageYOffset)\n}\n\nfunction rect(element, key) {\n return element.getBoundingClientRect()[key]\n}\n\n/**\n * @type {object} defaults\n */\nconst defaults = {\n top: 0, /* Where to stick from top */\n bottom: 0, /* Extra added to stopper selector */\n breakpoint: 0, /* Where to stop or start */\n resize: false, /* Handle resize with js, suited for sidebars and responsive */\n classOnly: true, /* Sticky only with css class with position fixed */\n elevation: false, /* If sticky element is bigger than screen, turn it off */\n fixClass: 'is-fixed', /* Default sticky class */\n selector: '.js-sticky-element', /* The element selector to stick */\n placeholder: true, /* If false, no placeholder is created */\n placeholderClass: 'sticky-element-placeholder', /* Placeholder css class */\n onBreakpointAbove: noop, /* function to be called when above breakpoint */\n onBreakpointBelow: noop /* function to be called when bellow breakpoint */\n}\n\n/**\n * @class StickyElement\n * @constructor\n * @param {object} options - plugin options\n * @param {HTMLElement} element - html element\n */\nfunction StickyElement(options, element) {\n this.options = Object.assign({}, defaults, options)\n this.element = element || document.querySelector(this.options.selector)\n this.stopper = document.querySelector(this.options.stopSelector)\n this.fixed = false\n this.ticking = false\n this.pinned = false\n\n if (this.options.placeholder) {\n this.placeholder = document.createElement('div')\n this.placeholder.className = this.options.placeholderClass\n }\n\n this.scrollTop = window.scrollY || window.pageYOffset\n this.initial = this.bounds()\n\n this.turnOn = this.turnOn.bind(this)\n this.turnOff = this.turnOff.bind(this)\n\n this.boundOnScroll = this.onScroll.bind(this)\n this.boundOnResize = this.onResize.bind(this)\n this.boundSwitchHandler = this.switchHandler.bind(this)\n\n this.boundBreakpointHandler = this.breakpointHandler.bind(this)\n this.onBreakpointAbove = this.options.onBreakpointAbove.bind(this)\n this.onBreakpointBelow = this.options.onBreakpointBelow.bind(this)\n\n this.turnOn()\n}\n\nStickyElement.prototype.bounds = function() {\n return {\n top: offset(this.element, 'top'),\n bottom: offset(this.element, 'bottom'),\n width: rect(this.element, 'width'),\n height: rect(this.element, 'height'),\n position: this.element.style.position\n }\n}\n\n\nStickyElement.prototype.css = function(def) {\n Object.keys(def).forEach(function(key) {\n this.element.style[key] = def[key]\n }, this)\n}\n\n\nStickyElement.prototype.addPlaceholder = function() {\n if (this.options.placeholder) {\n this.placeholder.style.height = this.initial.height + 'px'\n this.element.parentElement.insertBefore(this.placeholder, this.element)\n }\n}\n\n\nStickyElement.prototype.fix = function() {\n this.addPlaceholder()\n\n if (!this.options.classOnly) {\n this.css({\n position: 'fixed',\n top: this.options.top + 'px',\n width: this.initial.width + 'px'\n })\n }\n\n this.element.classList.add(this.options.fixClass)\n this.fixed = true\n}\n\n\nStickyElement.prototype.unfix = function() {\n if (document.body.contains(this.placeholder)) {\n this.placeholder.parentElement.removeChild(this.placeholder)\n }\n\n if (!this.options.classOnly) {\n this.css({\n position: this.initial.position,\n top: 'auto',\n width: 'auto'\n })\n }\n\n this.element.classList.remove(this.options.fixClass)\n this.fixed = false\n}\n\n\nStickyElement.prototype.pin = function() {\n this.css({\n transform: `translate3d(0, ${this.diff}px, 0)`\n })\n this.pinned = true\n}\n\n\nStickyElement.prototype.unpin = function() {\n this.css({\n transform: 'translate3d(0, 0, 0)'\n })\n this.pinned = false\n}\n\n\nStickyElement.prototype.requestTick = function(method) {\n if (!this.ticking) {\n window.requestAnimationFrame(method.bind(this))\n }\n this.ticking = true\n}\n\n\nStickyElement.prototype.update = function() {\n if (this.scrollTop + this.options.top >= this.initial.top) {\n if (!this.fixed) {\n this.fix()\n }\n }\n else {\n if (this.fixed) {\n this.unfix()\n }\n }\n\n if (this.stopper) {\n this.diff <= 0 ? this.pin() : this.unpin()\n }\n\n this.ticking = false\n}\n\n\nStickyElement.prototype.calcDiff = function() {\n if (this.stopper) {\n let { top, bottom } = this.options\n this.diff = rect(this.stopper, 'top') - rect(this.element, 'height') - top - bottom\n }\n}\n\n\nStickyElement.prototype.reset = function() {\n this.unfix()\n this.unpin()\n this.initial = this.bounds()\n}\n\n\nStickyElement.prototype.onScroll = function() {\n this.calcDiff()\n this.scrollTop = window.scrollY || window.pageYOffset\n this.requestTick(this.update)\n}\n\n\nStickyElement.prototype.onResize = function() {\n this.reset()\n this.calcDiff()\n this.requestTick(this.update)\n}\n\n\n/*\n * Turn Sticky Behaviour on/off according to breakpoint and elevation options\n */\nStickyElement.prototype.switchHandler = function() {\n let { top, bottom, breakpoint, elevation } = this.options\n let stickyHeight = Math.floor(this.bounds()['height']) + top + bottom\n let clientWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)\n let clientHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)\n\n if (breakpoint && elevation) {\n if ((clientWidth >= breakpoint) && (stickyHeight <= clientHeight)) {\n setTimeout(this.turnOn)\n return\n }\n\n\n if ((clientWidth <= breakpoint) && (stickyHeight >= clientHeight)) {\n setTimeout(this.turnOff)\n return\n }\n\n if ((clientWidth <= breakpoint) && (stickyHeight <= clientHeight)) {\n setTimeout(this.turnOff)\n return\n }\n\n if ((clientWidth >= breakpoint) && (stickyHeight >= clientHeight)) {\n setTimeout(this.turnOff)\n return\n }\n\n return\n }\n\n if (breakpoint) {\n setTimeout(clientWidth <= breakpoint ? this.turnOff : this.turnOn)\n return\n }\n\n if (elevation) {\n setTimeout(stickyHeight >= clientHeight ? this.turnOff : this.turnOn)\n return\n }\n}\n\n\n/*\n * Handle client attached handlers below/above breakpoint\n */\nStickyElement.prototype.breakpointHandler = function() {\n let { breakpoint } = this.options\n let clientWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)\n\n if (!breakpoint) return\n\n if (clientWidth >= breakpoint) {\n if (!this._breakpointAboveHandlerExecuted) {\n this.onBreakpointAbove()\n this._breakpointAboveHandlerExecuted = true\n this._breakpointBelowHandlerExecuted = false\n }\n }\n else {\n if (!this._breakpointBelowHandlerExecuted) {\n this.onBreakpointBelow()\n this._breakpointBelowHandlerExecuted = true\n this._breakpointAboveHandlerExecuted = false\n }\n }\n}\n\n\n/*\n * Turn ON sticky behaviour\n */\nStickyElement.prototype.turnOn = function() {\n if (this._switchedOn) return\n\n let { resize, breakpoint, elevation, onBreakpointBelow, onBreakpointAbove } = this.options\n\n this.boundOnScroll()\n this.boundOnResize()\n this.boundSwitchHandler()\n this.boundBreakpointHandler()\n\n window.addEventListener('scroll', this.boundOnScroll, false)\n if (resize) window.addEventListener('resize', this.boundOnResize, false)\n if (breakpoint || elevation) window.addEventListener('resize', this.boundSwitchHandler, false)\n if (breakpoint && (onBreakpointBelow !== noop || onBreakpointAbove !== noop)) window.addEventListener('resize', this.boundBreakpointHandler, false)\n\n this.reset()\n this._switchedOn = true\n}\n\n\n/*\n * Turn OFF sticky behaviour\n */\nStickyElement.prototype.turnOff = function() {\n if (!this._switchedOn) return\n\n window.removeEventListener('scroll', this.boundOnScroll, false)\n window.removeEventListener('resize', this.boundOnResize, false)\n window.removeEventListener('resize', this.boundBreakpointHandler, false)\n\n this.reset()\n this._switchedOn = false\n}\n\n/*\n * Factory\n */\nStickyElement.create = function(options) {\n options = options || {}\n return new StickyElement(options)\n}\n\n// Attach to jquery if presented\nif (window.jQuery) {\n $.fn.StickyElement = function(options) {\n return this.each(function() {\n if (!$.data(this, 'StickyElement')) {\n $.data(this, 'StickyElement', new StickyElement(options, this))\n }\n })\n }\n\n // lowercase, so we can initialize via data attr parser\n $.fn.stickyelement = $.fn.StickyElement;\n}\n\n// Attach to window\nwindow.StickyElement = StickyElement\n\n// DEFAULT EXPORT\nexport default StickyElement\n\n","(function (exports) {\n 'use strict';\n\n const rejson = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/;\n\n function isNumber(num) {\n var number = +num;\n\n if ((number - number) !== 0) {\n // Discard Infinity and NaN\n return false;\n }\n\n if (number === num) {\n return true;\n }\n\n if (typeof num === 'string') {\n // String parsed, both a non-empty whitespace string and an empty string\n // will have been coerced to 0. If 0 trim the string and see if its empty.\n if (number === 0 && num.trim() === '') {\n return false;\n }\n return true;\n }\n return false;\n }\n function hyphenToCamelCase(hyphen) {\n return hyphen.replace(/-([a-z])/g, function(match) {\n return match[1].toUpperCase();\n });\n }\n\n function stringToCamelCase(string) {\n return string.replace(/(?:^\\w|[A-Z]|\\b\\w)/g, function(letter, index) {\n return index == 0 ? letter.toLowerCase() : letter.toUpperCase();\n }).replace(/\\s+/g, '');\n }\n\n\n class Parser {\n static parse(dataset, _prefixes = []) {\n let result = {};\n const keys = Object.keys(dataset);\n const grouping = _prefixes.length;\n const prefixes = _prefixes.map(hyphenToCamelCase);\n\n for (let i = 0; i < keys.length; i++) {\n let key = keys[i];\n let value = dataset[key];\n\n if (value === 'true') {\n result[key] = true;\n continue;\n }\n\n if (value === 'false') {\n result[key] = false;\n continue;\n }\n\n if (isNumber(value)) {\n result[key] = Number(value);\n continue;\n }\n\n if (value === 'null') {\n result[key] = null;\n continue;\n }\n\n if (rejson.test(value)) {\n result[key] = JSON.parse(value);\n continue;\n }\n\n // @String\n result[key] = value;\n }\n\n // Group result object\n prefixes.forEach(prefix => {\n let subResult = {};\n\n Object.keys(result)\n .filter(key => key.startsWith(prefix))\n .forEach(key => {\n let slicedKey = stringToCamelCase(key.replace(prefix, ''));\n if (slicedKey === '') slicedKey = prefix;\n subResult[slicedKey] = result[key];\n delete result[key];\n });\n\n result[prefix] = subResult;\n });\n\n return result;\n }\n }\n\n const defaults = {\n maxPages: 10, // maximum total pages => example 100\n maxVisiblePages: 10, // maximum visible pages => example 10\n pageSize: 10, // maximum items per page\n totalItems: 0, // total items count on all pages\n showNextAndPrev: true,\n nextText: 'Next',\n prevText: 'Previous',\n pageURI: '', // example => \"search\", '/search', etc\n pageQueryKey: 'page',\n preserveQueryParams: true, // preserve current query params from current url in pager links\n wrapperClass: 'TK-Pager',\n nextWrapperClass: 'TK-Pager-Next',\n nextLinkClass: 'TK-Pager-Next-Link',\n prevWrapperClass: 'TK-Pager-Prev',\n prevLinkClass: 'TK-Pager-Prev-Link',\n pagesWrapperClass: 'TK-Pager-Links',\n pageLinkClass: 'TK-Pager-Link',\n callback: (instance, selectedPage) => console.log(selectedPage)\n };\n\n class Pager {\n constructor(element, options) {\n if (!(element instanceof HTMLElement)) {\n throw new Error('[Pager] DOM element is required for initialization');\n }\n\n this.wrapper = element;\n\n let dataset = Parser.parse(element.dataset, ['pager']);\n\n this.options = { ...defaults, ...dataset.pager, ...options };\n\n this._maxPages = Math.round(Number(this.options.maxPages));\n this._maxVisiblePages = Math.round(Number(this.options.maxVisiblePages));\n this._pageSize = Math.round(Number(this.options.pageSize));\n this._totalItems = Math.round(Number(this.options.totalItems));\n\n this._current = 1;\n\n this.wrapper.classList.add(this.options.wrapperClass);\n this.wrapper.addEventListener('click', this._delegatedPageClick.bind(this), false);\n }\n\n getActivePage() {\n return this._current;\n }\n\n setPage(page = 1) {\n this._current = Math.floor(Number(page));\n return this;\n }\n\n setMaxPages(n) {\n this._maxPages = Math.round(Number(n));\n return this;\n }\n\n setPageSize(n) {\n this._pageSize = Math.round(Number(n));\n return this;\n }\n\n setTotalItems(n) {\n this._totalItems = Math.round(Number(n));\n return this;\n }\n\n setMaxVisiblePages(n) {\n this._maxVisiblePages = Math.round(Number(n));\n return this;\n }\n\n calcTotalPages() {\n const { _pageSize, _totalItems } = this;\n let pages = Math.floor(_totalItems / _pageSize);\n\n if (_totalItems % _pageSize > 0) {\n pages++;\n }\n\n return Math.min(pages, this._maxPages);\n }\n\n calcAndSetTotalPages() {\n // this._maxPages = this.calcTotalPages();\n this._totalPages = this.calcTotalPages();\n return this;\n }\n\n hasPrev() {\n return this._current !== 1;\n }\n\n hasNext() {\n if (this._totalPages < this._maxPages) {\n return this._current < this._totalPages;\n }\n return this._current < this._maxPages;\n }\n\n getPageInterval(page = 1) {\n const index = this.getPageIntervalIndex(page);\n\n let interval = Array.from(Array(this._maxVisiblePages))\n .fill(index * this._maxVisiblePages)\n .map((n, i) => (n + i + 1));\n\n const lastPageIndex = interval.indexOf(this._totalPages);\n\n if (lastPageIndex >= 0) {\n interval.splice(lastPageIndex + 1);\n }\n\n return interval;\n }\n\n getPageIntervalIndex(page = 1) {\n return Math.floor((page - 1) / this._maxVisiblePages);\n }\n\n\n _html() {\n const { prevText, nextText, pageURI, pageQueryKey, preserveQueryParams, prevWrapperClass, nextWrapperClass, pagesWrapperClass, prevLinkClass, nextLinkClass, pageLinkClass } = this.options;\n\n const url = new URL(window.location.href);\n url.searchParams.delete(pageQueryKey);\n\n const page = preserveQueryParams && url.search ? `${pageURI}${url.search}&${pageQueryKey}` : `${pageURI}?${pageQueryKey}`;\n const interval = this.getPageInterval(this._current);\n\n const prevArrow = ``;\n const nextArrow = ``;\n\n const prev = `

`;\n const next = ``;\n\n const links = interval.map(n => n === this._current ?\n `${n}` :\n `${n}`\n ).join('');\n\n let html = '';\n\n // if (this.hasPrev()) {\n html += prev;\n // };\n\n html += `
${links}
`;\n\n // if (this.hasNext()) {\n html += next;\n // };\n\n return html;\n }\n\n empty() {\n this.wrapper.innerHTML = '';\n }\n\n render() {\n this.calcAndSetTotalPages();\n this.wrapper.innerHTML = this._html();\n this.wrapper.classList.toggle('TK-Pager--Has-Prev', this.hasPrev());\n this.wrapper.classList.toggle('TK-Pager--Has-Next', this.hasNext());\n }\n\n _delegatedPageClick(event) {\n const { target } = event;\n const match = target.nodeName.toLowerCase() === 'a' && Boolean(target.dataset.page);\n\n if (!match) return;\n\n event.preventDefault();\n\n const { callback } = this.options;\n const page = Number(target.dataset.page);\n\n if (typeof callback === 'function') {\n callback(this, page);\n }\n }\n }\n\n const defaults$1 = {\n key: '',\n value: '',\n display: '',\n tagClass: 'TK-Tag', /* css class for the tag */\n tagCloseClass: 'TK-Tag-X', /* css class for the close button in tag */\n };\n\n class Tag {\n constructor(options) {\n const { key, value, display } = options;\n\n if (!key) {\n throw new Error('[Tag] key is required');\n }\n\n if (!value) {\n throw new Error('[Tag] value is required');\n }\n\n if (!display) {\n throw new Error('[Tag] display name is required')\n }\n\n this.options = { ...defaults$1, ...options };\n\n this.key = key.trim(); // Key and value should be stored in its original form, otherwise the query does not work\n this.value = value.trim();\n this.display = display.trim();\n\n this.element = this.render();\n }\n\n render() {\n const { key, value, display } = this.valueOf();\n\n const span = document.createElement('span');\n const close = document.createElement('span');\n\n close.innerHTML = `✖`;\n close.dataset ? close.dataset.role = 'remove' : close.setAttribute('data-role', 'remove');\n close.classList.add(this.options.tagCloseClass);\n\n span.innerText = display;\n span.append(close);\n\n if (span.dataset) {\n span.dataset.key = key;\n span.dataset.value = value;\n span.dataset.display = display;\n }\n else {\n span.setAttribute('data-key', key);\n span.setAttribute('data-value', value);\n span.setAttribute('data-display', display);\n }\n\n span.classList.add(this.options.tagClass);\n\n return span;\n }\n\n valueOf() {\n return {\n key: this.key,\n value: this.value,\n display: this.display\n }\n }\n\n static validate(options) {\n const { key, value, display } = options;\n\n if (!key) {\n throw new Error('[Tag] key is required');\n }\n\n if (!value) {\n throw new Error('[Tag] value is required');\n }\n\n if (!display) {\n throw new Error('[Tag] display name is required')\n }\n }\n\n static create(options = defaults$1) {\n return new Tag(options);\n }\n }\n\n const strip = (html = '') => new DOMParser().parseFromString(html, 'text/html').body.textContent.trim();\n const escape = (html = '') => html ? document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML : html;\n const unescape = (html = '') => html ? new DOMParser().parseFromString(html, 'text/html').documentElement.textContent : html;\n\n const defaults$2 = {\n /*\n * Array of Tags for initial tags\n * Tag { key: String, value: String, display: String }\n */\n tags: [],\n\n /*\n * Input properties\n * If prefill is true it will prefill the input with the value from the query parameter\n */\n prefill: true,\n maxlength: 200, /* Max length for input */\n autofocus: false, /* Auto focus the input on initialization */\n placeholder: 'Looking for...', /* Placeholder for the input */\n\n /*\n * CSS Classes For Styling\n */\n tagClass: 'TK-Tag', /* css class for the tag */\n tagCloseClass: 'TK-Tag-X', /* css class for the close button in tag */\n tagsWrapperClass: 'TK-Tags-Wrapper', /* css class for div wrapping the tags */\n mainWrapperClass: 'TK-Tag-Input-Wrapper', /* css class for main wrapper, wraps all dom elements associated with this plugin */\n mainWrapperFocusClass: 'is-focus', /* css class to add when input is focused, class is added to the main wrapper */\n flexWrapperClass: 'TK-Tag-Input-Flex-Wrapper', /* css class for wrapper, wraps tags and input field */\n inputClass: 'TK-Tag-Input', /* css class for the input element */\n\n /*\n * Optional button to use\n */\n button: true, /* id selector to find associated button with the tag input */\n buttonText: '',\n buttonIcon: true,\n buttonIconSvg: '', /* add custom html for the button */\n buttonClass: 'TK-Tag-Input-Button', /* css class to add to the button */\n\n /*\n * User provided callback executed on enter press or button click\n * The callback is called with TagInput instance as first agrument\n */\n callback: (instance) => console.log(instance.valueOf())\n };\n\n\n const KEYS = {\n ENTER: 13,\n ESCAPE: 27,\n BACKSPACE: 8\n };\n\n\n class TagInput {\n constructor(input, options) {\n if (!input) {\n throw new Error('[TagInput] Input element is required for initialization');\n }\n\n if (input.nodeName.toLowerCase() !== 'input') {\n throw new Error('[TagInput] TagInput should be initialized with `input` element');\n }\n\n this.input = input;\n this.options = { ...defaults$2, ...options };\n\n this.tags = new Map();\n this._state = new Map();\n\n this.options.tags.map(t => this.tags.set(t.display, Tag.create(t)));\n\n this.wrapper = document.createElement('div');\n this.flexWrapper = document.createElement('div');\n this.tagsWrapper = document.createElement('div');\n\n this.button = document.createElement('button');\n this.button.type = 'button';\n this.button.ariaLabel = 'Click to search';\n\n if (this.options.buttonIcon) {\n this.button.innerHTML = this.options.buttonIconSvg;\n }\n\n if (this.options.buttonText) {\n this.button.innerHTML += `${this.options.buttonText}`;\n }\n\n /** @Handle initial options **/\n /** ========================================== **/\n if (this.options.placeholder) {\n this.input.placeholder = this.options.placeholder;\n }\n\n if (this.options.maxlength && parseInt(this.options.maxlength)) {\n this.input.maxLength = parseInt(this.options.maxlength);\n }\n\n if (this.options.autofocus) {\n this.input.focus();\n this.input.autofocus = true;\n }\n\n if (this.options.prefill) {\n this.prefill();\n }\n\n /** @Init **/\n /** ========================================== **/\n this._render();\n this.saveState();\n\n /** @Attach Events **/\n /** ========================================== **/\n\n this.wrapper.addEventListener('click', () => this.input.focus(), false);\n this.input.addEventListener('focus', () => this.wrapper.classList.add(this.options.mainWrapperFocusClass), false);\n this.input.addEventListener('blur', () => this.wrapper.classList.remove(this.options.mainWrapperFocusClass), false);\n\n this.input.addEventListener('keydown', this._onEnterPress.bind(this), false);\n this.options.button && this.button.addEventListener('click', this._onButtonClick.bind(this), false);\n\n this.tagsWrapper.addEventListener('click', this._delegatedTagRemove.bind(this), false);\n }\n\n _render() {\n const { input, button, wrapper, flexWrapper, tagsWrapper, options } = this;\n\n input.classList.add(options.inputClass);\n options.button && button.classList.add(options.buttonClass);\n wrapper.classList.add(options.mainWrapperClass);\n flexWrapper.classList.add(options.flexWrapperClass);\n tagsWrapper.classList.add(options.tagsWrapperClass);\n\n input.parentElement.insertBefore(wrapper, input);\n wrapper.append(flexWrapper);\n flexWrapper.append(tagsWrapper);\n flexWrapper.append(input);\n options.button && wrapper.append(button);\n\n this.tags.forEach(t => tagsWrapper.append(t.element));\n }\n\n _delegatedTagRemove(event) {\n const { target } = event;\n const match = target.nodeName.toLowerCase() === 'span' && target.classList.contains(this.options.tagCloseClass);\n\n if (!match) return;\n\n const { display } = target.parentElement.dataset;\n\n this.removeTag(display);\n }\n\n prefill() {\n const url = new URL(window.location.href);\n const params = url.searchParams;\n const value = unescape(params.get('q'));\n this.input.value = value;\n }\n\n _onEnterPress(event) {\n if (event.keyCode !== KEYS.ENTER) {\n return;\n }\n event.preventDefault();\n\n if (typeof this.options.callback === 'function') {\n this.options.callback(this);\n }\n }\n\n _onButtonClick(event) {\n event.preventDefault();\n\n if (typeof this.options.callback === 'function') {\n this.options.callback(this);\n }\n }\n\n addTag(options) {\n const tag = Tag.create(options);\n if (!this.tags.has(tag.display)) {\n this.tags.set(tag.display, tag);\n this.tagsWrapper.append(tag.element);\n }\n }\n\n removeTag(display) {\n if (!display) {\n throw new Error('Display name of the tag is required');\n }\n\n if (typeof display !== 'string') {\n throw new Error('Display name should be string');\n }\n\n if (this.tags.has(display)) {\n this.tags.get(display).element.remove();\n this.tags.delete(display);\n return true;\n }\n else {\n return false;\n }\n }\n\n removeAllTags() {\n this.tags.forEach(tag => this.removeTag(tag.display));\n }\n\n valueOf() {\n let tags = [];\n let value = this.input.value.trim();\n\n this.tags.forEach(t => tags.push(t.valueOf()));\n\n return { value, tags };\n\n /*\n * Do not use iterators for now\n */\n\n // return {\n // value: this.input.value.trim(),\n // tags: [...this.tags.values()].map(t => t.valueOf())\n // }\n }\n\n saveState() {\n const url = new URL(location.href);\n this._state.set(url.href, this.valueOf());\n }\n\n restoreState() {\n const url = new URL(location.href);\n\n if (!this._state.has(url.href)) {\n return;\n }\n\n const { tags } = this._state.get(url.href);\n\n this.removeAllTags();\n tags.forEach(tag => this.addTag(tag));\n }\n\n\n static create(input, options = defaults$2) {\n return new TagInput(input, options);\n }\n }\n\n const defaults$3 = {\n v2: { siteKey: '' },\n v3: { siteKey: '', action: 'search' },\n scriptSrc: 'https://www.google.com/recaptcha/api.js'\n };\n\n class Recaptcha {\n constructor(options) {\n const { v2, v3 } = options;\n\n if (v2 && !v2.siteKey) {\n throw new Error('[Recaptcha] \"v2\" site key is required');\n }\n\n if (v3) {\n if (!v3.siteKey) {\n throw new Error('[Recaptcha] \"v3\" site key is required');\n }\n if (!v3.action) {\n v3.action = defaults$3.v3.action;\n }\n }\n \n this.options = { ...defaults$3, ...options };\n\n this._script = null;\n this._scriptReady = false;\n this._scriptLoading = false;\n\n /** @var {Promise} - recaptcha script tag loaded promise */\n this._scriptLoaded = this._loadRecaptchaScript();\n\n /** Total time (in milliseconds) to wait for Recaptcha api to load (v2 only) */\n this._timeout = 3000;\n\n /** Interval time (in milliseconds) to test if Recaptcha api is loaded (v2 only) */\n this._delay = 0;\n this._retryDelay = 200;\n\n /** Init recaptcha v2 */\n this._recaptchaInit = this._recaptchaInit.bind(this);\n this._recaptchaRender = this._recaptchaRender.bind(this);\n this._recaptchaCallback = this._recaptchaCallback.bind(this);\n\n if (this.options.v2) {\n /** @var {Promise} - recaptcha v2 initialized promise */\n this._recaptchaInitialized = this._scriptLoaded.then(this._recaptchaInit);\n }\n }\n\n /*\n * @private\n * Load recaptcha javascript\n * Use only one script tag for \"v2\" and \"v3\"\n * Init automatically recaptcha \"v3\" if siteKey (v3) is provided\n */\n _loadRecaptchaScript() {\n const { scriptSrc } = this.options;\n const { siteKey } = this.options.v3;\n\n return new Promise((resolve, reject) => {\n if (this._scriptReady) {\n return grecaptcha.ready(resolve);\n }\n\n if (this._scriptLoading) {\n return reject(new Error('[Recaptcha] recaptcha script should be loaded only once'));\n }\n\n let script = document.createElement('script');\n\n script.defer = true;\n script.async = true;\n script.src = siteKey ? `${scriptSrc}?render=${siteKey}` : scriptSrc;\n script.id = `js-recaptcha-script-${Math.round(Math.random() * 10000)}-${Date.now()}`;\n\n script.addEventListener('load', (event) => {\n this._scriptReady = true;\n this._scriptLoading = false;\n return grecaptcha.ready(resolve);\n });\n\n script.addEventListener('error', (event) => {\n this._scriptReady = false;\n this._scriptLoading = false;\n return reject(new Error('[Recaptcha] script did not load'));\n });\n\n this._script = script;\n this._scriptLoading = true;\n\n document.body.append(script);\n });\n }\n\n /**\n * @private\n * This will render the widget when grecaptcha (v2) api is loaded\n * @return {Promise} - Promise containing the id of the recaptcha rendered widget\n */\n _recaptchaInit() {\n return new Promise((resolve, reject) => {\n if (!window.grecaptcha) {\n // check if we can try again according to timeout\n if (this._delay < this._timeout) {\n this._delay += this._retryDelay;\n setTimeout(this._recaptchaInit, this._retryDelay);\n }\n else {\n reject(new Error('Recaptcha script timeout'));\n }\n }\n else {\n grecaptcha.ready(() => {\n this._recaptchaRender();\n resolve(this._recaptchaId);\n });\n }\n });\n }\n\n /**\n * @private\n * Render Recaptcha v2 widget\n */\n _recaptchaRender() {\n const { siteKey } = this.options.v2;\n\n const recaptchaId = grecaptcha.render(this._script.id, {\n sitekey: siteKey,\n size: 'invisible',\n callback: this._recaptchaCallback\n });\n\n this._recaptchaId = recaptchaId;\n }\n\n /**\n * @private\n * User provided callback for recaptcha v2\n */\n _recaptchaCallback(token) {\n grecaptcha.reset(this._recaptchaId);\n this._script.dispatchEvent(new CustomEvent('token', { detail: { token } }));\n }\n\n /**\n * @private\n * Get Recaptcha v2 token\n */\n _getV2Token() {\n return this._recaptchaInitialized.then(() => {\n const { siteKey } = this.options.v2;\n\n grecaptcha.execute(this._recaptchaId);\n\n return new Promise((resolve, reject) => {\n const onToken = (event) => {\n const { token } = event.detail;\n this._script.removeEventListener('token', onToken);\n resolve(token);\n };\n\n this._script.addEventListener('token', onToken);\n });\n });\n }\n\n /**\n * @private\n * Get Recaptcha v3 token\n */\n _getV3Token() {\n const { action, siteKey } = this.options.v3;\n return this._scriptLoaded.then(() => grecaptcha.execute(siteKey, { action }));\n }\n\n /**\n * @public\n * @param {String} version - 'v2' || 'v3'\n * @return {Promise} - promise containing the token string\n */\n getToken(version) {\n if (version === 'v2') return this._getV2Token();\n if (version === 'v3') return this._getV3Token();\n return Promise.reject(new Error('Version \"v2\" or \"v3\" is required'));\n }\n }\n\n const SPAM_VALIDATION_ERROR = 'spam_validation';\n\n const defaults$4 = {\n recaptcha: {\n v2: false, // set object { siteKey: '' } to enable\n v3: false // set object { siteKey: '', action: '' } to enable\n },\n endpoint: '/search-v2/search/api',\n headers: {\n 'accept': 'application/json',\n 'content-type': 'application/json'\n },\n credentials: 'same-origin' // include cookies\n };\n\n class SearchApiService {\n constructor(options) {\n this.options = { ...defaults$4, ...options };\n\n const { v2, v3 } = this.options.recaptcha;\n\n if (v2 || v3) {\n this.recaptcha = new Recaptcha(this.options.recaptcha);\n }\n }\n\n /*\n * Do not use async/await for now\n */\n\n /**\n * @private\n */\n _fetch(payload) {\n const { endpoint, headers, credentials } = this.options;\n\n return fetch(endpoint, {\n method: 'POST',\n headers,\n credentials,\n body: JSON.stringify(payload)\n })\n .then(res => {\n if (res.ok) {\n return res.json().then(d => this._validateRecaptchaResponse(d));\n }\n else if (res.status == 400)\n {\n return res.json().then(d => this._validateRecaptchaResponse(d)).then(() => { throw new Error(res.statusText); });\n }\n else {\n throw new Error(res.statusText);\n }\n });\n }\n\n /**\n * @public\n * @param {Object} payload\n */\n fetch(payload) {\n if (this.recaptcha) {\n const { v2, v3 } = this.recaptcha.options;\n\n // if both recaptcha v2 and v3 are enabled\n if (v2 && v3) {\n return this.recaptcha.getToken('v3')\n .then(token => this._fetch({ ...payload, recaptchaToken: token, recaptchaVersion: 'v3' }))\n .catch(e => {\n if (e && e.message === SPAM_VALIDATION_ERROR) {\n return this.recaptcha.getToken('v2')\n .then(token => this._fetch({ ...payload, recaptchaToken: token, recaptchaVersion: 'v2' }));\n }\n\n throw e;\n });\n }\n\n // if only recaptcha v2 is enabled\n if (v2) {\n return this.recaptcha.getToken('v2')\n .then(token => this._fetch({ ...payload, recaptchaToken: token, recaptchaVersion: 'v2' }));\n }\n\n // if only recaptcha v3 is enabled\n if (v3) {\n return this.recaptcha.getToken('v3')\n .then(token => this._fetch({ ...payload, recaptchaToken: token, recaptchaVersion: 'v3' }));\n }\n }\n\n return this._fetch(payload);\n }\n\n _validateRecaptchaResponse(data) {\n if (this._isRecaptchaError(data)) {\n throw new Error(SPAM_VALIDATION_ERROR);\n }\n\n return data;\n }\n\n _isRecaptchaError(data) {\n if (Array.isArray(data)) {\n const recaptchaValidationItem = data.filter(x => x.key === 'captchaValidation')[0];\n if (recaptchaValidationItem && recaptchaValidationItem.errors) {\n return true;\n }\n }\n else if (data && data.ModelState && data.ModelState.captchaValidation) {\n return true;\n }\n\n return false;\n }\n }\n\n class SearchUrlService {\n /*\n * Search results page URL\n * @example -> `` -> Default is empty string -> The input is placed on the same page as the search results. It will append query parameters to the current page url.\n * @example -> `search` -> Relative child page path to the page where the input is placed -> www.example.com/page-with-tag-input/search.\n * @example -> `/search` -> Absolute page for the site -> www.example.com/search -> Will redirect to page with absolute url for the site.\n * @example -> `https://www.telerik.com/search` -> Other site, if the value starts with `http` or `https` it will redirect to another site\n */\n static generate({ uri = '', value = '', page = 0, tags = []}) {\n let url;\n const { origin, pathname } = window.location;\n\n if (!value) {\n throw new Error('Value should be provided');\n }\n\n if (typeof value !== 'string') {\n throw new Error('Value should be string');\n }\n\n if (value.length < 2) {\n throw new Error('Value should be minimum 2 characters')\n }\n\n // On the same page, only append query parameters to the current page\n if (uri === '') {\n url = new URL(`${origin}/${pathname}`);\n }\n\n // On child page relative to the current page\n if (!uri.startsWith('/')) {\n url = new URL(`${origin}/${pathname}/${uri}`);\n }\n\n // On absolute page for the same site\n if (uri.startsWith('/')) {\n url = new URL(`${origin}/${uri}`);\n }\n\n // On another website\n if (uri.startsWith('http')) {\n url = new URL(uri);\n }\n\n url.href = url.href.split('///').join('/').split('//').join('/').replace(':/', '://');\n\n url.searchParams.set('q', escape(value));\n\n tags.forEach(t => url.searchParams.append(t.key, t.value));\n\n // Set paging\n if (page > 0) {\n url.searchParams.set('page', page);\n }\n\n url.searchParams.sort();\n\n return url;\n }\n\n static parse(url = location.href) {\n const _url = new URL(url);\n const params = _url.searchParams;\n\n const result = {\n q: unescape(params.get('q')),\n filter: params.getAll('filter'),\n context: params.getAll('context'),\n page: Number(params.get('page')) || 1\n };\n\n return result;\n }\n\n static validate(url) {\n let result = false;\n\n try {\n new URL(url);\n result = true;\n }\n catch (error) {\n result = false;\n }\n\n return result;\n }\n\n static redirect(url) {\n if (!SearchUrlService.validate(url)) {\n throw new Error('Please provide valid URL');\n }\n\n window.location.href = url;\n }\n\n static push(url) {\n if (!SearchUrlService.validate(url)) {\n throw new Error('Please provide valid URL');\n }\n\n window.history.pushState(null, null, url.search);\n }\n\n static replace(url) {\n if (!SearchUrlService.validate(url)) {\n throw new Error('Please provide valid URL');\n }\n\n window.history.replaceState(null, null, url.search);\n }\n }\n\n const defaults$5 = {\n wrapperClass: 'TK-Search-Results-Spellcheck-Wrapper',\n didYouMeanText: 'Did you mean: {0}',\n };\n\n class SpellCheck {\n constructor(element, options) {\n if (!(element instanceof HTMLElement)) {\n throw new Error('[SpellCheck] DOM element is required for initialization');\n }\n\n this._spellCheckCorrectionWord = '';\n\n this.options = { ...defaults$5, ...options };\n\n this.wrapper = element;\n this.wrapper.classList.add(this.options.wrapperClass);\n\n this.container = document.createElement('div');\n this.render = this.render.bind(this);\n this.hide = this.hide.bind(this);\n }\n\n render(settings) {\n let newUrl = '';\n\n if (settings.redirect) {\n newUrl = SearchUrlService.generate({ value: this._spellCheckCorrectionWord, tags: settings.tags, page: 1, uri: settings.redirectUri });\n }\n else {\n newUrl = SearchUrlService.generate({ value: this._spellCheckCorrectionWord, tags: settings.tags, page: 1 });\n }\n \n const didYouMeanLabel = this.options.didYouMeanText.replace('{0}', `${escape(this._spellCheckCorrectionWord)}`);\n this.container.innerHTML = `${didYouMeanLabel} `;\n this.wrapper.innerHTML = '';\n this.wrapper.removeAttribute('hidden');\n this.wrapper.append(this.container);\n }\n\n hide() {\n this.wrapper.setAttribute('hidden', true); \n }\n\n setSpellCheckCorrectionWord(spellCheckCorrectionWord) {\n this._spellCheckCorrectionWord = spellCheckCorrectionWord;\n return this;\n }\n\n static create(element, options = defaults$5) {\n return new SpellCheck(element, options);\n }\n }\n\n const defaults$6 = {\n url: false,\n text: false,\n hashtag: false,\n popupWidth: 580,\n popupHeight: 400,\n wrapperClass: 'TK-Search-Social-Share',\n navClass: 'TK-Search-Social-Share-Nav',\n buttonClass: 'TK-Search-Social-Share-Button',\n buttonText: 'Share',\n networks: ['twitter', 'facebook', 'linkedin', 'reddit']\n };\n\n class SocialShare {\n static html(options) {\n const opts = { ...defaults$6, ...options };\n const { url, text, wrapperClass, navClass, buttonText, buttonClass, networks } = opts;\n\n const links = networks.map(media => {\n return ``;\n }).join('');\n\n const html = `\n \n `;\n\n return html;\n }\n\n static generateMediaURL({ media, url, text }) {\n let result = '';\n const _url = encodeURIComponent(url);\n const _text = encodeURIComponent(strip(text));\n\n switch (media) {\n case 'facebook':\n result = `http://www.facebook.com/sharer.php?u=${_url}`;\n break;\n case 'linkedin':\n result = `https://www.linkedin.com/shareArticle?mini=true&url=${_url}&summary=${_text}`;\n break;\n case 'reddit':\n result = `https://www.reddit.com/login?redirect=https://www.reddit.com/submit?url=${_url}`;\n break;\n case 'twitter':\n default:\n result = `https://twitter.com/share?url=${_url}&text=${_text}`;\n break;\n }\n\n return result;\n }\n\n static _delegatedToggleShare(event) {\n const { target } = event;\n const match = target.nodeName.toLowerCase() === 'button' && target.className === defaults$6.buttonClass;\n\n if (!match) {\n return;\n }\n\n target.previousSibling.classList.toggle('is-visible');\n\n event.preventDefault();\n event.stopPropagation();\n }\n\n static _delegatedOpenMediaPopup(event) {\n const { target } = event;\n const match = target.nodeName.toLowerCase() === 'a' && Boolean(target.dataset.media);\n\n if (!match) {\n return;\n }\n\n const { popupWidth, popupHeight } = defaults$6;\n\n const w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;\n const h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;\n\n const top = (h - popupHeight) / 2;\n const left = (w - popupWidth) / 2;\n\n const options = `status=1,width=${popupWidth},height=${popupHeight},top=${top},left=${left}`;\n\n event.preventDefault();\n event.stopPropagation();\n\n window.open(target.href, 'Share', options);\n }\n }\n\n const defaults$7 = {\n socialShare: false,\n socialShareButtonText: '',\n wrapperClass: 'TK-Search-Results-List-Wrapper',\n noResultsClass: 'TK-Search-Results-Zero',\n errorResultsClass: 'TK-Search-Results-Error',\n searchQueryClass: 'TK-Search-Results-Query',\n listClass: 'TK-Search-Results-List',\n listItemClass: 'TK-Search-Results-List-Item',\n itemHeadingClass: 'TK-Search-Results-List-Item-H',\n itemDescriptionClass: 'TK-Search-Results-List-Item-P',\n itemLastLinkClass: 'TK-Search-Results-List-Item-A',\n tryMoreGeneralText: 'Try more general keywords.',\n tryDiffKeywordText: 'Try different keywords.',\n goBackText: 'Go back',\n allWordsSpelledCorrectText: 'Make sure all words are spelled correctly.',\n searchDidNotMatchText: `Your search \"{0}\" did not match any documents. Search suggestions:`,\n somethingsNotRightText: \"Oh, no! Something's not right, but we're sorting it out.\"\n };\n\n class SearchResultsList {\n constructor(element, options) {\n if (!(element instanceof HTMLElement)) {\n throw new Error('[SearchResultsList] DOM element is required for initialization');\n }\n\n this.options = { ...defaults$7, ...options };\n\n this.wrapper = element;\n this.wrapper.classList.add(this.options.wrapperClass);\n\n this.list = document.createElement('ul');\n this.list.classList.add(this.options.listClass);\n this.list.addEventListener('click', this._resultClickHandler.bind(this), false);\n\n if (this.options.socialShare) {\n this.socialShare = true;\n this.wrapper.addEventListener('click', SocialShare._delegatedToggleShare);\n this.wrapper.addEventListener('click', SocialShare._delegatedOpenMediaPopup);\n }\n\n this.render = this.render.bind(this);\n this._itemHTML = this._itemHTML.bind(this);\n }\n\n empty() {\n this.list.innerHTML = '';\n this.wrapper.innerHTML = '';\n }\n\n render(options) {\n const { results, query } = options;\n\n if (options instanceof Error) {\n this.wrapper.innerHTML = this._errorResultsHTML();\n return;\n }\n\n if (!results) {\n throw new Error('[SearchResultsList] \"options.results\" is required');\n }\n\n if (!Array.isArray(results)) {\n throw new Error('[SearchResultsList] \"options.results\" should be array');\n }\n\n const html = results.map(this._itemHTML).join('');\n\n this.list.innerHTML = '';\n this.wrapper.innerHTML = '';\n\n if (results.length) {\n this.list.innerHTML = html;\n this.wrapper.append(this.list);\n }\n else {\n this.wrapper.innerHTML = this._noResultsHTML({ query });\n }\n }\n\n _itemHTML(options) {\n const { title, description, url } = options;\n const { listItemClass, itemHeadingClass, itemDescriptionClass, itemLastLinkClass, socialShareButtonText } = this.options;\n const socialShareHTML = this.socialShare && SocialShare.html({ url, text: title, buttonText: socialShareButtonText }) || '';\n\n const html = `\n
  • \n

    ${title}

    \n

    ${description}

    \n
    \n ${url}\n ${socialShareHTML}\n
    \n
  • \n `;\n\n const result = html.trim().split('\\n').map(x => x.trim()).join('');\n\n return result;\n }\n\n _noResultsHTML(options) {\n const { query } = options;\n const { noResultsClass, searchQueryClass, tryMoreGeneralText, allWordsSpelledCorrectText, tryDiffKeywordText, goBackText } = this.options;\n const searchDidNotMatchLabel = this.options.searchDidNotMatchText.replace('{0}', `${escape(query)}`);\n\n const html = `\n
    \n

    ${searchDidNotMatchLabel}

    \n
      \n
    • ${allWordsSpelledCorrectText}
    • \n
    • ${tryDiffKeywordText}
    • \n
    • ${tryMoreGeneralText}
    • \n
    \n
    \n ${goBackText}\n
    \n `;\n\n const result = html.trim().split('\\n').map(x => x.trim()).join('');\n\n return result;\n }\n\n _errorResultsHTML() {\n const { errorResultsClass, somethingsNotRightText, allWordsSpelledCorrectText, tryDiffKeywordText, tryMoreGeneralText, goBackText } = this.options;\n\n const html = `\n
    \n

    ${somethingsNotRightText}

    \n
      \n
    • ${allWordsSpelledCorrectText}
    • \n
    • ${tryDiffKeywordText}
    • \n
    • ${tryMoreGeneralText}
    • \n
    \n
    \n ${goBackText}\n
    \n `;\n\n const result = html.trim().split('\\n').map(x => x.trim()).join('');\n\n return result;\n }\n\n _resultClickHandler(event) {\n const target = event.target;\n if (target.nodeName.toLowerCase() !== 'a') {\n return;\n }\n\n const eventData = {\n text: target.innerText,\n url: target.href\n };\n\n this.wrapper.dispatchEvent(new CustomEvent('search-result-open', { detail: eventData }));\n }\n }\n\n const defaults$8 = {\n wrapperClass: 'TK-Search-Results-Count-Wrapper',\n searchQueryClass: 'TK-Search-Results-Query',\n maxPageSize: 10,\n resultForCurrentSearchText: `{0}-{1} of {2} results for \"{3}\"`\n };\n\n class SearchResultsCount {\n constructor(element, options) {\n if (!(element instanceof HTMLElement)) {\n throw new Error('[SearchResultsCount] DOM element is required for initialization');\n }\n\n this._totalResultsCount = 0;\n this._pageSize = 0;\n this._searchQuery = '';\n\n this.options = { ...defaults$8, ...options };\n\n this.wrapper = element;\n this.wrapper.classList.add(this.options.wrapperClass);\n\n this.container = document.createElement('div');\n this.render = this.render.bind(this);\n }\n\n empty() {\n this.wrapper.innerHTML = '';\n }\n\n render(page) {\n this.container.innerHTML = this._generateInnerHtml(page);\n\n this.wrapper.innerHTML = '';\n\n if (this._totalResultsCount > 0) {\n this.wrapper.append(this.container);\n }\n }\n\n setTotalResultsCount(totalResultsCount) {\n this._totalResultsCount = totalResultsCount;\n return this;\n }\n\n setPageSize(pageSize) {\n if (pageSize > this.options.maxPageSize) {\n this._pageSize = this.options.maxPageSize;\n }\n\n this._pageSize = pageSize;\n return this;\n }\n\n setSearchQuery(searchQuery) {\n const encodedSearchQuery = escape(searchQuery);\n this._searchQuery = encodedSearchQuery;\n return this;\n }\n\n _calcRange(page) {\n let leftPart = -1;\n let rightPart = -1;\n\n if (this._totalResultsCount < this.options.maxPageSize) {\n leftPart = 1;\n rightPart = this._totalResultsCount;\n }\n else {\n leftPart = (page - 1) * this._pageSize + 1;\n rightPart = page * this._pageSize;\n }\n\n return {\n leftPart: leftPart,\n rightPart: rightPart\n };\n }\n\n _generateInnerHtml(page) {\n let html = '';\n\n if (this._totalResultsCount > 0) {\n let range = this._calcRange(page);\n\n const resultsCountLabel = this.options.resultForCurrentSearchText\n .replace(\"{0}\", range.leftPart)\n .replace(\"{1}\", range.rightPart)\n .replace(\"{2}\", this._totalResultsCount)\n .replace(\"{3}\", `${this._searchQuery}`);\n\n html = resultsCountLabel;\n }\n\n return html;\n }\n\n static create(element, options = defaults$8) {\n return new SearchResultsCount(element, options);\n }\n }\n\n window.dataLayer = window.dataLayer || [];\n\n class TrackingEvent {\n constructor(name) {\n this.name = name;\n this.category = null;\n this.action = null;\n this.label = null;\n }\n\n valueOf() {\n const eventObject = {\n 'event': this.name,\n 'eventCategory': this.category,\n 'eventAction': this.action\n };\n\n if (this.label) {\n eventObject.eventLabel = this.label;\n }\n\n return eventObject;\n }\n }\n\n class SearchEventBase extends TrackingEvent {\n constructor(category) {\n super('search');\n this.category = category;\n }\n\n stringifyTags(tags) {\n if (!tags || tags.length === 0) {\n return '';\n }\n\n const tagsString = tags.map(t => `${t.key}: ${t.display}`).join();\n\n return tagsString;\n }\n }\n\n class SearchEvent extends SearchEventBase {\n constructor(tags, query) {\n super('website-search-terms');\n\n this.action = this.stringifyTags(tags);\n this.label = query;\n }\n }\n\n class SearchResultEvent extends SearchEventBase {\n constructor(tags, query, result) {\n super('website-search-result');\n\n this.action = query;\n\n const serializedTags = this.stringifyTags(tags);\n if (serializedTags) {\n this.action = `${serializedTags} - ${this.action}`;\n }\n\n this.label = result;\n }\n }\n\n class EventTracker {\n constructor() {\n this._dataLayer = window.dataLayer;\n }\n\n track(event) {\n if (!event) {\n return;\n }\n\n var eventObject = event.valueOf();\n\n this._dataLayer.push(eventObject);\n }\n }\n\n document.createElement('tk-site-search');\n\n const defaults$9 = {\n endpoint: '/webapi/search/do' // c# backend api\n };\n\n const scrollingTypes = ['auto', 'instant', 'smooth'];\n\n class SiteSearchElement {\n constructor(element, options) {\n if (!(element instanceof HTMLElement)) {\n throw new Error('[SiteSearchElement] DOM element is required for initialization');\n }\n\n this.element = element;\n this.element.dataset.version = '3.9.4';\n this.container = document.querySelector(element.dataset.containerSelector) || this.element; // Where to append additional elements (results, pager, etc...)\n\n let dataset = Parser.parse(element.dataset, ['list', 'pager', 'tag-input', 'recaptcha-v2', 'recaptcha-v3', 'spellcheck']);\n\n if (!Array.isArray(dataset.tagInput.tags)) {\n dataset.tagInput.tags = [];\n }\n\n this.options = { ...defaults$9, ...dataset, ...options };\n\n if (this.options.scrollIntoView) {\n if (scrollingTypes.indexOf(this.options.scrollIntoViewBehavior) === -1) {\n this.options.scrollIntoViewBehavior = scrollingTypes[0];\n }\n }\n\n this.input = document.createElement('input');\n this.input.type = 'search';\n this.element.append(this.input);\n\n this.tagInput = new TagInput(this.input, {\n ...this.options.tagInput,\n callback: (tagInput) => this._onSearch(tagInput)\n });\n\n if (this.options.hasCount) {\n this.resultsCountWrapper = document.createElement('div');\n this.container.append(this.resultsCountWrapper);\n this.resultsCount = new SearchResultsCount(this.resultsCountWrapper, this.options.list);\n }\n\n if (this.options.hasSpellcheck) {\n this.spellCheckWrapper = document.createElement('div');\n this.container.append(this.spellCheckWrapper);\n this.spellcheck = new SpellCheck(this.spellCheckWrapper, this.options.spellcheck);\n }\n\n if (this.options.hasList) {\n this.listWrapper = document.createElement('div');\n this.container.append(this.listWrapper);\n this.list = new SearchResultsList(this.listWrapper, this.options.list);\n this.listWrapper.addEventListener('search-result-open', this._onSearchResultOpen.bind(this), false);\n }\n\n if (this.options.hasPager) {\n this.pagerWrapper = document.createElement('div');\n this.container.append(this.pagerWrapper);\n this.pager = new Pager(this.pagerWrapper, {\n ...this.options.pager,\n callback: (pager, page) => this._onPageChanged(pager, page)\n });\n }\n\n const recaptchaOptions = {\n v2: this.options.hasRecaptchaV2 ? this.options.recaptchaV2 : false,\n v3: this.options.hasRecaptchaV3 ? this.options.recaptchaV3 : false\n };\n\n if (this.options.recaptchaScriptSrc) {\n recaptchaOptions.scriptSrc = this.options.recaptchaScriptSrc;\n }\n \n this.api = new SearchApiService({\n endpoint: this.options.endpoint,\n recaptcha: recaptchaOptions\n });\n\n if (this.list && this.pager) {\n this.tagInput.saveState();\n history.scrollRestoration = 'manual';\n window.addEventListener('popstate', (event) => this._onPopState(event));\n }\n\n this.eventTracker = new EventTracker();\n\n this._onInitialRender();\n }\n\n /*\n * Executed on initial render, on popstate event and on page changed\n */\n _onInitialRender() {\n const payloadFromURL = SearchUrlService.parse(location.href);\n const { q, page } = payloadFromURL;\n\n if (!q) {\n this._empty();\n return;\n }\n\n this.list && this.pager && this.api\n .fetch(payloadFromURL)\n .then(data => this._onOkApiFetch(data, page))\n .catch(error => this._onFailApiFetch(error))\n .finally(() => this._scrollIntoView());\n }\n\n /*\n * @param `widget` is TagInput or Pager instance\n * @param `page` is selected page if widget is instace of Pager\n * Executed on user interaction with TagInput or Pager\n */\n _onSearch(tagInput) {\n const { redirect, redirectUri } = this.options;\n const { value, tags } = this.tagInput.valueOf();\n\n if (value.length < 2) {\n console.warn('[SiteSearchElement] # _onSearch => TagInput value should be at least 2 characters => Early Exit');\n return;\n }\n\n this.eventTracker.track(new SearchEvent(tags, value));\n\n if (redirect) {\n SearchUrlService.redirect(SearchUrlService.generate({ value, tags, uri: redirectUri }));\n return;\n }\n\n const payload = {\n // page: 1,\n q: value,\n filter: tags.filter(t => t.key === 'filter').map(t => t.value),\n context: tags.filter(t => t.key === 'context').map(t => t.value)\n };\n\n SearchUrlService.push(SearchUrlService.generate({ value, tags }));\n\n this.tagInput.saveState();\n\n this.list && this.pager && this.api\n .fetch(payload)\n .then(data => this._onOkApiFetch(data, 1))\n .catch(error => this._onFailApiFetch(error))\n .finally(() => this._scrollIntoView());\n }\n\n _onPageChanged(pager, page = 1) {\n let url = new URL(location.href);\n url.searchParams.set('page', page);\n SearchUrlService.push(url);\n this.tagInput.prefill();\n this.tagInput.saveState();\n this._onInitialRender();\n }\n\n _onOkApiFetch(data, page) {\n const query = data.SearchQuery;\n const pageSize = data.PageSize;\n const totalResults = data.TotalResults;\n const spellCheckPhrase = data.SpellCheckPhrase;\n\n if (Array.isArray(data)) {\n if (data[0].errors) {\n throw new Error(data[0].errors[0]);\n }\n }\n\n const results = data.SearchResultData.map(x => ({\n url: x.Url,\n title: x.Title,\n description: x.Summary,\n query: data.SearchQuery\n }));\n\n if (this.resultsCount) {\n this.resultsCount\n .setPageSize(pageSize)\n .setTotalResultsCount(totalResults)\n .setSearchQuery(query)\n .render(page);\n }\n\n if (this.spellcheck) {\n if (spellCheckPhrase && page === 1) {\n const { tags } = this.tagInput.valueOf();\n const { redirect, redirectUri } = this.options;\n\n this.spellcheck\n .setSpellCheckCorrectionWord(spellCheckPhrase)\n .render({ redirect, redirectUri, tags });\n }\n else {\n this.spellcheck.hide();\n }\n }\n\n if (this.list) {\n this.list.render({ results, query });\n }\n\n if (this.pager) {\n if (totalResults <= pageSize) {\n this.pager.empty();\n }\n else {\n this.pager\n .setPageSize(pageSize)\n .setTotalItems(totalResults)\n .setPage(page)\n .render();\n }\n }\n\n this.element.dispatchEvent(new CustomEvent('search', { detail: { query, pageSize, totalResults, spellCheckPhrase, results } }));\n }\n\n _onFailApiFetch(error) {\n if (this.list) {\n this.list.render(error);\n }\n\n this.element.dispatchEvent(new CustomEvent('error', { detail: { error } }));\n }\n\n _onPopState(event) {\n this.tagInput.prefill();\n this.tagInput.restoreState();\n this._onInitialRender();\n }\n\n _scrollIntoView() {\n const { scrollIntoView, scrollIntoViewBehavior } = this.options;\n\n if (scrollIntoView) {\n this.element.parentElement.scrollIntoView({ behavior: scrollIntoViewBehavior, block: 'start' });\n }\n }\n\n _empty() {\n this.resultsCount && this.resultsCount.empty();\n this.spellcheck && this.spellcheck.hide();\n this.list && this.list.empty();\n this.pager && this.pager.empty();\n }\n\n _onSearchResultOpen(event) {\n const { value, tags } = this.tagInput.valueOf();\n this.eventTracker.track(new SearchResultEvent(tags, value, event.detail.url));\n }\n }\n\n /*\n * Attached to window with rollup iife\n *\n * window.biz.Pager\n * window.biz.TagInput\n * window.biz.Recaptcha\n * window.biz.SiteSearchElement\n * window.biz.SearchApiService\n * window.biz.SearchUrlService\n */\n\n exports.Pager = Pager;\n exports.TagInput = TagInput;\n exports.Recaptcha = Recaptcha;\n exports.SearchApiService = SearchApiService;\n exports.SearchUrlService = SearchUrlService;\n exports.SiteSearchElement = SiteSearchElement;\n\n}((this.biz = this.biz || {})));\n//# sourceMappingURL=index.es6.js.map\n","(function (biz) {\n 'use strict';\n\n (() => {\n /*\n * Init Custom Elements\n */\n const elements = Array.from(document.querySelectorAll('tk-site-search'));\n const searches = elements.map(element => new biz.SiteSearchElement(element));\n })();\n\n}(biz));\n//# sourceMappingURL=init.es6.js.map\n","// YouTube Iframe API Player States enum\r\n// Reference:\r\n// https://developers.google.com/youtube/iframe_api_reference#:~:text=of%20the%20player.-,Possible%20values%20are%3A,-%2D1%20%E2%80%93%20unstarted\r\nconst playerStates = {\r\n ENDED: 0,\r\n PAUSED: 2,\r\n};\r\n\r\nconst defaultOptions = {\r\n activeClass: 'is-active',\r\n wrapperClass: 'pip-wrapper',\r\n placeholderClass: 'pip-placeholder',\r\n};\r\n\r\nclass YTPictureInPicture {\r\n constructor(videoElement, player, options) {\r\n this.options = {\r\n ...defaultOptions,\r\n ...options,\r\n };\r\n\r\n this.videoElement = videoElement;\r\n this.player = player;\r\n this.isActive = false;\r\n\r\n this.createWrapper();\r\n this.createControls();\r\n\r\n let intersectionOptions = {\r\n threshold: 0.1,\r\n };\r\n\r\n const callback = (entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting) {\r\n this.deactivate(false);\r\n return;\r\n }\r\n\r\n this.activate();\r\n });\r\n };\r\n\r\n const observer = new IntersectionObserver(callback, intersectionOptions);\r\n\r\n observer.observe(this.placeholder);\r\n\r\n window.dispatchEvent(new Event('YTPictureInPicture_initialized'));\r\n }\r\n\r\n createControls() {\r\n this.closeBtn = document.createElement('button');\r\n this.closeBtn.classList.add('pip-close-btn');\r\n this.closeBtn.setAttribute('type', 'button');\r\n this.closeBtn.addEventListener('click', this.deactivate.bind(this, true));\r\n this.wrapper.append(this.closeBtn);\r\n }\r\n\r\n createWrapper() {\r\n this.wrapper = document.createElement('div');\r\n this.wrapper.classList.add(this.options.wrapperClass);\r\n this.placeholder = document.createElement('div');\r\n this.placeholder.classList.add(this.options.placeholderClass);\r\n const paddingBottom =\r\n this.videoElement.offsetHeight / this.videoElement.offsetWidth;\r\n this.placeholder.style.paddingBottom = `${paddingBottom * 100}%`;\r\n this.placeholder.append(this.wrapper);\r\n this.videoElement.parentNode.insertBefore(\r\n this.placeholder,\r\n this.videoElement\r\n );\r\n this.wrapper.appendChild(this.videoElement);\r\n }\r\n\r\n activate() {\r\n if (this.player.getPlayerState() === playerStates.ENDED \r\n || this.player.getPlayerState() === playerStates.PAUSED) return;\r\n this.wrapper.classList.add(this.options.activeClass);\r\n this.isActive = true;\r\n }\r\n\r\n deactivate(shouldPause = false) {\r\n this.wrapper.classList.remove(this.options.activeClass);\r\n this.isActive = false;\r\n if (shouldPause && this.player && this.player.pauseVideo) {\r\n this.player.pauseVideo();\r\n }\r\n }\r\n}\r\n\r\n// expose in window\r\nwindow.YTPictureInPicture = YTPictureInPicture;\r\n\r\nexport default YTPictureInPicture;\r\n","import YTPictureInPicture from './youtube-picture-in-picture.mjs';\r\nimport { DataAttrParser } from '@progress-wad/data-attr-parser/index.mjs';\r\n\r\nconst defaultOptions = {\r\n btnClass: 'yl-btn',\r\n activeClass: 'is-active',\r\n};\r\n\r\nclass YouTubeLiteEmbed {\r\n constructor(element, options) {\r\n this.element = element;\r\n this.options = {\r\n ...defaultOptions,\r\n ...options,\r\n ...DataAttrParser.parse(element.dataset, [ 'youtube' ]).youtube\r\n };\r\n\r\n this.videoId = this.options.id;\r\n\r\n // Generate unique id for the video instance, necessary for the iframe API init\r\n this.id = `video_${crypto.randomUUID().slice(0, 4)}`;\r\n\r\n this.iframe = document.createElement('div');\r\n this.iframe.id = this.id;\r\n this.element.append(this.iframe);\r\n\r\n this.params = {\r\n ...Object.fromEntries(\r\n new URLSearchParams(this.options.params || [])\r\n ),\r\n autoplay: 1,\r\n createdByApi: 1\r\n };\r\n\r\n this.addThumbnail();\r\n this.addPlayButton();\r\n this.attachEventListeners();\r\n }\r\n\r\n static addPreconnect(url) {\r\n const linkEl = document.createElement('link');\r\n linkEl.rel = 'preconnect';\r\n linkEl.href = url;\r\n document.head.append(linkEl);\r\n }\r\n\r\n static preconnectToDomains() {\r\n if (YouTubeLiteEmbed.arePreconnectsAdded) return;\r\n YouTubeLiteEmbed.addPreconnect('https://www.youtube.com');\r\n YouTubeLiteEmbed.addPreconnect('https://www.google.com');\r\n YouTubeLiteEmbed.arePreconnectsAdded = true;\r\n }\r\n\r\n static loadIframeAPI() {\r\n if (YouTubeLiteEmbed.iframeAPILoaded) return;\r\n\r\n let existingHandler = () => {};\r\n\r\n if (window.onYouTubeIframeAPIReady && typeof window.onYouTubeIframeAPIReady === 'function') {\r\n existingHandler = window.onYouTubeIframeAPIReady;\r\n }\r\n\r\n window.onYouTubeIframeAPIReady = () => {\r\n YouTubeLiteEmbed.iframeAPILoaded = true;\r\n window.dispatchEvent(new Event('YouTubeIframeAPIReady'));\r\n existingHandler();\r\n };\r\n\r\n const tag = document.createElement('script');\r\n tag.src = 'https://www.youtube.com/iframe_api';\r\n document.head.append(tag);\r\n }\r\n\r\n attachEventListeners() {\r\n this.element.addEventListener(\r\n 'pointerover',\r\n YouTubeLiteEmbed.preconnectToDomains,\r\n { once: true }\r\n );\r\n\r\n this.element.addEventListener('click', this.clickEventHandler.bind(this), {\r\n once: true,\r\n });\r\n }\r\n\r\n addThumbnail() {\r\n if (!this.options.poster || this.options.poster.toLowerCase() === 'hd') {\r\n const quality = this.options.poster === 'hd' ? 'maxresdefault' : 'hqdefault';\r\n this.element.style.backgroundImage = `url(https://i.ytimg.com/vi/${this.videoId}/${quality}.jpg)`;\r\n return;\r\n }\r\n\r\n this.element.style.backgroundImage = `url(${this.options.poster})`;\r\n }\r\n\r\n addPlayButton() {\r\n let btnElement = document.createElement('button');\r\n btnElement.type = 'button';\r\n btnElement.classList.add(this.options.btnClass);\r\n btnElement.setAttribute(\r\n 'aria-label',\r\n `Play video${this.options.label ? `: ${this.options.label}` : ''}`\r\n );\r\n this.element.append(btnElement);\r\n }\r\n\r\n onPlayerReady() {\r\n this.play();\r\n this.player.getIframe().player = this.player;\r\n }\r\n\r\n createPlayer() {\r\n this.player = new YT.Player(this.id, {\r\n videoId: this.videoId,\r\n playerVars: this.params,\r\n events: {\r\n onReady: this.onPlayerReady.bind(this)\r\n }\r\n });\r\n\r\n if (this.options.behaviour && this.options.behaviour.toLowerCase() === 'pip') {\r\n window.YTPictureInPicture.activeInstance = new YTPictureInPicture(this.element, this.player);\r\n }\r\n }\r\n\r\n play() {\r\n if (this.player.playVideo) this.player.playVideo();\r\n this.element.classList.add(this.options.activeClass);\r\n this.iframe.focus();\r\n }\r\n\r\n pause() {\r\n this.player.pauseVideo();\r\n this.element.classList.remove(this.options.activeClass);\r\n }\r\n\r\n clickEventHandler() {\r\n if (!YouTubeLiteEmbed.iframeAPILoaded) {\r\n window.addEventListener('YouTubeIframeAPIReady', this.createPlayer.bind(this));\r\n YouTubeLiteEmbed.loadIframeAPI();\r\n return;\r\n }\r\n\r\n this.createPlayer();\r\n }\r\n}\r\n\r\nexport default YouTubeLiteEmbed;\r\n","import YouTubeLiteEmbed from './youtube-lite-embed.mjs';\r\n\r\nwindow.addEventListener('DOMContentLoaded', () => {\r\n document.querySelectorAll('[data-youtube-id]').forEach((element) => {\r\n new YouTubeLiteEmbed(element);\r\n });\r\n});\r\n\r\n","const defaults = {\n stopPropagation: false,\n placeholder: 'Type to filter',\n wrapperClass: 'select-ui-wrapper',\n detailsClass: 'select-ui-details',\n summaryClass: 'select-ui-summary',\n inputClass: 'select-ui-input',\n listClass: 'select-ui-list',\n listItemClass: 'select-ui-list-item',\n listItemSelectedClass: 'is-selected',\n errorClass: 'select-ui-error',\n errorText: 'No items found',\n markTag: 'mark',\n};\n\nconst KEYS = {\n UP: 38,\n DOWN: 40,\n TAB: 9,\n ENTER: 13,\n SPACE: 32,\n SHIFT: 16,\n ESCAPE: 27,\n};\n\nconst ELEMENT_SCROLL_INTO_VIEW_SUPPORT = !!Element.prototype.scrollIntoView;\nconst ELEMENT_SCROLL_INTO_VIEW_IF_NEEDED_SUPPORT = !!Element.prototype.scrollIntoViewIfNeeded;\n\n/**\n * @class Select - Select UI class\n */\nexport class Select {\n /**\n * @param {HTMLSelectElement} select - select element\n * @param {Object} [settings=defaults] - settings object\n */\n constructor(select, settings) {\n if (!select || !(select instanceof HTMLSelectElement)) {\n throw new Error('select is required for initialization');\n }\n\n this.settings = Object.assign({}, defaults, settings);\n\n this.select = select;\n this.keyMap = new Map();\n\n this.wrapper = document.createElement('div');\n this.wrapper.id = `${this.select.id}-select-ui-wrapper`;\n this.wrapper.classList.add(this.settings.wrapperClass);\n\n this.details = document.createElement('details');\n this.summary = document.createElement('summary');\n\n this.details.classList.add(this.settings.detailsClass);\n this.summary.classList.add(this.settings.summaryClass);\n this.summary.setAttribute('aria-label', 'select');\n\n this.input = document.createElement('input');\n this.input.id = `select-ui-input--${this.select.id}`;\n this.input.type = 'search';\n this.input.autocomplete = 'off';\n this.input.required = this.select.required;\n this.input.placeholder = this.settings.placeholder;\n this.input.classList.add(this.settings.inputClass);\n\n this.list = document.createElement('ul');\n this.list.id = `${this.select.id}-list`;\n this.list.classList.add(this.settings.listClass);\n this.list.setAttribute('role', 'listbox');\n\n this.error = document.createElement('div');\n this.error.hidden = true;\n this.error.textContent = this.settings.errorText;\n this.error.classList.add(this.settings.errorClass);\n\n // create list items\n this.createItems();\n\n this.wrapper.append(this.details);\n this.details.append(this.summary);\n this.summary.append(this.input);\n this.details.append(this.list);\n this.wrapper.append(this.error);\n this.select.before(this.wrapper);\n\n // reconfigure labels pointing to the native select to the search input\n Array.from(document.querySelectorAll(`label[for=\"${this.select.id}\"]`)).forEach(label => label.setAttribute('for', this.input.id));\n\n this.cursor = -1; // index on visible items\n this.selectedIndex = -1; // selected item index\n\n // used for raf debouce\n this.scheduled_animation_frame = null;\n this.scrollToItemIfNeededDebounced = this.debounce(this.scrollToItemIfNeeded);\n\n // hide the native select\n this.hideSelect();\n\n this.input.addEventListener('blur', () => this.onBlur());\n this.input.addEventListener('focus', () => this.onFocus());\n this.input.addEventListener('click', () => this.open());\n this.input.addEventListener('input', event => this.onInput(event));\n this.input.addEventListener('keydown', event => this.onKeyDown(event));\n this.input.addEventListener('keyup', event => this.onKeyUp(event));\n\n this.summary.addEventListener('keyup', event => this.onSpace(event));\n this.details.addEventListener('toggle', event => this.onToggle(event));\n\n // prevent breaking + behaviour\n // when presed key combination is pressed from the input\n // we end up with an infinite loop and focus is boncing back and forth within the summary and input\n this.summary.addEventListener('focus', event => event.relatedTarget !== this.input && this.input.focus())\n\n // delegated item click handler\n this.list.addEventListener('click', event => this.onItemClick(event));\n\n document.addEventListener('click', event => this.closeOnClick(event));\n document.addEventListener('keydown', event => this.closeOnEscape(event));\n\n // watch native select for changes in dom\n this.observe();\n\n // select change handler\n this.select.addEventListener('change', event => this.onSelectChange(event));\n }\n\n /**\n * @private\n * Creates and set the list items from the select options\n */\n createItems() {\n const fragment = new DocumentFragment();\n\n Array.from(this.select.options).forEach((option, index) => {\n const li = document.createElement('li');\n\n li.dataset.index = index;\n li.dataset.value = option.hasAttribute('value') ? option.value : '';\n\n if (option.disabled) {\n li.dataset.disabled = '';\n }\n\n li.textContent = option.textContent;\n li.setAttribute('role', 'option');\n li.classList.add(this.settings.listItemClass);\n\n fragment.append(li);\n });\n\n this.list.innerHTML = '';\n this.list.append(fragment);\n\n this.items = Array.from(this.list.children);\n this.textItems = this.items.map(item => item.textContent);\n this.visibleItems = this.items.filter(item => !item.hidden);\n }\n\n /**\n * @public\n * open
    menu\n */\n open() {\n if (this.details.open) {\n return;\n }\n\n // clear previous filter and show all items\n this.resetFilterIfNeeded();\n\n // open the
    \n this.details.open = true;\n }\n\n /**\n * @public\n * close
    menu\n */\n close() {\n if (!this.details.open) {\n return;\n }\n\n if (!this.isValid()) {\n this.reset();\n }\n\n this.details.open = false;\n }\n\n /**\n * @private\n * close
    menu on ESCAPE\n * @param { Event } event\n */\n closeOnEscape(event) {\n const { keyCode } = event;\n\n if (keyCode === KEYS.ESCAPE) {\n this.close();\n }\n }\n\n /**\n * @private\n * close
    menu on outside click\n * @param { Event } event\n */\n closeOnClick(event) {\n const { target } = event;\n\n if (target === this.summary) {\n return;\n }\n\n if (this.wrapper.contains(target)) {\n return;\n }\n\n this.close();\n }\n\n /**\n * @private\n * on input focus\n */\n onFocus() {\n // reset visible items if input is empty\n if (!this.input.value.trim()) {\n this.visibleItems = this.items.filter(item => !item.hidden);\n }\n\n this.open();\n this.toggleErrorIfNeeded();\n }\n\n /**\n * @private\n * do not reset the state here, because it will be visible for the user\n * do not close
    on blur event, because the click event on items will not fire after that\n */\n onBlur() {\n this.toggleErrorIfNeeded();\n }\n\n /**\n * @private\n * @param {Event} event\n * change internal state on type\n */\n onInput(event) {\n const value = event.target.value.trim().toLowerCase();\n\n if (!value) {\n this.reset();\n }\n\n this.open();\n\n this.textItems.forEach((item, index) => this.items[index].hidden = !item.toLowerCase().includes(value));\n this.visibleItems = this.items.filter(item => !item.hidden);\n\n // if we have selected visible item set the cursor to it, otherwise -1 if not found\n this.cursor = this.visibleItems.findIndex(item => item === this.items[this.selectedIndex]);\n\n // reset marks\n this.unsetMarks();\n\n // set marks\n this.setMarks();\n\n this.toggleErrorIfNeeded();\n }\n\n /**\n * @private\n * @param {Event} event\n * modify keys registry - so we can detect + or other key combinations\n */\n onKeyDown(event) {\n const { key, keyCode } = event;\n const { UP, DOWN, TAB, ENTER, ESCAPE } = KEYS;\n\n this.keyMap.set(key, true);\n\n switch (keyCode) {\n case UP: {\n this.onArrowPress('up');\n event.preventDefault();\n break;\n }\n case DOWN: {\n this.onArrowPress('down');\n event.preventDefault();\n break;\n }\n case ENTER: {\n this.onEnterPress();\n event.preventDefault();\n break;\n }\n case TAB: {\n this.onTabPress();\n break;\n }\n case ESCAPE: {\n this.close();\n break;\n }\n default: {\n break;\n }\n }\n }\n\n /**\n * @private\n * modify keys registry\n */\n onKeyUp(event) {\n const { key } = event;\n\n if (this.keyMap.has(key)) {\n this.keyMap.set(key, false);\n }\n }\n\n /**\n * @private\n * @param {String} direction - \"up\" or \"down\" string\n */\n onArrowPress(direction) {\n if (!(direction === 'up' || direction === 'down')) {\n throw new Error('direction \"up\" or \"down\" is required');\n }\n\n const min = 0;\n const max = this.visibleItems.length - 1; // visible items count\n\n if (direction === 'up') this.cursor--;\n if (direction === 'down') this.cursor++;\n\n if (this.cursor < min) this.cursor = max;\n if (this.cursor > max) this.cursor = min;\n\n if (this.cursor >= min && this.cursor <= max) {\n this.selectItem(Number(this.visibleItems[this.cursor].dataset.index));\n }\n }\n\n /**\n * @private\n * On keydown handler\n */\n onEnterPress() {\n if (this.visibleItems.length === 1) {\n this.selectItem(this.visibleItems[0].dataset.index);\n this.close();\n return;\n }\n\n if (this.selectedIndex > -1) {\n this.close();\n return;\n }\n }\n\n /**\n * @private\n * On keydown handler - select first visible item on \n */\n onTabPress() {\n const vcount = this.visibleItems.length;\n\n if (vcount > 0) {\n if (vcount === 1 || this.cursor === -1) {\n this.selectItem(Number(this.visibleItems[0].dataset.index));\n }\n\n if (this.cursor > -1) {\n this.selectItem(Number(this.visibleItems[this.cursor].dataset.index));\n }\n }\n\n this.close();\n }\n\n /**\n * @private\n * summary handler\n * do not close details element if input is focused and is pressed\n */\n onSpace(event) {\n // we care about only\n if (event.keyCode !== KEYS.SPACE) {\n return;\n }\n\n // do nothing if input is not focused\n if (document.activeElement !== this.input) {\n return;\n }\n\n // otherwise do not close the
    element\n event.preventDefault();\n }\n\n /**\n * @private\n *
    toggle handler\n * focus input on details open\n */\n onToggle() {\n if (this.details.open) {\n this.input.focus();\n }\n }\n\n /**\n * @private\n * On item click\n * @param {Event} event\n */\n onItemClick(event) {\n let { target } = event; //
  • list item or or settings.markTag\n\n // if we click on the mark tag, get the parent
  • element\n if (target.nodeName.toLowerCase() === this.settings.markTag.toLowerCase()) {\n target = target.parentNode; //
  • element\n }\n\n this.selectItem(Number(target.dataset.index));\n this.close();\n }\n\n /**\n * @public\n * Select item by index\n * @param {Number} index - select opiton index\n * @param {Boolean} [notify=true] - dispatch change event\n */\n selectItem(index, notify = true) {\n if (index < 0 || index >= this.items.length) {\n return;\n }\n\n const selectedItem = this.items[index];\n\n this.items.forEach(item => {\n item.removeAttribute('aria-selected');\n item.classList.remove(this.settings.listItemSelectedClass)\n });\n\n selectedItem.setAttribute('aria-selected', true);\n selectedItem.classList.add(this.settings.listItemSelectedClass);\n\n this.selectedIndex = index;\n this.select.selectedIndex = index;\n this.input.value = selectedItem.textContent;\n\n if (notify) {\n this.select.dispatchEvent(new Event('change'));\n }\n\n // if we have selected visible item set the cursor to it, otherwise -1 if not found\n this.cursor = this.visibleItems.findIndex(item => item === selectedItem);\n\n // show selected item in view if needed\n this.scrollToItemIfNeededDebounced(index);\n }\n\n /**\n * @public\n * Resets the component\n */\n reset() {\n this.input.value = '';\n\n this.cursor = -1;\n this.selectedIndex = -1;\n\n this.select.selectedIndex = -1;\n this.select.dispatchEvent(new Event('change'));\n\n this.unsetMarks();\n\n this.items.forEach(item => {\n item.hidden = false;\n item.removeAttribute('aria-selected');\n item.classList.remove(this.settings.listItemSelectedClass)\n });\n }\n\n /**\n * @public\n * Check validity of the input\n * @return {Boolean} result\n */\n isValid() {\n const text = this.input.value;\n\n if (this.selectedIndex === -1) {\n return false;\n }\n\n if (text.toLowerCase() !== this.items[this.selectedIndex].textContent.toLowerCase()) {\n return false;\n }\n\n return true;\n }\n\n /**\n * @public\n * show error message\n */\n showError() {\n this.error.hidden = false;\n }\n\n /**\n * @public\n * hide error message\n */\n hideError() {\n this.error.hidden = true;\n }\n\n /**\n * @public\n * show or hide the error message when needed\n */\n toggleErrorIfNeeded() {\n this.visibleItems.length === 0 ? this.showError() : this.hideError();\n }\n\n /**\n * @private\n * mark search matches\n */\n setMarks() {\n const tag = this.settings.markTag;\n const value = this.input.value.trim().toLowerCase();\n\n if (!value) {\n return;\n }\n\n this.visibleItems.forEach(item => {\n const text = item.textContent;\n const regex = new RegExp(value, 'gi');\n const html = text.replaceAll(regex, `<${tag}>$&`);\n item.innerHTML = html;\n });\n }\n\n /**\n * @private\n * reset search marks\n */\n unsetMarks() {\n this.items.forEach((item, index) => {\n const textItem = this.textItems[index];\n\n if (item.innerHTML !== textItem) {\n item.innerHTML = textItem;\n }\n });\n }\n\n /**\n * @private\n * Watch native select for change add/remove options\n * If we have added or removed options in the native select menu - we should recreate the items in the ui list\n */\n observe() {\n this.observer = new MutationObserver(this.observerHandler.bind(this));\n this.observer.observe(this.select, { childList: true, subtree: false, attributes: false });\n }\n\n /**\n * @private\n * MutationObserver callback function\n * If we have added or removed options in the native select menu - we should recreate the items in the ui list\n */\n observerHandler() {\n this.reset();\n this.createItems();\n }\n\n /**\n * @private\n * Watch native select for outside changes\n */\n onSelectChange() {\n const { selectedIndex } = this.select;\n const hasRecords = !!this.observer.takeRecords().length;\n\n if (hasRecords) {\n this.observerHandler();\n }\n\n if (selectedIndex > -1) {\n this.selectItem(selectedIndex, hasRecords);\n }\n }\n\n /**\n * @public\n * Clear/Reset the search/filter if any\n * This enables to show all items on next open even if search/filter is previously performed\n */\n resetFilterIfNeeded() {\n const isNeeded = this.items.length !== this.visibleItems.length;\n\n if (!isNeeded) {\n return;\n }\n\n // remove marks\n this.unsetMarks();\n\n // show all items\n this.items.forEach(item => item.hidden = false);\n this.visibleItems = this.items.slice();\n\n // set cursor and scroll to selected item\n if (this.selectedIndex > -1) {\n this.selectItem(this.selectedIndex, false);\n }\n }\n\n /**\n * @private\n * Scroll to item if needed\n * @param {Number} index - the index of the item to scroll to\n */\n scrollToItemIfNeeded(index) {\n // out of bound\n if (index < 0 || index >= this.items.length) {\n return;\n }\n\n // no need to scroll if details is closed\n if (!this.details.open) {\n return;\n }\n\n // no need to scroll if input is not focused\n if (this.input !== document.activeElement) {\n return;\n }\n\n if (ELEMENT_SCROLL_INTO_VIEW_IF_NEEDED_SUPPORT) {\n this.input.scrollIntoViewIfNeeded(true)\n this.items[index].scrollIntoViewIfNeeded(true);\n }\n else if (ELEMENT_SCROLL_INTO_VIEW_SUPPORT) {\n this.input.scrollIntoView({ block: 'center' });\n this.items[index].scrollIntoView({ block: 'center' });\n }\n }\n\n /**\n * @private\n * Debounce handler implementation with raf\n * @param {Function} fn - function to debounce\n * @return {Function} dfn - debounced bound to this function\n */\n debounce(fn) {\n return (...args) => {\n if (this.scheduled_animation_frame) {\n cancelAnimationFrame(this.scheduled_animation_frame);\n }\n this.scheduled_animation_frame = requestAnimationFrame(fn.bind(this, ...args));\n }\n }\n\n /**\n * @private\n * hides original select\n * prevent \"An invalid form control with name='my_select' is not focusable.\" error\n * do not set \"hidden\" - it will prevent focus\n * do not set \"disabled\" - it will prevent the form field to be submitted\n * do not remove \"required\" - it will mess up with form validation\n */\n hideSelect() {\n this.select.style.width = 0;\n this.select.style.height = 0;\n this.select.style.margin = 0;\n this.select.style.padding = 0;\n this.select.style.opacity = 0;\n this.select.style.fontSize = 0;\n this.select.style.border = 'none';\n this.select.style.position = 'absolute';\n this.select.style.transform = 'scale(0)';\n }\n}\n\n","/**\n * @class MultiSelectItem - holds label, checkbox and mirror view\n */\nexport class MultiSelectItem {\n /**\n * @constructor\n * @parma {HTMLLabelElement} label -