{"version":3,"file":"sohoxi.js.map","sources":["../../src/utils/debug.js","../../src/polyfills/element-matches.js","../../src/polyfills/element-remove.js","../../src/polyfills/jquery-passive-events.js","../../src/utils/xss.js","../../src/utils/dom.js","../../src/utils/breakpoints.js","../../src/utils/debounced-resize.js","../../src/utils/debounced-resize.jquery.js","../../src/utils/environment.js","../../src/utils/behaviors.js","../../src/utils/utils.js","../../src/utils/object.js","../../src/utils/string.js","../../src/utils/number.js","../../src/utils/color.js","../../src/components/locale/info/umalqura-data.js","../../src/components/locale/locale.js","../../src/utils/deprecated.js","../../src/utils/base.js","../../src/utils/renderloop.js","../../src/utils/keyboard.js","../../src/components/modal/modal.manager.js","../../src/components/notification/notification.manager.js","../../src/components/theme/theme.js","../../src/components/personalize/personalize.styles.js","../../src/components/personalize/personalize.js","../../src/components/personalize/personalize.bootstrap.js","../../src/components/personalize/personalize.hooks.js","../../src/components/personalize/personalize.jquery.js","../../src/components/form/form.js","../../src/utils/highlight.js","../../src/components/arrange/arrange.js","../../src/components/arrange/arrange.jquery.js","../../src/components/drag/drag.js","../../src/components/drag/drag.jquery.js","../../src/components/place/place.js","../../src/components/place/place.jquery.js","../../src/components/icons/icons.js","../../src/components/icons/icons.jquery.js","../../src/components/tooltip/tooltip.js","../../src/components/tooltip/tooltip.jquery.js","../../src/components/notification-badge/notification-badge.js","../../src/components/notification-badge/notification-badge.jquery.js","../../src/components/button/button.js","../../src/components/button/button.jquery.js","../../src/components/button/button.set.js","../../src/components/button/button.set.jquery.js","../../src/components/hyperlinks/hyperlinks.js","../../src/components/hyperlinks/hyperlinks.jquery.js","../../src/components/mask/masks.js","../../src/components/mask/mask-api.js","../../src/components/mask/mask-input.js","../../src/components/mask/mask-input.jquery.js","../../src/components/popupmenu/popupmenu.js","../../src/components/popupmenu/popupmenu.jquery.js","../../src/components/about/about.js","../../src/components/about/about.jquery.js","../../src/utils/animations.js","../../src/components/accordion/accordion.js","../../src/components/accordion/accordion.jquery.js","../../src/components/actionsheet/actionsheet.js","../../src/components/actionsheet/actionsheet.jquery.js","../../src/utils/accordion-search-utils.js","../../src/utils/lifecycle/lifecycle.js","../../src/utils/lifecycle/lifecycle.jquery.js","../../src/components/expandablearea/expandablearea.js","../../src/components/expandablearea/expandablearea.jquery.js","../../src/components/listfilter/listfilter.js","../../src/components/tmpl/tmpl.js","../../src/components/autocomplete/autocomplete.js","../../src/components/autocomplete/autocomplete.jquery.js","../../src/components/searchfield/searchfield.js","../../src/components/searchfield/searchfield.jquery.js","../../src/components/applicationmenu/applicationmenu.js","../../src/components/applicationmenu/applicationmenu.jquery.js","../../src/components/blockgrid/blockgrid.js","../../src/components/blockgrid/blockgrid.jquery.js","../../src/components/breadcrumb/breadcrumb.js","../../src/components/breadcrumb/breadcrumb.jquery.js","../../src/components/busyindicator/busyindicator.js","../../src/components/busyindicator/busyindicator.jquery.js","../../src/utils/selectable.jquery.js","../../src/components/cards/cards.js","../../src/components/cards/cards.jquery.js","../../src/components/popover/popover.jquery.js","../../src/components/charts/charts.js","../../src/components/bullet/bullet.js","../../src/components/completion-chart/completion-chart.js","../../src/components/sparkline/sparkline.js","../../src/components/emptymessage/emptymessage.js","../../src/components/emptymessage/emptymessage.jquery.js","../../src/components/line/line.js","../../src/components/column/column.js","../../src/components/bar/bar.js","../../src/components/pie/pie.js","../../src/components/radar/radar.js","../../src/components/charts/charts.jquery.js","../../src/utils/date.js","../../src/components/calendar/calendar-shared.js","../../src/components/tag/tag.js","../../src/components/tag/tag.list.js","../../src/components/virtual-scroll/virtual-scroll.js","../../src/components/dropdown/dropdown.js","../../src/components/dropdown/dropdown.jquery.js","../../src/components/colorpicker/colorpicker.js","../../src/components/colorpicker/colorpicker.jquery.js","../../src/components/toolbar-flex/toolbar-flex.item.js","../../src/components/toolbar-flex/toolbar-flex.item.jquery.js","../../src/components/toolbar-flex/toolbar-flex.js","../../src/components/toolbar-flex/toolbar-flex.jquery.js","../../src/components/calendar/calendar-toolbar.js","../../src/components/monthview/monthview.js","../../src/components/week-view/week-view.js","../../src/components/calendar/calendar.js","../../src/components/calendar/calendar.jquery.js","../../src/components/circlepager/circlepager.js","../../src/components/circlepager/circlepager.jquery.js","../../src/components/calendar/calendar-toolbar.jquery.js","../../src/components/compositeform/compositeform.js","../../src/components/compositeform/compositeform.jquery.js","../../src/components/contextualactionpanel/contextualactionpanel.js","../../src/components/contextualactionpanel/contextualactionpanel.jquery.js","../../src/components/timepicker/timepicker.js","../../src/components/timepicker/timepicker.jquery.js","../../src/components/validation/validation.js","../../src/components/toast/toast.js","../../src/components/toast/toast.jquery.js","../../src/components/validation/validator.js","../../src/components/validation/validation.jquery.js","../../src/components/validation/validation.utils.js","../../src/components/datepicker/datepicker.js","../../src/components/datepicker/datepicker.jquery.js","../../src/components/fontpicker/fontpicker.style.js","../../src/components/fontpicker/fontpicker.js","../../src/components/fontpicker/fontpicker.jquery.js","../../src/components/toolbar/toolbar.js","../../src/components/toolbar/toolbar.jquery.js","../../src/components/editor/editor.js","../../src/components/editor/editor.jquery.js","../../src/components/hierarchy/hierarchy.js","../../src/components/hierarchy/hierarchy.jquery.js","../../src/components/field-filter/field-filter.js","../../src/components/field-filter/field-filter.jquery.js","../../src/components/field-options/field-options.js","../../src/components/field-options/field-options.jquery.js","../../src/components/fileupload/fileupload.js","../../src/components/fileupload/fileupload.jquery.js","../../src/components/fileupload-advanced/fileupload-advanced.js","../../src/components/fileupload-advanced/fileupload-advanced.jquery.js","../../src/components/homepage/homepage.js","../../src/components/homepage/homepage.jquery.js","../../src/components/link/link.js","../../src/components/link/link.jquery.js","../../src/components/pager/pager.js","../../src/components/pager/pager.jquery.js","../../src/components/listview/listview.js","../../src/components/listview/listview.jquery.js","../../src/components/listbuilder/listbuilder.js","../../src/components/listbuilder/listbuilder.jquery.js","../../src/components/modal/modal.js","../../src/components/message/message.js","../../src/components/message/message.jquery.js","../../src/components/modal/modal.jquery.js","../../src/components/monthview/monthview.jquery.js","../../src/components/multiselect/multiselect.js","../../src/components/multiselect/multiselect.jquery.js","../../src/components/module-nav/module-nav.common.js","../../src/components/module-nav/module-nav.switcher.js","../../src/components/module-nav/module-nav.switcher.jquery.js","../../src/components/module-nav/module-nav.js","../../src/components/module-nav/module-nav.jquery.js","../../src/components/module-nav/module-nav.settings.js","../../src/components/module-nav/module-nav.settings.jquery.js","../../src/components/notification/notification.js","../../src/components/notification/notification.jquery.js","../../src/components/progress/progress.js","../../src/components/progress/progress.jquery.js","../../src/components/popdown/popdown.js","../../src/components/popdown/popdown.jquery.js","../../src/components/rating/rating.js","../../src/components/rating/rating.jquery.js","../../src/components/signin/signin.js","../../src/components/signin/signin.jquery.js","../../src/components/slider/slider.js","../../src/components/slider/slider.jquery.js","../../src/components/spinbox/spinbox.js","../../src/components/spinbox/spinbox.jquery.js","../../src/components/splitter/splitter.js","../../src/components/splitter/splitter.jquery.js","../../src/components/swaplist/swaplist.js","../../src/components/swaplist/swaplist.jquery.js","../../src/components/swipe-action/swipe-action.js","../../src/components/swipe-action/swipe-action.jquery.js","../../src/components/scrollaction/scrollaction.js","../../src/components/scrollaction/scrollaction.jquery.js","../../src/components/stepchart/stepchart.js","../../src/components/stepchart/stepchart.jquery.js","../../src/components/tabs/tabs.js","../../src/components/tabs/tabs.jquery.js","../../src/components/tag/tag.jquery.js","../../src/components/tag/tag.list.jquery.js","../../src/components/textarea/textarea.js","../../src/components/textarea/textarea.jquery.js","../../src/components/searchfield/toolbarsearchfield.jquery.js","../../src/components/trackdirty/trackdirty.js","../../src/components/trackdirty/trackdirty.jquery.js","../../src/components/tree/tree.js","../../src/components/tree/tree.jquery.js","../../src/components/treemap/treemap.js","../../src/components/treemap/treemap.jquery.js","../../src/components/virtual-scroll/virtual-scroll.jquery.js","../../src/components/week-view/week-view.jquery.js","../../src/components/wizard/wizard.js","../../src/components/wizard/wizard.jquery.js","../../src/components/datagrid/datagrid.formatters.js","../../src/utils/excel.js","../../src/components/datagrid/datagrid.groupby.js","../../src/components/lookup/lookup.js","../../src/components/lookup/lookup.jquery.js","../../src/components/datagrid/datagrid.editors.js","../../src/components/datagrid/datagrid.js","../../src/components/datagrid/datagrid.jquery.js","../../src/components/form-compact/form-compact.js","../../src/components/form-compact/form-compact.jquery.js","../../src/components/header/header.js","../../src/components/header/header.jquery.js","../../src/components/tabs-multi/multi-tabs.js","../../src/components/tabs-multi/multi-tabs.jquery.js","../../src/components/list-detail/list-detail.js","../../src/components/list-detail/list-detail.jquery.js","../../src/components/stepprocess/stepprocess.js","../../src/components/stepprocess/stepprocess.jquery.js","../../src/behaviors/initialize/initialize.js","../../src/behaviors/initialize/initialize.jquery.js","../../src/behaviors/longpress/longpress.js","../../src/components/components.js"],"sourcesContent":["/* eslint-disable no-console */\n\n// Easy flag for determining whether or not time will be logged to the console.\nexport const enableTimeLogging = false;\n\n/**\n * Start the logging timer\n * @param {string} label Provide a way to match a timing operation.\n * @returns {void}\n */\nexport function logTimeStart(label) {\n if (enableTimeLogging) {\n console.time(label);\n }\n}\n\n/**\n * End the logging timer and print the result\n * @param {string} label End this matching timing operation\n * @returns {void}\n */\nexport function logTimeEnd(label) {\n if (enableTimeLogging) {\n console.timeEnd(label);\n }\n}\n\n// Easy flag for allowing console debugging\nexport const enableConsoleLogging = false;\n\n/**\n * Simple wrapper for `console.[whatever]` to abstract out console access.\n * @param {string} type console display type\n * @param {string} message message type\n * @returns {void}\n */\nexport function log(type, message) {\n if (!enableConsoleLogging) {\n return;\n }\n\n if (!console) { // eslint-disable-line\n return;\n }\n\n if (!message && typeof type === 'string') {\n message = type;\n type = 'log';\n }\n\n if (typeof !console[type] !== 'function') { // eslint-disable-line\n type = 'log';\n }\n\n console[type](`${message}`); // eslint-disable-line\n}\n\n/**\n * Returns a list of all elements that currently have a $.data() property.\n * @param {jQuery[]|HTMLElement} rootElem the root element to work from.\n * @returns {array} containing all matching elements with a data property attached.\n */\nexport function getComponents(rootElem) {\n const elem = !rootElem ? $('body') : $(rootElem);\n const allElems = elem.find('*');\n const results = [];\n\n allElems.each((i, thisElem) => {\n const data = $(thisElem).data();\n if (data && Object.keys(data).length) {\n results.push({\n data,\n element: thisElem\n });\n }\n });\n\n return results;\n}\n","// Polyfill for `Element.prototype.matches`\n// https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill\nif (!Element.prototype.matches) {\n Element.prototype.matches =\n Element.prototype.matchesSelector ||\n Element.prototype.mozMatchesSelector ||\n Element.prototype.msMatchesSelector ||\n Element.prototype.oMatchesSelector ||\n Element.prototype.webkitMatchesSelector ||\n function (s) {\n const matches = (this.document || this.ownerDocument).querySelectorAll(s);\n let i = matches.length;\n while (--i >= 0 && matches.item(i) !== this) {} // eslint-disable-line\n return i > -1;\n };\n}\n","// from:https://github.com/jserz/js_piece/blob/master/DOM/ChildNode/remove()/remove().md\n/* eslint-disable */\n(function (arr) {\n arr.forEach(function (item) {\n if (item.hasOwnProperty('remove')) {\n return;\n }\n Object.defineProperty(item, 'remove', {\n configurable: true,\n enumerable: true,\n writable: true,\n value: function remove() {\n if (this.parentNode === null) {\n return;\n }\n this.parentNode.removeChild(this);\n }\n });\n });\n})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);\n","/**\n * Polyfills passive event support in jQuery\n * This eliminates a ton of console warnings when doing scrolling.\n * - https://github.com/jquery/jquery/issues/2871\n * - https://github.com/infor-design/enterprise/issues/414\n */\nconst isIE11 = !!window.MSInputMethodContext && !!document.documentMode;\nif (!isIE11) {\n jQuery.event.special.touchstart = {\n setup(_, ns, handle) {\n if (ns.includes('noPreventDefault')) {\n this.addEventListener('touchstart', handle, {\n passive: false\n });\n } else {\n this.addEventListener('touchstart', handle, {\n passive: true\n });\n }\n }\n };\n jQuery.event.special.touchmove = {\n setup(_, ns, handle) {\n this.addEventListener('touchmove', handle, {\n passive: true\n });\n }\n };\n jQuery.event.special.mousewheel = {\n setup(_, ns, handle) {\n this.addEventListener('mousewheel', handle, {\n passive: true\n });\n }\n };\n}\n","const xssUtils = {};\n\n/**\n * Takes a string and removes all html tags\n * @param {string} str The string to parse\n * @returns {string} The string minus html tags.\n */\nxssUtils.stripHTML = function stripHTML(str) {\n let newStr = str;\n\n if (!newStr) {\n return '';\n }\n\n newStr = newStr.replace(/<\\/?[^>]+(>|$)/g, '');\n return newStr;\n};\n\n/**\n * Remove all html tags except for the ones specified. I.E. White list to a specific set of accepted tags.\n * @private\n * @param {string} html HTML in string form\n * @param {string} allowed Comma seperated string of allowed tags e.g. '

''\n * @returns {string} the modified value\n */\nxssUtils.stripTags = function (html, allowed) {\n if (!html) {\n return '';\n }\n\n if (typeof html === 'number') {\n return html;\n }\n\n const allowList = ((`${allowed || ''}`)\n .toLowerCase()\n .match(/<[a-z][a-z0-9]*>/g) || [])\n .join(''); // making sure the allowed arg is a string containing only tags in lowercase ()\n\n const tags = /<\\/?([a-z][a-z0-9]*)\\b[^>]*>/gi;\n const commentsAndPhpTags = /|<\\?(?:php)?[\\s\\S]*?\\?>/gi;\n let returnHTML = '';\n returnHTML = html.replace(commentsAndPhpTags, '')\n .replace(tags, ($0, $1) => allowList.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''); //eslint-disable-line\n returnHTML = returnHTML.replace(tags, ($0, $1) => allowList.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''); //eslint-disable-line\n returnHTML = returnHTML.replace(tags, ($0, $1) => allowList.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''); //eslint-disable-line\n\n return returnHTML;\n};\n\n/**\n * Remove console methods\n * @private\n * @param {string} html HTML in string form\n * @returns {string} the modified value\n */\nxssUtils.sanitizeConsoleMethods = function (html) {\n const methods = ['assert', 'clear', 'count', 'debug', 'dirxml', 'dir', 'error', 'exception', 'groupCollapsed', 'groupEnd', 'group', 'info', 'log', 'markTimeline', 'profileEnd', 'profile', 'table', 'timeEnd', 'timeStamp', 'time', 'trace', 'warn'];\n const expr = new RegExp(`console\\\\.(${methods.join('|')})((\\\\s+)?\\\\(([^)]+)\\\\);?)?`, 'igm');\n\n return typeof html !== 'string' ? html : html.replace(expr, '');\n};\n\n/**\n * Remove Script tags and all onXXX functions\n * @private\n * @param {string} html HTML in string form\n * @returns {string} the modified value\n */\nxssUtils.sanitizeHTML = function (html) {\n if (!html) return '';\n // Remove on xxx functions https://regex101.com/r/hsLeFl/1/\n let santizedHtml = html.replace(/\\bon\\w+=\\S+?(?=(>|&|<| |\"))/g, '');\n // Remove Script tags\n santizedHtml = santizedHtml.replace(/)<[^<]*)*<\\/script>/g, '');\n // Remove iframe tags\n santizedHtml = santizedHtml.replace(/)<[^<]*)*<\\/iframe>/g, '');\n\n // Remove console methods\n santizedHtml = this.sanitizeConsoleMethods(santizedHtml);\n // Remove nested script tags\n santizedHtml = santizedHtml.replace(/<\\/script>/g, '');\n\n return santizedHtml;\n};\n\n/**\n * Changing all of white-spaces characters to a single\n * space in the whole string then removes all exceeding\n * white-spaces before and after the text.\n * @private\n * @param {object} html the HTML object to get the inner html value.\n * @returns {string} the new value of inner html.\n */\nxssUtils.removeWhiteSpaceCharacters = function (html) {\n if (!html) {\n return '';\n }\n\n let value = html[0].innerHTML;\n value = value.replace(/\\s+/g, ' ').trim();\n html[0].innerHTML = value;\n\n return html[0].innerHTML;\n};\n\n/**\n * Make sure a string is only alphanumeric (with dashes allowed.)\n * @private\n * @param {string} string HTML in string form\n * @returns {string} the modified value\n */\nxssUtils.ensureAlphaNumeric = function (string) {\n if (typeof string === 'number') {\n return string;\n }\n return this.stripTags(string).replace(/[^a-z0-9-]/gi, '', '');\n};\n\n/**\n * Make sure a string is only alphanumeric with spaces.\n * @private\n * @param {string} string HTML in string form\n * @returns {string} the modified value\n */\nxssUtils.ensureAlphaNumericWithSpaces = function (string) {\n if (typeof string === 'number') {\n return string;\n }\n return this.stripTags(string).replace(/[^a-z0-9 ]/gi, '', '');\n};\n\n/**\n * Converting given string into camel case.\n * @private\n * @param {string} string To be convert into camel case\n * @returns {string} the modified value\n */\nxssUtils.toCamelCase = function (string) {\n if (typeof string !== 'string') {\n return string;\n }\n string = this.stripTags(string).replace(/[^a-z0-9 ]/gi, '', '');\n return string.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());\n};\n\n/**\n * Escapes HTML, replacing special characters with encoded symbols.\n * Symbols taken from https://bit.ly/1iVkGlc\n * @private\n * @param {string} value HTML in string form\n * @returns {string} the modified value\n */\nxssUtils.escapeHTML = function (value) {\n const newValue = value;\n if (typeof newValue === 'string') {\n const map = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '\\\\': '\'\n };\n const reg = /[&<>\"'\\\\]/ig;\n return newValue.replace(reg, match => (map[match]));\n }\n return newValue;\n};\n\n/**\n * Un-escapes HTML, replacing encoded symbols with special characters.\n * Symbols taken from https://bit.ly/1iVkGlc\n * @private\n * @param {string} value HTML in string form\n * @returns {string} the modified value\n */\nxssUtils.unescapeHTML = function (value) {\n if (value === '') {\n return '';\n }\n\n if (typeof value === 'string') {\n const match = regx => ((value.match(regx) || [''])[0]);\n const doc = new DOMParser().parseFromString(value, 'text/html');\n\n // Keep leading/trailing spaces\n return `${match(/^\\s*|\\\\/)}${doc.documentElement.textContent.trim()}${match(/\\s*$|\\\\/)}`;\n }\n return value;\n};\n\n/**\n * htmlentities() is a PHP function which converts special characters (like <)\n * into their escaped/encoded values (like <). This is a JS verson of it.\n * This allows you to show to display the string without the browser reading it as HTML.\n * This is useful for encoding hrefs.\n * @private\n * @param {string} string string to process\n * @returns {string} the processed value\n */\nxssUtils.htmlEntities = function (string) {\n return String(string)\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\\\\/g, '\')\n .replace(/\"/g, '"');\n};\n\n/**\n * Ensure that a link is a local link (relative to the current page)\n * @private\n * @param {string} url string to process\n * @returns {boolean} If it is local or not\n */\nxssUtils.isUrlLocal = function (url) {\n const isEmpty = (url === '');\n return !isEmpty &&\n ((url[0] === '/' && (url.length === 1 || (url[1] !== '/' && url[1] !== '\\\\'))) || // \"/\" or \"/foo\" but not \"//\" or \"/\\\"\n (url.length > 1 && url[0] === '~' && url[1] === '/')) || // \"~/\" or \"~/foo\"\n (url.length >= 1 && url[0] === '#'); // \"#\" or \"#foo\"\n};\n\nexport { xssUtils };\n","import { xssUtils } from './xss';\n\nconst DOM = {};\n\n/**\n * DOM operations are only allowed on elements that are based on HTML or SVG. This method\n * determines whether or not the element is valid for performing a DOM operation.\n * @param {HTMLElement|SVGElement} el the element being examined\n * @returns {boolean} true if the element is valid\n */\nDOM.isValidElement = function isValidElement(el) {\n return (el instanceof HTMLElement || el instanceof SVGElement);\n};\n\n/**\n * Returns an array containing an element's attributes.\n * @param {HTMLElement|SVGElement} element the element whose attributes are being accessed\n * @returns {object} list of attributes in name/value pairs.\n */\nDOM.getAttributes = function getAttributes(element) {\n if (!element || (!(element instanceof HTMLElement) && !(element instanceof SVGElement))) {\n return {};\n }\n\n return element.attributes;\n};\n\n/**\n * Adding, removing, and testing for classes\n * @param {HTMLElement} element the element to test\n * @returns {boolean} whether or not a className exists\n */\nDOM.hasAnyClass = function hasAnyClass(element) {\n const cn = element.className;\n return cn && cn.length > 0;\n};\n\n/**\n * Checks the element for the existence of a particular class.\n * @param {HTMLElement|SVGElement} elem a element being checked.\n * @param {string} className a string representing a class name to check for.\n * @returns {boolean} whether or not the element's class attribute contains the string.\n */\nDOM.hasClass = function hasClass(elem, className) {\n let r = false;\n if (!elem?.getAttribute) {\n return r;\n }\n\n if ('classList' in elem) {\n r = elem.classList.contains(className);\n } else {\n const classAttr = elem.getAttribute('class');\n r = classAttr ? classAttr.split(/\\s+/).indexOf(className) !== -1 : false;\n }\n return r;\n};\n\n/**\n * Add a class to any element and handle multiple classes.\n * Handles DOM and SVG elements down to IE11\n * @param {HTMLElement} el a element being checked.\n * @param {...string} className a string representing a class name.\n */\nDOM.addClass = function addClass(el, ...className) {\n if (!el) {\n return;\n }\n let classStr = '';\n for (let i = 0; i < className.length; i++) {\n if (el.classList) {\n el.classList.add(className[i]);\n } else if (!DOM.hasClass(el, [i])) {\n if (classStr.length) {\n classStr += ' ';\n }\n classStr += className[i];\n }\n }\n if (classStr.length) {\n $(el).addClass(classStr);\n }\n};\n\n/**\n * Remove a class from any element and handle multiple classes.\n * Handles DOM and SVG elements down to IE11\n * @param {HTMLElement} el a element being checked.\n * @param {...string} className a string representing a class name.\n */\nDOM.removeClass = function removeClass(el, ...className) {\n if (!el) {\n return;\n }\n\n let classStr = '';\n for (let i = 0; i < className.length; i++) {\n if (el.classList) {\n el.classList.remove(className[i]);\n } else if (!DOM.hasClass(el, [i])) {\n if (classStr.length) {\n classStr += ' ';\n }\n classStr += className[i];\n }\n }\n if (classStr.length) {\n $(el).removeClass(classStr);\n }\n};\n\n/**\n * Checks if an element is valid\n * @param {HTMLElement|SVGElement|jQuery[]} el The element being checked\n * @returns {boolean} represents all values normally contained by a DOMRect or ClientRect\n */\nDOM.isElement = function isElement(el) {\n if ((el instanceof HTMLElement) || (el instanceof SVGElement) || (el instanceof $ && el.length)) {\n return true;\n }\n return false;\n};\n\n/**\n * Runs the generic _getBoundingClientRect()_ method on an element, but returns its results\n * as a plain object instead of a ClientRect\n * @param {HTMLElement|SVGElement|jQuery[]} el The element being manipulated\n * @returns {object} represents all values normally contained by a DOMRect or ClientRect\n */\nDOM.getDimensions = function getDimensions(el) {\n if (!DOM.isElement(el)) {\n return {};\n }\n\n if (el instanceof $) {\n if (!el.length) {\n return {};\n }\n\n el = el[0];\n }\n\n const rect = el.getBoundingClientRect();\n const rectObj = {};\n\n for (let prop in rect) { // eslint-disable-line\n if (!isNaN(rect[prop])) {\n rectObj[prop] = rect[prop];\n }\n }\n\n return rectObj;\n};\n\n/**\n * Prepend content to a DOM element (like jQuery.Prepend)\n * @param {HTMLElement|SVGElement|jQuery[]} el The element to prepend to\n * @param {string|jQuery} contents The html string or jQuery object.\n * @param {string} stripTags A list of tags to strip to prevent xss, or * for sanitizing and allowing all tags.\n */\nDOM.prepend = function prepend(el, contents, stripTags) {\n let domEl = el;\n\n if (el instanceof $ && el.length) {\n domEl = domEl[0];\n }\n\n if (domEl instanceof HTMLElement || domEl instanceof SVGElement) {\n domEl.insertAdjacentHTML('afterbegin', this.xssClean(contents, stripTags));\n }\n};\n\n/**\n * Append content to a DOM element (like jQuery.append)\n * @param {HTMLElement|SVGElement|jQuery[]} el The element to append to\n * @param {string|jQuery} contents The html string or jQuery object.\n * @param {string} stripTags A list of tags to strip to prevent xss, or * for sanitizing and allowing all tags.\n */\nDOM.append = function append(el, contents, stripTags) {\n let domEl = el;\n\n if (el instanceof $ && el.length) {\n domEl = domEl[0];\n }\n\n if (domEl instanceof HTMLElement || domEl instanceof SVGElement) {\n domEl.insertAdjacentHTML('beforeend', this.xssClean(contents, stripTags));\n }\n};\n\nDOM.after = function after(el, contents, stripTags) {\n let domEl = el;\n\n if (el instanceof $ && el.length) {\n domEl = domEl[0];\n }\n\n if (domEl instanceof HTMLElement || domEl instanceof SVGElement) {\n domEl.insertAdjacentHTML('afterend', this.xssClean(contents, stripTags));\n }\n};\n\n/**\n * Remove a DOM Element\n * @param {HTMLElement|SVGElement|jQuery[]} el The element to remove.\n */\nDOM.remove = function append(el) {\n let domEl = el;\n\n if (el instanceof $ && el.length) {\n domEl = domEl[0];\n }\n\n if ((domEl instanceof HTMLElement || domEl instanceof SVGElement) && el.parentNode) {\n el.parentNode.removeChild(el);\n }\n};\n\n/**\n * Set an attribute with an extra check that the object exists.\n * @param {HTMLElement|SVGElement|jQuery[]} el The element to set the attribute on\n * @param {string} attribute The attribute name.\n * @param {string} value The attribute value.\n */\nDOM.setAttribute = function append(el, attribute, value) {\n let domEl = el;\n\n if (el instanceof $ && el.length) {\n domEl = domEl[0];\n }\n\n if (domEl instanceof HTMLElement || domEl instanceof SVGElement) {\n domEl.setAttribute('attribute', value);\n }\n};\n\n/**\n * Clean the markup before insertion.\n * @param {string|jQuery} contents The html string or jQuery object.\n * @param {string} stripTags A list of tags to strip to prevent xss, or * for sanitizing and allowing all tags.\n * @returns {string} the cleaned up markup\n */\nDOM.xssClean = function xssClean(contents, stripTags) {\n let markup = contents;\n\n if (stripTags && stripTags !== '*') {\n markup = xssUtils.stripTags(contents, stripTags);\n }\n\n if (stripTags === '*') {\n markup = xssUtils.sanitizeHTML(contents);\n }\n\n return markup;\n};\n\n/**\n * Append content to a DOM element (like jQuery.append)\n * @param {HTMLElement|SVGElement|jQuery[]} el The element to append to\n * @param {string|jQuery} contents The html string or jQuery object.\n * @param {string} stripTags A list of tags to strip to prevent xss, or * for sanitizing and allowing all tags.\n */\nDOM.html = function html(el, contents, stripTags) {\n let domEl = el;\n\n if (el instanceof $ && el.length) {\n domEl = domEl[0];\n }\n\n if (domEl instanceof HTMLElement || domEl instanceof SVGElement) {\n domEl.innerHTML = this.xssClean(contents, stripTags);\n }\n};\n\n/**\n * Recursively checks parent nodes for a matching CSS selector\n * @param {HTMLElement/SVGElement} el the lower-level element being checked\n * @param {string} selector a valid CSS selector\n * @param {boolean} closest if true, returns the first match found, or undefined if no matches are found.\n * @returns {Array|HTMLElement|SVGElement|undefined} containing references to parent elements that match the selector\n */\nDOM.parents = function parents(el, selector, closest) {\n const parentEls = [];\n if (!DOM.isValidElement(el)) {\n return parentEls;\n }\n\n // Pushes to the element array.\n function checkEl(thisEl) {\n if (thisEl !== document && thisEl.matches(selector)) {\n parentEls.push(thisEl);\n }\n }\n\n // Loop until we hit the tag.\n // If we're only looking for the closest element, break out once we find it.\n while (el.parentNode) {\n if (closest && parentEls.length) {\n break;\n }\n\n el = el.parentNode;\n checkEl(el);\n }\n\n // Return the first one, or the entire array.\n if (closest) {\n return parentEls[0]; // can be `undefined`\n }\n return parentEls;\n};\n\n/**\n * Get the next sibling with an optional css selector.\n * @param {HTMLElement/SVGElement} el The element being checked\n * @param {string} selector a valid CSS selector\n * @returns {HTMLElement} The next sibling\n */\nDOM.getNextSibling = function getNextSibling(el, selector) {\n if (el instanceof $ && el.length) {\n el = el[0];\n }\n\n // Get the next sibling element\n let sibling = el.nextElementSibling;\n\n // If there's no selector, return the first sibling\n if (!selector) return sibling;\n\n // If the sibling matches our selector, use it\n // If not, jump to the next sibling and continue the loop\n while (sibling) {\n if (sibling.matches(selector)) return sibling;\n sibling = sibling.nextElementSibling;\n }\n\n return undefined;\n};\n\n/**\n * Get the next previous with an optional css selector.\n * @param {HTMLElement/SVGElement} el The element being checked\n * @param {string} selector a valid CSS selector\n * @returns {HTMLElement} The previous sibling\n */\nDOM.getPreviousSibling = function getPreviousSibling(el, selector) {\n if (el instanceof $ && el.length) {\n el = el[0];\n }\n\n // Get the previous sibling element\n let sibling = el.previousElementSibling;\n\n // If there's no selector, return the first sibling\n if (!selector) return sibling;\n\n // If the sibling matches our selector, use it\n // If not, jump to the previous sibling and continue the loop\n while (sibling) {\n if (sibling.matches(selector)) return sibling;\n sibling = sibling.previousElementSibling;\n }\n\n return undefined;\n};\n\n/**\n * Get the sibling elements.\n * @param {HTMLElement/SVGElement} el The element to get siblings\n * @returns {array} Array of sibling elements\n */\nDOM.getSiblings = function getSiblings(el) {\n if (el instanceof $ && el.length) {\n el = el[0];\n }\n return [].slice.call(el.parentNode.children).filter(child => child !== el);\n};\n\n/**\n * Returns a simple CSS selector string that represents an existing page element.\n * Generally used in reporting (error/console messages).\n * @param {HTMLElement|SVGElement} el the element to report on\n * @returns {string} containing a simple CSS selector that represents the element\n */\nDOM.getSimpleSelector = function getSimpleSelector(el) {\n const tagName = el.tagName.toLowerCase();\n const id = el.id ? `#${el.id}` : '';\n const className = el.className ? `.${el.className.split(' ').join('.')}` : '';\n\n return `${tagName}${id}${className}`;\n};\n\nexport { DOM };\n","import { DOM } from './dom';\n\n// =================================================================\n// Soho JS-level Breakpoint Access\n// NOTE: these should match whatever the breakpoints are in \"/sass/_config.scss\"\n// =================================================================\nconst breakpoints = {\n phone: 320,\n slim: 400,\n phablet: 610,\n 'phone-to-tablet': 767,\n 'wide-tablet': 968,\n 'tablet-to-desktop': 1280,\n desktop: 1024,\n 'desktop-to-extralarge': 1600\n};\n\n/**\n * @returns {array} a list of available breakpoint names\n */\nconst availableBreakpoints = Object.keys(breakpoints);\nbreakpoints.available = availableBreakpoints;\n\n/**\n * Get the name of the current CSS breakpoint by checking the popuplated 'content' value of the\n * tag's `::after` pseudo-element. These names should be reflected in the breakpoints object\n * above.\n * @returns {string} name of the current breakpoint\n */\nbreakpoints.current = function () {\n if (!window.getComputedStyle) return '';\n const afterElement = window.getComputedStyle ? window.getComputedStyle(document.body, ':after') : false;\n if (!afterElement) {\n return '';\n }\n return (afterElement.getPropertyValue ? afterElement.getPropertyValue('content') : '' || '').replace(/\"/g, '');\n};\n\n/**\n * @param {string} breakpoint matches one of the entries in the \"Soho.breakpoints\" object.\n * @returns {boolean} whether or not the window is currently wider than the breakpoint provided.\n */\nbreakpoints.isAbove = function isAbove(breakpoint) {\n const bp = breakpoints[breakpoint];\n if (!bp) {\n return false;\n }\n\n const windowWidth = $(window).width();\n return windowWidth > bp - 1;\n};\n\n/**\n * @param {string} breakpoint matches one of the entries in the \"Soho.breakpoints\" object.\n * @returns {boolean} whether or not the window is currently more narrow\n * than the breakpoint provided.\n */\nbreakpoints.isBelow = function isBelow(breakpoint) {\n const bp = breakpoints[breakpoint];\n if (!bp) {\n return false;\n }\n\n const windowWidth = $(window).width();\n return windowWidth < bp;\n};\n\n/**\n * Compares the last-stored breakpoint with a check on the \"current\" breakpoint to see if the\n * breakpoint has changed.\n * @returns {void}\n */\nbreakpoints.compare = function compare() {\n if (!this.last) {\n this.last = '';\n }\n\n const cur = this.current();\n if (this.last !== cur) {\n $('body').triggerHandler('breakpoint-change', [{\n previous: this.last,\n current: cur\n }]);\n this.last = cur;\n }\n};\n\n/**\n * Checks an element for Soho visibility classes and determines whether or not\n * should be hidden based on those values at the current breakpoint.\n * NOTE: this method does NOT determine if the element is ACTUALLY hidden with a\n * `display: none;` or `visibility: hidden;` rule. It determines whether or not a CSS\n * visibility rule alone would hide the element.\n * @param {HTMLElement} element the element being checked.\n * @returns {boolean} whether or not the element is hidden at this breakpoint.\n */\nbreakpoints.isHidden = function (element) {\n if (!element || !DOM.isElement(element)) {\n return false;\n }\n\n // If there are no CSS classes on the element, return false.\n const cl = element.classList;\n if (!cl.length) {\n return false;\n }\n\n // If it's always hidden, always return true.\n if (cl.contains('hidden')) {\n return true;\n }\n\n const bp = this.current();\n const map = {\n phonedown: 'xs',\n phone: 'sm',\n tablet: 'md',\n desktop: 'lg',\n extralarge: 'xl',\n };\n const size = map[bp];\n const hiddenClassName = `hidden-${size}`;\n const visibleClassName = `visible-${size}-`;\n\n // Should be hidden on this breakpoint\n if (cl.contains(hiddenClassName)) {\n return true;\n }\n\n // If explicitly visible, return\n if (cl.toString().indexOf(visibleClassName) > -1) {\n return false;\n }\n\n // Simply return false if none of these thing are found\n return false;\n};\n\n/**\n * jQuery wrapper for `Soho.breakpoints.isHidden()`\n * NOTE: if a jQuery selector with multiple elements is passed to this function,\n * it will only operate on the first one.\n * This method is NOT chainable.\n * @returns {boolean} whether or not the element is hidden at this breakpoint.\n */\n$.fn.isHiddenAtBreakpoint = function () {\n if (!this.length) {\n return false;\n }\n return breakpoints.isHidden($(this).first()[0]);\n};\n\nexport { breakpoints };\n","/**\n * Debounce method\n * @param {function} func the callback function to be run on a stagger.\n * @param {number} [threshold] the amount of time in CPU ticks to delay.\n * @param {boolean} [execAsap] if true, executes the callback immediately\n * instead of waiting for the threshold to complete.\n * @returns {void}\n */\nfunction debounce(func, threshold, execAsap) {\n let timeout;\n\n return function debounced(...args) {\n const obj = this;\n function delayed() {\n if (!execAsap) {\n func.apply(obj, args);\n }\n timeout = null;\n }\n\n if (timeout) {\n clearTimeout(timeout);\n } else if (execAsap) {\n func.apply(obj, args);\n }\n\n timeout = setTimeout(delayed, threshold || 250);\n };\n}\n\nexport { debounce };\n","import { debounce } from './debounced-resize';\n\nconst debouncedResizeName = 'debouncedResize';\n\n/**\n * Bind the smartResize method to $.fn()\n * @param {function} fn the callback function to be bound on debounced resize\n * @returns {void}\n */\n$.fn[debouncedResizeName] = function (fn) {\n if (fn) {\n return this.bind('resize', debounce(fn));\n }\n return this.trigger(debouncedResizeName);\n};\n","import packageJson from '../../package.json';\nimport { breakpoints } from './breakpoints';\n\n// jQuery Components\nimport './debounced-resize.jquery';\n\n// Utility Name\nconst UTIL_NAME = 'environment';\n\n/**\n * @class {Environment}\n */\nconst Environment = {\n\n browser: {},\n\n features: {\n resizeObserver: typeof ResizeObserver !== 'undefined',\n touch: (('ontouchstart' in window) || (navigator.MaxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0))\n },\n\n os: {},\n devicespecs: {},\n\n /**\n * @returns {boolean} true if the page locale is currently read Right-To-Left instead\n * of the default Left-to-Right.\n */\n get rtl() {\n return $('html').attr('dir') === 'rtl';\n },\n\n /**\n * Builds run-time environment settings\n */\n set() {\n $('html').attr('data-sohoxi-version', packageJson.version);\n\n // Set the viewport meta tag to limit scaling\n this.viewport = document.querySelector('meta[name=viewport]');\n if (this.viewport) {\n this.viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, user-scalable=0');\n }\n\n this.addBrowserClasses();\n this.addGlobalResize();\n this.addDeviceSpecs();\n },\n\n /**\n * Global Classes for browser, version and device as needed.\n */\n addBrowserClasses() {\n // eslint-disable-next-line compat/compat\n const ua = navigator.userAgent || navigator.vendor || window.opera;\n const platform = navigator.platform;\n const html = $('html');\n let cssClasses = ''; // User-agent string\n\n if (ua.indexOf('Safari') !== -1 &&\n ua.indexOf('Chrome') === -1 &&\n ua.indexOf('Android') === -1) {\n cssClasses += 'is-safari ';\n this.browser.name = 'safari';\n }\n\n this.browser.isWKWebView = function () {\n return false;\n };\n\n if (navigator.platform.substr(0, 2) === 'iP' || Environment.browser.isIPad()) {\n const lte9 = /constructor/i.test(window.HTMLElement);\n const idb = !!window.indexedDB;\n\n if ((window.webkit && window.webkit.messageHandlers) || !lte9 || idb) {\n // WKWebView\n this.browser.name = 'wkwebview';\n cssClasses += 'is-safari is-wkwebview ';\n this.browser.isWKWebView = function () {\n return true;\n };\n }\n }\n\n if (ua.indexOf('Chrome') !== -1) {\n cssClasses += 'is-chrome ';\n this.browser.name = 'chrome';\n }\n\n const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'];\n if (macosPlatforms.indexOf(platform) > -1 && !/Linux/.test(platform)) {\n cssClasses += 'is-mac ';\n this.os.name = 'mac';\n }\n\n if (ua.indexOf('Firefox') > 0) {\n cssClasses += 'is-firefox ';\n this.browser.name = 'firefox';\n }\n\n // Class-based detection for IE\n if (ua.match(/Edg\\//)) {\n cssClasses += 'ie-edge ';\n this.browser.name = 'edge';\n this.browser.version = navigator.appVersion.indexOf('Edge/18') > -1 ? '18' : '17';\n cssClasses += `ie-edge${this.browser.version}`;\n }\n if (ua.match(/Trident/)) {\n cssClasses += 'ie ';\n this.browser.name = 'ie';\n }\n if (navigator.appVersion.indexOf('MSIE 8.0') > -1 ||\n ua.indexOf('MSIE 8.0') > -1 ||\n document.documentMode === 8) {\n cssClasses += 'ie8 ';\n this.browser.version = '8';\n }\n if (navigator.appVersion.indexOf('MSIE 9.0') > -1) {\n cssClasses += 'ie9 ';\n this.browser.version = '9';\n }\n if (navigator.appVersion.indexOf('MSIE 10.0') > -1) {\n cssClasses += 'ie10 ';\n this.browser.version = '10';\n } else if (ua.match(/Trident\\/7\\./)) {\n cssClasses += 'ie11 ';\n this.browser.version = '11';\n }\n\n // Class-based detection for iOS\n // /iPhone|iPod|iPad|Silk|Android|BlackBerry|Opera Mini|IEMobile/\n if ((/iPhone|iPod|iPad/).test(ua) || Environment.browser.isIPad()) {\n cssClasses += 'ios ';\n this.os.name = 'ios';\n\n const iDevices = ['iPod', 'iPad', 'iPhone'];\n for (let i = 0; i < iDevices.length; i++) {\n if (new RegExp(iDevices[i]).test(ua)) {\n cssClasses += `${iDevices[i].toLowerCase()} `;\n this.device = iDevices[i];\n }\n }\n }\n\n if ((/Android/.test(ua))) {\n cssClasses += 'android ';\n this.os.name = 'android';\n }\n\n if (!this.os.name && /Linux/.test(platform)) {\n this.os.name = 'linux';\n }\n\n html.addClass(cssClasses);\n },\n\n addDeviceSpecs() {\n const unknown = '-';\n const nAppVer = navigator.appVersion;\n const nUAgent = navigator.userAgent;\n let browser = navigator.appName;\n let appVersion = ` ${parseFloat(navigator.appVersion)}`;\n let majorVersion = parseInt(navigator.appVersion, 10);\n let nameOffset;\n let verOffset;\n let ix;\n let browserVersionName = '';\n\n if ((verOffset = nUAgent.indexOf('Opera')) !== -1) { //eslint-disable-line\n browser = 'Opera';\n appVersion = nUAgent.substring(verOffset + 6);\n if ((verOffset = nUAgent.indexOf('Version')) !== -1) { //eslint-disable-line\n appVersion = nUAgent.substring(verOffset + 8);\n }\n }\n if ((verOffset = nUAgent.indexOf('OPR')) !== -1) { //eslint-disable-line\n browser = 'Opera';\n appVersion = nUAgent.substring(verOffset + 4);\n } else if ((verOffset = nUAgent.indexOf('Edg')) !== -1) { //eslint-disable-line\n browser = 'Microsoft Edge';\n appVersion = nUAgent.substring(verOffset + 4);\n } else if ((verOffset = nUAgent.indexOf('MSIE')) !== -1) { //eslint-disable-line\n browser = 'Microsoft Internet Explorer';\n appVersion = nUAgent.substring(verOffset + 5);\n } else if ((verOffset = nUAgent.indexOf('Chrome')) !== -1) { //eslint-disable-line\n browser = 'Chrome';\n appVersion = nUAgent.substring(verOffset + 7);\n if (nUAgent.indexOf('Edg') > -1) {\n browserVersionName = 'Microsoft Edge';\n }\n if (Number(appVersion.substring(0, 3)) >= 108) {\n appVersion = appVersion.substring(0, 3);\n }\n } else if ((verOffset = nUAgent.indexOf('Safari')) !== -1) { //eslint-disable-line\n browser = 'Safari';\n appVersion = nUAgent.substring(verOffset + 7);\n if ((verOffset = nUAgent.indexOf('Version')) !== -1) { //eslint-disable-line\n appVersion = nUAgent.substring(verOffset + 8);\n }\n } else if (this.browser.isWKWebView()) { //eslint-disable-line\n browser = `WKWebView`; //eslint-disable-line\n appVersion = '';\n majorVersion = '';\n } else if ((verOffset = nUAgent.indexOf('Firefox')) !== -1) { //eslint-disable-line\n browser = 'Firefox';\n appVersion = nUAgent.substring(verOffset + 8);\n } else if (nUAgent.indexOf('Trident/') !== -1) { //eslint-disable-line\n browser = 'Microsoft Internet Explorer';\n appVersion = nUAgent.substring(nUAgent.indexOf('rv:') + 3);\n } else if ((nameOffset = nUAgent.lastIndexOf(' ') + 1) < (verOffset = nUAgent.lastIndexOf('/'))) { //eslint-disable-line\n browser = nUAgent.substring(nameOffset, verOffset);\n appVersion = nUAgent.substring(verOffset + 1);\n if (browser.toLowerCase() === browser.toUpperCase()) {\n browser = navigator.appName;\n }\n }\n // Trim the version string\n if ((ix = appVersion.indexOf(';')) !== -1) appVersion = appVersion.substring(0, ix); //eslint-disable-line\n if ((ix = appVersion.indexOf(' ')) !== -1) appVersion = appVersion.substring(0, ix); //eslint-disable-line\n if ((ix = appVersion.indexOf(')')) !== -1) appVersion = appVersion.substring(0, ix); //eslint-disable-line\n\n majorVersion = ` ${parseInt(appVersion, 10)}`;\n if (isNaN(majorVersion)) {\n appVersion = ` ${parseFloat(navigator.appVersion)}`;\n majorVersion = parseInt(navigator.appVersion, 10);\n }\n\n // mobile version\n const mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nAppVer);\n\n let os = unknown;\n\n const clientStrings = [\n { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },\n { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },\n { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },\n { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },\n { s: 'Android', r: /Android/ },\n { s: 'Open BSD', r: /OpenBSD/ },\n { s: 'Sun OS', r: /SunOS/ },\n { s: 'Linux', r: /(Linux|X11)/ },\n { s: 'iOS', r: /(iPhone|iPad|iPod)/ },\n { s: 'Mac OS X', r: /Mac OS X/ },\n { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },\n { s: 'UNIX', r: /UNIX/ }\n ];\n\n for (const id in clientStrings) { //eslint-disable-line\n const cs = clientStrings[id];\n if (cs.r.test(nUAgent)) {\n os = cs.s;\n break;\n }\n }\n\n let osVersion = unknown;\n\n if (/Windows/.test(os)) {\n osVersion = /Windows (.*)/.exec(os)[1];\n }\n\n switch (os) { //eslint-disable-line\n case 'Windows 10':\n osVersion = '';\n os = 'Windows';\n break;\n\n case 'Mac OS X':\n osVersion = '';\n os = 'Mac OS';\n break;\n\n case 'Android':\n osVersion = /Android ([\\.\\_\\d]+)/.exec(nUAgent)[1]; //eslint-disable-line\n break;\n\n case 'iOS':\n osVersion = /OS (\\d+)_?(\\d+)?/.exec(nUAgent); //eslint-disable-line\n osVersion = `${osVersion[1]}.${osVersion[2]}.${(osVersion[3] | 0)}`; //eslint-disable-line\n break;\n }\n\n if (Environment.browser.isIPad()) {\n const osVersionStr = nUAgent.substr(nUAgent.indexOf('Version'), nUAgent.substr(nUAgent.indexOf('Version')).indexOf(' '));\n osVersion = osVersionStr.replace('Version/', '');\n os = 'IOS';\n }\n\n this.devicespecs = {\n currentBrowser: browser,\n browserVersion: appVersion.trim(),\n browserMajorVersion: majorVersion,\n isMobile: mobile || Environment.browser.isIPad(),\n os,\n currentOSVersion: osVersion,\n browserVersionName\n };\n },\n\n /**\n * Setup a global resize event trigger for controls to listen to\n */\n addGlobalResize() {\n // Global resize event\n $(window).debouncedResize(() => {\n $('body').triggerHandler('resize', [window]);\n breakpoints.compare();\n });\n\n // Also detect whenenver a load or orientation change occurs\n $(window).on('orientationchange load', () => breakpoints.compare());\n },\n\n /**\n * Tears down global UI-specific event handlers\n * @returns {void}\n */\n removeGlobalEvents() {\n $(window).off(`scroll.${UTIL_NAME}`);\n\n $('body').off([\n `focusin.${UTIL_NAME}`,\n `focusout.${UTIL_NAME}`\n ].join(' '));\n }\n};\n\n/**\n * @returns {boolean} whether or not the current browser is MS Edge\n */\nEnvironment.browser.isEdge = function () {\n return Environment.browser.name === 'edge';\n};\n\n/**\n * @returns {boolean} whether or not the current browser is IE11\n */\nEnvironment.browser.isIE11 = function () {\n return Environment.browser.name === 'ie' && Environment.browser.version === '11';\n};\n\n/**\n * @returns {boolean} whether or not the current browser is Safari and includes wkWebView as safari\n */\nEnvironment.browser.isSafari = function () {\n return Environment.browser.name === 'safari' || Environment.browser.name === 'wkwebview';\n};\n\n/**\n * @returns {boolean} whether or not the current browser is IE10\n */\nEnvironment.browser.isIE10 = function () {\n return Environment.browser.name === 'ie' && Environment.browser.version === '10';\n};\n\n/**\n * @returns {boolean} whether or not the current browser is Firefox\n */\nEnvironment.browser.isFirefox = function () {\n return Environment.browser.name === 'firefox';\n};\n\nEnvironment.browser.isIPad = function () {\n return !!(navigator.userAgent.match(/(iPad)/) ||\n (navigator.platform === 'MacIntel' && typeof navigator.standalone !== 'undefined'));\n};\n\n/**\n * Automatically set up the environment by virtue of including this script\n */\nEnvironment.set();\n\nexport { Environment };\n","import { DOM } from './dom';\nimport { Environment as env } from './environment';\n\n/**\n * HideFocus Behavior\n * Only shows the focus state on key entry (tabs or arrows).\n * @param {HTMLElement|SVGElement} element the base element\n * @returns {HideFocus} component instance\n */\nfunction HideFocus(element) {\n return this.init(element);\n}\n\nHideFocus.prototype = {\n init(element) {\n if (!this.element && (element instanceof HTMLElement || element instanceof SVGElement)) {\n this.element = element;\n }\n\n const $el = $(this.element);\n let isClick = false;\n let isFocused = false;\n let labelClicked = false;\n\n // Checkbox, Radio buttons or Switch\n if ($el.is('.checkbox, .radio, .switch')) {\n let label = $el.next();\n if (label.is('[type=\"hidden\"]')) {\n label = label.next();\n }\n this.label = label[0];\n\n $el.addClass('hide-focus')\n .on('focusin.hide-focus', (e) => {\n if (!isClick && !isFocused && !labelClicked) {\n $el.removeClass('hide-focus');\n $el.triggerHandler('hidefocusremove', [e]);\n }\n isClick = false;\n isFocused = true;\n labelClicked = false;\n })\n .on('focusout.hide-focus', (e) => {\n $el.addClass('hide-focus');\n labelClicked = label.is(labelClicked);\n isClick = false;\n isFocused = false;\n $el.triggerHandler('hidefocusadd', [e]);\n });\n\n label.on('mousedown.hide-focus', function (e) {\n labelClicked = this;\n isClick = true;\n $el.addClass('hide-focus');\n $el.triggerHandler('hidefocusadd', [e]);\n });\n } else {\n // All other elements (ie. Hyperlinks)\n const handleMousedown = (e) => {\n isClick = true;\n $el.addClass('hide-focus');\n $el.triggerHandler('hidefocusadd', [e]);\n };\n const isTouch = env.features.touch;\n\n // In some cases, detect a child element as the target for some events\n let eventTargetEl = $el;\n if ($el.hasClass('accordion-header')) {\n eventTargetEl = $el.find('a');\n }\n\n // Click/Touch events go to the event target\n eventTargetEl.on('mousedown.hide-focus', (e) => {\n handleMousedown(e);\n });\n if (isTouch) {\n eventTargetEl.on('touchstart.hide-focus', (e) => {\n handleMousedown(e);\n });\n }\n\n // Focus events apply to the container\n $el.addClass('hide-focus');\n $el.on('focusin.hide-focus', (e) => {\n if (!isClick) {\n $el.removeClass('hide-focus');\n $el.triggerHandler('hidefocusremove', [e]);\n }\n isClick = false;\n isFocused = true;\n })\n .on('focusout.hide-focus', (e) => {\n $el.addClass('hide-focus');\n isClick = false;\n isFocused = false;\n $el.triggerHandler('hidefocusadd', [e]);\n });\n\n // Store separate event target, if applicable\n if (!$el.is(eventTargetEl)) {\n this.separateEventTarget = eventTargetEl;\n }\n }\n\n return this;\n },\n\n updated() {\n return this\n .teardown()\n .init();\n },\n\n teardown() {\n if (this.label) {\n $(this.label).off('mousedown.hide-focus');\n }\n\n const elemEvents = [\n 'focusin.hide-focus',\n 'focusout.hide-focus',\n 'mousedown.hide-focus',\n 'touchstart.hide-focus'\n ];\n const elemEventStr = elemEvents.join(' ');\n $(this.element).off(elemEventStr);\n if (this.separateEventTarget?.length) {\n this.separateEventTarget.off(elemEventStr);\n this.separateEventTarget = null;\n }\n\n this.element?.classList.remove('hide-focus');\n\n return this;\n },\n\n destroy() {\n this.teardown();\n $.removeData(this.element, 'hidefocus');\n }\n};\n\n/**\n * jQuery component wrapper for the HideFocus behavior\n * @returns {jQuery[]} components being acted on\n */\n$.fn.hideFocus = function () {\n return this.each(function () {\n let instance = $.data(this, 'hidefocus');\n if (instance) {\n instance.updated();\n } else {\n instance = $.data(this, 'hidefocus', new HideFocus(this));\n }\n });\n};\n\n/**\n * Allows for the smooth scrolling of an element's content area.\n * @param {HTMLElement|SVGElement|jQuery[]} el The element being manipulated.\n * @param {number} target target distance.\n * @param {number} duration the time that will be needed for the scrolling to complete.\n * @returns {$.Deferred} promise that resolved when scrolling completes.\n */\nfunction smoothScrollTo(el, target, duration) {\n const dfd = $.Deferred();\n\n if (!DOM.isElement(el)) {\n // Not a workable element\n return dfd.reject();\n }\n\n // Strip the jQuery\n if (el instanceof $ && el.length) {\n el = el[0];\n }\n\n // undefined (not zero) target should instantly resolve\n if (target === undefined || target === null) {\n return dfd.resolve();\n }\n\n if (isNaN(duration)) {\n duration = 0;\n }\n\n target = Math.round(target);\n duration = Math.round(duration);\n\n if (duration < 0) {\n // bad duration\n return dfd.fail();\n }\n\n if (duration === 0) {\n el.scrollLeft += target;\n return dfd.resolve();\n }\n\n const startTime = Date.now();\n const endTime = startTime + duration;\n const startLeft = el.scrollLeft;\n const distance = target;\n\n // based on http://en.wikipedia.org/wiki/Smoothstep\n function smoothStep(start, end, point) {\n if (point <= start) { return 0; }\n if (point >= end) { return 1; }\n const x = (point - start) / (end - start); // interpolation\n return x * x * (3 - 2 * x);\n }\n\n // This is to keep track of where the element's scrollLeft is\n // supposed to be, based on what we're doing\n let previousLeft = el.scrollLeft;\n\n // This is like a think function from a game loop\n function scrollFrame() {\n if (el.scrollLeft !== previousLeft) {\n // interrupted\n dfd.reject();\n return;\n }\n\n // set the scrollLeft for this frame\n const now = Date.now();\n const point = smoothStep(startTime, endTime, now);\n const frameLeft = Math.round(startLeft + (distance * point));\n el.scrollLeft = frameLeft;\n\n // check if we're done!\n if (now >= endTime) {\n dfd.resolve();\n return;\n }\n\n // If we were supposed to scroll but didn't, then we\n // probably hit the limit, so consider it done; not\n // interrupted.\n if (el.scrollLeft === previousLeft && el.scrollLeft !== frameLeft) {\n dfd.resolve();\n return;\n }\n previousLeft = el.scrollLeft;\n\n // schedule next frame for execution\n setTimeout(scrollFrame, 0);\n }\n\n // boostrap the animation process\n setTimeout(scrollFrame, 0);\n\n return dfd;\n}\n\n/**\n * Binds the Soho Behavior _smoothScrollTo()_ to a jQuery selector\n * @param {number} target target distance to scroll the element\n * @param {number} duration the time that will be needed for the scrolling to complete.\n * @returns {$.Deferred} promise that resolved when scrolling completes.\n */\n$.fn.smoothScroll = function (target, duration) {\n return smoothScrollTo(this, target, duration);\n};\n\n/**\n * Uses 'requestAnimationFrame' or 'setTimeout' to defer a function.\n * @param {function} callback the callback that runs on a deferment.\n * @param {number} timer how long to delay before running the callback.\n * @returns {function} either `requestAnimationFrame` or `setTimeout`\n */\nfunction defer(callback, timer) {\n const deferMethod = typeof window.requestAnimationFrame !== 'undefined' ? window.requestAnimationFrame : setTimeout;\n return deferMethod(callback, timer);\n}\n\nexport { HideFocus, smoothScrollTo, defer };\n","/* eslint-disable prefer-rest-params */\n\nimport { defer } from './behaviors';\nimport { Environment as env } from './environment';\nimport { DOM } from './dom';\n\n/**\n * Used for changing the stacking order of jQuery events. This is needed to override certain\n * Events invoked by other plugins http://stackoverflow.com/questions/2360655\n * @private\n * @param {string} name the event name\n * @param {function} fn callback function that will be called during the supplied event name\n * @returns {void}\n */\n$.fn.bindFirst = function (name, fn) {\n this.on(name, fn);\n this.each(function () {\n const handlers = $._data(this, 'events')[name.split('.')[0]]; // eslint-disable-line\n // take out the handler we just inserted from the end\n const handler = handlers.pop();\n // move it at the beginning\n handlers.splice(0, 0, handler);\n });\n};\n\n/**\n * @private\n * uniqueIdCount is a baseline unique number that will be used when generating\n * uniqueIds for elements and components.\n */\nexport let uniqueIdCount = []; // eslint-disable-line\n\n/**\n * Detect whether or not a text string represents a valid CSS property. This check\n * includes an attempt at checking for vendor-prefixed versions of the CSS property\n * provided.\n * @private\n * @param {string} prop a possible CSS property\n * @returns {string|null} If the property exists, it will be returned in string format.\n * If the property doesn't exist, a null result is returned.\n */\n$.fn.cssPropSupport = function (prop) {\n if (!prop) {\n return null;\n }\n\n const el = $('

')[0];\n const propStr = prop.toString();\n const prefixes = ['Moz', 'Webkit', 'O', 'ms'];\n const capitalizedProp = propStr.charAt(0).toUpperCase() + propStr.substr(1);\n\n if (prop in el.style) {\n $(el).remove();\n return prop;\n }\n\n for (let i = 0; i < prefixes.length; i++) {\n const vendorProp = prefixes[i] + capitalizedProp;\n if (vendorProp in el.style) {\n $(el).remove();\n return vendorProp;\n }\n }\n\n $(el).remove();\n return null;\n};\n\n/**\n * Returns the name of the TransitionEnd event.\n * @private\n * @returns {string} a (possibly) vendor-adjusted CSS transition property name.\n */\n$.fn.transitionEndName = function () {\n const prop = $.fn.cssPropSupport('transition');\n const eventNames = {\n WebkitTransition: 'webkitTransitionEnd',\n MozTransition: 'transitionend',\n MSTransition: 'msTransitionEnd',\n OTransition: 'oTransitionEnd',\n transition: 'transitionend'\n };\n\n return eventNames[prop] || null;\n};\n\n/**\n * Checks to see if a provided element is visible based on its CSS `visibility` property.\n * @private\n * @param {HTMLElement} element the element being checked.\n * @returns {boolean} whether or not the element is visible.\n */\nfunction visible(element) {\n return $.expr.filters.visible(element) &&\n !$(element).parents().addBack().filter(function () { return $.css(this, 'visibility') === 'hidden'; }).length;\n}\n\n/**\n * From jQueryUI Core: https://github.com/jquery/jquery-ui/blob/24756a978a977d7abbef5e5bce403837a01d964f/ui/jquery.ui.core.js#L93\n * Adapted from: http://stackoverflow.com/questions/7668525/is-there-a-jquery-selector-to-get-all-elements-that-can-get-focus\n * Adds the ':focusable' selector to Sizzle to allow for the selection of elements\n * that can currently be focused.\n * @private\n * @param {HTMLElement} element the element being checked\n * @returns {boolean} whether or not the element is focusable.\n */\nfunction focusable(element) {\n let map;\n let mapName;\n let img;\n const nodeName = element.nodeName.toLowerCase();\n const isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex'));\n\n if (nodeName === 'area') {\n map = element.parentNode;\n mapName = map.name;\n if (!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {\n return false;\n }\n img = $(`img[usemap=#${mapName}]`)[0];\n return !!img && visible(img);\n }\n\n // The element and all of its ancestors must be visible.\n // Return out fast if this isn't the case.\n if (!visible(element)) {\n return false;\n }\n\n const match = /input|select|textarea|button|object/.test(nodeName);\n if (match) {\n return !element.disabled;\n }\n if (nodeName === 'a') {\n return (element.href !== undefined || isTabIndexNotNaN);\n }\n return isTabIndexNotNaN;\n}\n\n// Adds a `:focusable` selector to jQuery's selector library.\n$.extend($.expr[':'], {\n focusable(element) {\n return focusable(element, !isNaN($.attr(element, 'tabindex')));\n }\n});\n\n/**\n * Returns a key/value list of currently attached event listeners\n * @private\n * @returns {object} containing list of event names as keys, and event listener functions as values.\n */\n$.fn.listEvents = function () {\n let data = {};\n\n this.each(function () {\n data = $._data(this, 'events'); // eslint-disable-line\n });\n\n return data;\n};\n\nconst utils = {};\n\n/**\n * Generates a unique ID for an element based on the element's configuration, any\n * Soho components that are generated against it, and provided prefixes/suffixes.\n * @private\n * @param {HTMLElement} element the element being used for uniqueId capture\n * @param {string} [className] CSS classname (will be interpreted automatically\n * if it's not provided)\n * @param {string} [prefix] optional prefix\n * @param {string} [suffix] optional suffix\n * @returns {string} the compiled uniqueID\n */\nutils.uniqueId = function (element, className, prefix, suffix) {\n const predefinedId = element.id;\n\n if (predefinedId && $(`#${predefinedId}`).length < 2) {\n return predefinedId;\n }\n\n prefix = (!prefix ? '' : `${prefix}-`);\n suffix = (!suffix ? '' : `-${suffix}`);\n className = (!className ? utils.getArrayFromList(element.classList).join('-') : className);\n\n if (!uniqueIdCount[className]) {\n uniqueIdCount[className] = 1;\n }\n const str = `${prefix}${className}-${uniqueIdCount[className]}${suffix}`;\n uniqueIdCount[className] += 1;\n return str;\n};\n\n/**\n * Grabs an attribute from an HTMLElement containing stringified JSON syntax,\n * and interprets it into options.\n * @private\n * @param {HTMLElement} element the element whose settings are being interpreted\n * @param {string} [attr] optional different attribute to parse for settings\n * @returns {object} a list of interpreted settings for this element\n */\nutils.parseSettings = function parseSettings(element, attr) {\n let options = {};\n if (!element ||\n (!(element instanceof HTMLElement) && !(element instanceof $)) ||\n (element instanceof $ && !element.length)) {\n return options;\n }\n\n if (element instanceof $) {\n element = element[0];\n }\n\n // Use `data-options` as a default.\n attr = attr || 'data-options';\n\n const str = element.getAttribute(attr);\n if (!str || typeof str !== 'string' || str.indexOf('{') === -1) {\n return options;\n }\n\n // replace single to double quotes, since single-quotes may be necessary\n // due to entry in markup.\n function replaceDoubleQuotes(changedStr) {\n return changedStr.replace(/'/g, '\"');\n }\n\n // Manually parse a string more in-depth\n function manualParse(changedStr) {\n // get keys\n let regex = /({|,)(?:\\s*)(?:')?([A-Za-z_$\\.][A-Za-z0-9_ \\-\\.$]*)(?:')?(?:\\s*):/g; // eslint-disable-line\n\n // add double quotes to keys\n changedStr = changedStr.replace(regex, '$1\\\"$2\\\":'); // eslint-disable-line\n\n // get strings in values\n regex = /:(?:\\s*)(?!(true|false|null|undefined))([A-Za-z_$\\.#][A-Za-z0-9_ \\-\\.$]*)/g; // eslint-disable-line\n\n // add double quotes to strings in values\n changedStr = changedStr.replace(regex, ':\\\"$2\\\"'); // eslint-disable-line\n changedStr = replaceDoubleQuotes(changedStr);\n return changedStr;\n }\n\n try {\n options = JSON.parse(replaceDoubleQuotes(str));\n } catch (err) {\n options = JSON.parse(manualParse(str));\n }\n\n return options;\n};\n\n/**\n * Deprecate `utils.parseOptions` in favor of `utils.parseSettings`.\n * This method is slated to be removed in a future v4.10.0 or v5.0.0.\n * @private\n * @deprecated as of v4.4.0. Please use `parseSettings()` instead.\n * @param {HTMLElement|jQuery[]} element the element whose options are being parsed\n * @param {string} [attr] an optional alternate attribute name to use when obtaining settings\n * @returns {Object|Object[]} an object representation of parsed settings.\n */\nutils.parseOptions = function parseOptions(element, attr) {\n return utils.parseSettings(element, attr);\n};\n\n/**\n* jQuery Behavior Wrapper for `utils.parseOptions`.\n* @deprecated as of v4.4.0. This is no longer necessary to call directly and should be avoided.\n* @private\n* @param {HTMLElement|jQuery[]} element the element whose options are being parsed\n* @param {string} [attr] an optional alternate attribute name to use when obtaining settings\n* @returns {Object|Object[]} an object representation of parsed settings.\n*/\n$.fn.parseOptions = function (element, attr) {\n const results = [];\n const isCalledDirectly = (element instanceof HTMLElement ||\n element instanceof SVGElement || element instanceof $);\n let targets = this;\n\n if (isCalledDirectly) {\n targets = $(element);\n } else {\n attr = element;\n element = undefined;\n }\n\n targets.each(function (i, item) {\n results.push({\n element: this,\n options: utils.parseOptions(item, attr)\n });\n });\n\n if (results.length === 1) {\n return results[0].options;\n }\n return results;\n};\n\n/**\n * Performs the usual Boolean coercion with the exception of the strings \"false\"\n * (case insensitive) and \"0\"\n * @private\n * @param {boolean|string|number} b the value to be checked\n * @returns {boolean} whether or not the value passed coerces to true.\n */\nutils.coerceToBoolean = function (b) {\n return !(/^(false|0)$/i).test(b) && !!b;\n};\n\n/**\n * Coerces all properties inside of a settings object to a boolean.\n * @param {Object} settings incoming settings\n * @param {String[]} [targetPropsArr=undefined] optional array of specific settings keys to target.\n * If no keys are provided, all keys will be targeted.\n * @returns {Object} modified settings.\n */\nutils.coerceSettingsToBoolean = function (settings, targetPropsArr) {\n if (!targetPropsArr || !Array.isArray(targetPropsArr)) {\n Object.keys(settings).forEach((key) => {\n targetPropsArr.push(key);\n });\n }\n\n let i;\n let l;\n for (i = 0, l = targetPropsArr.length; i < l; i++) {\n settings[targetPropsArr[i]] = utils.coerceToBoolean(settings[targetPropsArr[i]]);\n }\n\n return settings;\n};\n\n/**\n * Timer - can be used for play/pause or stop for given time.\n * Use as new instance [ var timer = new $.fn.timer(function() {}, 6000); ]\n * then can be listen events as:\n * [ $(timer.event).on('update', function(e, data){console.log(data.counter)}); ]\n * or can access as [ timer.cancel(); -or- timer.pause(); -or- timer.resume(); ]\n * @private\n * @param {function} [callback] method that will run on each timer update\n * @param {number} delay amount of time between timer ticks\n * @returns {object} containing methods that can be run on the timer\n */\n$.fn.timer = function (callback, delay) {\n const self = $(this);\n const speed = 10;\n let interval;\n let counter = 0;\n\n function cancel() {\n self.triggerHandler('cancel');\n clearInterval(interval);\n counter = 0;\n }\n\n function pause() {\n self.triggerHandler('pause');\n clearInterval(interval);\n }\n\n function update() {\n interval = setInterval(function () {\n counter += speed;\n self.triggerHandler('update', [{ counter }]);\n if (counter > delay) {\n self.triggerHandler('timeout');\n callback.apply(arguments); // eslint-disable-line\n clearInterval(interval);\n counter = 0;\n }\n }, speed);\n }\n\n function resume() {\n self.triggerHandler('resume');\n update();\n }\n\n update();\n\n return {\n event: this,\n cancel,\n pause,\n resume\n };\n};\n\n/**\n * Copies a string to the clipboard. Must be called from within an event handler such as click.\n * May return false if it failed, but this is not always\n * possible. Browser support for Chrome 43+, Firefox 42+, Edge and IE 10+.\n * No Safari support, as of (Nov. 2015). Returns false.\n * IE: The clipboard feature may be disabled by an adminstrator. By default a prompt is\n * shown the first time the clipboard is used (per session).\n * @private\n * @param {string} text incoming text content\n * @returns {string|boolean} copied text, or a false result if there was an error\n */\n$.copyToClipboard = function (text) { // eslint-disable-line\n if (window.clipboardData && window.clipboardData.setData) {\n // IE specific code path to prevent textarea being shown while dialog is visible.\n return window.clipboardData.setData('Text', text);\n }\n if (document.queryCommandSupported && document.queryCommandSupported('copy')) {\n const textarea = document.createElement('textarea');\n textarea.textContent = text;\n textarea.style.position = 'fixed'; // Prevent scrolling to bottom of page in MS Edge.\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand('copy'); // Security exception may be thrown by some browsers.\n } catch (ex) {\n // console.warn('Copy to clipboard failed.', ex);\n return false;\n } finally {\n document.body.removeChild(textarea);\n }\n }\n};\n\n/**\n * Clearable (Shows an X to clear)\n * @param {object} options clearable\n * @param {boolean} options.tabbale If true the x will be tabbable\n */\n$.fn.clearable = function (options) {\n const self = this;\n this.element = $(this);\n\n const COMPONENT_NAME = 'clearable';\n\n // Create an X icon button styles in icons.scss\n this.xButton = this.element.find('.icon.close').first();\n if (!this.xButton || !this.xButton.length) {\n this.xButton = $.createIconElement({ classes: 'close is-empty', icon: 'close' }).icon();\n }\n\n if (options?.tabbable) {\n this.xButton = $('')\n .append($.createIconElement({ classes: 'close', icon: 'close' }).icon());\n }\n\n // Clears the contents of the base element\n this.clear = function () {\n self.element.val('').trigger('change').focus().trigger('cleared');\n self.checkContents();\n };\n\n // Event listener for the xButton's `keydown` event\n this.handleKeydown = function (e) {\n const key = e.key;\n\n if (key === 'Enter' || (e.altKey && (key === 'Delete' || key === 'Backspace'))) {\n e.preventDefault();\n self.clear();\n }\n };\n\n // Checks the contents of the base element (presumably an input field) for empty\n this.checkContents = function () {\n const text = self.element.val();\n if (!text || !text.length) {\n this.xButton.addClass('is-empty');\n } else {\n this.xButton.removeClass('is-empty');\n }\n\n this.element.trigger('contents-checked');\n };\n\n // Add the button to field parent\n this.xButton.insertAfter(self.element);\n this.xButton[0].tabIndex = options?.tabbable ? 0 : -1;\n this.xButton[0].setAttribute('focusable', true);\n\n // Handle Events\n this.xButton\n .off([\n `click.${COMPONENT_NAME}`,\n `keydown.${COMPONENT_NAME}`\n ].join(' '))\n .on('click.clearable', this.clear)\n .on('keydown.clearable', this.handleKeydown);\n\n if (options?.tabbable) {\n this.xButton.hideFocus();\n }\n\n const elemEvents = [\n `blur.${COMPONENT_NAME}`,\n `change.${COMPONENT_NAME}`,\n `keyup.${COMPONENT_NAME}`\n ].join(' ');\n\n this.element\n .off(elemEvents)\n .on(elemEvents, () => {\n self.checkContents();\n });\n\n // Set initial state\n this.checkContents();\n};\n\n/**\n * Replacement for String.fromCharCode() that takes meta keys into account when determining which\n * @private\n * character key was pressed.\n * @param {jQuery.Event} e jQuery-wrapped `keypress` event\n * @returns {string} text tcharacter\n */\nutils.actualChar = function (e) {\n let key = e.which;\n let character = '';\n const toAscii = {\n 188: '44',\n // '109': '45', // changes \"m\" to \"-\" when using keypress\n 190: '46',\n 191: '47',\n 192: '96',\n 220: '92',\n 222: '39',\n 221: '93',\n 219: '91',\n 173: '45',\n 187: '61', // IE Key codes\n 186: '59', // IE Key codes\n 189: '45' // IE Key codes\n };\n const shiftUps = {\n 96: '~',\n 49: '!',\n 50: '@',\n 51: '#',\n 52: '$',\n 53: '%',\n 54: '^',\n 55: '&',\n 56: '*',\n 57: '(',\n 48: ')',\n 45: '_',\n 61: '+',\n 91: '{',\n 93: '}',\n 92: '|',\n 59: ':',\n 37: '%',\n 38: '&',\n 39: '\"',\n 44: '<',\n 46: '>',\n 47: '?'\n };\n\n // Normalize weird keycodes\n if (Object.prototype.hasOwnProperty.call(toAscii, key)) {\n key = toAscii[key];\n }\n\n // Handle Numpad keys\n if (key >= 96 && key <= 105) {\n key -= 48;\n }\n\n // Convert Keycode to Character String\n if (!e.shiftKey && (key >= 65 && key <= 90)) {\n character = String.fromCharCode(key + 32);\n } else if (!e.shiftKey && (key >= 37 && key <= 40)) { // arrow keys\n character = '';\n } else if (e.shiftKey &&\n Object.prototype.hasOwnProperty.call(shiftUps, key)) { // User was pressing Shift + any key\n character = shiftUps[key];\n } else {\n character = String.fromCharCode(key);\n }\n\n return character;\n};\n\n/**\n * Get the actualy typed key from the event.\n * @private\n * @param {object} e The event to check for the key.\n * @returns {string} The actual key typed.\n */\n$.actualChar = function (e) {\n return utils.actualChar(e);\n};\n\n/**\n * Equate two values quickly in a truthy fashion\n * @private\n * @param {any} a first value\n * @param {any} b second value\n * @returns {boolean} whether the two items compare in a truthy fashion.\n */\nutils.equals = function equals(a, b) {\n return JSON.stringify(a) === JSON.stringify(b);\n};\n\n/**\n * Converts an element wrapped in a jQuery collection down to its original HTMLElement reference.\n * If an HTMLElement is passed in, simply returns it.\n * If anything besides HTMLElements or jQuery[] is passed in, returns undefined;\n * @private\n * @param {any} item the item being evaluated\n * @returns {HTMLElement|undefined} the unwrapped item, or nothing.\n */\nDOM.convertToHTMLElement = function convertToHTMLElement(item) {\n if (item instanceof HTMLElement) {\n return item;\n }\n\n if (item instanceof $) {\n if (item.length) {\n item = item[0];\n } else {\n item = undefined;\n }\n return item;\n }\n\n return undefined;\n};\n\n/**\n * Returns a list of all focusable elements contained within the current element.\n * Somewhat lifted from https://gomakethings.com/how-to-get-the-first-and-last-focusable-elements-in-the-dom/\n * @param {HTMLElement} el the element to search.\n * @param {array} [additionalSelectors] containing strings representing CSS selectors that should also be considered when making the query for focusable elements.\n * @param {array} [ignoreSelectors] containing strings representing CSS selectors that should be filtered out from selection.\n * @returns {array} containing the focusable elements.\n */\nDOM.focusableElems = function focusableElems(el, additionalSelectors = [], ignoreSelectors = []) {\n const focusableElemSelector = [\n 'button:not([disabled]):not([tabindex=\"-1\"])',\n '[href]:not([disabled]):not([tabindex=\"-1\"])',\n 'input:not([disabled]):not([tabindex=\"-1\"])',\n 'div.dropdown:not(.is-disabled):not([tabindex=\"-1\"])',\n 'div.multiselect:not(.is-disabled):not([tabindex=\"-1\"])',\n 'textarea:not([disabled]):not([tabindex=\"-1\"])',\n '[focusable]:not([focusable=\"false\"])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable]',\n 'iframe'\n ].concat(additionalSelectors).filter(item => ignoreSelectors.indexOf(item) === -1);\n const elems = [...el.querySelectorAll(focusableElemSelector.join(', '))];\n return elems.filter((elem) => {\n if (elem.tagName.toLowerCase() === 'use') {\n return false;\n }\n return true;\n });\n};\n\n/**\n * See if the object is simple or more complex (has a constructor).\n * @param {object} obj The object to check\n * @returns {boolean} Returns true if simple\n */\nutils.isPlainObject = function isPlainObject(obj) {\n if (!obj || Object.prototype.toString.call(obj) !== '[object Object]') {\n return false;\n }\n\n // Objects with no prototype (e.g., `Object.create( null )`) are plain\n const proto = Object.getPrototypeOf(obj);\n if (!proto) {\n return true;\n }\n\n return obj !== null && typeof (obj) === 'object' && Object.getPrototypeOf(obj) === Object.prototype;\n};\n\n/**\n * Merge an array be each index position\n * @param {array} arr1 The first array\n * @param {array} arr2 The second array\n * @returns {array} The merged array\n */\nutils.mergeByPosition = function mergeByPosition(arr1, arr2) {\n const len = Math.max(arr1?.length || 0, arr2?.length || 0);\n if (!arr1) {\n arr1 = [];\n }\n\n if (len === 0) {\n return [];\n }\n\n for (let i = 0; i < len; i++) {\n if (arr2[i] !== undefined && arr2[i] !== null) {\n arr1[i] = arr2[i];\n }\n }\n return arr1 || [];\n};\n\n/**\n * Merge the contents of two or more objects together into the first object.\n * @param {boolean|object} deepOrTarget If a boolean (true), the merge becomes recursive (aka. deep copy). Passing false for this argument is not supported. If an object then this object well get the extended objects applied.\n * @param {object} object1 An object containing additional properties to merge in.\n * @param {object} objectN Additional objects containing properties to merge in.\n * @returns {object} The merged object\n */\nutils.extend = function extend() {\n // Variables\n let extended = arguments[0] || {};\n let deep = false;\n let i = 0;\n\n // Check if a deep merge\n if (Object.prototype.toString.call(arguments[0]) === '[object Boolean]') {\n deep = arguments[0];\n extended = Array.isArray(arguments[1]) ? [] : {};\n i++;\n }\n\n // Merge the object into the extended object\n const merge = function (obj) {\n for (let prop in obj) { //eslint-disable-line\n if (obj.hasOwnProperty(prop)) { //eslint-disable-line\n // If property is an object, merge properties - in several ways\n if (obj[prop] instanceof jQuery) {\n // Needed for now until jQuery is fully dropped\n extended[prop] = $(obj[prop]);\n } else if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {\n const newObj = obj[prop];\n const isPlain = utils.isPlainObject(newObj);\n const emptyObj = Array.isArray(newObj) ? [] : {};\n extended[prop] = isPlain ? extend(true, emptyObj, extended[prop], newObj) : newObj;\n } else {\n if (Array.isArray(obj[prop])) { //eslint-disable-line\n extended[prop] = utils.mergeByPosition(extended[prop], obj[prop]);\n } else if (obj[prop] !== undefined) {\n extended[prop] = obj[prop] === undefined && extended[prop] !== undefined ?\n extended[prop] : obj[prop];\n }\n }\n }\n\n // Add functions and jQuery objects\n if (!obj.hasOwnProperty(prop) && !extended[prop] && Object.prototype.toString.call(obj[prop]) === '[object Function]') { //eslint-disable-line\n extended[prop] = obj[prop];\n }\n }\n };\n\n // Loop through each object and conduct a merge\n for (; i < arguments.length; i++) {\n merge(arguments[i]); //eslint-disable-line\n }\n\n return extended;\n};\n\n/**\n * Hack for IE11 and SVGs that get moved around/appended at inconvenient times.\n * The action of changing the xlink:href attribute to something else and back will fix the problem.\n * @private\n * @param {HTMLElement} rootElement the base element\n * @returns {void}\n */\nutils.fixSVGIcons = function fixSVGIcons(rootElement) {\n if (env.browser.name !== 'ie' && env.browser.version !== '11') {\n return;\n }\n\n if (rootElement === undefined) {\n return;\n }\n\n const xlinkNS = 'http://www.w3.org/1999/xlink';\n\n // Handle jQuery\n if (rootElement instanceof $) {\n if (!rootElement.length) {\n return;\n }\n\n if (rootElement.length === 1) {\n rootElement = rootElement[0];\n } else {\n rootElement.each((i, elem) => {\n fixSVGIcons(elem);\n });\n return;\n }\n }\n\n // Handle NodeList in an IE-friendly way\n // https://developer.mozilla.org/en-US/docs/Web/API/NodeList#Example\n if (rootElement instanceof NodeList) {\n Array.prototype.forEach.call(rootElement, (elem) => {\n fixSVGIcons(elem);\n });\n return;\n }\n\n setTimeout(() => {\n const uses = rootElement.getElementsByTagName('use');\n for (let i = 0; i < uses.length; i++) {\n const attr = uses[i].getAttributeNS(xlinkNS, 'href');\n uses[i].setAttributeNS(xlinkNS, 'href', 'x');\n uses[i].setAttributeNS(xlinkNS, 'href', attr);\n }\n }, 1);\n};\n\n/**\n * Gets the current size of the viewport\n * @private\n * @returns {object} width/height of the viewport\n */\nutils.getViewportSize = function getViewportSize() {\n return {\n width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),\n height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0)\n };\n};\n\n/**\n * Gets the various scrollable containers that an element is nested inside of, and returns\n * their scrollHeight and scrollLeft values.\n * @private\n * @param {HTMLElement} element the base element to check for containment\n * @returns {object} containing references to the container element and its top/left\n */\nutils.getContainerScrollDistance = function getContainerScrollDistance(element) {\n if (!DOM.isElement(element)) {\n return [];\n }\n\n const containers = [];\n const scrollableElements = [\n '.scrollable', '.scrollable-x', '.scrollable-y', '.modal',\n '.card-content', '.widget-content', '.tab-panel',\n '.datagrid-content'\n ];\n\n $(element).parents(scrollableElements.join(', ')).each(function () {\n const el = this;\n\n containers.push({\n element: el,\n left: el.scrollLeft,\n top: el.scrollTop\n });\n });\n\n // Push the body's scroll area if it's not a \"no-scroll\" area\n if (!document.body.classList.contains('no-scroll')) {\n containers.push({\n element: document.body,\n left: document.body.scrollLeft,\n top: document.body.scrollTop\n });\n }\n\n return containers;\n};\n\n/**\n * Takes an element that is currently hidden by some means (FX: \"display: none;\")\n * and gets its potential dimensions by checking a clone of the element that is NOT hidden.\n * @private\n * @param {HTMLElement|SVGElement|jQuery[]} el The element being manipulated.\n * @param {object} options incoming options.\n * @param {jQuery[]} [parentElement] the parent element where a clone of this\n * hidden element will be attached.\n * @returns {object} containing various width/height properties of the element provided.\n */\nutils.getHiddenSize = function getHiddenSize(el, options) {\n const defaults = {\n dims: { width: 0, height: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 },\n parentElement: undefined,\n includeMargin: false\n };\n\n if (!DOM.isElement(el)) {\n return defaults.dims;\n }\n\n el = $(el);\n options = { ...defaults, ...options };\n\n // element becomes clone and appended to a parentElement, if defined\n const hasDefinedParentElement = DOM.isElement(options.parentElement);\n if (hasDefinedParentElement) {\n el = el.clone().appendTo(options.parentElement);\n }\n\n const dims = options.dims;\n const hiddenParents = el.parents().add(el);\n const props = {\n transition: 'none',\n webkitTransition: 'none',\n mozTransition: 'none',\n msTransition: 'none',\n visibility: 'hidden',\n display: 'block',\n };\n const oldProps = [];\n\n hiddenParents.each(function () {\n const old = {};\n const propTypes = Object.keys(props);\n propTypes.forEach((name) => {\n if (this.style[name]) {\n old[name] = this.style[name];\n this.style[name] = props[name];\n }\n });\n\n oldProps.push(old);\n });\n\n dims.padding = {\n bottom: el.css('padding-bottom'),\n left: el.css('padding-left'),\n right: el.css('padding-right'),\n top: el.css('padding-top')\n };\n dims.width = el.width();\n dims.outerWidth = el.outerWidth(options.includeMargin);\n dims.innerWidth = el.innerWidth();\n dims.scrollWidth = el[0].scrollWidth;\n dims.height = el.height();\n dims.innerHeight = el.innerHeight();\n dims.outerHeight = el.outerHeight(options.includeMargin);\n dims.scrollHeight = el[0].scrollHeight;\n\n hiddenParents.each(function (i) {\n const old = oldProps[i];\n const propTypes = Object.keys(props);\n propTypes.forEach((name) => {\n if (old[name]) {\n this.style[name] = old[name];\n }\n });\n });\n\n // element is ONLY removed when a parentElement is defined because it was cloned.\n if (hasDefinedParentElement) {\n el.remove();\n }\n\n return dims;\n};\n\n/**\n * Binds the Soho Util _getHiddenSize()_ to a jQuery selector\n * @private\n * @param {object} options - incoming options\n * @returns {object} hidden size\n */\n$.fn.getHiddenSize = function (options) {\n return utils.getHiddenSize(this, options);\n};\n\n/**\n * Checks if a specific input is a String\n * @private\n * @param {any} value an object of unknown type to check\n * @returns {boolean} whether or not a specific input is a String\n */\nutils.isString = function isString(value) {\n return typeof value === 'string' || value instanceof String;\n};\n\n/**\n * Checks if a specific input is a Number\n * @private\n * @param {any} value an object of unknown type to check\n * @returns {boolean} whether or not a specific input is a Number\n */\nutils.isNumber = function isNumber(value) {\n return typeof value === 'number' && value.length === undefined && !isNaN(value);\n};\n\n/**\n * Safely changes the position of a text caret inside of an editable element.\n * In most cases, will call \"setSelectionRange\" on an editable element immediately, but in some\n * cases, will be deferred with `requestAnimationFrame` or `setTimeout`.\n * @private\n * @param {HTMLElement} element the element to get selection\n * @param {number} startPos starting position of the text caret\n * @param {number} endPos ending position of the text caret\n * @returns {void}\n */\nutils.safeSetSelection = function safeSetSelection(element, startPos, endPos) {\n // If this text field doesn't support text caret selection, return out\n const compatibleTypes = ['text', 'password', 'search', 'url', 'week', 'month'];\n if (!(element instanceof HTMLInputElement) || compatibleTypes.indexOf(element.type) === -1) {\n return;\n }\n\n if (startPos && endPos === undefined) {\n endPos = startPos;\n }\n\n if (document.activeElement === element) {\n if (env.os.name === 'android') {\n defer(() => {\n element.setSelectionRange(startPos, endPos, 'none');\n }, 0);\n } else {\n element.setSelectionRange(startPos, endPos, 'none');\n }\n }\n};\n\n/**\n * Checks to see if a variable is valid for containing Soho component options.\n * @private\n * @param {object|function} o an object or function\n * @returns {boolean} whether or not the object type is valid\n */\nfunction isValidOptions(o) {\n return (typeof o === 'object' || typeof o === 'function');\n}\n\n/**\n * In some cases, functions are passed to component constructors as the settings argument.\n * This method runs the settings function if it's present and returns the resulting object.\n * @private\n * @param {object|function} o represents settings\n * @returns {object} processed settings\n */\nfunction resolveFunctionBasedSettings(o) {\n if (typeof o === 'function') {\n return o();\n }\n return o;\n}\n\n/**\n * Merges various sets of options into a single object,\n * whose intention is to be set as options on a Soho component.\n * @private\n * @param {HTMLElement|SVGElement|jQuery[]} [element] the element to process for inline-settings\n * @param {Object|function} incomingOptions desired settings\n * @param {Object|function} [defaultOptions] optional base settings\n * @returns {object} processed settings\n */\nutils.mergeSettings = function mergeSettings(element, incomingOptions, defaultOptions) {\n if (!incomingOptions || !isValidOptions(incomingOptions)) {\n if (isValidOptions(defaultOptions)) {\n incomingOptions = defaultOptions;\n } else {\n incomingOptions = {};\n }\n }\n\n return utils.extend(\n true,\n {},\n resolveFunctionBasedSettings(defaultOptions),\n resolveFunctionBasedSettings(incomingOptions),\n (element !== undefined ? utils.parseSettings(element) : {})\n );\n};\n\n/**\n * Test if a string is Html or not\n * @private\n * @param {string} string The string to test.\n * @returns {boolean} True if it is html.\n */\nutils.isHTML = function (string) {\n return /(<([^>]+)>)/i.test(string);\n};\n\nconst math = {};\n\n/**\n * Convert `setTimeout/Interval` delay values (CPU ticks) into frames-per-second\n * (FPS) numeric values.\n * @private\n * @param {number} delay CPU Ticks\n * @returns {number} Frames Per Second\n */\nmath.convertDelayToFPS = function convertDelayToFPS(delay) {\n if (isNaN(delay)) {\n throw new Error('provided delay value is not a number');\n }\n return delay / 16.7;\n};\n\n/**\n * Convert `setTimeout/Interval` delay values (CPU ticks) into frames-per-second\n * (FPS) numeric values.\n * @private\n * @param {number} fps (Frames Per Second)\n * @returns {number} delay in CPU ticks\n */\nmath.convertFPSToDelay = function convertFPSToDelay(fps) {\n if (isNaN(fps)) {\n throw new Error('provided delay value is not a number');\n }\n return fps * 16.7;\n};\n\n/**\n * Determines whether the passed value is a finite number.\n * @private\n * @param {number} value The number\n * @returns {boolean} If it is finite or not.\n */\nmath.isFinite = function isFinite(value) {\n // 1. If Type(number) is not Number, return false.\n if (typeof value !== 'number') {\n return false;\n }\n // 2. If number is NaN, +∞, or −∞, return false.\n if (value !== value || value === Infinity || value === -Infinity) { //eslint-disable-line\n return false;\n }\n // 3. Otherwise, return true.\n return true;\n};\n\n/**\n * `Array.ForEach()`-style method that is also friendly to `NodeList` types.\n * @param {Array|NodeList} array incoming items\n * @param {function} callback the method to run\n * @param {object} scope the context in which to run the method\n */\nutils.forEach = function forEach(array, callback, scope) {\n for (let i = 0; i < array.length; i++) {\n callback.call(scope, array[i], i, array); // passes back stuff we need\n }\n};\n\n/**\n * Returns the sign of a number, indicating whether the number is positive, negative or zero\n * @param {number} x A number.\n * @returns {number} A number representing the sign of the given argument. If the argument is a positive number, negative number, positive zero or negative zero, the function will return 1, -1, 0 or -0 respectively. Otherwise, NaN is returned.\n */\nmath.sign = function (x) {\n if (Math.sign) { // eslint-disable-line compat/compat\n return Math.sign(x); // eslint-disable-line compat/compat\n }\n\n x = +x;\n if (x === 0 || isNaN(x)) {\n return x;\n }\n return x > 0 ? 1 : -1;\n};\n\n/**\n * Convenience method for using `Array.prototype.slice()` on an Array-like object (or an actual array)\n * to make a copy.\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#Array-like_objects\n * @param {Array|NodeList} listObj an array-like object\n * @returns {array} containing the list in array format.\n */\nutils.getArrayFromList = function (listObj) {\n const unboundSlice = Array.prototype.slice;\n return Function.prototype.call.bind(unboundSlice)(listObj);\n};\n\n/**\n * Gets the OS scollbar width in pixels.\n * @returns {number} The width as a number.\n */\nutils.getScrollbarWidth = function () {\n const outer = document.createElement('div');\n outer.style.visibility = 'hidden';\n outer.style.width = '100px';\n document.body.appendChild(outer);\n\n const widthNoScroll = outer.offsetWidth;\n outer.style.overflow = 'scroll';\n\n const inner = document.createElement('div');\n inner.style.width = '100%';\n outer.appendChild(inner);\n\n const widthWithScroll = inner.offsetWidth;\n outer.parentNode.removeChild(outer);\n\n return widthNoScroll - widthWithScroll;\n};\n\n/**\n * Create deep copy for given array or object.\n * @param {array|object} arrayOrObject The array or object to be copied.\n * @returns {array|object} The copied array or object.\n */\nutils.deepCopy = function (arrayOrObject) {\n return utils.extend(true, Array.isArray(arrayOrObject) ? [] : {}, arrayOrObject);\n};\n\n/**\n * Check if the event is subscribed to\n * @param {HTMLElement} elem The object to check\n * @param {object} e The event object to check\n * @param {string} eventName The event name to look for\n * @param {string} namespace The namespace to look for\n * @returns {boolean} True if the event is subscribed to\n */\nutils.isSubscribedTo = function (elem, e, eventName, namespace) {\n const events = $._data(elem).events; //eslint-disable-line\n\n for (const event in events) { //eslint-disable-line\n if (event === eventName && !(events[event].length === 1 &&\n events[event][0].namespace === namespace)) {\n return true;\n }\n }\n return false;\n};\n\n/**\n * Check if given element is within the viewport.\n * @private\n * @param {object} element The element to check\n * @returns {boolean} Whether or not the element is in the viewport.\n */\nutils.isInViewport = function isInViewport(element) {\n const b = element.getBoundingClientRect();\n return (\n b.top >= 0 && b.left >= 0 &&\n b.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n b.right <= (window.innerWidth || document.documentElement.clientWidth)\n );\n};\n\n/**\n * Get siblings height for given element.\n * @privateel\n * @param {object} el The element to get siblings height.\n * @returns {number} The calculated height.\n */\nutils.getSiblingsHeight = function getSiblingsHeight(el) {\n const siblings = DOM.getSiblings(el);\n return siblings.map(sibling => sibling.offsetHeight).reduce((a, h) => a + h, 0);\n};\n\n/**\n * Get parent available height for given element.\n * @privateel\n * @param {object} el The element to get parent available height.\n * @returns {number} The calculated height.\n */\nutils.getParentAvailableHeight = function getParentAvailableHeight(el) {\n return el.parentNode.offsetHeight - utils.getSiblingsHeight(el);\n};\n\n/**\n * Clear all currently selected.\n * @privateel\n * @returns {void}\n */\nutils.clearSelection = function clearSelection() {\n if (window.getSelection) {\n window.getSelection().removeAllRanges();\n } else if (document.selection) {\n document.selection.empty();\n }\n};\n\n/**\n * Toggle the form compact mode and any child classes as need.\n * @param {HTMLElement} elem The top level element or form element\n * @returns {void}\n */\nutils.toggleCompactMode = function toggleCompactMode(elem) {\n const className = 'form-layout-compact';\n const hasClass = elem.classList.contains(className);\n if (hasClass) {\n elem.classList.remove(className);\n } else {\n elem.classList.add(className);\n }\n\n const datagrids = elem.querySelectorAll('.datagrid-container');\n for (let i = 0; i < datagrids.length; i++) {\n const api = $(datagrids[i]).data('datagrid');\n if (!api.rowHeight) {\n return;\n }\n if (hasClass) {\n api.rowHeight(api.oldRowHeight || 'large');\n } else {\n api.oldRowHeight = api.settings.rowHeight;\n api.rowHeight('extra-small');\n }\n }\n};\n\n/**\n * Generate additional attributes.\n * @private\n * @param {object} elem The DOM node to add to\n * @param {object} api The object base api\n * @param {object|Array} setting The attribute setting\n * @param {string} suffix Append an extra string at the end\n * @param {boolean} overrideExistingId Write over the current id value if there already\n */\nutils.addAttributes = function addAttributes(elem, api, setting, suffix, overrideExistingId) {\n if (!setting) {\n return;\n }\n\n // Add the given attribute to element, if not alreay exist\n const addAttr = (name, value) => {\n if (elem[0] && (overrideExistingId ? true : !elem[0].hasAttribute(name))) {\n elem.attr(name, value + (suffix ? `-${suffix.toLowerCase()}` : ''));\n }\n };\n\n if (Array.isArray(setting)) {\n setting.forEach((item) => {\n const value = typeof item.value === 'function' ? item.value(api) : item.value;\n addAttr(item.name, value);\n });\n return;\n }\n\n const value = typeof setting.value === 'function' ? setting.value(api) : setting.value;\n addAttr(setting.name, value);\n};\n\n/**\n * Generate additional attributes as an html string\n * @private\n * @param {object} api The object base api\n * @param {object|Array} setting The attribute setting\n * @param {string} suffix Append an extra string at the end\n * @returns {string} the attributes as a string\n */\nutils.stringAttributes = function addAttributes(api, setting, suffix) {\n let attributes = '';\n if (!setting) {\n return attributes;\n }\n\n if (Array.isArray(setting)) {\n setting.forEach((item) => {\n const value = typeof item.value === 'function' ? item.value(api) : item.value;\n attributes += ` ${item.name}=\"${value + (suffix ? `-${suffix}` : '')}\"`;\n });\n return attributes;\n }\n\n const value = typeof setting.value === 'function' ? setting.value(api) : setting.value;\n attributes += ` ${setting.name}=\"${value + (suffix ? `-${suffix.toLowerCase()}` : '')}\"`;\n return attributes;\n};\n\n/**\n * Using an array of attribute settings objects, determines if an attribute setting with a specified\n * name exists in that group of settings. This can be used to get the final value of an attribute before\n * it's rendered into the DOM.\n * @param {object} api the object base API\n * @param {string} thisName the name of the setting to search for\n * @param {object|Array} setting the attribute settings\n * @returns {string|undefined} the value contained by the setting\n */\nutils.getAttribute = function getAttribute(api, thisName, setting) {\n let value;\n if (typeof thisName !== 'string' || !thisName.length || !Array.isArray(setting)) {\n return value;\n }\n\n setting.forEach((item) => {\n if (!value && item.name === thisName) {\n value = typeof item.value === 'function' ? item.value(api) : item.value;\n }\n });\n\n return value;\n};\n\n/**\n * Watches an element and waits for the specified property's `transitionend` event to complete.\n * @param {HTMLElement} el the element to act on\n * @param {string} property the CSS property used to qualify the correct transitionend event\n * @returns {Promise} fulfulled when the CSS transition completes\n */\nutils.waitForTransitionEnd = function (el, property) {\n // eslint-disable-next-line\n return new Promise((resolve) => {\n const transitionEnded = (e) => {\n if (e.propertyName !== property) return;\n el.removeEventListener('transitionend', transitionEnded);\n resolve();\n };\n el.addEventListener('transitionend', transitionEnded);\n });\n};\n\nexport { utils, math };\n","const objectUtils = {};\n\n/**\n * Checks to see if an object has any identifiable properties beyond standard Object properties\n * that can be used for comparison or evaluation.\n * @param {object} obj the object to check.\n * @returns {boolean} true if the object is empty, false if it contains properties.\n */\nobjectUtils.isEmpty = function (obj) {\n return Object.keys(obj).length === 0 && obj.constructor === Object;\n};\n\nexport { objectUtils };\n","const stringUtils = {};\n\n/**\n * Re-usable Empty String that can be referenced everywhere to save small amounts of space.\n */\nstringUtils.EMPTY = '';\n\n/**\n* The splice() method changes the content of a string by removing a range of\n* characters and/or adding new characters.\n*\n* @param {string} str The string that will be manipulated.\n* @param {number} start Index at which to start changing the string.\n* @param {number} delCount An integer indicating the number of old chars to remove.\n* @param {string} newSubStr The String that is spliced in.\n* @returns {string} A new string with the spliced substring.\n*/\nstringUtils.splice = function splice(str, start, delCount, newSubStr) {\n return str.slice(0, start) + newSubStr + str.slice(start + Math.abs(delCount));\n};\n\n/**\n * Takes a string with possible duplicate characters and returns a string\n * containing ALL unique characters. Useful for construction of REGEX objects\n * with characters from an input field, etc.\n * @param {string} str The string to process\n * @returns {string} The processed string\n */\nstringUtils.removeDuplicates = function removeDuplicates(str) {\n return str\n .split('')\n .filter(function(item, pos, self) { //eslint-disable-line\n return self.indexOf(item) === pos;\n })\n .join('');\n};\n\n/**\n * Takes a string and uses a regex test to detect the presence of HTML elements.\n * @param {string} str The string to search\n * @returns {boolean} True if the string is contained.\n */\nstringUtils.containsHTML = function containsHTML(str) {\n return /<[a-z][\\s\\S]*>/i.test(str);\n};\n\n/**\n * Takes a string containing HTML and strips it of extraneous white space.\n * @param {string} str The string to parse\n * @returns {string} The string minus extraneous white space.\n */\nstringUtils.stripWhitespace = function stripWhitespace(str) {\n return str.replace(/\\n/g, '')\n .replace(/[\\t ]+[\\t ]+<')\n .replace(/>[\\t ]+$/g, '>');\n};\n\n/**\n * Capitalizes the first letter of a string\n * @param {string} str the incoming text\n * @returns {string} the modified text\n */\nstringUtils.capitalize = function capitalize(str) {\n return str.charAt(0).toUpperCase() + str.slice(1);\n};\n\n/**\n * [capitalize description]\n * @param {string} val A text string (\"true\" or \"false\") that can be converted to a boolean.\n * @returns {boolean} true or false\n */\nstringUtils.toBoolean = function capitalize(val) {\n const num = +val;\n return !isNaN(num) ? !!num : !!String(val).toLowerCase().replace(!!0, '');\n};\n\n/**\n * Return the width in pixels, assuming fontsize 14 as a default\n * @param {string} text A text string to measure.\n * @param {string} fontsize The elements font size (defaults to 14)\n * @returns {number} The text width.\n */\nstringUtils.textWidth = function capitalize(text, fontsize = 14) {\n this.canvas = this.canvas || (this.canvas = document.createElement('canvas'));\n const context = this.canvas.getContext('2d');\n context.font = `${fontsize}px arial`;\n\n const metrics = context.measureText(text);\n return Math.round(metrics.width);\n};\n\n/**\n * Pad a date into a string with zeros added.\n * @private\n * @param {number} year The year to use.\n * @param {number} month The month to use.\n * @param {number} day The day to use.\n * @returns {void}\n */\nstringUtils.padDate = function padDate(year, month, day) {\n return year + `0${month + 1}`.slice(-2) + `0${day}`.slice(-2);\n};\n\n/**\n * Calculate the width for given text string.\n * @private\n * @param {string} text string to process\n * @param {number} padding value for left + right\n * @param {string} font size and family used with the given text string\n * @returns {number} calculated width\n */\nstringUtils.textWidth = function textWidth(text, padding, font) {\n const canvasTW = document.createElement('canvas');\n if (!canvasTW) return 0;\n const context = canvasTW.getContext('2d');\n if (!context) return 0;\n context.font = font || '14px arial';\n\n const metrics = context.measureText(text);\n return Math.round(metrics.width + (padding || 0));\n};\n\n/**\n * Escape user input that will be treated as a literal string. This prevents incorrect\n * RegExp matching when converting user input.\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping\n * @private\n * @param {string} s string to process.\n * @returns {string} string after escaping.\n */\nstringUtils.escapeRegExp = function escapeRegExp(s) {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'); // $& whole matched string\n};\n\n/**\n * Return the count of a occurences in a string\n * @param {string} string The string\n * @param {string} subString The substring to count\n * @returns {number} The frequency\n */\nstringUtils.count = function count(string, subString) {\n return string.split(subString).length - 1;\n};\n\n/**\n * Checks a string to see if it represents a valid URL\n * @param {string} val incoming string\n * @returns {boolean} true if the value is a valid URL\n */\nstringUtils.isValidURL = function isValidURL(val) {\n const urlRegexPattern = new RegExp('^(https?:\\\\/\\\\/)?' + // validate protocol\n '((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|' + // validate domain name\n '((\\\\d{1,3}\\\\.){3}\\\\d{1,3}))' + // validate OR ip (v4) address\n '(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*' + // validate port and path\n '(\\\\?[;&a-z\\\\d%_.~+=-]*)?' + // validate query string\n '(\\\\#[-a-z\\\\d_]*)?$', 'i'); // validate fragment locator\n return !!urlRegexPattern.test(val);\n};\n\nexport { stringUtils }; //eslint-disable-line\n","const numberUtils = {};\n\n/**\n * Truncates the number down by simply cutting off the number to the decimals provided.\n * Handles large numbers as strings up to 18, 6.\n * @param {string|number} number The number to truuncate.\n * @param {number} [decimals=2] The decimals to truncate to.\n * @returns {string} The truncated number.\n */\nnumberUtils.truncate = function truncate(number, decimals = 2) {\n const numString = number.toString();\n const parts = numString.split('.');\n parts[1] = !parts[1] ? '' : parts[1].substr(0, decimals);\n return `${parts[0]}.${parts[1]}`;\n};\n\n/**\n * Rounds the number down by true rounding.\n * Handles large numbers as strings up to 18, 6.\n * @param {string|number} number The number to round.\n * @param {number} [decimals=2] The decials to round to.\n * @returns {string} The rounded number.\n */\nnumberUtils.round = function round(number, decimals = 2) {\n return numberUtils.toFixed(number, decimals);\n};\n\n/**\n * Support function for toFixed that replaces JS toFixed and handles rounding properly.\n * This function does not handle big numbers.\n * @param {string|number} number The number to fix.\n * @param {decimals} [decimals=2] The decimal precision.\n * @returns {string} The string formatted to the precision.\n */\nnumberUtils.fixTo = function toFixed(number, decimals = 2) {\n if (decimals > 10 && number.toString().indexOf('.') > -1) {\n return number.toLocaleString('en-US', { useGrouping: false, minimumFractionDigits: decimals });\n }\n return (+(Math.round(+(number + 'e' + decimals)) + 'e' + -decimals)).toFixed(decimals); //eslint-disable-line\n};\n\n/**\n * Returns a string representation of the number that does not use exponential notation\n * and has exactly digits after the decimal place. The number is rounded if necessary,\n * and the fractional part is padded with zeros if necessary so that it has the specified length.\n * This implementation handles greater or equal to 1e+21 so accepts string or number.\n * @param {string|number} number The number to fix\n * @param {decimals} [decimals=2] The decimal precision.\n * @returns {string} The string formatted to the precision.\n */\nnumberUtils.toFixed = function toFixed(number, decimals = 2) {\n // Parse the number into three parts. Max supported number is 18.15\n let numStr = number.toString();\n let hasMinus = false;\n if (numStr.substr(0, 1) === '-') {\n hasMinus = true;\n numStr = numStr.replace('-', '');\n }\n let parsedNum = '';\n const noDecimals = numStr.split('.');\n const parts = [noDecimals[0].substr(0, 10), noDecimals[0].substr(10), noDecimals[1]];\n\n let firstPart = parts[0];\n const lastPart = parts[1];\n parsedNum = this.fixTo((lastPart || firstPart) + (parts[2] ? `.${parts[2]}` : ''), decimals).toString();\n\n if (lastPart && parsedNum.length === 11 && parsedNum === '10000000000') {\n parsedNum = '0000000000';\n firstPart = (parseInt(firstPart, 10) + 1).toString();\n }\n\n if (lastPart && lastPart.substr(0, 1) !== '0') {\n parsedNum = firstPart + parsedNum;\n }\n if (lastPart && lastPart.substr(0, 1) === '0') {\n parsedNum = `${firstPart}${parsedNum}`;\n }\n if (hasMinus) {\n return `-${parsedNum}`;\n }\n return parsedNum;\n};\n\n/**\n * Returns the number of decimal places in a number.\n * @param {string|number} number The number to check.\n * @returns {number} The number of decimal places.\n */\nnumberUtils.decimalPlaces = function decimalPlaces(number) {\n if (Math.floor(number) === number) {\n return 0;\n }\n\n if (number.toString().indexOf('.') === -1) {\n return 0;\n }\n return number.toString().split('.')[1].length || 0;\n};\n\nexport { numberUtils };\n","/* eslint-disable yoda */\nconst colorUtils = {};\n\n// Safely converts a single RGBA color component (R, G, B, or A) to\n// its corresponding two-digit hexidecimal value.\nfunction componentToHex(c) {\n const hex = Number(c).toString(16);\n return hex.length === 1 ? `0${hex}` : hex;\n}\n\n/**\n * Convert the provided hex to an RGBA with an opacity.\n * @private\n * @param {string} hex to set.\n * @param {string} opacity to check.\n * @returns {string} converted rgba\n */\ncolorUtils.hexToRgba = function hexToRgba(hex, opacity) {\n let c;\n if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {\n c = hex.substring(1).split('');\n\n if (c.length === 3) {\n c = [c[0], c[0], c[1], c[1], c[2], c[2]];\n }\n\n c = `0x${c.join('')}`;\n // eslint-disable-next-line\n return `rgba(${[(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',')},${opacity.toString()})`;\n }\n return '';\n};\n\n/**\n * Converts a hex color to an object containing separate R, G, and B values.\n * @param {string} hex string representing a hexidecimal color\n * @returns {object|null} containing separate \"r\", \"g\", and \"b\" values.\n */\ncolorUtils.hexToRgb = function hexToRgb(hex) {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result ? {\n r: parseInt(result[1], 16),\n g: parseInt(result[2], 16),\n b: parseInt(result[3], 16)\n } : null;\n};\n\n/**\n * Takes separate R, G, and B values, and converts them to a hexidecimal string\n * @param {number|string|obj} r a number between 0-255 representing the amount of red. Can also be an object with `r`, `g`, and `b` values.\n * @param {number|string} g a number between 0-255 representing the amount of green\n * @param {number|string} b a number between 0-255 representing the amount of blue\n * @returns {string} containing the matching hexidecimal color value\n */\ncolorUtils.rgbToHex = function rgbToHex(r, g, b) {\n if (typeof r === 'object' && (typeof r.r === 'number' || typeof r.r === 'string')) {\n g = r.g;\n b = r.b;\n r = r.r;\n }\n return `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;\n};\n\n/**\n * Converts an RGB color value to an HSL color value\n * @param {number|string|obj} r a number between 0-255 representing the amount of red. Can also be an object with `r`, `g`, and `b` values.\n * @param {number|string} g a number between 0-255 representing the amount of green\n * @param {number|string} b a number between 0-255 representing the amount of blue\n * @returns {object} containing hue/saturation/lightness values (h, s, l).\n */\ncolorUtils.rgbToHsl = function rgbToHsl(r, g, b) {\n if (typeof r === 'object' && (typeof r.r === 'number' || typeof r.r === 'string')) {\n g = r.g;\n b = r.b;\n r = r.r;\n }\n\n // Ensure all values are numbers\n r = Number(r);\n g = Number(g);\n b = Number(b);\n\n // Make all the values fractions\n r /= 255;\n g /= 255;\n b /= 255;\n\n // Find greatest/smallest channel values\n const cmin = Math.min(r, g, b);\n const cmax = Math.max(r, g, b);\n const delta = cmax - cmin;\n let h = 0;\n let s = 0;\n let l = 0;\n\n // Calculate Hue\n // delta of `0` means there is no adjustment.\n if (delta === 0) {\n h = 0;\n } else if (cmax === r) {\n // Red is max\n h = ((g - b) / delta) % 6;\n } else if (cmax === g) {\n // Green is Max\n h = (b - r) / delta + 2;\n } else {\n // Blue is Max\n h = (r - g) / delta + 4;\n }\n\n h = Math.round(h * 60);\n\n // If the hue comes out negative, make it a positive\n if (h < 0) {\n h += 360;\n }\n\n // Calculate Lightness\n l = (cmax + cmin) / 2;\n\n // Calculate Saturation\n if (delta !== 0) {\n s = delta / (1 - Math.abs(2 * l - 1));\n }\n\n // multiply the final saturation/lightness values by 100 (make them percentages)\n s = +(s * 100).toFixed(1);\n l = +(l * 100).toFixed(1);\n\n return { h, s, l };\n};\n\n/**\n * Converts an HSL color value to an RGB color value\n * @param {number|string|obj} h a number between 0-255 representing the amount of red. Can also be an object with `h`, `s`, and `l` properties.\n * @param {number|string} s a number between 0-255 representing the amount of green\n * @param {number|string} l a number between 0-255 representing the amount of blue\n * @returns {object} containing hue/saturation/lightness values (h, s, l).\n */\ncolorUtils.hslToRgb = function hslToRgb(h, s, l) {\n if (typeof h === 'object' && (typeof h.h === 'number' || typeof h.h === 'string')) {\n h = h.h;\n s = h.s;\n l = h.l;\n }\n\n // Ensure all values are numbers\n h = Number(h);\n s = Number(s);\n l = Number(l);\n\n // make saturation/lightness fractions of 1\n s /= 100;\n l /= 100;\n\n // chroma (c), second largest component (x),\n // and amount to add to each channel to match lightness (m)\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs((h / 60) % 2 - 1));\n const m = l - c / 2;\n let r = 0;\n let g = 0;\n let b = 0;\n\n // whichever 60deg slice of an entire 360deg pie the hue lies within\n // determines the values for r/g/b\n if (0 <= h && h < 60) {\n r = c;\n g = x;\n b = 0;\n } else if (60 <= h && h < 120) {\n r = x;\n g = c;\n b = 0;\n } else if (120 <= h && h < 180) {\n r = 0;\n g = x;\n b = c;\n } else if (180 <= h && h < 240) {\n r = 0;\n g = x;\n b = c;\n } else if (240 <= h && h < 300) {\n r = x;\n g = 0;\n b = c;\n } else if (300 <= h && h < 360) {\n r = c;\n g = 0;\n b = x;\n }\n\n // Add (m) to all channels, multiply each by 255, and round to get final values.\n r = Math.round((r + m) * 255);\n g = Math.round((g + m) * 255);\n b = Math.round((b + m) * 255);\n\n return { r, g, b };\n};\n\n/**\n* Takes a color and performs a change in luminosity of that color programatically.\n* @private\n* @param {string} hex The original Hexadecimal base color.\n* @param {string} lum A percentage used to set luminosity\n* change on the base color: -0.1 would be 10% darker, 0.2 would be 20% brighter\n* @returns {string} hexadecimal color.\n*/\ncolorUtils.getLuminousColorShade = function getLuminousColorShade(hex, lum) {\n // validate hex string\n hex = this.validateHex(hex).substr(1);\n lum = lum || 0;\n\n // convert to decimal and change luminosity\n let rgb = '#';\n let c;\n let i;\n\n for (i = 0; i < 3; i++) {\n c = parseInt(hex.substr(i * 2, 2), 16);\n c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);\n rgb += (`00${c}`).substr(c.length);\n }\n\n return rgb;\n};\n\n/**\n * Validates a string containing a hexadecimal number\n * @private\n * @param {string} hex A hex color.\n * @returns {string} a validated hexadecimal string.\n */\ncolorUtils.validateHex = function validateHex(hex) {\n hex = String(hex).replace(/[^0-9a-f]/gi, '');\n\n if (hex.length < 6) {\n hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];\n }\n\n return `#${hex}`;\n};\n\n/**\n * Get calculated contrast color\n * @private\n * @param {string} hex A hex color.\n * @param {string} light Optional a custom hex color to return.\n * @param {string} dark Optional a custom hex color to return.\n * @returns {string} a calculated contrast color string.\n */\ncolorUtils.getContrastColor = function getContrastColor(hex, light, dark) {\n hex = hex ? hex.replace('#', '') : '';\n const parse = x => parseInt(hex.substr(x, 2), 16);\n const r = parse(0);\n const g = parse(2);\n const b = parse(4);\n const diff = ((r * 299) + (g * 587) + (b * 114)) / 1000;\n return (diff >= 128) ? (dark || 'black') : (light || 'white');\n};\n\n/**\n * Returns a less saturated shade of a provided color.\n * @param {string} hex the starting hexadecimal color\n * @param {number} [sat=1] a number representing a percentage change (between 0 and 1) of saturation.\n * @returns {string} the modified hexidecimal color\n */\ncolorUtils.getDesaturatedColor = function getDesaturatedColor(hex, sat = 1) {\n hex = hex ? hex.replace('#', '') : '';\n const col = colorUtils.hexToRgb(hex);\n\n // Grayscale constants\n // https://en.m.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems\n const gray = col.r * 0.3086 + col.g * 0.6094 + col.b * 0.0820;\n\n col.r = Math.round(col.r * sat + gray * (1 - sat));\n col.g = Math.round(col.g * sat + gray * (1 - sat));\n col.b = Math.round(col.b * sat + gray * (1 - sat));\n\n return colorUtils.rgbToHex(col.r, col.g, col.b);\n};\n\n/**\n * Compares the luminosity of two hex colors.\n * @param {string|obj} baseColorHex a string representing a hexadecimal color\n * @param {string|obj} compareColorHex a string representing a hexadecimal color\n * @returns {boolean} true if the base color is more luminous, false if the compared color is more luminous.\n */\ncolorUtils.isLighter = function isLighter(baseColorHex, compareColorHex) {\n // Convert to RGB Objects\n const baseColorRGB = colorUtils.hexToRgb(baseColorHex);\n const compareColorRGB = colorUtils.hexToRgb(compareColorHex);\n\n // Convert to HSL Objects\n const baseColorHSL = colorUtils.rgbToHsl(baseColorRGB);\n const compareColorHSL = colorUtils.rgbToHsl(compareColorRGB);\n\n // Compare the HSL\n return baseColorHSL.l <= compareColorHSL.l;\n};\n\nexport { colorUtils }; //eslint-disable-line\n","/* eslint-disable */\n// Modified version of Amro Osama's code. From at https://github.com/kbwood/calendars/blob/master/src/js/jquery.calendars.ummalqura.js\nexport const ummalquraData = [\n 20, 50, 79, 109, 138, 168, 197, 227, 256, 286, 315, 345, 374, 404, 433, 463, 492, 522, 551, 581, 611, 641, 670, 700, 729, 759, 788, 818, 847, 877, 906, 936, 965, 995, 1024, 1054, 1083, 1113, 1142, 1172, 1201, 1231, 1260, 1290, 1320, 1350, 1379, 1409, 1438, 1468, 1497, 1527, 1556, 1586, 1615, 1645, 1674, 1704, 1733, 1763, 1792, 1822, 1851, 1881, 1910, 1940, 1969, 1999, 2028, 2058, 2087, 2117, 2146, 2176, 2205, 2235, 2264, 2294, 2323, 2353, 2383, 2413, 2442, 2472, 2501, 2531, 2560, 2590, 2619, 2649, 2678, 2708, 2737, 2767, 2796, 2826, 2855, 2885, 2914, 2944, 2973, 3003, 3032, 3062, 3091, 3121, 3150, 3180, 3209, 3239, 3268, 3298, 3327, 3357, 3386, 3416, 3446, 3476, 3505, 3535, 3564, 3594, 3623, 3653, 3682, 3712, 3741, 3771, 3800, 3830, 3859, 3889, 3918, 3948, 3977, 4007, 4036, 4066, 4095, 4125, 4155, 4185, 4214, 4244, 4273, 4303, 4332, 4362, 4391, 4421, 4450, 4480, 4509, 4539, 4568, 4598, 4627, 4657, 4686, 4716, 4745, 4775, 4804, 4834, 4863, 4893, 4922, 4952, 4981, 5011, 5040, 5070, 5099, 5129, 5158, 5188, 5218, 5248, 5277, 5307, 5336, 5366, 5395, 5425, 5454, 5484, 5513, 5543, 5572, 5602, 5631, 5661, 5690, 5720, 5749, 5779, 5808, 5838, 5867, 5897, 5926, 5956, 5985, 6015, 6044, 6074, 6103, 6133, 6162, 6192, 6221, 6251, 6281, 6311, 6340, 6370, 6399, 6429, 6458, 6488, 6517, 6547, 6576, 6606, 6635, 6665, 6694, 6724, 6753, 6783, 6812, 6842, 6871, 6901, 6930, 6960, 6989, 7019, 7048, 7078, 7107, 7137, 7166, 7196, 7225, 7255, 7284, 7314, 7344, 7374, 7403, 7433, 7462, 7492, 7521, 7551, 7580, 7610, 7639, 7669, 7698, 7728, 7757, 7787, 7816, 7846, 7875, 7905, 7934, 7964, 7993, 8023, 8053, 8083, 8112, 8142, 8171, 8201, 8230, 8260, 8289, 8319, 8348, 8378, 8407, 8437, 8466, 8496, 8525, 8555, 8584, 8614, 8643, 8673, 8702, 8732, 8761, 8791, 8821, 8850, 8880, 8909, 8938, 8968, 8997, 9027, 9056, 9086, 9115, 9145, 9175, 9205, 9234, 9264, 9293, 9322, 9352, 9381, 9410, 9440, 9470, 9499, 9529, 9559, 9589, 9618, 9648, 9677, 9706, 9736, 9765, 9794, 9824, 9853, 9883, 9913, 9943, 9972, 10002, 10032, 10061, 10090, 10120, 10149, 10178, 10208, 10237, 10267, 10297, 10326, 10356, 10386, 10415, 10445, 10474, 10504, 10533, 10562, 10592, 10621, 10651, 10680, 10710, 10740, 10770, 10799, 10829, 10858, 10888, 10917, 10947, 10976, 11005, 11035, 11064, 11094, 11124, 11153, 11183, 11213, 11242, 11272, 11301, 11331, 11360, 11389, 11419, 11448, 11478, 11507, 11537, 11567, 11596, 11626, 11655, 11685, 11715, 11744, 11774, 11803, 11832, 11862, 11891, 11921, 11950, 11980, 12010, 12039, 12069, 12099, 12128, 12158, 12187, 12216, 12246, 12275, 12304, 12334, 12364, 12393, 12423, 12453, 12483, 12512, 12542, 12571, 12600, 12630, 12659, 12688, 12718, 12747, 12777, 12807, 12837, 12866, 12896, 12926, 12955, 12984, 13014, 13043, 13072, 13102, 13131, 13161, 13191, 13220, 13250, 13280, 13310, 13339, 13368, 13398, 13427, 13456, 13486, 13515, 13545, 13574, 13604, 13634, 13664, 13693, 13723, 13752, 13782, 13811, 13840, 13870, 13899, 13929, 13958, 13988, 14018, 14047, 14077, 14107, 14136, 14166, 14195, 14224, 14254, 14283, 14313, 14342, 14372, 14401, 14431, 14461, 14490, 14520, 14550, 14579, 14609, 14638, 14667, 14697, 14726, 14756, 14785, 14815, 14844, 14874, 14904, 14933, 14963, 14993, 15021, 15051, 15081, 15110, 15140, 15169, 15199, 15228, 15258, 15287, 15317, 15347, 15377, 15406, 15436, 15465, 15494, 15524, 15553, 15582, 15612, 15641, 15671, 15701, 15731, 15760, 15790, 15820, 15849, 15878, 15908, 15937, 15966, 15996, 16025, 16055, 16085, 16114, 16144, 16174, 16204, 16233, 16262, 16292, 16321, 16350, 16380, 16409, 16439, 16468, 16498, 16528, 16558, 16587, 16617, 16646, 16676, 16705, 16734, 16764, 16793, 16823, 16852, 16882, 16912, 16941, 16971, 17001, 17030, 17060, 17089, 17118, 17148, 17177, 17207, 17236, 17266, 17295, 17325, 17355, 17384, 17414, 17444, 17473, 17502, 17532, 17561, 17591, 17620, 17650, 17679, 17709, 17738, 17768, 17798, 17827, 17857, 17886, 17916, 17945, 17975, 18004, 18034, 18063, 18093, 18122, 18152, 18181, 18211, 18241, 18270, 18300, 18330, 18359, 18388, 18418, 18447, 18476, 18506, 18535, 18565, 18595, 18625, 18654, 18684, 18714, 18743, 18772, 18802, 18831, 18860, 18890, 18919, 18949, 18979, 19008, 19038, 19068, 19098, 19127, 19156, 19186, 19215, 19244, 19274, 19303, 19333, 19362, 19392, 19422, 19452, 19481, 19511, 19540, 19570, 19599, 19628, 19658, 19687, 19717, 19746, 19776, 19806, 19836, 19865, 19895, 19924, 19954, 19983, 20012, 20042, 20071, 20101, 20130, 20160, 20190, 20219, 20249, 20279, 20308, 20338, 20367, 20396, 20426, 20455, 20485, 20514, 20544, 20573, 20603, 20633, 20662, 20692, 20721, 20751, 20780, 20810, 20839, 20869, 20898, 20928, 20957, 20987, 21016, 21046, 21076, 21105, 21135, 21164, 21194, 21223, 21253, 21282, 21312, 21341, 21371, 21400, 21430, 21459, 21489, 21519, 21548, 21578, 21607, 21637, 21666, 21696, 21725, 21754, 21784, 21813, 21843, 21873, 21902, 21932, 21962, 21991, 22021, 22050, 22080, 22109, 22138, 22168, 22197, 22227, 22256, 22286, 22316, 22346, 22375, 22405, 22434, 22464, 22493, 22522, 22552, 22581, 22611, 22640, 22670, 22700, 22730, 22759, 22789, 22818, 22848, 22877, 22906, 22936, 22965, 22994, 23024, 23054, 23083, 23113, 23143, 23173, 23202, 23232, 23261, 23290, 23320, 23349, 23379, 23408, 23438, 23467, 23497, 23527, 23556, 23586, 23616, 23645, 23674, 23704, 23733, 23763, 23792, 23822, 23851, 23881, 23910, 23940, 23970, 23999, 24029, 24058, 24088, 24117, 24147, 24176, 24206, 24235, 24265, 24294, 24324, 24353, 24383, 24413, 24442, 24472, 24501, 24531, 24560, 24590, 24619, 24648, 24678, 24707, 24737, 24767, 24796, 24826, 24856, 24885, 24915, 24944, 24974, 25003, 25032, 25062, 25091, 25121, 25150, 25180, 25210, 25240, 25269, 25299, 25328, 25358, 25387, 25416, 25446, 25475, 25505, 25534, 25564, 25594, 25624, 25653, 25683, 25712, 25742, 25771, 25800, 25830, 25859, 25888, 25918, 25948, 25977, 26007, 26037, 26067, 26096, 26126, 26155, 26184, 26214, 26243, 26272, 26302, 26332, 26361, 26391, 26421, 26451, 26480, 26510, 26539, 26568, 26598, 26627, 26656, 26686, 26715, 26745, 26775, 26805, 26834, 26864, 26893, 26923, 26952, 26982, 27011, 27041, 27070, 27099, 27129, 27159, 27188, 27218, 27248, 27277, 27307, 27336, 27366, 27395, 27425, 27454, 27484, 27513, 27542, 27572, 27602, 27631, 27661, 27691, 27720, 27750, 27779, 27809, 27838, 27868, 27897, 27926, 27956, 27985, 28015, 28045, 28074, 28104, 28134, 28163, 28193, 28222, 28252, 28281, 28310, 28340, 28369, 28399, 28428, 28458, 28488, 28517, 28547, 28577,\n // From 1356\n 28607, 28636, 28665, 28695, 28724, 28754, 28783, 28813, 28843, 28872, 28901, 28931, 28960, 28990, 29019, 29049, 29078, 29108, 29137, 29167, 29196, 29226, 29255, 29285, 29315, 29345, 29375, 29404, 29434, 29463, 29492, 29522, 29551, 29580, 29610, 29640, 29669, 29699, 29729, 29759, 29788, 29818, 29847, 29876, 29906, 29935, 29964, 29994, 30023, 30053, 30082, 30112, 30141, 30171, 30200, 30230, 30259, 30289, 30318, 30348, 30378, 30408, 30437, 30467, 30496, 30526, 30555, 30585, 30614, 30644, 30673, 30703, 30732, 30762, 30791, 30821, 30850, 30880, 30909, 30939, 30968, 30998, 31027, 31057, 31086, 31116, 31145, 31175, 31204, 31234, 31263, 31293, 31322, 31352, 31381, 31411, 31441, 31471, 31500, 31530, 31559, 31589, 31618, 31648, 31676, 31706, 31736, 31766, 31795, 31825, 31854, 31884, 31913, 31943, 31972, 32002, 32031, 32061, 32090, 32120, 32150, 32180, 32209, 32239, 32268, 32298, 32327, 32357, 32386, 32416, 32445, 32475, 32504, 32534, 32563, 32593, 32622, 32652, 32681, 32711, 32740, 32770, 32799, 32829, 32858, 32888, 32917, 32947, 32976, 33006, 33035, 33065, 33094, 33124, 33153, 33183, 33213, 33243, 33272, 33302, 33331, 33361, 33390, 33420, 33450, 33479, 33509, 33539, 33568, 33598, 33627, 33657, 33686, 33716, 33745, 33775, 33804, 33834, 33863, 33893, 33922, 33952, 33981, 34011, 34040, 34069, 34099, 34128, 34158, 34187, 34217, 34247, 34277, 34306, 34336, 34365, 34395, 34424, 34454, 34483, 34512, 34542, 34571, 34601, 34631, 34660, 34690, 34719, 34749, 34778, 34808, 34837, 34867, 34896, 34926, 34955, 34985, 35015, 35044, 35074, 35103, 35133, 35162, 35192, 35222, 35251, 35280, 35310, 35340, 35370, 35399, 35429, 35458, 35488, 35517, 35547, 35576, 35605, 35635, 35665, 35694, 35723, 35753, 35782, 35811, 35841, 35871, 35901, 35930, 35960, 35989, 36019, 36048, 36078, 36107, 36136, 36166, 36195, 36225, 36254, 36284, 36314, 36343, 36373, 36403, 36433, 36462, 36492, 36521, 36551, 36580, 36610, 36639, 36669, 36698, 36728, 36757, 36786, 36816, 36845, 36875, 36904, 36934, 36963, 36993, 37022, 37052, 37081, 37111, 37141, 37170, 37200, 37229, 37259, 37288, 37318, 37347, 37377, 37406, 37436, 37465, 37495, 37524, 37554, 37584, 37613, 37643, 37672, 37701, 37731, 37760, 37790, 37819, 37849, 37878, 37908, 37938, 37967, 37997, 38027, 38056, 38085, 38115, 38144, 38174, 38203, 38233, 38262, 38292, 38322, 38351, 38381, 38410, 38440, 38469, 38499, 38528, 38558, 38587, 38617, 38646, 38676, 38705, 38735, 38764, 38794, 38823, 38853, 38882, 38912, 38941, 38971, 39001, 39030, 39059, 39089, 39118, 39148, 39178, 39208, 39237, 39267, 39297, 39326, 39355, 39385, 39414, 39444, 39473, 39503, 39532, 39562, 39592, 39621, 39650, 39680, 39709, 39739, 39768, 39798, 39827, 39857, 39886, 39916, 39946, 39975, 40005, 40035, 40064, 40094, 40123, 40153, 40182, 40212, 40241, 40271, 40300, 40330, 40359, 40389, 40418, 40448, 40477, 40507, 40536, 40566, 40595, 40625, 40655, 40685, 40714, 40744, 40773, 40803, 40832, 40862, 40892, 40921, 40951, 40980, 41009, 41039, 41068, 41098, 41127, 41157, 41186, 41216, 41245, 41275, 41304, 41334, 41364, 41393, 41422, 41452, 41481, 41511, 41540, 41570, 41599, 41629, 41658, 41688, 41718, 41748, 41777, 41807, 41836, 41865, 41894, 41924, 41953, 41983, 42012, 42042, 42072, 42102, 42131, 42161, 42190, 42220, 42249, 42279, 42308, 42337, 42367, 42397, 42426, 42456, 42485, 42515, 42545, 42574, 42604, 42633, 42662, 42692, 42721, 42751, 42780, 42810, 42839, 42869, 42899, 42929, 42958, 42988, 43017, 43046, 43076, 43105, 43135, 43164, 43194, 43223, 43253, 43283, 43312, 43342, 43371, 43401, 43430, 43460, 43489, 43519, 43548, 43578, 43607, 43637, 43666, 43696, 43726, 43755, 43785, 43814, 43844, 43873, 43903, 43932, 43962, 43991, 44021, 44050, 44080, 44109, 44139, 44169, 44198, 44228, 44258, 44287, 44317, 44346, 44375, 44405, 44434, 44464, 44493, 44523, 44553, 44582, 44612, 44641, 44671, 44700, 44730, 44759, 44788, 44818, 44847, 44877, 44906, 44936, 44966, 44996, 45025, 45055, 45084, 45114, 45143, 45172, 45202, 45231, 45261, 45290, 45320, 45350, 45380, 45409, 45439, 45468, 45498, 45527, 45556, 45586, 45615, 45644, 45674, 45704, 45733, 45763, 45793, 45823, 45852, 45882, 45911, 45940, 45970, 45999, 46028, 46058, 46088, 46117, 46147, 46177, 46206, 46236, 46265, 46295, 46324, 46354, 46383, 46413, 46442, 46472, 46501, 46531, 46560, 46590, 46620, 46649, 46679, 46708, 46738, 46767, 46797, 46826, 46856, 46885, 46915, 46944, 46974, 47003, 47033, 47063, 47092, 47122, 47151, 47181, 47210, 47240, 47269, 47298, 47328, 47357, 47387, 47417, 47446, 47476, 47506, 47535, 47565, 47594, 47624, 47653, 47682, 47712, 47741, 47771, 47800, 47830, 47860, 47890, 47919, 47949, 47978, 48008, 48037, 48066, 48096, 48125, 48155, 48184, 48214, 48244, 48273, 48303, 48333, 48362, 48392, 48421, 48450, 48480, 48509, 48538, 48568, 48598, 48627, 48657, 48687, 48717, 48746, 48776, 48805, 48834, 48864, 48893, 48922, 48952, 48982, 49011, 49041, 49071, 49100, 49130, 49160, 49189, 49218, 49248, 49277, 49306, 49336, 49365, 49395, 49425, 49455, 49484, 49514, 49543, 49573, 49602, 49632, 49661, 49690, 49720, 49749, 49779, 49809, 49838, 49868, 49898, 49927, 49957, 49986, 50016, 50045, 50075, 50104, 50133, 50163, 50192, 50222, 50252, 50281, 50311, 50340, 50370, 50400, 50429, 50459, 50488, 50518, 50547, 50576, 50606, 50635, 50665, 50694, 50724, 50754, 50784, 50813, 50843, 50872, 50902, 50931, 50960, 50990, 51019, 51049, 51078, 51108, 51138, 51167, 51197, 51227, 51256, 51286, 51315, 51345, 51374, 51403, 51433, 51462, 51492, 51522, 51552, 51582, 51611, 51641, 51670, 51699, 51729, 51758, 51787, 51816, 51846, 51876, 51906, 51936, 51965, 51995, 52025, 52054, 52083, 52113, 52142, 52171, 52200, 52230, 52260, 52290, 52319, 52349, 52379, 52408, 52438, 52467, 52497, 52526, 52555, 52585, 52614, 52644, 52673, 52703, 52733, 52762, 52792, 52822, 52851, 52881, 52910, 52939, 52969, 52998, 53028, 53057, 53087, 53116, 53146, 53176, 53205, 53235, 53264, 53294, 53324, 53353, 53383, 53412, 53441, 53471, 53500, 53530, 53559, 53589, 53619, 53648, 53678, 53708, 53737, 53767, 53796, 53825, 53855, 53884, 53914, 53943, 53973, 54003, 54032, 54062, 54092, 54121, 54151, 54180, 54209, 54239, 54268, 54297, 54327, 54357, 54387, 54416, 54446, 54476, 54505, 54535, 54564, 54593, 54623, 54652, 54681, 54711, 54741, 54770, 54800, 54830, 54859, 54889, 54919, 54948, 54977, 55007, 55036, 55066, 55095, 55125, 55154, 55184, 55213, 55243, 55273, 55302, 55332, 55361, 55391, 55420, 55450, 55479, 55508, 55538, 55567, 55597, 55627, 55657, 55686, 55716, 55745, 55775, 55804, 55834, 55863, 55892, 55922, 55951, 55981, 56011, 56040, 56070, 56100, 56129, 56159, 56188, 56218, 56247, 56276, 56306, 56335, 56365, 56394, 56424, 56454, 56483, 56513, 56543, 56572, 56601, 56631, 56660, 56690, 56719, 56749, 56778, 56808, 56837, 56867, 56897, 56926, 56956, 56985, 57015, 57044, 57074, 57103, 57133, 57162, 57192, 57221, 57251, 57280, 57310, 57340, 57369, 57399, 57429, 57458, 57487, 57517, 57546, 57576, 57605, 57634, 57664, 57694, 57723, 57753, 57783, 57813, 57842, 57871, 57901, 57930, 57959, 57989, 58018, 58048, 58077, 58107, 58137, 58167, 58196, 58226, 58255, 58285, 58314, 58343, 58373, 58402, 58432, 58461, 58491, 58521, 58551, 58580, 58610, 58639, 58669, 58698, 58727, 58757, 58786, 58816, 58845, 58875, 58905, 58934, 58964, 58994, 59023, 59053, 59082, 59111, 59141, 59170, 59200, 59229, 59259, 59288, 59318, 59348, 59377, 59407, 59436, 59466, 59495, 59525, 59554, 59584, 59613, 59643, 59672, 59702, 59731, 59761, 59791, 59820, 59850, 59879, 59909, 59939, 59968, 59997, 60027, 60056, 60086, 60115, 60145, 60174, 60204, 60234, 60264, 60293, 60323, 60352, 60381, 60411, 60440, 60469, 60499, 60528, 60558, 60588, 60618, 60647, 60677, 60707, 60736, 60765, 60795, 60824, 60853, 60883, 60912, 60942, 60972, 61002, 61031, 61061, 61090, 61120, 61149, 61179, 61208, 61237, 61267, 61296, 61326, 61356, 61385, 61415, 61445, 61474, 61504, 61533, 61563, 61592, 61621, 61651, 61680, 61710, 61739, 61769, 61799, 61828, 61858, 61888, 61917, 61947, 61976, 62006, 62035, 62064, 62094, 62123, 62153, 62182, 62212, 62242, 62271, 62301, 62331, 62360, 62390, 62419, 62448, 62478, 62507, 62537, 62566, 62596, 62625, 62655, 62685, 62715, 62744, 62774, 62803, 62832, 62862, 62891, 62921, 62950, 62980, 63009, 63039, 63069, 63099, 63128, 63157, 63187, 63216, 63246, 63275, 63305, 63334, 63363, 63393, 63423, 63453, 63482, 63512, 63541, 63571, 63600, 63630, 63659, 63689, 63718, 63747, 63777, 63807, 63836, 63866, 63895, 63925, 63955, 63984, 64014, 64043, 64073, 64102, 64131, 64161, 64190, 64220, 64249, 64279, 64309, 64339, 64368, 64398, 64427, 64457, 64486, 64515, 64545, 64574, 64603, 64633, 64663, 64692, 64722, 64752, 64782, 64811, 64841, 64870, 64899, 64929, 64958, 64987, 65017, 65047, 65076, 65106, 65136, 65166, 65195, 65225, 65254, 65283, 65313, 65342, 65371, 65401, 65431, 65460, 65490, 65520, 65549, 65579, 65608, 65638, 65667, 65697, 65726, 65755, 65785, 65815, 65844, 65874, 65903, 65933, 65963, 65992, 66022, 66051, 66081, 66110, 66140, 66169, 66199, 66228, 66258, 66287, 66317, 66346, 66376, 66405, 66435, 66465, 66494, 66524, 66553, 66583, 66612, 66641, 66671, 66700, 66730, 66760, 66789, 66819, 66849, 66878, 66908, 66937, 66967, 66996, 67025, 67055, 67084, 67114, 67143, 67173, 67203, 67233, 67262, 67292, 67321, 67351, 67380, 67409, 67439, 67468, 67497, 67527, 67557, 67587, 67617, 67646, 67676, 67705, 67735, 67764, 67793, 67823, 67852, 67882, 67911, 67941, 67971, 68000, 68030, 68060, 68089, 68119, 68148, 68177, 68207, 68236, 68266, 68295, 68325, 68354, 68384, 68414, 68443, 68473, 68502, 68532, 68561, 68591, 68620, 68650, 68679, 68708, 68738, 68768, 68797, 68827, 68857, 68886, 68916, 68946, 68975, 69004, 69034, 69063, 69092, 69122, 69152, 69181, 69211, 69240, 69270, 69300, 69330, 69359, 69388, 69418, 69447, 69476, 69506, 69535, 69565, 69595, 69624, 69654, 69684, 69713, 69743, 69772, 69802, 69831, 69861, 69890, 69919, 69949, 69978, 70008, 70038, 70067, 70097, 70126, 70156, 70186, 70215, 70245, 70274, 70303, 70333, 70362, 70392, 70421, 70451, 70481, 70510, 70540, 70570, 70599, 70629, 70658, 70687, 70717, 70746, 70776, 70805, 70835, 70864, 70894, 70924, 70954, 70983, 71013, 71042, 71071, 71101, 71130, 71159, 71189, 71218, 71248, 71278, 71308, 71337, 71367, 71397, 71426, 71455, 71485, 71514, 71543, 71573, 71602, 71632, 71662, 71691, 71721, 71751, 71781, 71810, 71839, 71869, 71898, 71927, 71957, 71986, 72016, 72046, 72075, 72105, 72135, 72164, 72194, 72223, 72253, 72282, 72311, 72341, 72370, 72400, 72429, 72459, 72489, 72518, 72548, 72577, 72607, 72637, 72666, 72695, 72725, 72754, 72784, 72813, 72843, 72872, 72902, 72931, 72961, 72991, 73020, 73050, 73080, 73109, 73139, 73168, 73197, 73227, 73256, 73286, 73315, 73345, 73375, 73404, 73434, 73464, 73493, 73523, 73552, 73581, 73611, 73640, 73669, 73699, 73729, 73758, 73788, 73818, 73848, 73877, 73907, 73936, 73965, 73995, 74024, 74053, 74083, 74113, 74142, 74172, 74202, 74231, 74261, 74291, 74320, 74349, 74379, 74408, 74437, 74467, 74497, 74526, 74556, 74585, 74615, 74645, 74675, 74704, 74733, 74763, 74792, 74822, 74851, 74881, 74910, 74940, 74969, 74999, 75029, 75058, 75088, 75117, 75147, 75176, 75206, 75235, 75264, 75294, 75323, 75353, 75383, 75412, 75442, 75472, 75501, 75531, 75560, 75590, 75619, 75648, 75678, 75707, 75737, 75766, 75796, 75826, 75856, 75885, 75915, 75944, 75974, 76003, 76032, 76062, 76091, 76121, 76150, 76180, 76210, 76239, 76269, 76299, 76328, 76358, 76387, 76416, 76446, 76475, 76505, 76534, 76564, 76593, 76623, 76653, 76682, 76712, 76741, 76771, 76801, 76830, 76859, 76889, 76918, 76948, 76977, 77007, 77036, 77066, 77096, 77125, 77155, 77185, 77214, 77243, 77273, 77302, 77332, 77361, 77390, 77420, 77450, 77479, 77509, 77539, 77569, 77598, 77627, 77657, 77686, 77715, 77745, 77774, 77804, 77833, 77863, 77893, 77923, 77952, 77982, 78011, 78041, 78070, 78099, 78129, 78158, 78188, 78217, 78247, 78277, 78307, 78336, 78366, 78395, 78425, 78454, 78483, 78513, 78542, 78572, 78601, 78631, 78661, 78690, 78720, 78750, 78779, 78808, 78838, 78867, 78897, 78926, 78956, 78985, 79015, 79044, 79074, 79104, 79133, 79163, 79192, 79222, 79251, 79281, 79310, 79340, 79369, 79399, 79428, 79458, 79487, 79517, 79546, 79576, 79606, 79635, 79665, 79695, 79724, 79753, 79783, 79812, 79841, 79871, 79900, 79930, 79960, 79990\n];\n","/* eslint-disable no-nested-ternary, no-useless-escape */\nimport { Environment as env } from '../../utils/environment';\nimport { numberUtils } from '../../utils/number';\nimport { stringUtils } from '../../utils/string';\nimport { utils } from '../../utils/utils';\nimport { ummalquraData } from './info/umalqura-data';\n\n// If `SohoConfig` exists with a `culturesPath` property, use that path for retrieving\n// culture files. This allows manually setting the directory for the culture files.\nlet existingCulturePath = '';\nlet minifyCultures = false;\n\nif (typeof window.SohoConfig === 'object') {\n if (typeof window.SohoConfig.culturesPath === 'string') {\n existingCulturePath = window.SohoConfig.culturesPath;\n }\n if (typeof window.SohoConfig.minifyCultures === 'boolean') {\n minifyCultures = window.SohoConfig.minifyCultures;\n }\n}\n\n/**\n* The Locale component handles i18n\n* Data From: http://www.unicode.org/repos/cldr-aux/json/22.1/main/\n* For Docs See: http://ibm.co/1nXyNxp\n* @class Locale\n* @constructor\n*\n* @param {string} currentLocale The Currently Set Locale\n* @param {object} cultures Contains all currently-stored cultures.\n* @param {string} culturesPath the web-server's path to culture files.\n* @param {boolean} minify if true, adds a `.min.js` suffix to the culture's filename.\n*/\nconst Locale = { // eslint-disable-line\n\n currentLocale: { name: '', data: {} }, // default\n currentLanguage: { name: '' }, // default\n cultures: {},\n languages: {},\n dff: [],\n culturesPath: existingCulturePath,\n defaultLocales: [\n { lang: 'af', default: 'af-ZA' },\n { lang: 'ar', default: 'ar-EG' },\n { lang: 'bg', default: 'bg-BG' },\n { lang: 'cs', default: 'cs-CZ' },\n { lang: 'da', default: 'da-DK' },\n { lang: 'de', default: 'de-DE' },\n { lang: 'el', default: 'el-GR' },\n { lang: 'en', default: 'en-US' },\n { lang: 'es', default: 'es-ES' },\n { lang: 'et', default: 'et-EE' },\n { lang: 'fi', default: 'fi-FI' },\n { lang: 'fr', default: 'fr-FR' },\n { lang: 'he', default: 'he-IL' },\n { lang: 'hi', default: 'hi-IN' },\n { lang: 'hr', default: 'hr-HR' },\n { lang: 'hu', default: 'hu-HU' },\n { lang: 'id', default: 'id-ID' },\n { lang: 'it', default: 'it-IT' },\n { lang: 'iw', default: 'he-IL' },\n { lang: 'ja', default: 'ja-JP' },\n { lang: 'ko', default: 'ko-KR' },\n { lang: 'lt', default: 'lt-LT' },\n { lang: 'lv', default: 'lv-LV' },\n { lang: 'ms', default: 'ms-bn' },\n { lang: 'nb', default: 'no-NO' },\n { lang: 'nn', default: 'no-NO' },\n { lang: 'nl', default: 'nl-NL' },\n { lang: 'no', default: 'no-NO' },\n { lang: 'pl', default: 'pl-PL' },\n { lang: 'pt', default: 'pt-PT' },\n { lang: 'ro', default: 'ro-RO' },\n { lang: 'ru', default: 'ru-RU' },\n { lang: 'sk', default: 'sk-SK' },\n { lang: 'sl', default: 'sl-SI' },\n { lang: 'sv', default: 'sv-SE' },\n { lang: 'th', default: 'th-TH' },\n { lang: 'tl', default: 'tl-PH' },\n { lang: 'tr', default: 'tr-TR' },\n { lang: 'uk', default: 'uk-UA' },\n { lang: 'vi', default: 'vi-VN' },\n { lang: 'zh', default: 'zh-CN' }\n ],\n supportedLocales: ['af-ZA', 'ar-EG', 'ar-SA', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR',\n 'en-AU', 'en-GB', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'es-AR', 'es-ES', 'es-419', 'es-MX',\n 'es-US', 'et-EE', 'fi-FI', 'fr-CA', 'fr-FR', 'he-IL', 'hi-IN', 'hr-HR',\n 'hu-HU', 'id-ID', 'it-IT', 'ja-JP', 'ko-KR', 'lt-LT', 'lv-LV', 'ms-bn', 'ms-my', 'nb-NO', 'nn-NO',\n 'nl-NL', 'no-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'ru-RU', 'sk-SK', 'sl-SI', 'sv-SE', 'th-TH', 'tl-PH', 'tr-TR',\n 'uk-UA', 'vi-VN', 'zh-CN', 'zh-Hans', 'zh-Hant', 'zh-TW'],\n translatedLocales: ['fr-CA', 'fr-FR', 'pt-BR', 'pt-PT'],\n defaultLocale: 'en-US',\n minify: minifyCultures,\n\n /**\n * Sets the current lang tag in the Html element\n * @private\n * @param {string} locale The locale, if just a two digit code is passed we use the default.\n */\n updateLanguageTag(locale) {\n const html = $('html');\n if (locale.length === 2) {\n locale = this.defaultLocales.filter(a => a.lang === locale);\n }\n\n html.attr('lang', locale);\n if (this.isRTL()) {\n html.attr('dir', 'rtl');\n } else {\n html.attr('dir', 'ltr');\n }\n\n // ICONS: Right to Left Direction\n Locale.flipIconsHorizontally(this.isRTL());\n\n $('body').removeClass('busy-loading-locale');\n },\n\n /**\n * Get the path to the directory with the cultures\n * @private\n * @returns {string} path containing culture files.\n */\n getCulturesPath() {\n if (!this.culturesPath) {\n const scripts = document.getElementsByTagName('script');\n const partialPathRegexp = /sohoxi(.min){0,1}(.{0,1}[a-z0-9]*)\\.js/;\n\n for (let i = 0; i < scripts.length; i++) {\n let src = scripts[i].src;\n\n // remove from ? to end\n const idx = src.indexOf('?');\n if (src !== '' && idx > -1) {\n src = src.substr(0, idx);\n }\n\n if (scripts[i].id === 'sohoxi-script') {\n return `${src.substring(0, src.lastIndexOf('/'))}/`;\n }\n\n if (src.match(partialPathRegexp)) {\n this.culturesPath = `${src.replace(partialPathRegexp, '')}cultures/`;\n }\n }\n }\n return this.culturesPath;\n },\n\n /**\n * Checks if the culture is set as an inline script in the head tag.\n * @private\n * @returns {boolean} whether or not a culture file exists in the document header.\n */\n cultureInHead() {\n let isThere = false;\n const scripts = document.getElementsByTagName('script');\n const partialPath = 'cultures';\n\n for (let i = 0; i < scripts.length; i++) {\n const src = scripts[i].src;\n\n if (src.indexOf(partialPath) > -1) {\n isThere = true;\n }\n }\n\n return isThere;\n },\n\n /**\n * Internally stores a new culture file for future use.\n * @private\n * @param {string} locale The locale to check.\n * @returns {string} The actual locale to use.\n */\n correctLocale(locale) {\n // Map incorrect java locale to correct locale\n if (locale === 'in-ID') {\n locale = 'id-ID';\n }\n if (locale.substr(0, 2) === 'iw') {\n locale = 'he-IL';\n }\n\n const lang = locale.split('-')[0];\n if (this.supportedLocales.indexOf(locale) === -1) {\n locale = this.defaultLocales.filter(a => a.lang === lang);\n\n if (locale && locale[0]) {\n return locale[0].default;\n }\n\n locale = this.defaultLocale;\n }\n\n return locale;\n },\n\n /**\n * Check if the language is supported, if not return 'en'\n * and fix a few inconsistencies.\n * @private\n * @param {string} lang The locale to check.\n * @returns {string} The actual lang to use.\n */\n correctLanguage(lang) {\n if (lang?.substr(0, 3) === 'en-') {\n lang = lang.substr(0, 2);\n }\n\n let correctLanguage = this.defaultLocales.filter(a => a.lang === lang);\n\n if (correctLanguage && correctLanguage[0]) {\n return this.remapLanguage(lang);\n }\n\n correctLanguage = this.remapLanguage(lang);\n return correctLanguage;\n },\n\n /**\n * Adjust some languages.\n * @private\n * @param {[type]} lang The two digit language code.\n * @returns {string} Corrected language\n */\n remapLanguage(lang) {\n let correctLanguage = lang;\n\n // Map incorrect java locale to correct locale\n if (lang === 'in') {\n correctLanguage = 'id';\n }\n if (lang === 'iw') {\n correctLanguage = 'he';\n }\n // Another special case\n if (lang === 'nb' || lang === 'nn' || lang === 'nb-NO' || lang === 'nn-NO') {\n correctLanguage = 'no';\n }\n if (this.translatedLocales.indexOf(lang) > -1) return this.translatedLocales[this.translatedLocales.indexOf(lang)];\n\n return correctLanguage.substr(0, 2);\n },\n\n /**\n * Internally stores a new culture file for future use.\n * @private\n * @param {string} locale The 4-character Locale ID\n * @param {object} data Translation data and locale-specific functions, such as calendars.\n * @param {object} langData Translation data if deperated.\n * @returns {void}\n */\n addCulture(locale, data, langData) {\n const lang = this.remapLanguage(locale);\n\n this.cultures[locale] = data;\n this.cultures[locale].name = locale;\n if (data.messages) {\n this.languages[lang] = {\n name: lang,\n direction: data.direction || (langData ? langData.direction : ''),\n nativeName: data.nativeName || (langData ? langData.nativeName : ''),\n messages: data.messages || (langData ? langData.messages : {})\n };\n this.languages[locale] = {\n name: locale,\n direction: data.direction || (langData ? langData.direction : ''),\n nativeName: data.nativeName || (langData ? langData.nativeName : ''),\n messages: data.messages || (langData ? langData.messages : {})\n };\n } else if (!data.messages) {\n const parentLocale = this.parentLocale(locale);\n if (parentLocale.default && parentLocale.default !== locale &&\n !this.cultures[parentLocale.default]) {\n this.appendLocaleScript(parentLocale.default);\n }\n }\n },\n\n /**\n * Find the parent locale (meaning shared translations), if it exists.\n * @private\n * @param {string} locale The locale we are checking.\n * @returns {string} The parent locale.\n */\n parentLocale(locale) {\n const lang = locale.substr(0, 2);\n const match = this.defaultLocales.filter(a => a.lang === lang);\n const parentLocale = match[0] || [{ default: 'en-US' }];\n\n // fr-FR and fr-CA are different / do not have a default\n if (this.translatedLocales.indexOf(locale) > -1) {\n return { lang: 'fr', default: 'fr-CA' };\n }\n return parentLocale;\n },\n\n appendedLocales: [],\n\n /**\n * Append the local script to the page.\n * @private\n * @param {string} locale The locale name to append.\n * @param {boolean} isCurrent If we should set this as the current locale\n * @param {string} parentLocale If we should resolve the promise base on locale\n * @param {string} filename Optional parameter to load locale with different filename\n * @returns {void}\n */\n appendLocaleScript(locale, isCurrent, parentLocale, filename) {\n const script = document.createElement('script');\n const min = this.minify ? '.min' : '';\n script.async = false;\n\n if (this.appendedLocales.indexOf(locale) > -1) {\n return;\n }\n this.appendedLocales.push(locale);\n\n if (!filename) {\n script.src = `${this.getCulturesPath() + locale}${min}.js`;\n } else {\n script.src = `${this.getCulturesPath() + filename}${min}.js`;\n }\n\n script.onload = () => {\n if (isCurrent && !parentLocale) {\n this.setCurrentLocale(locale, this.cultures[locale]);\n this.dff[locale].resolve(locale);\n }\n if (parentLocale && this.dff[parentLocale]) {\n this.setCurrentLocale(locale, this.cultures[locale]);\n this.setCurrentLocale(parentLocale, this.cultures[parentLocale]);\n this.dff[parentLocale].resolve(parentLocale);\n }\n if (parentLocale && this.dff[locale] && this.cultures[locale]) {\n this.setCurrentLocale(locale, this.cultures[locale]);\n this.dff[locale].resolve(locale);\n }\n if (!isCurrent && !parentLocale && this.dff[locale]) {\n this.dff[locale].resolve(locale);\n }\n };\n\n script.onerror = () => {\n if (this.dff[locale]) {\n this.dff[locale].reject();\n }\n };\n\n if (typeof window.SohoConfig === 'object' && typeof window.SohoConfig.nonce === 'string') {\n script.setAttribute('nonce', window.SohoConfig.nonce);\n }\n\n document.head.appendChild(script);\n },\n\n /**\n * Sets the current locale.\n * @param {string} locale The locale to fetch and set.\n * @returns {jquery.deferred} which is resolved once the locale culture is retrieved and set\n */\n set(locale) {\n const self = this;\n locale = this.correctLocale(locale);\n this.dff[locale] = $.Deferred();\n\n if (locale === '') {\n self.dff.resolve();\n return this.dff.promise();\n }\n\n if (!this.cultures['en-US']) {\n this.appendLocaleScript('en-US', locale === 'en-US');\n }\n\n let hasParentLocale = false;\n const parentLocale = this.parentLocale(locale);\n if (parentLocale.default && parentLocale.default !== locale &&\n !this.cultures[parentLocale.default]) {\n hasParentLocale = true;\n }\n\n if (!hasParentLocale && locale && !this.cultures[locale] &&\n this.currentLocale.name !== locale && locale !== 'en-US') {\n this.setCurrentLocale(locale);\n // Fetch the local and cache it\n this.appendLocaleScript(locale, true);\n }\n\n // Also load the default locale for that locale\n if (hasParentLocale) {\n if (parentLocale.default !== 'en-US') {\n this.appendLocaleScript(parentLocale.default, false);\n }\n this.appendLocaleScript(locale, false, parentLocale.default);\n }\n\n if (locale && self.currentLocale.data && self.currentLocale.dataName === locale) {\n self.dff[locale].resolve(self.currentLocale.name);\n }\n\n self.setCurrentLocale(locale, self.cultures[locale]);\n\n if (self.cultures[locale] && this.cultureInHead()) {\n self.dff[locale].resolve(self.currentLocale.name);\n }\n\n return this.dff[locale].promise();\n },\n\n /**\n * Loads the locale without setting it.\n * @param {string} locale The locale to fetch and set.\n * @param {string} filename Optional Locale's filename if different from default.\n * @returns {jquery.deferred} which is resolved once the locale culture is retrieved and set\n */\n getLocale(locale, filename) {\n if (!locale) {\n return null;\n }\n\n locale = this.correctLocale(locale);\n this.dff[locale] = $.Deferred();\n\n if (locale === '') {\n const dff = $.Deferred();\n dff.resolve();\n return dff.promise();\n }\n\n if (locale && locale !== 'en-US' && !this.cultures['en-US']) {\n this.appendLocaleScript('en-US', false);\n }\n\n if (locale && !this.cultures[locale] && this.currentLocale.name !== locale && locale !== 'en-US') {\n this.appendLocaleScript(locale, false, false, filename);\n }\n\n if (locale && this.currentLocale.data && this.currentLocale.dataName === locale) {\n this.dff[locale].resolve(locale);\n }\n if (this.cultures[locale] && this.cultureInHead()) {\n this.dff[locale].resolve(locale);\n }\n\n return this.dff[locale].promise();\n },\n\n /**\n * Sets the current language, this can be independent and different from the current locale.\n * @param {string} lang The two digit language code to use.\n * @returns {jquery.deferred} which is resolved once the locale culture is retrieved and set\n */\n setLanguage(lang) {\n // If not call set and load it and then set back the locale after.\n // Make a new object for currentLanguage independent of currentLocale\n // Change translate to use the right one\n const currentLocale = this.currentLocale.name;\n\n // Map incorrect java locale to correct locale\n lang = this.correctLanguage(lang);\n\n // Ensure the language / culture is loaded.\n if (!this.languages[lang]) {\n this.set(lang).done(() => {\n this.set(currentLocale);\n this.setLanguage(lang);\n });\n }\n\n const correctLocale = this.correctLocale(lang);\n if (this.languages[lang]) {\n this.currentLanguage = this.languages[lang];\n this.updateLanguageTag(lang);\n this.dff[correctLocale] = $.Deferred();\n return this.dff[correctLocale].resolve();\n }\n\n this.currentLanguage.name = lang;\n return this.dff[correctLocale];\n },\n\n /**\n * Chooses a stored locale dataset and sets it as \"current\"\n * @private\n * @param {string} name the 4-character Locale ID\n * @param {object} data translation data and locale-specific functions, such as calendars.\n * @returns {void}\n */\n setCurrentLocale(name, data) {\n const lang = this.remapLanguage(name);\n this.currentLocale.name = name;\n const selectedLang = (this.languages[lang] !== undefined && this.languages[name] !== undefined) &&\n (this.languages[lang].nativeName !== this.languages[name].nativeName) ? name : lang;\n\n if (data) {\n this.currentLocale.data = data;\n this.currentLocale.dataName = name;\n this.currentLanguage = {};\n this.currentLanguage.name = lang;\n\n if (this.languages[selectedLang]) {\n this.currentLanguage = this.languages[selectedLang];\n this.currentLanguage.name = lang;\n this.updateLanguageTag(name);\n }\n\n if (this.translatedLocales.indexOf(name) > -1) {\n this.languages[selectedLang].direction = data.direction;\n this.languages[selectedLang].messages = data.messages;\n this.languages[selectedLang].name = lang;\n this.languages[selectedLang].nativeName = data.nativeName;\n\n this.languages[name] = {\n direction: data.direction,\n messages: data.messages,\n name,\n nativeName: data.nativeName\n };\n }\n\n if (typeof d3 === 'object') {\n // Set the d3 locale for charts (unless disabled)\n d3.formatDefaultLocale({\n decimal: data?.numbers?.decimal || '.',\n thousands: data?.numbers?.group || ',',\n grouping: data?.numbers?.groupSizes || [3],\n percent: data?.numbers?.percentSign || '%',\n currency: !data?.currencySign ? ['$', ''] : data.currencyFormat.split('¤')[0] === '' ? [data.currencySign, ''] : ['', data.currencySign],\n minus: data?.numbers?.minusSign || '-'\n });\n }\n }\n },\n\n /**\n * Formats a date object and returns it parsed back using the current locale or settings.\n * The symbols for date formatting use the CLDR at https://bit.ly/2Jg0a6m\n * @param {date} value The date to show in the current locale.\n * @param {object} options Additional date formatting settings.\n * @returns {string} the formatted date.\n */\n formatDate(value, options) {\n if (!options) {\n options = { date: 'short' }; // can be date, time, datetime or pattern\n }\n const localeData = this.useLocale(options);\n\n if (!value) {\n return undefined;\n }\n\n if (value === '0000' || value === '000000' || value === '00000000') {\n // Means no date in some applications\n return '';\n }\n\n // Convert if a timezone string.\n if (typeof value === 'string' && /T|Z/g.test(value)) {\n value = this.newDateObj(value);\n }\n\n // Convert if a string..\n if (!(value instanceof Date) && typeof value === 'string') {\n let tDate2 = Locale.parseDate(value, options);\n if (isNaN(tDate2) && options.date === 'datetime' &&\n value.substr(4, 1) === '-' &&\n value.substr(7, 1) === '-') {\n tDate2 = new Date(\n value.substr(0, 4),\n value.substr(5, 2) - 1,\n value.substr(8, 2),\n value.substr(11, 2),\n value.substr(14, 2),\n value.substr(17, 2)\n );\n }\n value = tDate2;\n }\n\n if (!(value instanceof Date) && typeof value === 'number') {\n const tDate3 = new Date(value);\n value = tDate3;\n }\n\n if (!value) {\n return undefined;\n }\n\n let pattern;\n let ret = '';\n const cal = (localeData.calendars ? localeData.calendars[0] : null);\n\n if (options.date && cal) {\n pattern = cal.dateFormat[options.date];\n }\n\n // If pattern is defined in options it will override date format from locale\n if (options.pattern) {\n pattern = options.pattern;\n }\n\n if (typeof options === 'string' && options !== '') {\n pattern = options;\n }\n\n if (!pattern) {\n pattern = cal.dateFormat.short;\n }\n\n let year = (value instanceof Array ? value[0] : value.getFullYear());\n let month = (value instanceof Array ? value[1] : value.getMonth());\n let day = (value instanceof Array ? value[2] : value.getDate());\n const dayOfWeek = (value.getDay ? value.getDay() : '');\n const hours = (value instanceof Array ? value[3] : value.getHours());\n const mins = (value instanceof Array ? value[4] : value.getMinutes());\n const seconds = (value instanceof Array ? value[5] : value.getSeconds());\n const millis = (value instanceof Array ? value[6] : value.getMilliseconds());\n\n if (options.fromGregorian || options.toUmalqura) {\n const islamicParts = this.gregorianToUmalqura(value);\n day = islamicParts[2];\n month = islamicParts[1];\n year = islamicParts[0];\n }\n if (options.toGregorian || options.fromUmalqura) {\n const gregorianDate = this.umalquraToGregorian(year, month, day);\n day = gregorianDate.getDate();\n month = gregorianDate.getMonth();\n year = gregorianDate.getFullYear();\n }\n\n // Special\n pattern = pattern.replace('de', 'nnnnn');\n pattern = pattern.replace('ngày', 'nnnn');\n pattern = pattern.replace('tháng', 't1áng');\n pattern = pattern.replace('den', 'nnn');\n\n // Day of Month\n ret = pattern.replace('dd', this.pad(day, 2));\n ret = ret.replace('d', day);\n\n // years\n ret = ret.replace('yyyy', year);\n ret = ret.replace('yy', year.toString().substr(2));\n ret = ret.replace('y', year);\n\n // Time\n const showDayPeriods = ret.indexOf(' a') > -1 || ret.indexOf('a') === 0;\n\n if (showDayPeriods && hours === 0) {\n ret = ret.replace('hh', 12);\n ret = ret.replace('h', 12);\n }\n\n ret = ret.replace('hh', (hours > 12 ? this.pad(hours - 12, 2) : this.pad(hours, 2)));\n ret = ret.replace('h', (hours > 12 ? hours - 12 : hours));\n ret = ret.replace('HH', this.pad(hours, 2));\n ret = ret.replace('H', hours);\n ret = ret.replace('mm', this.pad(mins, 2));\n ret = ret.replace('ss', this.pad(seconds, 2));\n ret = ret.replace('SSS', this.pad(millis, 3));\n\n // months\n ret = ret.replace('MMMM', cal ? cal.months.wide[month] : null); // full\n ret = ret.replace('MMM', cal ? cal.months.abbreviated[month] : null); // abreviation\n if (pattern.indexOf('MMM') === -1) {\n ret = ret.replace('MM', this.pad(month + 1, 2)); // number padded\n ret = ret.replace('M', month + 1); // number unpadded\n }\n\n // PM\n if (showDayPeriods && cal) {\n ret = ret.replace(' a', ` ${hours >= 12 ? cal.dayPeriods[1] : cal.dayPeriods[0]}`);\n if (ret.indexOf('a') === 0) {\n ret = ret.replace('a', ` ${hours >= 12 ? cal.dayPeriods[1] : cal.dayPeriods[0]}`);\n }\n ret = ret.replace('EEEE', cal.days.wide[dayOfWeek]); // Day of Week\n }\n\n // Day of Week\n if (cal) {\n ret = ret.replace('EEEE', cal.days.wide[dayOfWeek]); // Day of Week\n }\n if (cal) {\n ret = ret.replace('EEE', cal.days.abbreviated[dayOfWeek]); // Day of Week\n }\n if (cal) {\n ret = ret.replace('EE', cal.days.narrow[dayOfWeek]); // Day of Week\n }\n ret = ret.replace('nnnnn', 'de');\n ret = ret.replace('nnnn', 'ngày');\n ret = ret.replace('t1áng', 'tháng');\n ret = ret.replace('nnn', 'den');\n\n // Timezone\n if (ret.indexOf('zz') > -1) {\n const timezoneDate = new Date();\n const shortName = this.getTimeZone(timezoneDate, 'short');\n const longName = this.getTimeZone(timezoneDate, 'long');\n\n ret = ret.replace('zzzz', longName);\n ret = ret.replace('zz', shortName);\n }\n\n // Replace Left to Right Mark\n ret = ret.replace(/‎|\\u200E/gi, ' ');\n return ret.trim();\n },\n\n /**\n * Get date object by given date string.\n * @private\n * @param {string} dateStr The date string\n * @returns {object} The date object.\n */\n newDateObj(dateStr) {\n let date = new Date(dateStr);\n // Safari was not render the right date/time with timezone string\n if (env.browser.name === 'safari' && typeof dateStr === 'string' && /T|Z/g.test(dateStr)) {\n let arr = dateStr.replace(/Z/, '').replace(/T|:/g, '-').split('-');\n arr = arr.map((x, i) => +(i === 1 ? x - 1 : x));\n date = new Date(...arr);\n }\n return date;\n },\n\n /**\n * Formats a number into the locales hour format.\n * @param {number} hour The hours to show in the current locale.\n * @param {object} options Additional date formatting settings.\n * @returns {string} the hours in either 24 h or 12 h format\n */\n formatHour(hour, options) {\n let timeSeparator = this.calendar().dateFormat.timeSeparator;\n let locale = this.currentLocale.name;\n if (typeof options === 'object') {\n locale = options.locale || locale;\n timeSeparator = options.timeSeparator || this.calendar(locale).dateFormat.timeSeparator;\n }\n if (typeof hour === 'string' && hour.indexOf(timeSeparator) === -1) {\n timeSeparator = ':';\n }\n\n const date = new Date();\n if (typeof hour === 'number') {\n const split = hour.toString().split('.');\n date.setHours(split[0]);\n date.setMinutes(split[1] ? (parseFloat(`0.${split[1]}`) * 60) : 0);\n } else {\n const parts = hour.split(timeSeparator);\n date.setHours(parts[0]);\n date.setMinutes(parts[1] || 0);\n }\n return this.formatDate(date, { date: 'hour' });\n },\n\n /**\n * Formats a number into the locales hour format.\n * @param {number} startHour The hours to show in the current locale.\n * @param {number} endHour The hours to show in the current locale.\n * @param {object} options Additional date formatting settings.\n * @returns {string} the hours in either 24 h or 12 h format\n */\n formatHourRange(startHour, endHour, options) {\n let locale = this.currentLocale.name;\n let dayPeriods = this.calendar(locale).dayPeriods;\n let removePeriod = false;\n if (typeof options === 'object') {\n locale = options.locale || locale;\n dayPeriods = this.calendar(locale).dayPeriods;\n }\n let range = `${Locale.formatHour(startHour, options)} - ${Locale.formatHour(endHour, options)}`;\n\n if (range.indexOf(':00 AM -') > -1 || range.indexOf(':00 PM -') > -1) {\n removePeriod = true;\n }\n\n if (stringUtils.count(range, dayPeriods[0]) > 1) {\n range = range.replace(dayPeriods[0], '');\n }\n\n if (stringUtils.count(range, dayPeriods[1]) > 1) {\n range = range.replace(` ${dayPeriods[1]}`, '');\n }\n\n range = range.replace(' ', ' ');\n if (removePeriod) {\n range = range.replace(':00 -', ' -');\n }\n return range;\n },\n\n /**\n * Get the timezone part of a date\n * @param {date} date The date object to use.\n * @param {string} timeZoneName Can be short or long.\n * @returns {string} The time zone as a string.\n */\n getTimeZone(date, timeZoneName) {\n const currentLocale = Locale.currentLocale.name || 'en-US';\n\n if (env.browser.name === 'ie' && env.browser.version === '11') {\n // eslint-disable-next-line prefer-regex-literals\n return (date).toTimeString().match(new RegExp('[A-Z](?!.*[\\(])', 'g')).join('');\n }\n\n const short = date.toLocaleDateString(currentLocale);\n const full = date.toLocaleDateString(currentLocale, { timeZoneName: timeZoneName === 'long' ? 'long' : 'short' });\n\n // Trying to remove date from the string in a locale-agnostic way\n const shortIndex = full.indexOf(short);\n if (shortIndex >= 0) {\n const trimmed = full.substring(0, shortIndex) + full.substring(shortIndex + short.length);\n\n // by this time `trimmed` should be the timezone's name with some punctuation -\n // trim it from both sides\n return trimmed.replace(/^[\\s,.\\-:;]+|[\\s,.\\-:;]+$/g, '');\n }\n\n // in some magic case when short representation of date is not present in the long one, just return the long one as a fallback, since it should contain the timezone's name\n return full;\n },\n\n /**\n * Takes a date object in the current locale and adjusts it for the given timezone.\n * @param {date} date The utc date to show in the desired timezone.\n * @param {string} timeZone The timezone name to show.\n * @param {string} timeZoneName How to display the time zone name. Defaults to none. But can be short or long.\n * @returns {date} the utc date\n */\n dateToTimeZone(date, timeZone, timeZoneName) {\n if (env.browser.name === 'ie' && env.browser.version === '11') {\n // eslint-disable-next-line prefer-regex-literals\n return `${(date).toLocaleString(Locale.currentLocale.name)} ${(date).toTimeString().match(new RegExp('[A-Z](?!.*[\\(])', 'g')).join('')}`;\n }\n\n return (date).toLocaleString(Locale.currentLocale.name, { timeZone, timeZoneName });\n },\n\n /**\n * Formats a Date Object and return it in UTC format\n * @param {date} date The date to show in the current locale.\n * @returns {date} the utc date\n */\n dateToUTC(date) {\n return new Date(Date.UTC(\n date.getFullYear(),\n date.getMonth(),\n date.getDate(),\n date.getHours(),\n date.getMinutes(),\n date.getSeconds()\n ));\n },\n\n /**\n * Formats a number into the current locale using toLocaleString\n * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Using_locales\n * @param {number} number The number to convert\n * @param {string} locale The number to convert\n * @param {object} options The number to convert\n * @param {string} groupSeparator If provided will replace with browser default character\n * @returns {string} The converted number.\n */\n toLocaleString(number, locale, options, groupSeparator) {\n if (typeof number !== 'number') {\n return '';\n }\n const args = { locale: locale || Locale.currentLocale.name, options: options || undefined };\n let n = number.toLocaleString(args.locale, args.options);\n if (!(/undefined|null/.test(typeof groupSeparator))) {\n const gSeparator = this.getSeparator(args.locale, 'group');\n if (gSeparator !== '.') {\n n = n.replace(new RegExp(gSeparator, 'g'), groupSeparator.toString());\n }\n }\n return n;\n },\n\n /**\n * Find browser default separator for given locale\n * @private\n * @param {string} locale The locale\n * @param {string} separatorType The separator type be found `group`|`decimal`\n * @returns {string} The browser default separator character\n */\n getSeparator(locale, separatorType) {\n const number = 1000.1;\n return Intl.NumberFormat(locale)\n .formatToParts(number)\n .find(part => part.type === separatorType)\n .value;\n },\n\n /**\n * Convert a number in arabic/chinese or hindi numerals to an \"english\" number.\n * @param {[type]} string The string number in arabic/chinese or hindi\n * @returns {number} The english number.\n */\n convertNumberToEnglish(string) {\n const arabic = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];\n const devanagari = ['०', '१', '२', '३', '४', '५', '६', '७', '८', '९']; // Hindi\n const chineseFinancialTraditional = ['零', '壹', '貳', '叄', '肆', '伍', '陸', '柒', '捌', '玖'];\n const chineseFinancialSimplified = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];\n const chinese = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九'];\n\n for (let i = 0; i <= 9; i++) {\n string = string.replace(arabic[i], i);\n string = string.replace('٬', '');\n string = string.replace(',', '');\n string = string.replace(devanagari[i], i);\n string = string.replace(chineseFinancialTraditional[i], i);\n string = string.replace(chineseFinancialSimplified[i], i);\n string = string.replace(chinese[i], i);\n\n if (i === 0) { // Second option for zero in chinese\n string = string.replace('零', i);\n }\n }\n return parseFloat(string);\n },\n\n /**\n * Check if the date is valid using the current locale to do so.\n * @param {date} date The date to show in the current locale.\n * @returns {boolean} whether or not the date is valid.\n */\n isValidDate(date) {\n if (Object.prototype.toString.call(date) === '[object Date]') {\n // it is a date\n if (isNaN(date.getTime())) { // d.valueOf() could also work\n return false;\n }\n return true;\n }\n return false;\n },\n\n /**\n * Takes a formatted date string and parses back it into a date object\n * @param {string} dateString The string to parse in the current format\n * @param {string|object} options The source format for example 'yyyy-MM-dd' or { dateFormat: 'yyyy-MM-dd', locale: 'nl-NL'}\n * @param {boolean} isStrict If true missing date parts will be considered invalid. If false the current month/day.\n * @returns {date|array|undefined} A correct date object, if islamic calendar then an array is used or undefined if invalid.\n */\n parseDate(dateString, options, isStrict) {\n if (!dateString) {\n return undefined;\n }\n\n let dateFormat = options;\n let locale = this.currentLocale.name;\n let thisLocaleCalendar = this.calendar();\n\n if (typeof options === 'object') {\n locale = options.locale || locale;\n dateFormat = options.dateFormat || this.calendar(locale).dateFormat[dateFormat.date];\n }\n\n if (typeof options === 'object' && options.pattern) {\n dateFormat = options.dateFormat || options.pattern;\n }\n\n if (typeof options === 'object' && options.calendarName && options.locale) {\n thisLocaleCalendar = this.calendar(options.locale, options.language, options.calendarName);\n }\n\n if (!dateFormat) {\n dateFormat = this.calendar(locale).dateFormat.short;\n }\n\n const orgDatestring = dateString;\n if (dateString === '0000' || dateString === '000000' || dateString === '00000000') {\n // Means no date in some applications\n return undefined;\n }\n\n if (dateFormat.pattern) {\n dateFormat = dateFormat.pattern;\n }\n\n let formatParts;\n let dateStringParts;\n const dateObj = {};\n const isDateTime = (dateFormat.toLowerCase().indexOf('h') > -1);\n const isUTC = (dateString.toLowerCase().indexOf('z') > -1);\n let i;\n let l;\n const ampmHasDot = !!thisLocaleCalendar.dayPeriods.filter(x => x.indexOf('.') > -1).length &&\n dateFormat.indexOf('a') > -1 &&\n dateFormat.indexOf('ah') < 0 &&\n dateFormat.indexOf('H') < 0;\n const hasDot = (dateFormat.match(/M/g) || []).length === 3 && thisLocaleCalendar &&\n thisLocaleCalendar.months && thisLocaleCalendar.months.abbreviated &&\n thisLocaleCalendar.months.abbreviated.filter(v => /\\./.test(v)).length;\n\n if (isDateTime) {\n // Remove Timezone\n const shortTimeZone = Locale.getTimeZone(new Date(), 'short');\n const longTimeZone = Locale.getTimeZone(new Date(), 'long');\n dateString = dateString.replace(` ${shortTimeZone}`, '');\n dateString = dateString.replace(` ${longTimeZone}`, '');\n dateFormat = dateFormat.replace(' zzzz', '').replace(' zz', '');\n\n // Replace [space & colon & dot] with \"/\"\n const regex = hasDot ? /[T\\s:-]/g : /[T\\s:.-]/g;\n dateFormat = dateFormat.replace(regex, '/').replace(/z/i, '');\n dateFormat = ampmHasDot ? dateFormat.replace('//', '/') : dateFormat;\n dateString = dateString.replace(ampmHasDot ? /[T\\s:-]/g : regex, '/').replace(/z/i, '');\n }\n\n // Remove spanish de\n dateFormat = dateFormat.replace(' de ', ' ');\n dateString = dateString.replace(' de ', ' ');\n\n // Fix ah\n dateFormat = dateFormat.replace('ah', 'a/h');\n dateString = dateString.replace('午', '午/');\n\n // Remove commas\n dateFormat = dateFormat.replace(',', '');\n dateString = dateString.replace(',', '');\n\n // Adjust short dates where no separators or special characters are present.\n const hasMdyyyy = dateFormat.indexOf('Mdyyyy');\n const hasdMyyyy = dateFormat.indexOf('dMyyyy');\n let startIndex = -1;\n let endIndex = -1;\n if (hasMdyyyy > -1 || hasdMyyyy > -1) {\n startIndex = hasMdyyyy > -1 ? hasMdyyyy : hasdMyyyy > -1 ? hasdMyyyy : 0;\n endIndex = startIndex + dateString.indexOf('/') > -1 ? dateString.indexOf('/') : dateString.length;\n dateString = `${dateString.substr(startIndex, endIndex - 4)}/${dateString.substr(endIndex - 4, dateString.length)}`;\n dateString = `${dateString.substr(startIndex, dateString.indexOf('/') / 2)}/${dateString.substr(dateString.indexOf('/') / 2, dateString.length)}`;\n }\n if (hasMdyyyy > -1) {\n dateFormat = dateFormat.replace('Mdyyyy', 'M/d/yyyy');\n }\n if (hasdMyyyy > -1) {\n dateFormat = dateFormat.replace('dMyyyy', 'd/M/yyyy');\n }\n\n if (dateFormat.indexOf(' ') !== -1) {\n const regex = hasDot ? /[\\s:]/g : /[\\s:.]/g;\n dateFormat = dateFormat.replace(regex, '/');\n dateString = dateString.replace(regex, '/');\n }\n\n // Extra Check in case month has spaces\n if (dateFormat.indexOf('MMMM') > -1 && Locale.isRTL() && dateFormat &&\n dateFormat !== 'MMMM/dd' && dateFormat !== 'dd/MMMM') {\n const lastIdx = dateString.lastIndexOf('/');\n dateString = dateString.substr(0, lastIdx - 1).replace('/', ' ') + dateString.substr(lastIdx);\n }\n\n if (dateFormat.indexOf(' ') === -1 && dateFormat.indexOf('.') === -1 && dateFormat.indexOf('/') === -1 && dateFormat.indexOf('-') === -1) {\n // Remove delimeter for the data string.\n if (dateString.indexOf(' ') !== -1) {\n dateString = dateString.split(' ').join('');\n } else if (dateString.indexOf('.') !== -1) {\n dateString = dateString.split('.').join('');\n } else if (dateString.indexOf('/') !== -1) {\n dateString = dateString.split('/').join('');\n } else if (dateString.indexOf('-') !== -1) {\n dateString = dateString.split('-').join('');\n }\n\n let lastChar = dateFormat[0];\n let newFormat = '';\n let newDateString = '';\n\n for (i = 0, l = dateFormat.length; i < l; i++) {\n newDateString += (dateFormat[i] !== lastChar ? `/${dateString[i]}` : dateString[i]);\n newFormat += (dateFormat[i] !== lastChar ? `/${dateFormat[i]}` : dateFormat[i]);\n\n if (i > 1) {\n lastChar = dateFormat[i];\n }\n }\n\n dateString = newDateString;\n dateFormat = newFormat;\n }\n\n formatParts = dateFormat.split('/');\n dateStringParts = dateString.split('/');\n\n if (formatParts.length === 1) {\n formatParts = dateFormat.split('.');\n }\n\n if (dateStringParts.length === 1) {\n dateStringParts = dateString.split('.');\n }\n\n if (formatParts.length === 1) {\n formatParts = dateFormat.split('-');\n }\n\n if (dateStringParts.length === 1) {\n dateStringParts = dateString.split('-');\n }\n\n if (formatParts.length === 1) {\n formatParts = dateFormat.split(' ');\n }\n\n if (dateStringParts.length === 1) {\n dateStringParts = dateString.split(' ');\n }\n\n // Check the incoming date string's parts to make sure the values are\n // valid against the localized Date pattern.\n const month = this.getDatePart(formatParts, dateStringParts, 'M', 'MM', 'MMM', 'MMMM');\n const year = this.getDatePart(formatParts, dateStringParts, 'y', 'yy', 'yyyy');\n let hasDays = false;\n let hasDayPeriodsFirst = false;\n let amSetting = thisLocaleCalendar.dayPeriods[0];\n let pmSetting = thisLocaleCalendar.dayPeriods[1];\n\n if (!ampmHasDot) {\n amSetting = amSetting.replace(/\\./g, '');\n pmSetting = pmSetting.replace(/\\./g, '');\n }\n\n for (i = 0, l = dateStringParts.length; i < l; i++) {\n const pattern = `${formatParts[i]}`;\n const value = dateStringParts[i];\n const numberValue = parseInt(value, 10);\n\n if (!hasDays) {\n hasDays = pattern.toLowerCase().indexOf('d') > -1;\n }\n\n let lastDay;\n let abrMonth;\n let textMonths;\n\n switch (pattern) {\n case 'd':\n lastDay = new Date(year, month, 0).getDate();\n\n if (numberValue < 1 || numberValue > 31 || numberValue > lastDay) {\n return undefined;\n }\n dateObj.day = value;\n break;\n case 'dd':\n if ((numberValue < 1 || numberValue > 31) || (numberValue < 10 && value.substr(0, 1) !== '0')) {\n return undefined;\n }\n dateObj.day = value;\n break;\n case 'M':\n if (numberValue < 1 || numberValue > 12) {\n return undefined;\n }\n dateObj.month = value - 1;\n break;\n case 'MM':\n if ((numberValue < 1 || numberValue > 12) || (numberValue < 10 && value.substr(0, 1) !== '0')) {\n return undefined;\n }\n dateObj.month = value - 1;\n break;\n case 'MMM':\n abrMonth = this.calendar(locale).months.abbreviated;\n\n for (let len = 0; len < abrMonth.length; len++) {\n if (orgDatestring.indexOf(abrMonth[len]) > -1) {\n dateObj.month = len;\n }\n }\n\n break;\n case 'MMMM':\n textMonths = this.calendar(locale).months.wide;\n\n for (let k = 0; k < textMonths.length; k++) {\n if (orgDatestring.indexOf(textMonths[k]) > -1) {\n dateObj.month = k;\n }\n }\n\n break;\n case 'yy':\n dateObj.year = this.twoToFourDigitYear(value);\n break;\n case 'y':\n case 'yyyy':\n dateObj.year = (value.length === 2) ?\n this.twoToFourDigitYear(value) : value;\n break;\n case 'h':\n if (numberValue < 0 || numberValue > 12) {\n return undefined;\n }\n dateObj.h = hasDayPeriodsFirst ? dateObj.h : value;\n break;\n case 'hh':\n if (numberValue < 0 || numberValue > 12) {\n return undefined;\n }\n dateObj.h = hasDayPeriodsFirst ? dateObj.h : value.length === 1 ? `0${value}` : value;\n break;\n case 'H':\n if (numberValue < 0 || numberValue > 24) {\n return undefined;\n }\n dateObj.h = hasDayPeriodsFirst ? dateObj.h : value;\n break;\n case 'HH':\n if (numberValue < 0 || numberValue > 24) {\n return undefined;\n }\n dateObj.h = hasDayPeriodsFirst ? dateObj.h : value.length === 1 ? `0${value}` : value;\n break;\n case 'ss':\n if (numberValue < 0 || numberValue > 60) {\n dateObj.ss = 0;\n break;\n }\n dateObj.ss = value;\n break;\n case 'SSS':\n dateObj.ms = value;\n break;\n case 'mm':\n if (numberValue < 0 || numberValue > 60) {\n dateObj.mm = 0;\n break;\n }\n dateObj.mm = value;\n break;\n case 'a':\n if (!dateObj.h && formatParts[i + 1] && formatParts[i + 1].toLowerCase().substr(0, 1) === 'h') {\n // in a few cases am/pm is before hours\n dateObj.h = dateStringParts[i + 1];\n hasDayPeriodsFirst = true;\n }\n\n if ((value.toLowerCase() === amSetting) ||\n (value.toUpperCase() === amSetting)) {\n dateObj.a = 'AM';\n\n if (dateObj.h) {\n if (dateObj.h === 12 || dateObj.h === '12') {\n dateObj.h = 0;\n }\n }\n }\n\n if ((value.toLowerCase() === pmSetting) ||\n (value.toUpperCase() === pmSetting)) {\n dateObj.a = 'PM';\n\n if (dateObj.h) {\n if (dateObj.h < 12) {\n dateObj.h = parseInt(dateObj.h, 10) + 12;\n }\n }\n }\n break;\n default:\n break;\n }\n }\n\n const isLeap = y => ((y % 4 === 0) && (y % 100 !== 0)) || (y % 400 === 0);\n const closestLeap = (y) => {\n let closestLeapYear = typeof y === 'number' && !isNaN(y) ? y : (new Date()).getFullYear();\n for (let i2 = 0; i2 < 4; i2++) {\n if (isLeap(closestLeapYear)) {\n break;\n }\n closestLeapYear--;\n }\n return closestLeapYear;\n };\n\n dateObj.return = undefined;\n dateObj.leapYear = isLeap(dateObj.year);\n\n if ((isDateTime && !dateObj.h && !dateObj.mm)) {\n return undefined;\n }\n\n if (!dateObj.year && dateObj.year !== 0 && !isStrict) {\n dateObj.isUndefindedYear = true;\n for (i = 0, l = formatParts.length; i < l; i++) {\n if (formatParts[i].indexOf('y') > -1 && dateStringParts[i] !== undefined) {\n dateObj.isUndefindedYear = false;\n break;\n }\n }\n if (dateObj.isUndefindedYear) {\n const isFeb29 = parseInt(dateObj.day, 10) === 29 && parseInt(dateObj.month, 10) === 1;\n dateObj.year = isFeb29 ? closestLeap() : (new Date()).getFullYear();\n\n if (thisLocaleCalendar.name === 'islamic-umalqura') {\n const umDate = this.gregorianToUmalqura(new Date(dateObj.year, 0, 1));\n dateObj.year = umDate[0];\n }\n } else {\n delete dateObj.year;\n }\n }\n\n // Fix incomelete 2 and 3 digit years\n if (dateObj.year && dateObj.year.length === 2) {\n dateObj.year = `20${dateObj.year}`;\n }\n\n dateObj.year = $.trim(dateObj.year);\n dateObj.day = $.trim(dateObj.day);\n\n if (dateObj.year === '' || (dateObj.year && !((`${dateObj.year}`).length === 2 || (`${dateObj.year}`).length === 4))) {\n delete dateObj.year;\n }\n\n if (!dateObj.month && dateObj.month !== 0 && !isStrict) {\n dateObj.isUndefindedMonth = true;\n for (i = 0, l = formatParts.length; i < l; i++) {\n if (formatParts[i].indexOf('M') > -1 && dateStringParts[i] !== undefined) {\n dateObj.isUndefindedMonth = false;\n break;\n }\n }\n if (dateObj.isUndefindedMonth) {\n dateObj.month = (new Date()).getMonth();\n }\n }\n\n if (!dateObj.day && dateObj.day !== 0 && (!isStrict || !hasDays)) {\n dateObj.isUndefindedDay = true;\n for (i = 0, l = formatParts.length; i < l; i++) {\n if (formatParts[i].indexOf('d') > -1 && dateStringParts[i] !== undefined) {\n dateObj.isUndefindedDay = false;\n break;\n }\n }\n if (dateObj.isUndefindedDay) {\n dateObj.day = 1;\n } else {\n delete dateObj.day;\n }\n }\n\n if (isDateTime) {\n if (isUTC) {\n if (dateObj.h !== undefined) {\n dateObj.return = new Date(Date.UTC(dateObj.year, dateObj.month, dateObj.day, dateObj.h, dateObj.mm)); //eslint-disable-line\n }\n if (dateObj.ss !== undefined) {\n dateObj.return = new Date(Date.UTC(dateObj.year, dateObj.month, dateObj.day, dateObj.h, dateObj.mm, dateObj.ss)); //eslint-disable-line\n }\n if (dateObj.ms !== undefined) {\n dateObj.return = new Date(Date.UTC(dateObj.year, dateObj.month, dateObj.day, dateObj.h, dateObj.mm, dateObj.ss, dateObj.ms)); //eslint-disable-line\n }\n } else {\n if (dateObj.h !== undefined) {\n dateObj.return = new Date(dateObj.year, dateObj.month, dateObj.day, dateObj.h, dateObj.mm); //eslint-disable-line\n }\n if (dateObj.ss !== undefined) {\n dateObj.return = new Date(dateObj.year, dateObj.month, dateObj.day, dateObj.h, dateObj.mm, dateObj.ss); //eslint-disable-line\n }\n if (dateObj.ms !== undefined) {\n dateObj.return = new Date(dateObj.year, dateObj.month, dateObj.day, dateObj.h, dateObj.mm, dateObj.ss, dateObj.ms); //eslint-disable-line\n }\n }\n } else {\n dateObj.return = new Date(dateObj.year, dateObj.month, dateObj.day);\n }\n\n if (thisLocaleCalendar.name === 'islamic-umalqura') {\n return [\n parseInt(dateObj.year, 10),\n parseInt(dateObj.month, 10),\n parseInt(dateObj.day, 10),\n parseInt(dateObj.h || 0, 10),\n parseInt(dateObj.mm || 0, 10),\n parseInt(dateObj.ss || 0, 10),\n parseInt(dateObj.ms || 0, 10)\n ];\n }\n\n return (this.isValidDate(dateObj.return) ? dateObj.return : undefined);\n },\n\n /**\n * Convert the two digit year year to the correct four digit year.\n * @private\n * @param {number} twoDigitYear The two digit year.\n * @returns {number} Converted 3 digit year.\n */\n twoToFourDigitYear(twoDigitYear) {\n return parseInt((twoDigitYear > 39 ? '19' : '20') + twoDigitYear, 10);\n },\n\n /**\n * Format out the date into parts.\n * @private\n * @param {array} formatParts An array of the format bits.\n * @param {array} dateStringParts An array of the date parts.\n * @param {string} filter1 The first option to filter.\n * @param {string} filter2 The second option to filter.\n * @param {string} filter3 The third option to filter.\n * @param {string} filter4 The fourth option to filter.\n * @returns {string} The filtered out date part.\n */\n getDatePart(formatParts, dateStringParts, filter1, filter2, filter3, filter4) {\n let ret = 0;\n\n $.each(dateStringParts, (i) => {\n if (filter1 === formatParts[i] ||\n filter2 === formatParts[i] ||\n filter3 === formatParts[i] ||\n filter4 === formatParts[i]) {\n ret = dateStringParts[i];\n }\n });\n\n return ret;\n },\n\n /**\n * Use the current locale data or the one passed in.\n * @private\n * @param {object} options The options to parse.\n * @returns {object} The locale data.\n */\n useLocale(options) {\n let localeData = this.currentLocale.data;\n if (options && options.locale && this.cultures[options.locale]) {\n localeData = this.cultures[options.locale];\n }\n if (options && options.language && this.languages[options.language]) {\n const newData = utils.extend(true, {}, this.currentLocale.data);\n if (newData.calendars) {\n newData.calendars[0] = this.calendar(\n options.locale || this.currentLocale.name,\n options.language\n );\n return newData;\n }\n }\n if (!localeData.numbers) {\n localeData.numbers = this.numbers();\n }\n return localeData;\n },\n\n /**\n * Use the current language data or the one passed in.\n * @private\n * @param {object} options The options to parse.\n * @returns {object} The language data.\n */\n useLanguage(options) {\n let languageData = this.languages[this.currentLanguage.name];\n if (options && options.locale) {\n const lang = this.remapLanguage(options.locale);\n languageData = this.languages[lang];\n }\n if (options && options.locale &&\n this.currentLanguage.name !== this.currentLocale.name &&\n this.languages[this.currentLanguage.name]) {\n languageData = this.languages[this.currentLanguage.name];\n }\n if (options && options.language && this.languages[options.language]) {\n languageData = this.languages[options.language];\n }\n return languageData;\n },\n\n /**\n * Formats a decimal with thousands and padding in the current locale or settings.\n * @param {number} number The source number.\n * @param {object} options additional options (see Number Format Patterns)\n * @returns {string} the formatted number.\n */\n formatNumber(number, options) {\n const localeData = this.useLocale(options);\n let formattedNum;\n let curFormat;\n let percentFormat;\n const decimal = options && options.decimal ? options.decimal : localeData.numbers.decimal;\n let minimumFractionDigits = options && options.minimumFractionDigits !== undefined ? options.minimumFractionDigits : (options && options.style && options.style === 'currency' ? 2 : (options && options.style && options.style === 'percent') ? 0 : 2);\n let maximumFractionDigits = options && options.maximumFractionDigits !== undefined ? options.maximumFractionDigits : (options && options.style && (options.style === 'currency' || options.style === 'percent') ? 2 : (options && options.minimumFractionDigits ? options.minimumFractionDigits : 3));\n\n if (number === undefined || number === null || number === '') {\n return undefined;\n }\n\n if (options && options.style === 'integer') {\n maximumFractionDigits = 0;\n minimumFractionDigits = 0;\n }\n\n if (options && options.style === 'currency') {\n const sign = options && options.currencySign ? options.currencySign : localeData.currencySign;\n let format = options && options.currencyFormat ? options.currencyFormat :\n localeData.currencyFormat;\n\n if (!format) {\n format = '¤#,##0.00'; // default to en-us\n }\n curFormat = format.replace('¤', sign);\n }\n\n if (options && options.style === 'percent') {\n const percentSign = !localeData.numbers ? '%' : localeData.numbers.percentSign;\n\n percentFormat = !localeData.numbers ? '### %' : localeData.numbers.percentFormat;\n percentFormat = percentFormat.replace('¤', percentSign);\n }\n\n if (typeof number === 'string' && !(options?.parseNumber === false)) {\n number = Locale.parseNumber(number, options);\n }\n\n if (number.toString().indexOf('e') > -1) {\n number = number.toFixed(maximumFractionDigits + 1);\n }\n\n if (options && options.style === 'percent') {\n // the toFixed for maximumFractionDigits + 1 means we won't loose any precision\n number = (number * 100).toFixed(minimumFractionDigits);\n }\n\n const parts = this.truncateDecimals(number, minimumFractionDigits, maximumFractionDigits, options && options.round).split('.');\n let groupSizes = [3, 3]; // In case there is no data\n if (localeData && localeData.numbers && localeData.numbers.groupSizes) {\n groupSizes = localeData.numbers.groupSizes;\n }\n if (options && options.groupSizes) {\n groupSizes = options.groupSizes;\n }\n\n const sep = options && options.group !== undefined ? options.group : localeData.numbers.group;\n const expandedNum = this.expandNumber(parts[0], groupSizes, sep);\n parts[0] = expandedNum;\n formattedNum = parts.join(decimal);\n\n // Position the negative at the front - There is no CLDR info for this.\n const minusSign = (localeData && localeData.numbers &&\n localeData.numbers.minusSign) ? localeData.numbers.minusSign : '-';\n const isNegative = (formattedNum.indexOf(minusSign) > -1);\n formattedNum = formattedNum.replace(minusSign, '');\n\n const escape = str => str.replace(/([.?*+^$[\\]\\\\(){}|-])/g, '\\\\$1');\n let expr = '';\n\n if (minimumFractionDigits === 0) { // Not default\n expr = new RegExp(`(${escape(decimal)}[0-9]*?)0+$`);\n formattedNum = formattedNum.replace(expr, '$1'); // remove trailing zeros\n }\n\n if (minimumFractionDigits > 0) {\n expr = new RegExp(`(${escape(decimal)}.{${minimumFractionDigits}}[0-9]*?)0+$`);\n formattedNum = formattedNum.replace(expr, '$1'); // remove trailing zeros\n }\n\n expr = new RegExp(`${escape(decimal)}$`);\n formattedNum = formattedNum.replace(expr, ''); // remove trailing decimal\n\n if (options && options.style === 'currency') {\n formattedNum = curFormat.replace('###', formattedNum);\n }\n\n if (options && options.style === 'percent') {\n formattedNum = percentFormat.replace('###', formattedNum);\n }\n\n if (isNegative) {\n formattedNum = minusSign + formattedNum;\n }\n return formattedNum;\n },\n\n /**\n * Expand the number to the groupsize.\n * @private\n * @param {string} numberString The number to expand\n * @param {array} groupSizes The groupSizes option.\n * @param {string} sep The thousands separator option.\n * @returns {string} The expanded number.\n */\n expandNumber(numberString, groupSizes, sep) {\n let len = numberString.length;\n let isNegative = false;\n\n if (numberString.substr(0, 1) === '-') {\n numberString = numberString.substr(1);\n len = numberString.length;\n isNegative = true;\n }\n\n if (len <= 3) {\n return (isNegative ? '-' : '') + numberString;\n }\n\n if (groupSizes[0] === 0) {\n return (isNegative ? '-' : '') + numberString;\n }\n\n const firstGroup = numberString.substr(numberString.length - groupSizes[0]);\n const nthGroup = numberString.substr(0, numberString.length - groupSizes[0]);\n if (groupSizes[1] === 0) {\n return (isNegative ? '-' : '') + nthGroup + (nthGroup === '' ? '' : sep) + firstGroup;\n }\n const reversed = nthGroup.split('').reverse().join('');\n const regex = new RegExp(`.{1,${groupSizes[1]}}`, 'g');\n const reversedSplit = reversed.match(regex).join(sep);\n return (isNegative ? '-' : '') + reversedSplit.split('').reverse().join('') + sep + firstGroup;\n },\n\n /**\n * Truncate a number to a specific min and max digits.\n * @private\n * @param {number} number The starting number.\n * @param {number} minDigits Minimum number of digits to show on the decimal portion.\n * @param {number} maxDigits Maximum number of digits to show on the decimal portion.\n * @param {boolean} round If true round, if false truncate.\n * @returns {string} The updated number as a string.\n */\n truncateDecimals(number, minDigits, maxDigits, round) {\n let processed = number;\n if (round) {\n processed = numberUtils.round(number, maxDigits);\n } else {\n processed = numberUtils.truncate(number, maxDigits);\n }\n\n // Add zeros\n const actualDecimals = numberUtils.decimalPlaces(processed);\n if (actualDecimals < minDigits) {\n processed = processed.toString() + new Array(minDigits - actualDecimals + 1).join('0');\n }\n return processed.toString();\n },\n\n /**\n * Takes a formatted number string and returns back real number object.\n * @param {string} input The source number (as a string).\n * @param {object} options Any special options to pass in such as the locale.\n * @returns {number} The number as an actual Number type unless the number is a big int (19 significant digits), in this case a string will be returned\n */\n parseNumber(input, options) {\n const localeData = this.useLocale(options);\n const numSettings = localeData.numbers;\n let numString;\n\n numString = input;\n\n if (!numString) {\n return NaN;\n }\n\n if (typeof input === 'number') {\n numString = numString.toString();\n }\n\n const group = options && options.group !== undefined ? options.group : numSettings ? numSettings.group : ',';\n const decimal = options && options.decimal ? options.decimal : numSettings ? numSettings.decimal : '.';\n const percentSign = options && options.percentSign ? options.percentSign : numSettings ? numSettings.percentSign : '%';\n const currencySign = options && options.currencySign ? options.currencySign : localeData.currencySign || '$';\n\n // eslint-disable-next-line prefer-regex-literals\n const exp = (group === ' ' || group === '') ? new RegExp(/\\s/g) : new RegExp(`\\\\${group}`, 'g');\n numString = numString.replace(exp, '');\n numString = numString.replace(decimal, '.');\n numString = numString.replace(percentSign, '');\n numString = numString.replace(currencySign, '');\n numString = numString.replace('$', '');\n numString = numString.replace(' ', '');\n\n return numString.length >= 18 ? numString : parseFloat(numString);\n },\n\n /**\n * Takes a translation key and returns the translation in the current locale.\n * @param {string} key The key to search for on the string.\n * @param {object} [options] A list of options, supported are a non default locale and showAsUndefined and showBrackets which causes a translated phrase to be shown in square brackets\n * instead of defaulting to the default locale's version of the string.\n * @returns {string|undefined} a translated string, or nothing, depending on configuration\n */\n translate(key, options) {\n const languageData = this.useLanguage(options);\n let showAsUndefined = false;\n let showBrackets = true;\n if (key === '&nsbp;') {\n return '';\n }\n if (typeof options === 'boolean') {\n showAsUndefined = options;\n }\n if (typeof options === 'object' && options.showAsUndefined !== undefined) {\n showAsUndefined = options.showAsUndefined;\n }\n if (typeof options === 'object' && options.showBrackets !== undefined) {\n showBrackets = options.showBrackets;\n }\n\n if (languageData === undefined || languageData.messages === undefined) {\n return showAsUndefined ? undefined : `${showBrackets ? '[' : ''}${key}${showBrackets ? ']' : ''}`;\n }\n\n if (languageData.messages[key] === undefined) {\n const enLang = 'en';\n // Substitue English Expression if missing\n if (!this.languages || !this.languages[enLang] || !this.languages[enLang].messages ||\n this.languages[enLang].messages[key] === undefined) {\n return showAsUndefined ? undefined : `${showBrackets ? '[' : ''}${key}${showBrackets ? ']' : ''}`;\n }\n return this.languages[enLang].messages[key].value;\n }\n\n return languageData.messages[key].value;\n },\n\n /**\n * Add an object full of translations to the given locale.\n * @param {string} lang The language to add them to.\n * @param {object} messages Strings in the form of\n */\n extendTranslations(lang, messages) {\n if (!this.languages[lang]) {\n return;\n }\n const base = this.languages[lang].messages;\n this.languages[lang].messages = utils.extend(true, base, messages);\n },\n\n /**\n * Shortcut function to get 'first' calendar\n * @private\n * @param {string} locale The locale to use\n * @param {string} lang The translations of the calendar items\n * @param {string} name the name of the calendar (fx: \"gregorian\", \"islamic-umalqura\")\n * @returns {object} containing calendar data.\n */\n calendar(locale, lang, name) {\n let calendars = [];\n if (this.currentLocale.data.calendars && !locale) {\n calendars = this.currentLocale.data.calendars;\n }\n\n if (lang && lang.length > 2) {\n lang = lang.substr(0, 2);\n }\n\n if (locale && this.cultures[locale]) {\n calendars = this.cultures[locale].calendars;\n }\n\n if (name && calendars) {\n for (let i = 0; i < calendars.length; i++) {\n const cal = calendars[i];\n if (cal.name === name) {\n return cal;\n }\n }\n }\n\n if (!calendars[0]) {\n // Defaults to en-US\n return {\n dateFormat: {\n separator: '/',\n timeSeparator: ':',\n short: 'M/d/yyyy',\n medium: 'MMM d, yyyy',\n long: 'MMMM d, yyyy',\n full: 'EEEE, MMMM d, y',\n month: 'MMMM d',\n year: 'MMMM yyyy',\n timestamp: 'h:mm:ss a',\n datetime: 'M/d/yyyy h:mm a'\n },\n timeFormat: 'HH:mm:ss',\n dayPeriods: ['AM', 'PM']\n };\n }\n const calendar = utils.extend(true, {}, calendars[0]);\n\n if (lang && locale.substr(0, 2) !== lang) {\n const defaultLocale = this.defaultLocales.filter(a => a.lang === lang);\n\n if (defaultLocale[0]) {\n const culture = this.cultures[defaultLocale[0].default];\n calendar.days = culture.calendars[0].days;\n calendar.months = culture.calendars[0].months;\n calendar.dayPeriods = culture.calendars[0].dayPeriods;\n }\n }\n return calendar;\n },\n\n /**\n * Shortcut function to get numbers\n * @private\n * @returns {object} containing information for formatting numbers\n */\n numbers() {\n return this.currentLocale.data.numbers ? this.currentLocale.data.numbers : {\n percentSign: '%',\n percentFormat: '### %',\n minusSign: '-',\n decimal: '.',\n group: ','\n };\n },\n\n /**\n * Padd a number to the given width and decimals\n * @private\n * @param {string} n the number\n * @param {number} width the decimal with\n * @param {string} z the padding character\n * @returns {string} the padded string\n */\n pad(n, width, z) {\n z = z || '0';\n n += '';\n return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;\n },\n\n /**\n * Describes whether or not this locale is one that is read in \"right-to-left\" fashion.\n * @returns {boolean} whether or not this locale is \"right-to-left\".\n */\n isRTL() {\n return !this.currentLanguage ? false :\n this.currentLanguage.direction === 'right-to-left';\n },\n\n /**\n * Describes whether or not the default calendar is islamic.\n * @param {string} locale The locale to check if not the current.\n * @returns {boolean} whether or not this locale is \"right-to-left\".\n */\n isIslamic(locale) {\n return this.calendar(locale)?.name === 'islamic-umalqura';\n },\n\n /**\n * Takes a string and converts its contents to upper case, taking into account\n * Locale-specific character conversions. In most cases this method will simply\n * pipe the string to `String.prototype.toUpperCase()`.\n * @private\n * @param {string} str the incoming string\n * @returns {string} modified string\n */\n toUpperCase(str) {\n if (typeof this.currentLocale.data.toUpperCase === 'function') {\n return this.currentLocale.data.toUpperCase(str);\n }\n\n return str.toLocaleUpperCase();\n },\n\n /**\n * Takes a string and converts its contents to lower case, taking into account\n * Locale-specific character conversions. In most cases this method will simply\n * pipe the string to `String.prototype.toLowerCase()`\n * @private\n * @param {string} str - the incoming string\n * @returns {string} The localized string\n */\n toLowerCase(str) {\n if (typeof this.currentLocale.data.toLowerCase === 'function') {\n return this.currentLocale.data.toLowerCase(str);\n }\n\n return str.toString().toLocaleLowerCase();\n },\n\n /**\n * Takes a string and capitalizes the first letter, taking into account Locale-specific\n * character conversions. In most cases this method will simply use a simple algorithm\n * for captializing the first letter of the string.\n * @private\n * @param {string} str the incoming string\n * @returns {string} the modified string\n */\n capitalize(str) {\n return this.toUpperCase(str.charAt(0)) + str.slice(1);\n },\n\n /**\n * Takes a string and capitalizes the first letter of each word in a string, taking\n * into account Locale-specific character conversions. In most cases this method\n * will simply use a simple algorithm for captializing the first letter of the string.\n * @private\n * @param {string} str the incoming string\n * @returns {string} the modified string\n */\n capitalizeWords(str) {\n const words = str.split(' ');\n\n for (let i = 0; i < words.length; i++) {\n words[i] = this.capitalize(words[i]);\n }\n\n return words.join(' ');\n },\n\n /**\n * Convert gregorian to umalqura date.\n * @param {object} date the date\n * @returns {array} year, month, day, hours, minutes, seconds, milliseconds\n */\n gregorianToUmalqura(date) {\n // fromGregorian\n // Modified version of Amro Osama's code. From at https://github.com/kbwood/calendars/blob/master/src/js/jquery.calendars.ummalqura.js\n if (typeof date.getMonth !== 'function') {\n return null;\n }\n\n const getJd = (year, month, day) => {\n if (year < 0) {\n year++;\n }\n if (month < 3) {\n month += 12;\n year--;\n }\n const a = Math.floor(year / 100);\n const b = 2 - a + Math.floor(a / 4);\n return Math.floor(365.25 * (year + 4716)) +\n Math.floor(30.6001 * (month + 1)) + day + b - 1524.5;\n };\n const jd = getJd(date.getFullYear(), date.getMonth() + 1, date.getDate());\n\n const julianToUmalqura = (julianDate) => {\n const mcjdn = julianDate - 2400000 + 0.5;\n let index = 0;\n for (let i = 0; i < ummalquraData.length; i++) {\n if (ummalquraData[i] > mcjdn) {\n break;\n }\n index++;\n }\n const lunation = index + 15292;\n const ii = Math.floor((lunation - 1) / 12);\n const year = ii + 1;\n const month = lunation - 12 * ii;\n const day = mcjdn - ummalquraData[index - 1] + 1;\n return { year, month: month - 1, day };\n };\n const umalquraDate = julianToUmalqura(jd);\n\n return [\n umalquraDate.year,\n umalquraDate.month,\n umalquraDate.day,\n date.getHours(),\n date.getMinutes(),\n date.getSeconds(),\n date.getMilliseconds()\n ];\n },\n\n /**\n * Convert umalqura to gregorian date.\n * @param {number|array} year the year\n * @param {number} month the month\n * @param {number} day the day\n * @param {number} hours the day\n * @param {number} mins the day\n * @param {number} secs the day\n * @param {number} mills the day\n * @returns {object} the date\n */\n umalquraToGregorian(year, month, day, hours = 0, mins = 0, secs = 0, mills = 0) {\n // Modified version of Amro Osama's code. From at https://github.com/kbwood/calendars/blob/master/src/js/jquery.calendars.ummalqura.js\n\n // Handle if an array is passed\n if (Array.isArray(year)) {\n month = year[1];\n day = year[2] || 0;\n hours = year[3] || 0;\n mins = year[4] || 0;\n secs = year[5] || 0;\n mills = year[6] || 0;\n year = year[0];\n }\n\n const isNumber = n => typeof n === 'number' && !isNaN(n);\n if (!isNumber(year) || !isNumber(month) || !isNumber(day)) {\n return null;\n }\n\n const getJd = (y, m, d) => {\n const index = (12 * (y - 1)) + m - 15292;\n const mcjdn = d + ummalquraData[index - 1] - 1;\n return mcjdn + 2400000 - 0.5;\n };\n const jd = getJd(year, month + 1, day);\n\n const julianToGregorian = (julianDate) => {\n const z = Math.floor(julianDate + 0.5);\n let a = Math.floor((z - 1867216.25) / 36524.25);\n a = z + 1 + a - Math.floor(a / 4);\n const b = a + 1524;\n const c = Math.floor((b - 122.1) / 365.25);\n const d = Math.floor(365.25 * c);\n const e = Math.floor((b - d) / 30.6001);\n const gday = b - d - Math.floor(e * 30.6001);\n const gmonth = e - (e > 13.5 ? 13 : 1);\n let gyear = c - (gmonth > 2.5 ? 4716 : 4715);\n // No zero year\n if (gyear <= 0) {\n gyear--;\n }\n return { year: gyear, month: gmonth - 1, day: gday };\n };\n const gregorianDateObj = julianToGregorian(jd);\n\n const gregorianDate = new Date(\n gregorianDateObj.year,\n gregorianDateObj.month,\n gregorianDateObj.day,\n hours,\n mins,\n secs,\n mills\n );\n return gregorianDate;\n },\n\n /**\n * Modifies a specified list of icons by flipping them horizontally to make them\n * compatible for RTL-based locales.\n * @private\n * @param {boolean} flipRTL Check to see if icons should be flipped for RTL or LTR\n * @returns {void}\n */\n flipIconsHorizontally(flipRTL = true) {\n const icons = [\n 'attach',\n 'bottom-aligned',\n 'bullet-list',\n 'cancel',\n 'cart',\n 'collapse-app-tray',\n 'cut',\n 'document',\n 'drilldown',\n 'duplicate',\n 'expand-app-tray',\n 'export',\n 'first-page',\n 'folder',\n 'import',\n 'last-page',\n 'launch',\n 'left-align',\n 'left-text-align',\n 'left-arrow',\n 'new-document',\n 'next-page',\n 'number-list',\n 'paste',\n 'previous-page',\n 'quote',\n 'redo',\n 'refresh',\n 'right-align',\n 'right-arrow',\n 'right-text-align',\n 'save',\n 'search-folder',\n 'search-list',\n 'search',\n 'send',\n 'tack',\n 'tree-collapse',\n 'tree-expand',\n 'undo',\n 'unlocked',\n 'add-grid-record',\n 'add-grid-row',\n 'additional-help',\n 'bubble',\n 'bullet-steps',\n 'cascade',\n 'change-font',\n 'clear-screen',\n 'script',\n 'clockwise-90',\n 'close-cancel',\n 'close-save',\n 'contacts',\n 'copy-from',\n 'copy-mail',\n 'copy-url',\n 'counter-clockwise-90',\n 'create-report',\n 'delete-grid-record',\n 'delete-grid-row',\n 'display',\n 'employee-directory',\n 'export-2',\n 'export-to-pdf',\n 'generate-key',\n 'get-more-rows',\n 'group-selection',\n 'headphones',\n 'help',\n 'helper-list-select',\n 'history',\n 'invoice-released',\n 'language',\n 'logout',\n 'key',\n 'lasso',\n 'line-bar-chart',\n 'line-chart',\n 'new-expense-report',\n 'new-payment-request',\n 'new-time-sheet',\n 'new-travel-plan',\n 'no-attachment',\n 'no-comment',\n 'no-filter',\n 'overlay-line',\n 'pdf-file',\n 'phone',\n 'payment-request',\n 'pie-chart',\n 'queries',\n 'quick-access',\n 'refresh-current',\n 'restore-user',\n 'run-quick-access',\n 'save-close',\n 'save-new',\n 'search-results-history',\n 'select',\n 'send-submit',\n 'show-last-x-days',\n 'special-item',\n 'stacked',\n 'timesheet',\n 'unsubscribe',\n 'update-preview',\n 'zoom-100',\n 'zoom-in',\n 'zoom-out',\n 'caret-left',\n 'caret-right'\n ];\n\n $('svg').each(function () {\n const iconName = $(this).getIconName();\n\n if (flipRTL && (iconName && $.inArray(iconName, icons) !== -1 && $(this).closest('.monthview').length === 0)) {\n $(this).addClass('icon-rtl-rotate');\n } else {\n $(this).removeClass('icon-rtl-rotate');\n }\n });\n }\n};\n\nexport { Locale };\n","/**\n * Safely gets the name of a method\n * @param {function|string} method the method to be checked (or method name to be reported)\n * @returns {string} the name of the method\n */\nfunction methodName(method) {\n if (typeof method !== 'function') {\n if (typeof method === 'string') {\n return method;\n }\n throw new Error(`${typeof method} was provided where a function was expected.`);\n }\n\n // ES6-friendly\n if (typeof method.name === 'string') {\n return `${method.name}()`;\n }\n\n // Regex for ES5 (IE11)\n // See https://stackoverflow.com/a/17923727/4024149\n const result = /^function\\s+([\\w$]+)\\s*\\(/.exec(method.toString());\n return result ? `${result[1]}()` : '(anonymous function)';\n}\n\n/**\n * Warns about a deprecated property/method via a console warning\n * @param {function|string} newMethod the new method to call\n * @param {function|string} oldMethod the name of the old method\n * @param {string} [context=''] optional additional context\n * @returns {void}\n */\nfunction warnAboutDeprecation(newMethod, oldMethod, context = '') {\n if (typeof console !== 'object') {\n return;\n }\n\n const newMethodName = methodName(newMethod);\n const oldMethodName = methodName(oldMethod);\n if (context.length) {\n context = ` (${context})`;\n }\n\n // eslint-disable-next-line no-console\n console.warn(`IDS Enterprise${context}: \"${oldMethodName}\" is deprecated. Please use \"${newMethodName}\" instead.`);\n}\n\n/**\n * Warns about an upcoming removal of a piece of code with no replacement.\n * @param {function|string} removedName the name of the removed code\n * @returns {void}\n */\nfunction warnAboutRemoval(removedName) {\n if (typeof console !== 'object') {\n return;\n }\n // eslint-disable-next-line no-console\n console.warn(`IDS Enterprise: \"${removedName}\" is deprecated and will be later removed. Please adjust your code accordingly.`);\n}\n\n/**\n * Deprecates a method in the codebase\n * @param {function} newMethod the new method to call\n * @param {function} oldMethod the name of the old method\n * @param {...object} [args] arguments that will be passed to the new function\n * @returns {function} wrapper method\n */\nfunction deprecateMethod(newMethod, oldMethod) {\n const wrapper = function deprecatedMethodWrapper(...args) {\n warnAboutDeprecation(newMethod, oldMethod);\n newMethod.apply(this, args);\n };\n wrapper.prototype = newMethod.prototype;\n\n return wrapper;\n}\n\nexport { warnAboutDeprecation, deprecateMethod, warnAboutRemoval };\n","import { warnAboutDeprecation } from './deprecated';\n\n/**\n * Provides a global object that detects the existence of a Base Tag,\n * and provides some methods that can be used to get an accurate relative\n * URL using the base tag.\n * @class Base\n * @constructor\n * @param {HTMLElement} element the Base Tag Element\n * @returns {Base} component instance\n */\nfunction Base(element) {\n this.element = $(element);\n return this;\n}\n\nBase.prototype = {\n\n /**\n * @private\n * @returns {string} current page URL\n */\n get url() {\n return window.location.href\n .replace(window.location.hash, '');\n },\n\n /**\n * @private\n * @returns {string} the base tag's `href` attribute\n */\n get href() {\n return this.element[0].getAttribute('href');\n },\n\n /**\n * This method is slated to be removed in a future v4.18.0 or v5.0.0.\n * @deprecated as of v4.12.0. Please use the `url` property instead.\n * @returns {string} current page URL\n */\n getCurrentURL() {\n warnAboutDeprecation('url', 'getCurrentURL');\n return this.url;\n },\n\n /**\n * Gets a copy of a URL prepended with the contents of the Base Tag's hash.\n * If there's no base tag present, this simply returns the hash provided.\n * @param {string} hash the URL to be checked.\n * @returns {string} the current URL prepended with the Base Tag's ref, if necessary\n */\n getBaseURL(hash) {\n // If no valid base tag exists, just return the hash provided.\n if (!this.element.length || (!this.href || this.href === '/')) {\n if (!hash) {\n return '';\n }\n return hash;\n }\n\n if (hash) {\n if (hash.indexOf('/') === 0) {\n return hash;\n }\n\n hash = (hash.indexOf('#') === -1 ? '#' : '') + hash;\n return this.url + hash;\n }\n\n return this.url;\n }\n};\n\n// Setup a Base Tag Component instance\nconst base = new Base($('base[href]'));\n\n/**\n * Setup a default function that just returns the contents of the hash,\n * if no base tag is present.\n * @param {string} hash the URL to be checked.\n * @returns {string} the current URL prepended with the Base Tag's ref, if necessary\n */\n$.getBaseURL = function (hash) {\n return base.getBaseURL(hash);\n};\n\nexport { base };\n","import { utils } from './utils';\n\n// Default RenderLoop Settings\nconst RENDERLOOP_DEFAULTS = {\n noAutoStart: false\n};\n\n// Only start the renderloop if it's not disabled by a Global config setting.\nlet instanceSettings = {};\nif (typeof window.SohoConfig === 'object' && typeof window.SohoConfig.renderLoop === 'object') {\n instanceSettings = utils.extend({}, RENDERLOOP_DEFAULTS, window.SohoConfig.renderLoop);\n}\n\n/**\n * Gets an accurate timestamp from\n * @private\n * @returns {number} a current timestamp\n */\nfunction timestamp() {\n // eslint-disable-next-line compat/compat\n return window.performance && window.performance.now ?\n // eslint-disable-next-line compat/compat\n window.performance.now() :\n new Date().getTime();\n}\n\n/**\n * RenderLoop Queue items\n * @param {object} opts options\n * @returns {this} RenderLoopItem\n */\nfunction RenderLoopItem(opts) {\n // Either ID or a duration is required\n this.id = opts.id;\n this.duration = opts.duration || -1;\n if (this.duration < 1 && (typeof this.id !== 'string' || !this.id.length)) {\n throw new Error('cannot build a RenderLoopItem with no duration and no namespace');\n }\n this.updateDuration = opts.updateDuration || 1;\n\n // functions\n this.setFuncs(opts);\n\n // internal state\n this.paused = false;\n this.elapsedTime = 0;\n this.startTime = timestamp();\n\n return this;\n}\n\nRenderLoopItem.prototype = {\n\n /**\n * @private\n * @param {object} opts incoming settings\n */\n setFuncs(opts) {\n if (typeof opts.updateCallback !== 'function' && typeof opts.timeoutCallback !== 'function') {\n throw new Error('cannot register callback to RenderLoop because callback is not a function');\n }\n\n if (typeof opts.updateCallback === 'function') {\n this.updateCallback = opts.updateCallback;\n }\n\n if (typeof opts.timeoutCallback === 'function') {\n this.timeoutCallback = opts.timeoutCallback;\n }\n },\n\n pause() {\n this.paused = true;\n },\n\n resume() {\n this.paused = false;\n },\n\n /**\n * @param {boolean} noTimeout causes the item to be destroyed without triggering the `timeoutCallback` function\n */\n destroy(noTimeout) {\n if (noTimeout) {\n this.noTimeout = true;\n }\n this.doRemoveOnNextTick = true;\n }\n};\n\n/**\n * Sets up a timed rendering loop that can be used for controlling animations\n * globally in an application that implements Soho.\n * @constructor\n * @param {object} [settings] incoming settings\n * @param {boolean} [settings.noAutoStart] if true, will not auto-start the renderLoop\n */\nfunction RenderLoop(settings) {\n this.items = [];\n this.element = $('body');\n this.settings = utils.mergeSettings(null, settings, RENDERLOOP_DEFAULTS);\n\n if (this.settings.noAutoStart !== true) {\n this.start();\n }\n\n return this;\n}\n\nRenderLoop.prototype = {\n\n /**\n * Start the entire render loop\n * @returns {void}\n */\n start() {\n this.doLoop = true;\n this.startTime = timestamp();\n\n const self = this;\n let last = timestamp();\n let now;\n let deltaTime;\n\n function tick() {\n // Don't continue if the loop is stopped externally\n if (!self.doLoop) {\n return;\n }\n\n now = timestamp();\n deltaTime = (now - last) / 1000;\n\n // Iterate through each item stored in the queue and \"update\" each one.\n // In some cases, items will be removed from the queue automatically.\n // In some cases, `update` events will be triggered on loop items, if they are\n // ready to be externally updated.\n self.items.forEach((loopItem) => {\n // Remove if we've set the `doRemoveOnNextTick` flag.\n if (loopItem.doRemoveOnNextTick) {\n self.remove(loopItem);\n return;\n }\n\n // Add to elapsedTime\n if (!loopItem.paused) {\n loopItem.elapsedTime++;\n }\n\n // Check duration\n if (typeof loopItem.duration === 'number' && loopItem.duration > -1) {\n if (!loopItem.startTime) {\n loopItem.startTime = now;\n }\n\n if (loopItem.elapsedTime >= loopItem.duration) {\n loopItem.destroy();\n return;\n }\n }\n\n // Call the updateCallback, if applicable.\n let modifiedArgs;\n if (typeof loopItem.updateCallback === 'function') {\n // If this item doesn't update on each tick, simply count down.\n // Otherwise, call the update function\n if (loopItem.updateDuration && loopItem.updateDuration > 1) {\n if (isNaN(loopItem.timeUntilNextUpdate)) {\n loopItem.timeUntilNextUpdate = loopItem.updateDuration;\n }\n\n if (loopItem.timeUntilNextUpdate > 0) {\n --loopItem.timeUntilNextUpdate;\n return;\n }\n }\n\n // Arguments produced for the updateCallback contain:\n // [0] the current RenderLoopItem\n // [1] overall timing values for the RenderLoop\n modifiedArgs = [loopItem, {\n last,\n delta: deltaTime,\n now\n }];\n\n loopItem.updateCallback.apply(null, modifiedArgs);\n }\n });\n\n // Continue the loop\n last = now;\n requestAnimationFrame(tick);\n }\n\n tick();\n },\n\n /**\n * Stops the entire render loop\n * @returns {void}\n */\n stop() {\n this.doLoop = false;\n },\n\n /**\n * @returns {number} amount of time that has passed since the RenderLoop was started.\n */\n totalDuration() {\n return timestamp() - this.startTime;\n },\n\n /**\n * External method for getting the callback queue contents\n * @returns {array} list of internal RenderLoopItems\n */\n queue() {\n return this.items;\n },\n\n /**\n * @private\n * @param {function} updateCallback - (can also be the \"updateCallback\" function)\n * @param {function} [timeoutCallback] callback function that gets fired at\n * the end of this item's lifecycle\n * @param {number} [duration] the amount of time in frames that this item should exist\n * @param {string} [namespace] the namespace for this item\n * @returns {RenderLoopItem} the item that was registered\n */\n buildRenderLoopItem(updateCallback, timeoutCallback, duration, namespace) {\n let noNamespace = typeof namespace !== 'string' || !namespace.length;\n\n // valid for a callback not to have a duration, as long as it's\n // namespaced for future manual removal\n if (typeof duration === 'string') {\n if (noNamespace) {\n namespace = duration;\n duration = -1;\n noNamespace = false;\n } else {\n const numberDuration = Number(duration);\n if (!isNaN(numberDuration)) {\n duration = numberDuration;\n }\n }\n } else if (typeof duration !== 'number') {\n duration = -1;\n }\n\n if (typeof namespace !== 'string' || !namespace.length) {\n namespace = ''; // TODO: make unique\n }\n\n const loopItem = new RenderLoopItem({\n id: namespace,\n updateCallback,\n timeoutCallback,\n duration\n });\n\n return loopItem;\n },\n\n /**\n * @param {RenderLoopItem|function} loopItem - (can also be the \"updateCallback\" function)\n * @param {function} [timeoutCallback] callback function that gets fired at\n * the end of this item's lifecycle\n * @param {number} [duration] the amount of time in frames that this item should exist\n * @param {string} [namespace] the namespace for this item\n * @returns {RenderLoopItem} the item that was registered\n */\n register(loopItem, timeoutCallback, duration, namespace) {\n // If we're not working with a RenderLoopItem off the bat, take arguments\n // and convert to a RenderLoopItem. Consider the first argument\n // to be the \"updateCallback\" function\n if (!(loopItem instanceof RenderLoopItem)) {\n loopItem = this.buildRenderLoopItem(loopItem, timeoutCallback, duration, namespace);\n }\n\n this.items.push(loopItem);\n\n return loopItem;\n },\n\n /**\n * @param {function} callback callback function to be unregistered\n * @param {string} [namespace] namespace to be unregistered\n * @returns {RenderLoopItem} the item that was unregistered\n */\n unregister(callback, namespace) {\n if (typeof callback !== 'function' && typeof callback !== 'string' && typeof namespace !== 'string') {\n throw new Error('must provide either a callback function or a namespace string to remove an entry from the RenderLoop queue.');\n }\n\n // If callback is defined as a string, simply swap it for the namespace.\n if (typeof callback === 'string') {\n namespace = callback;\n callback = undefined;\n }\n\n return this.remove({\n cb: callback,\n id: namespace\n });\n },\n\n /**\n * @private\n * Uses a callback function, or a defined namespace, to grab a RenderLoop item from the queue.\n * @param {function} updateCallback callback function to be retrieved\n * @param {string} [namespace] namespace to be retrieved\n * @returns {RenderLoopItem} the RenderLoopItem that represents the item that was paused.\n */\n getFromQueue(updateCallback, namespace) {\n // If callback is defined as a string, simply swap it for the namespace.\n if (typeof callback === 'string') {\n namespace = updateCallback;\n updateCallback = undefined;\n }\n\n let retreivedItem;\n\n if (typeof callback === 'function') {\n // Remove by callback method\n this.items.forEach((item) => {\n if (`${item.updateCallback}` !== `${updateCallback}`) {\n return true;\n }\n retreivedItem = item;\n return false;\n });\n } else if (typeof namespace === 'string') {\n // Remove by namespace\n this.items.forEach((item) => {\n if (item.id !== namespace) {\n return true;\n }\n retreivedItem = item;\n return false;\n });\n }\n\n return retreivedItem;\n },\n\n /**\n * @private\n * Actually does the removal of a registered callback from the queue\n * Pulled out into its own function because it can be automatically called by\n * the tick, or manually triggered from an external API call.\n * @param {renderLoopItem|Object} obj the renderLoopItem\n * @returns {RenderLoopItem} reference to the removed renderLoopItem\n */\n remove(obj) {\n let removedItem;\n\n if (obj instanceof RenderLoopItem) {\n removedItem = obj;\n this.items = this.items.filter(item => item !== obj);\n } else if (typeof obj.updateCallback === 'function') {\n // Remove by callback method\n this.items = this.items.filter((item) => {\n if (`${item.updateCallback}` !== `${obj.updateCallback}`) {\n return true;\n }\n removedItem = item;\n return false;\n });\n } else if (typeof obj.id === 'string') {\n // Remove by namespace\n this.items = this.items.filter((item) => {\n if (item.id !== obj.id) {\n return true;\n }\n removedItem = item;\n return false;\n });\n }\n\n if (typeof removedItem.timeoutCallback === 'function' && !removedItem.noTimeout) {\n removedItem.timeoutCallback.apply(null, removedItem);\n }\n\n this.element.triggerHandler('remove.renderLoop', [removedItem]);\n\n // If this is undefined, an item was NOT removed from the queue successfully.\n return removedItem;\n },\n\n /**\n * @param {function} callback callback function to be paused\n * @param {string} [namespace] namespace to be paused\n * @returns {RenderLoopItem} the RenderLoopItem that represents the item that was paused.\n */\n pause(callback, namespace) {\n if (typeof callback !== 'function' && typeof callback !== 'string' && typeof namespace !== 'string') {\n throw new Error('must provide either a callback function or a namespace string to pause an entry in the RenderLoop queue.');\n }\n\n const pausedItem = this.getFromQueue(callback, namespace);\n\n pausedItem.pause();\n\n return pausedItem;\n },\n\n /**\n * @param {function} callback callback function to be resumed\n * @param {string} [namespace] namespace to be resumed\n * @returns {RenderLoopItem} the RenderLoopItem that represents the item that was resumed.\n */\n resume(callback, namespace) {\n if (typeof callback !== 'function' && typeof callback !== 'string' && typeof namespace !== 'string') {\n throw new Error('must provide either a callback function or a namespace string to pause an entry in the RenderLoop queue.');\n }\n\n const resumableItem = this.getFromQueue(callback, namespace);\n\n resumableItem.resume();\n\n return resumableItem;\n },\n\n};\n\n// Setup a single instance of RenderLoop for export.\nconst renderLoop = new RenderLoop(instanceSettings);\n\nexport { RenderLoopItem, renderLoop };\n","const UTIL_NAME = 'keyboard';\n\nfunction Keyboard() {\n this.pressedKeys = new Map();\n this.init();\n}\n\nKeyboard.prototype = {\n /**\n * Initializes the keyboard management system\n * @private\n */\n init() {\n $(document)\n .on(`keydown.${UTIL_NAME}`, (e) => {\n this.press(e.key);\n this.announceKeys();\n })\n .on(`keyup.${UTIL_NAME}`, (e) => {\n this.unpress(e.key);\n this.announceKeys();\n });\n },\n\n /**\n * Triggers a 'keys' event on the body tag that passes the current list of keys pressed.\n * @private\n */\n announceKeys() {\n const keys = [];\n this.pressedKeys.forEach((val, key) => {\n keys.push(key);\n });\n\n $('body').triggerHandler('keys', [keys]);\n },\n\n /**\n * Add a key to the pressedKeys Map.\n * @private\n * @param {string} key a string representing a {KeyboardEvent.key} that was pressed.\n * @returns {Map} the current set of pressed keys\n */\n press(key) {\n return this.pressedKeys.set(`${key}`, true);\n },\n\n /**\n * Remove a key from the pressedKeys map.\n * @private\n * @param {string} key a string representing a {KeyboardEvent.key} that is no longer being pressed.\n * @returns {boolean} whether or not the key had been previously logged as \"pressed\".\n */\n unpress(key) {\n return this.pressedKeys.delete(`${key}`);\n },\n\n /**\n * Describes whether or not a key is currently being pressed.\n * @param {string} key a string representing the {KeyboardEvent.key} that needs to be checked.\n * @returns {boolean} whether or not the key is currently logged as \"pressed\".\n */\n isPressed(key) {\n return this.pressedKeys.has(`${key}`);\n }\n};\n\nconst keyboard = new Keyboard();\n\nexport { keyboard };\n","const EVENT_NAMESPACE = 'modalmanager';\n\n// Used for z-index tracking\nconst zCounter = 1020;\n\n// Checks to see if an object can be identified as a Modal instance.\n// NOTE: This is not 100% accurate, we should find a better way to do this.\n// Pulling in an import of the Modal prototype causes circular dependencies.\nfunction isModalAPI(obj) {\n return obj !== undefined && obj.element instanceof $ && typeof obj.destroy === 'function';\n}\n\n/**\n * Top-level component that manages all modal instances. This component is responsible for handling global\n * events that apply to all modals, as well as for retaining a proper stacking order for nested modals.\n * @class ModalManager\n * @returns {ModalManager} component instance\n */\nfunction ModalManager() {\n this.modals = [];\n this.handleEvents();\n return this;\n}\n\nModalManager.prototype = {\n\n /**\n * @returns {number} representing the Fade in/out duration of the Modal Overlay,\n * measured in IDS RenderLoop ticks.\n */\n get modalFadeDuration() {\n return 10;\n },\n\n /**\n * @returns {array} containing references to all currently visible (not necessarily \"displayed\") modals.\n */\n get currentlyOpen() {\n return this.modals.filter(api => api.visible);\n },\n\n /**\n * @returns {Modal} api containing a reference to the current modal window that is displayed.\n */\n get currentlyActive() {\n let active;\n this.modals.forEach((api) => {\n if (api.active) {\n active = api;\n }\n });\n return active;\n },\n\n /**\n * @param {Modal} api the incoming Modal API to set as displayed\n */\n set currentlyActive(api) {\n if (!isModalAPI(api)) {\n throw new Error('Cannot set the provided Modal API to currently displayed.');\n }\n\n this.modals.forEach((thisAPI, i) => {\n // Set Active\n thisAPI.active = ($(thisAPI.element).is(api.element));\n\n if (thisAPI.active) {\n // Active Modal sits at the top\n thisAPI.element[0].style.zIndex = `${(zCounter + i)}`;\n } else if (thisAPI.visible) {\n // Inactive Modals are open, but sit behind the overlay\n thisAPI.element[0].style.zIndex = `${(zCounter - 100 + i)}`;\n } else {\n // All others disappear\n thisAPI.element[0].style.zIndex = '';\n }\n });\n this.refresh();\n },\n\n /**\n * @returns {Modal} api representing the furthest-down Modal on the stack.\n */\n get last() {\n let api;\n const currentlyOpen = this.currentlyOpen;\n const size = currentlyOpen.length;\n if (size) {\n api = currentlyOpen[size - 1];\n }\n return api;\n },\n\n /**\n * Builds the modal containment structure and overlay elements that are reused between modals.\n * @private\n * @returns {void}\n */\n render() {\n const fragment = document.createDocumentFragment();\n const rootElem = document.createElement('div');\n const overlay = document.createElement('div');\n\n rootElem.id = 'ids-modal-root';\n rootElem.classList.add('modal-page-container');\n rootElem.setAttribute('aria-hidden', true);\n fragment.appendChild(rootElem);\n\n overlay.classList.add('overlay');\n overlay.setAttribute('aria-hidden', true);\n rootElem.appendChild(overlay);\n\n document.body.appendChild(rootElem);\n this.rootElem = rootElem;\n this.overlayElem = overlay;\n },\n\n /**\n * Updates the visual state of the overlay/root containers\n * @returns {void}\n */\n refresh() {\n let active = this.currentlyActive;\n if (!active) {\n active = this.activateLast();\n }\n\n this.checkOverlayVisibility();\n\n if (active) {\n this.showContainers(active);\n } else {\n this.hideContainers(active);\n }\n },\n\n /**\n * Shows the root container and fades in the overlay to the correct opacity.\n * DO NOT CALL THIS DIRECTLY.\n * @private\n */\n showContainers() {\n this.rootElem.removeAttribute('aria-hidden');\n this.overlayElem.removeAttribute('aria-hidden');\n\n // Add the 'modal-engaged' class after all the HTML markup and CSS classes have a\n // chance to be established\n // (Fixes an issue in non-V8 browsers (FF, IE) where animation doesn't work correctly).\n // http://stackoverflow.com/questions/12088819/css-transitions-on-new-elements\n $('body')[0].classList.add('modal-engaged');\n },\n\n /**\n * Hides the root container and fades out the overlay.\n * DO NOT CALL THIS DIRECTLY.\n * @private\n * @param {Modal} api the active Modal API\n */\n hideContainers() {\n this.rootElem.setAttribute('aria-hidden', true);\n this.overlayElem.setAttribute('aria-hidden', true);\n\n $('body')[0].classList.remove('modal-engaged');\n },\n\n /**\n * @param {Modal} api the modal API to unregister\n * @returns {void}\n */\n register(api) {\n if (!isModalAPI(api)) {\n throw new Error('The provided API is not a Modal API, and cannot be registered.');\n }\n\n this.checkRootElements();\n const hasInstance = this.modals.filter(thisAPI => $(thisAPI.element).is(api)) > 0;\n if (!hasInstance) {\n this.modals.push(api);\n }\n },\n\n /**\n * @param {Modal} api the modal API to unregister\n * @returns {void}\n */\n unregister(api) {\n if (!isModalAPI(api)) {\n throw new Error('The provided API is not a Modal API, and cannot be unregistered.');\n }\n\n this.checkRootElements();\n this.modals = this.modals.filter(thisAPI => !($(thisAPI.element).is(api.element)));\n },\n\n /**\n * Adjusts the overlay's visiblity/opacity. If no modals are present, the overlay \"hides\".\n * Otherwise, the overlay is adjusted to the currently active Modal's `opacity` setting.\n * @private\n * @returns {void}\n */\n checkOverlayVisibility() {\n let opacity = 0;\n const active = this.currentlyActive;\n if (active) {\n opacity = this.currentlyActive.settings.overlayOpacity;\n }\n\n this.overlayElem.style.background = `rgba(0, 0, 0, ${opacity ? `${opacity}` : ''})`;\n },\n\n /**\n * Checks that the root element and the overlay exist. In some environments (Angular),\n * the root node may become modified after these elements are drawn, and links may need\n * to be re-established.\n * @private\n * @returns {void}\n */\n checkRootElements() {\n const rootElem = document.querySelector('#ids-modal-root');\n if (rootElem instanceof HTMLElement) {\n this.rootElem = rootElem;\n const overlayElem = rootElem.querySelector('.overlay');\n if (overlayElem instanceof HTMLElement) {\n this.overlayElem = overlayElem;\n }\n return;\n }\n\n if (this.rootElem instanceof HTMLElement) {\n document.body.appendChild(this.rootElem);\n } else {\n this.render();\n }\n },\n\n /**\n * Closes a Modal on the page globally by its element\n * @param {jQuery|HTMLElement} [elem] the `.modal` element to close. If this is not provided, the last Modal API on the stack is used.\n * @param {boolean} [cancelled=false] passes along a flag that designates this close action as \"cancelled\" to the Modal API.\n * @returns {void}\n */\n close(elem, cancelled = false) {\n let api;\n if (!elem || !(elem instanceof HTMLElement || (elem instanceof $ && elem.length))) {\n api = this.last;\n } else {\n api = $(elem).data('modal');\n }\n if (!api) {\n return;\n }\n\n if (cancelled) {\n api.isCancelled = true;\n }\n api.close();\n },\n\n /**\n * Closes all registered modals\n * @param {boolean} [cancelled=false] passes along a flag that designates this close action as \"cancelled\" to the Modal API.\n * @returns {void}\n */\n closeAll(cancelled = false) {\n this.modals.forEach((api) => {\n if (cancelled) {\n api.isCancelled = true;\n }\n api.close(undefined, true);\n });\n\n this.refresh();\n },\n\n /**\n * Destroys all registered modals\n * @param {boolean} [capsOnly=true] If true, nly destroy the caps.\n * @returns {void}\n */\n destroyAll(capsOnly = false) {\n this.modals.forEach((api) => {\n const okToDestroy = !capsOnly || api.capAPI;\n if (okToDestroy) {\n api.close(undefined, true);\n api.destroy();\n }\n });\n\n this.refresh();\n },\n\n /**\n * Closes the last open modal in the stack\n * @param {boolean} [cancelled=false] passes along a flag that designates this close action as \"cancelled\" to the Modal API.\n * @returns {void}\n */\n closeLast(cancelled = false) {\n this.close(undefined, cancelled);\n },\n\n /**\n * Activates the last currently open Modal on the stack.\n * @returns {Modal} representing the API that was activated.\n */\n activateLast() {\n const api = this.last;\n if (api) {\n this.currentlyActive = api;\n }\n return api;\n },\n\n /**\n * Find a modal instance if the modal has been opened and not destroyed.\n * @param {string} id The id to look for\n * @returns {Modal|undefined} a matching Modal API instance, if available\n */\n findById(id) {\n if (!id) {\n return undefined;\n }\n const results = this.modals.filter(api => api.id === id);\n return results[0] || undefined;\n },\n\n /**\n * Sets up the events\n * @private\n * @returns {void}\n */\n handleEvents() {\n if (this.hasEstablishedEvents) {\n this.teardown();\n }\n\n // Setup a global keydown event that can handle the closing of modals in the proper order.\n $(document).on(`keydown.${EVENT_NAMESPACE}`, (e) => {\n const modalTargetElem = $(e.target).parents('.modal');\n const keyCode = e.which || e.keyCode;\n\n switch (keyCode) {\n // Escape Key\n case 27:\n this.close(modalTargetElem, true);\n break;\n case 9:\n this.setModalFocus(e.shiftKey ? 'last' : 'first', e);\n break;\n default:\n break;\n }\n });\n\n // Setup a listener for building out core Modal container markup.\n // If state is being refreshed, simply run the method.\n const self = this;\n if (!this.preRendered) {\n $(document).ready(() => {\n self.checkRootElements();\n self.preRendered = true;\n });\n } else {\n this.checkRootElements();\n }\n\n this.hasEstablishedEvents = true;\n },\n\n /**\n * Redirects the page's focus to an element within the currently active Modal.\n * @param {string} place the location to set the Modal's current focus\n * @param {jQuery.Event} e the original jquery event\n * @returns {void}\n */\n setModalFocus(place, e) {\n const api = this.currentlyActive;\n if (!api) {\n return;\n }\n\n // If focus already exists within the modal, with the exception of certain placements\n // on certain elements, return out and allow change of focus to occur as normal.\n if (api.isFocused) {\n if (!($(api.focusableElems.last).is(document.activeElement) && place === 'first') &&\n !($(api.focusableElems.first).is(document.activeElement) && place === 'last')) {\n return;\n }\n }\n e.preventDefault();\n api.setFocus(place);\n },\n\n /**\n * @returns {void}\n */\n teardown() {\n $(document).off(`keydown.${EVENT_NAMESPACE}`);\n delete this.hasEstablishedEvents;\n\n if (this.preRendered) {\n this.rootElem.parentNode.remove(this.rootElem);\n delete this.overlayElem;\n delete this.rootElem;\n delete this.preRendered;\n }\n }\n};\n\n// Export a single instance\nconst modalManager = new ModalManager();\nexport { modalManager };\n","function hasNotificationAPI(obj) {\n return obj !== undefined && obj.element instanceof $ && typeof obj.destroy === 'function';\n}\n\nfunction NotificationManager() {\n this.notifications = [];\n return this;\n}\n\nNotificationManager.prototype = {\n /**\n * Register notification in manager\n * @param {any} api Notification API\n * @returns {void}\n */\n register(api) {\n if (!hasNotificationAPI(api)) {\n throw new Error('The provided object has no Notification API, and cannot be registered.');\n }\n\n const hasInstance = this.notifications.filter(thisAPI => $(thisAPI.element).is(api)) > 0;\n if (!hasInstance) {\n this.notifications.push(api);\n }\n },\n\n /**\n * Unregister notification in manager\n * @param {any} api Notification API\n */\n unregister(api) {\n if (!hasNotificationAPI(api)) {\n throw new Error('The provided object has no Notification API, and cannot be registered.');\n }\n\n const index = this.notifications.findIndex(thisAPI => $(thisAPI.element).is(api));\n if (index >= 0) {\n this.notifications.splice(index, 1);\n }\n },\n\n /**\n * Close notification\n * @param {string} id ID of notification\n */\n closeById(id) {\n if (this.notifications.length > 0) {\n const index = this.notifications.findIndex(notification => notification.settings.id === id);\n if (index >= 0) {\n this.close(this.notifications[index]);\n this.notifications.splice(index, 1);\n }\n }\n },\n\n /**\n * Close the latest notification in the list\n */\n closeLatest() {\n if (this.notifications.length > 0) {\n this.close(this.notifications.pop());\n }\n },\n\n /**\n * Close all notifications\n */\n closeAll() {\n while (this.notifications.length > 0) {\n this.close(this.notifications.pop());\n }\n },\n\n /**\n * Close notification API\n * @private\n * @param {any} api Notification api\n */\n close(api) {\n const element = api.element;\n api.close();\n\n if (element && this.notifications.length > 0) {\n element.data('notification', this.notifications[0]);\n }\n }\n};\n\nconst notificationManager = new NotificationManager();\nexport { notificationManager };\n","import { color as classicLightColors } from './theme-classic-colors.json';\nimport { color as classicDarkColors } from './theme-classic-dark-colors.json';\nimport { color as classicContrastColors } from './theme-classic-contrast-colors.json';\nimport { color as newLightColors } from './theme-new-colors.json';\nimport { color as newDarkColors } from './theme-new-dark-colors.json';\nimport { color as newContrastColors } from './theme-new-contrast-colors.json';\nimport { Locale } from '../locale/locale';\n\n/**\n* The Theme Component is a lightweight wrapper for theme information, which contains the colors\n* that are used on the theme.\n* @class Theme\n*/\nconst theme = {\n\n /**\n * @property {object} [currentTheme]\n * @property {string} [currentTheme.id]\n * @property {string} [currentTheme.name]\n */\n currentTheme: { id: 'theme-new-light', name: Locale.translate('NewLightTheme') },\n\n /**\n * Get all of the colors for all themes\n * @returns {object[]} An array of color objects\n */\n allColors: [\n { id: 'theme-classic-light', colors: classicLightColors },\n { id: 'theme-classic-dark', colors: classicDarkColors },\n { id: 'theme-classic-contrast', colors: classicContrastColors },\n { id: 'theme-new-light', colors: newLightColors },\n { id: 'theme-new-dark', colors: newDarkColors },\n { id: 'theme-new-contrast', colors: newContrastColors }\n ],\n\n /**\n * Return a list of all the available themes\n * @returns {object[]} The list of themes\n */\n themes: function themes() {\n return [\n { id: 'theme-classic-light', name: Locale.translate('ClassicLightTheme'), themeId: 'theme-classic', modeId: 'light', modeName: Locale.translate('Light') },\n { id: 'theme-classic-dark', name: Locale.translate('ClassicDarkTheme'), themeId: 'theme-classic', modeId: 'dark', modeName: Locale.translate('Dark') },\n { id: 'theme-classic-contrast', name: Locale.translate('ClassicHighContrastTheme'), themeId: 'theme-classic', modeId: 'contrast', modeName: Locale.translate('Contrast') },\n { id: 'theme-new-light', name: Locale.translate('NewLightTheme'), themeId: 'theme-new', modeId: 'light', modeName: Locale.translate('Light') },\n { id: 'theme-new-dark', name: Locale.translate('NewDarkTheme'), themeId: 'theme-new', modeId: 'dark', modeName: Locale.translate('Dark') },\n { id: 'theme-new-contrast', name: Locale.translate('NewHighContrastTheme'), themeId: 'theme-new', modeId: 'contrast', modeName: Locale.translate('Contrast') }\n ];\n },\n\n /**\n * Get the colors used in the current theme\n * @param {string} themeId The id of the theme.\n * @returns {object} An object full of the colors 01-10\n */\n themeColors: function themeColors() {\n const result = this.allColors.filter(color => color.id === this.currentTheme.id);\n if (!result[0]) {\n return { palette: { }, colors: { }, brand: { } };\n }\n\n return result[0].colors;\n },\n\n /**\n * Get the colors used in the current theme that are reccomended for personalization\n * @returns {object} An object full of the colors with id, name abd hex value\n */\n personalizationColors: function personalizationColors() {\n const palette = this.themeColors().palette;\n const personalize = {};\n const opts = { showBrackets: false };\n let defaulColor = '#0066D4';\n\n switch (this.currentTheme.id) {\n case 'theme-new-light':\n defaulColor = '#0066D4';\n break;\n case 'theme-new-contrast':\n defaulColor = '#0066D4';\n break;\n case 'theme-new-dark':\n defaulColor = '#0066D4';\n break;\n case 'theme-classic-light':\n defaulColor = '#2578a9';\n break;\n case 'theme-classic-contrast':\n defaulColor = '#134d71';\n break;\n case 'theme-classic-dark':\n defaulColor = '#50535A';\n break;\n default:\n defaulColor = '#0066D4';\n break;\n }\n personalize.default = { id: 'default', name: Locale.translate('Default', opts), backgroundColorClass: 'primary-bg-color', value: defaulColor };\n personalize.alabaster = { id: 'alabaster', name: Locale.translate('Alabaster', opts), backgroundColorClass: 'alabaster', value: palette.white.value };\n personalize.amber = { id: 'amber', name: Locale.translate('Amber', opts), backgroundColorClass: 'amber09', value: palette.amber['90'].value };\n personalize.amethyst = { id: 'amethyst', name: Locale.translate('Amethyst', opts), backgroundColorClass: 'amethyst06', value: palette.amethyst['60'].value };\n personalize.azure = { id: 'azure', name: Locale.translate('Azure', opts), backgroundColorClass: 'azure07', value: palette.azure['70'].value };\n personalize.emerald = { id: 'emerald', name: Locale.translate('Emerald', opts), backgroundColorClass: 'emerald08', value: palette.emerald['80'].value };\n personalize.graphite = { id: 'graphite', name: Locale.translate('Graphite', opts), backgroundColorClass: 'graphite06', value: palette.graphite['60'].value };\n personalize.ruby = { id: 'ruby', name: Locale.translate('Ruby', opts), backgroundColorClass: 'ruby09', value: palette.ruby['90'].value };\n personalize.slate = { id: 'slate', name: Locale.translate('Slate', opts), backgroundColorClass: 'slate06', value: palette.slate['60'].value };\n personalize.turquoise = { id: 'turquoise', name: Locale.translate('Turquoise', opts), backgroundColorClass: 'turquoise09', value: palette.turquoise['90'].value };\n\n return personalize;\n },\n\n /**\n * Set the current application theme\n * @param {string} themeId The id of the theme\n * @returns {[type]} [description]\n */\n setTheme: function setTheme(themeId) {\n themeId = themeId.replace('soho', 'classic').replace('uplift', 'new');\n const result = this.themes().filter(themeObj => themeObj.id === themeId);\n\n if (result.length === 0) {\n return '';\n }\n this.currentTheme = result[0];\n return result;\n },\n\n /**\n * @returns {boolean} whether or not the current theme is an Uplift mode\n */\n get uplift() {\n return this.currentTheme.id.indexOf('-uplift-') > -1 || this.currentTheme.id.indexOf('-new-') > -1;\n },\n\n /**\n * @returns {boolean} whether or not the current theme is the new/uplift theme\n */\n get new() {\n return this.currentTheme.id.indexOf('-uplift-') > -1 || this.currentTheme.id.indexOf('-new-') > -1;\n }\n\n};\n\nexport { theme };\n","import { colorUtils } from '../../utils/color';\n\nfunction personalizeStyles(colors) {\n const baseColorObj = colorUtils.hexToRgb(colors.base);\n const hyperlinkColorObj = colorUtils.hexToRgb(colors.hyperlinkText);\n\n return `\n\n.is-personalizable ::selection {\n background: ${colors.selection} !important;\n}\n\n.is-personalizable .header::selection {\n background-color: ${colors.darker} !important;\n}\n\n.is-personalizable:not(.header) .btn-primary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled),\n.btn-primary:not(:disabled):not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane).is-personalizable {\n background-color: ${colors.btnPrimaryColor};\n border-color: ${colors.btnPrimaryColor};\n}\n\n.header.is-personalizable .buttonset .searchfield-wrapper.is-open button:not(.close) {\n background-color: ${colors.contrast};\n color: ${colors.darkest} !important;\n}\n\n.header.is-personalizable .buttonset .searchfield-wrapper.is-open button.go-button:hover {\n background-color: ${colors.secondaryButtonHover} !important;\n color: ${colors.darkest} !important;\n}\n\nhtml.theme-classic-dark .is-personalizable .btn-primary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):disabled {\n color: #888B94 !important;\n}\n\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-primary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled) {\n background-color: ${colors.lighter};\n border-color: ${colors.lighter};\n}\n\nhtml[class*=\"new-\"] .is-personalizable:not(.header) .btn-secondary:not(:disabled):not(.go-button):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane),\nhtml[class*=\"new-\"] .is-personalizable:not(.header) .btn-secondary:not(:disabled):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane) svg.icon {\n border-color: ${colors.btnSecondaryBorderColor};\n color: ${colors.btnSecondaryBorderColor};\n}\n\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-secondary:not(.go-button):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled),\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-secondary:not(.go-button):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled) svg.icon {\n border-color: ${colors.lighter};\n color: ${colors.lighter};\n}\n\nhtml.theme-classic-dark .is-personalizable:not(.header) .btn-primary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):disabled {\n background-color: ${colors.baseDisabled} !important;\n border-color: ${colors.baseDisabled} !important;\n}\n\nhtml[class*=\"theme-classic-\"]:not(.theme-classic-dark) .is-personalizable:not(.header) .btn-primary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):disabled,\nhtml[class*=\"theme-classic-\"]:not(.theme-classic-dark) .btn-primary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane).is-personalizable:disabled {\n color: ${colors.baseDisabledText};\n background-color: ${colors.baseDisabled};\n border-color: ${colors.baseDisabled};\n}\n\n.is-personalizable:not(.header) .btn-link:not(:disabled),\n.btn-link.is-personalizable:not(:disabled) {\n color: ${colors.btnLinkColor};\n}\n\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-link:not(:disabled),\nhtml.theme-new-dark .btn-link.is-personalizable:not(:disabled),\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-link:not(:disabled) .icon,\nhtml.theme-new-dark .btn-link.is-personalizable:not(:disabled) .icon {\n color: ${colors.lighter};\n}\n\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-link:not(:disabled):hover,\nhtml.theme-new-dark .btn-link.is-personalizable:not(:disabled):hover,\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-link:not(:disabled):hover .icon,\nhtml.theme-new-dark .btn-link.is-personalizable:not(:disabled):hover .icon {\n color: ${colors.lightest};\n}\n\n.is-personalizable:not(.header) .btn-link:not(:disabled):hover {\n background-color: ${colors.lightestPalette};\n}\n\n.is-personalizable:not(.header) .btn-link:not(:disabled) .icon,\n.btn-link.is-personalizable:not(:disabled) .icon {\n color: ${colors.btnLinkColor};\n}\n\n.is-personalizable button.is-pressed,\nbutton.is-personalizable.is-pressed {\n color: ${colors.base};\n}\n\n.is-personalizable button.is-pressed .icon,\nbutton.is-personalizable.is-pressed .icon {\n color: ${colors.base};\n}\n\n.is-personalizable:not(.header) .btn-primary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled),\n.btn-primary.is-personalizable:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) {\n background-color: ${colors.btnPrimaryColorHover};\n border-color: ${colors.btnPrimaryColorHover};\n}\n\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-primary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) {\n background-color: ${colors.lightest};\n border-color: ${colors.lightest};\n}\n\nhtml[class*=\"new-\"]:not(.theme-new-dark) .is-personalizable:not(.header) .btn-secondary:not(.go-button):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) {\n background-color: ${colors.lightestPalette};\n}\n\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-secondary:not(.go-button):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) {\n color: ${colors.lightest};\n border-color: ${colors.lightest};\n}\n\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-secondary:not(.go-button):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) svg.icon {\n color: ${colors.lightest};\n}\n\nhtml[class*=\"new-\"] .is-personalizable:not(.header) .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) {\n background-color: ${colors.btnTertiaryBgHoverColor};\n color: ${colors.btnTertiaryHoverColor};\n}\n\nhtml[class*=\"new-\"] .hero-widget.is-personalizable .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled),\nhtml[class*=\"new-\"] .hero-widget.is-personalizable .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) svg {\n color: ${colors.contrast} !important;\n}\n\nhtml.theme-new-dark .is-personalizable:not(.hero-widget) .btn-tertiary:not:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled),\nhtml.theme-new-dark .is-personalizable .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) svg.icon,\nhtml.theme-new-dark .is-personalizable .btn-link:hover:not(:disabled),\nhtml.theme-new-dark .is-personalizable .btn-link:hover:not(:disabled) svg.icon,\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled),\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) svg.icon,\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-link:hover:not(:disabled),\nhtml.theme-new-dark .is-personalizable:not(.header) .btn-link:hover:not(:disabled) svg.icon {\n color: ${colors.lightest};\n}\n\nhtml[class*=\"new-\"] .is-personalizable .btn-tertiary:hover:not(:disabled):not(.close):not(.btn-filter):not(.personalize-actionable):not(.destructive) svg.icon {\n color: ${colors.btnTertiaryHoverColor};\n}\n\nhtml.theme-new-dark .is-personalizable .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled),\nhtml.theme-new-dark .is-personalizable .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled) svg.icon {\n color: ${colors.contrast} !important;\n}\n\nhtml.theme-new-dark .is-personalizable:not(.hero-widget) .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled) svg.icon {\n color: ${colors.contrast} !important;\n}\n\nbutton.is-personalizable button:focus:not(.hide-focus),\n.is-personalizable a.btn:focus:not(.hide-focus),\na.btn.is-personalizable:focus:not(.hide-focus),\n.is-personalizable a.btn-menu:focus:not(.hide-focus),\na.btn-menu.is-personalizable:focus:not(.hide-focus),\n.is-personalizable a.btn-icon:focus:not(.hide-focus),\na.btn-icon.is-personalizable:focus:not(.hide-focus),\n.is-personalizable a.btn-tertiary:focus:not(.hide-focus),\na.btn-tertiary.is-personalizable:focus:not(.hide-focus),\n.is-personalizable a.btn-close:focus:not(.hide-focus),\na.btn-close.is-personalizable:focus:not(.hide-focus) {\n box-shadow: 0 0 0 2px transparent,\n 0 0 0 1px ${colors.base},\n 0 0 4px 2px rgba(${baseColorObj.r}, ${baseColorObj.g}, ${baseColorObj.b}, 0.3) !important;\n}\n\n.is-personalizable .btn-menu:not(.btn-primary):not(.btn-secondary).is-open,\n.btn-menu:not(.btn-primary):not(.btn-secondary).is-personalizable.is-open,\n.btn-actions:not(.btn-primary):not(.btn-secondary).is-personalizable.is-open {\n color: ${colors.base};\n}\n\n.is-personalizable .btn-menu:not(.btn-primary):not(.btn-secondary).is-open .icon,\n.btn-menu:not(.btn-primary):not(.btn-secondary).is-personalizable.is-open .icon,\n.btn-actions:not(.btn-primary):not(.btn-secondary).is-personalizable.is-open .icon {\n color: ${colors.base};\n}\n\n.is-personalizable:not(.application-menu) .btn-actions:not(.btn-primary):not(.btn-secondary).is-open .icon {\n color: ${colors.btnActionsHoverColor};\n}\n\n.is-personalizable .hyperlink:not(.today),\n.hyperlink:not(.today).is-personalizable {\n color: ${colors.hyperlinkText} !important;\n opacity: 0.8;\n}\n\n.is-personalizable .hyperlink:not(.today):hover,\n.hyperlink:not(.today).is-personalizable:hover {\n color: ${colors.hyperlinkTextHover} !important;\n}\n\n.is-personalizable .hyperlink:not(.today):focus:not(.hide-focus),\n.hyperlink:not(.today).is-personalizable:focus:not(.hide-focus) {\n border-color: ${colors.hyperlinkText};\n box-shadow: 0 0 4px 3px rgba(${hyperlinkColorObj.r}, ${hyperlinkColorObj.g}, ${hyperlinkColorObj.b}, 0.3);\n}\n\n.is-personalizable button:not(.btn-monthyear-pane):not(.btn-editor) svg.ripple-effect,\nbutton:not(.btn-monthyear-pane):not(.btn-editor).is-personalizable svg.ripple-effect,\n.is-personalizable a svg.ripple-effect,\na.is-personalizable svg.ripple-effect {\n background-color: ${colors.base} !important;\n}\n\n.is-personalizable .btn-primary svg.ripple-effect,\n.btn-primary.is-personalizable svg.ripple-effect,\n.is-personalizable .btn-secondary svg.ripple-effect,\n.btn-secondary.is-personalizable svg.ripple-effect {\n background-color: ${colors.contrast} !important;\n border-color: ${colors.contrast} !important;\n}\n\n.is-personalizable .count-container .instance-count svg.icon {\n background-color: ${colors.base} !important;\n}\n\n.is-personalizable.has-more-button.tab-container.horizontal {\n &::after {\n background-image:\n linear-gradient(to left,\n rgba($header-bg-color, 1),\n rgba($header-bg-color, 0));\n height: 39px;\n }\n}\n\n.tab-container.module-tabs.is-personalizable {\n border-top: 1px solid ${colors.tabBorderColor};\n border-bottom: 1px solid ${colors.tabBorderColor};\n}\n\n.tab-container.module-tabs .tab-focus-indicator.is-visible {\n border-color: ${colors.tabFocusIndicator} !important;\n box-shadow: ${colors.tabFocusBoxShadow} !important;\n}\n\n.tab-container.is-personalizable.header-tabs:not(.alternate) .tab-focus-indicator.is-visible,\n.tab-container.personalize-header.header-tabs:not(.alternate) .tab-focus-indicator.is-visible,\n.tab-container.is-personalizable.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled).is-selected,\n.tab-container.personalize-header.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled).is-selected,\n.header.is-personalizable .tab-container.header-tabs:not(.alternate) .tab-focus-indicator.is-visible,\n.header.is-personalizable .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab.is-selected,\n.personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab.is-selected:not(.is-disabled),\n.personalize-header.tab-container.header-tabs:not(.alternate) .tab-focus-indicator.is-visible,\n.header.is-personalizable .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled).is-selected {\n border-color: ${colors.contrast} !important;\n}\n\n.tab-container.is-personalizable.header-tabs:not(.alternate) > .tab-list-container .tab:hover:not(.is-disabled),\n.tab-container.is-personalizable.header-tabs:not(.alternate) > .tab-list-container .tab:hover:not(.is-disabled),\n.header.is-personalizable .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:hover:not(.is-disabled),\n.personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:hover:not(.is-disabled) {\n color: ${colors.contrast};\n background-color: ${colors.tabHoverColor};\n border-bottom: 4px solid ${colors.contrast};\n}\n\n.module-tabs.is-personalizable .tab:not(:first-child) {\n border-left: 1px solid ${colors.tabDivider} !important;\n}\n\n.module-tabs.is-personalizable {\n background-color: ${colors.darker} !important;\n}\n\n.module-tabs.is-personalizable .tab.is-selected {\n background-color: ${colors.tabSelectedColor} !important;\n color: ${colors.moduleTabsSelectedTextColor} !important;\n}\n\n.module-tabs.is-personalizable .tab:not(.is-selected) {\n background-color: ${colors.tabColor} !important;\n}\n\n.module-tabs.is-personalizable .tab {\n color: ${colors.contrast};\n}\n\n.tab-container.module-tabs.is-personalizable .tab.dismissible svg.close {\n color: ${colors.tabCloseInactiveColor};\n}\n\n.tab-container.module-tabs.is-personalizable .tab.dismissible svg.close:hover {\n color: ${colors.tabCloseHoverColor};\n}\n\n.is-personalizable.tab-container.module-tabs .tab-more,\n.is-personalizable.tab-container.module-tabs .add-tab-button {\n color: ${colors.contrast};\n}\n\n.tab-container.vertical.is-personalizable > .tab-list-container > .tab-list > .tab.is-selected {\n background-color: ${colors.tabVerticalSelectedColor} !important;\n}\n\n.tab-container.vertical.is-personalizable > .tab-list-container > .tab-list > .tab a {\n font-weight: ${colors.tabTextWeight} !important;\n}\n\n.tab-container.vertical.is-personalizable > .tab-list-container > .tab-list > .tab.is-selected a {\n color: ${colors.tabSelectedTextColor} !important;\n font-weight: ${colors.tabTextSelectedWeight} !important;\n}\n\n.tab-container.vertical.is-personalizable > .tab-list-container > .tab-list > .tab:hover {\n background-color: ${colors.tabHoverColor} !important;\n}\n\n.tab-container.vertical.is-personalizable > .tab-list-container > .tab-list > .tab.is-selected:hover {\n background-color: ${colors.tabVerticalSelectedColor} !important;\n}\n\n.tab-container.vertical.is-personalizable > .tab-list-container > .tab-list > .tab:hover a {\n color: ${colors.tabHoverTextColor} !important;\n}\n\n.tab-container.vertical.is-personalizable > .tab-list-container > .tab-list > .tab.is-selected:hover a {\n color: ${colors.tabSelectedTextColor} !important;\n}\n\n.tab-container.vertical.is-personalizable > .tab-list-container > .tab-focus-indicator.is-selected.is-visible,\n.tab-container.vertical.is-personalizable .tab-focus-indicator.is-visible {\n border-color: ${colors.tabFocusIndicator} !important;\n box-shadow: ${colors.tabFocusBoxShadow} !important;\n}\n\n.builder-header.is-personalizable{\n background-color: ${colors.lighter};\n}\n\n.header.is-personalizable {\n background-color: ${colors.tabHeaderColor};\n border-bottom: 1px solid ${colors.headerTabBorder};\n}\n\n.scrollable-flex-header .breadcrumb {\n background-color: ${colors.base};\n}\n\n.header.is-personalizable.has-tabs .tab-container.header-tabs:not(.alternate) {\n background-color: ${colors.tabHeaderColor};\n}\n\n.header.is-personalizable .title {\n color: ${colors.contrast} !important;\n}\n\n.header.is-personalizable h1,\n.header.is-personalizable h2,\n.header.is-personalizable h3 {\n color: ${colors.contrast} !important;\n}\n\n.object-count.personalize-text {\n color: ${colors.contrast} !important;\n}\n\n.header.is-personalizable button svg.ripple-effect {\n background-color: ${colors.contrast} !important;\n}\n\n.header.is-personalizable button:not(:disabled),\n.header.is-personalizable button:not(:disabled):not(.close) .icon,\n.header.is-personalizable button:not(:disabled) .app-header.icon > span,\n.subheader.is-personalizable button:not(:disabled):not(.searchfield-category-button),\n.subheader.is-personalizable button:not(:disabled):not(.searchfield-category-button) .icon,\n.subheader.is-personalizable button:not(:disabled):not(.searchfield-category-button) .app-header.icon > span,\n.is-personalizable .personalize-subheader button:not(:disabled),\n.is-personalizable .personalize-subheader button:not(:disabled):not(.close):not(.notification-close) .icon,\n.is-personalizable .personalize-subheader button:not(:disabled) .app-header.icon > span {\n color: ${colors.contrast} !important;\n}\n\nhtml[class*=\"theme-new\"] .header.is-personalizable button:not(.close):not(:disabled):hover .icon {\n color: ${colors.btnTertiaryHoverColor} !important;\n}\n\nhtml[class*=\"-dark\"] .is-personalizable .personalize-subheader button:not(:disabled),\nhtml[class*=\"-dark\"] .is-personalizable .subheader button:not(:disabled),\nhtml[class*=\"-dark\"] .is-personalizable .personalize-subheader button:not(:disabled) .icon,\nhtml[class*=\"-dark\"] .is-personalizable .subheader button:not(:disabled) .icon {\n color: #ffff !important;\n}\n\n.header.is-personalizable button:not(:disabled) .app-header.icon > span,\n.is-personalizable .personalize-subheader button:not(:disabled) .app-header.icon > span {\n background-color: ${colors.contrast} !important;\n opacity: .8;\n}\n\n.header.is-personalizable:not(.has-alternate-tabs) .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled):hover {\n border-bottom: 4px solid ${colors.contrast};\n}\n\nhtml[class*=\"theme-new\"] .header.is-personalizable:not(.has-alternate-tabs) .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled):hover {\n border-bottom: 2px solid ${colors.contrast};\n}\n\n.header.is-personalizable .tab-container.horizontal > .tab-list-container .tab:not(.is-disabled):hover {\n border-bottom: 4px solid ${colors.base};\n}\n\n.header.is-personalizable .searchfield-wrapper.has-categories button.btn-icon:not(:disabled):hover,\n.header.is-personalizable .searchfield-wrapper.has-categories button:not(.searchfield-category-button):not(:disabled):hover .icon {\n background-color: ${colors.contrast} !important;\n}\n\nhtml[class*=\"theme-new-\"] .header.is-personalizable button:not(.go-button):not(.close):not(.searchfield-category-button):not(:disabled):hover,\nhtml[class*=\"theme-new-\"] .header.is-personalizable button:not(:disabled):hover .app-header.icon > span,\nhtml[class*=\"theme-new-\"] .header.is-personalizable .toolbar [class^='btn']:hover:not(.go-button):not(.close):not(.searchfield-category-button):not([disabled]),\nhtml[class*=\"theme-new-\"] .subheader.is-personalizable button:not(.go-button):not(.close):not(.searchfield-category-button):not(:disabled):hover,\nhtml[class*=\"theme-new-\"] .subheader.is-personalizable button:not(:disabled):hover .app-header.icon > span,\nhtml[class*=\"theme-new-\"] .subheader.is-personalizable .toolbar [class^='btn']:hover:not(.go-button):not(.close):not(.searchfield-category-button):not([disabled]),\nhtml[class*=\"theme-new-\"] .personalize-subheader button:not(.go-button):not(.close):not(.searchfield-category-button):not(:disabled):hover,\nhtml[class*=\"theme-new-\"] .personalize-subheader button:not(:disabled):hover .app-header.icon > span,\nhtml[class*=\"theme-new-\"] .personalize-subheader .toolbar [class^='btn']:hover:not(.go-button):not(.close):not(.searchfield-category-button):not([disabled]) {\n color: ${colors.btnTertiaryHoverColor} !important;\n background-color: ${colors.btnTertiaryBgHoverColor} !important;\n opacity: 1;\n}\n\n.header .flex-toolbar [class^='btn'][disabled] {\n color: ${colors.btnDisabledColor};\n}\n\n.header.is-personalizable .buttonset .searchfield-wrapper.non-collapsible.toolbar-searchfield-wrapper.has-categories .btn-icon.close:hover {\n background-color: ${colors.contrast} !important;\n}\n\nhtml.theme-new-dark .header.is-personalizable .buttonset .searchfield-wrapper.non-collapsible.toolbar-searchfield-wrapper.has-categories .btn-icon.close,\nhtml.theme-new-dark .header.is-personalizable .buttonset .searchfield-wrapper.non-collapsible.toolbar-searchfield-wrapper.has-categories .btn-icon.close svg.close,\nhtml.theme-classic-dark .header.is-personalizable .buttonset .searchfield-wrapper.non-collapsible.toolbar-searchfield-wrapper.has-categories .btn-icon.close,\nhtml.theme-classic-dark .header.is-personalizable .buttonset .searchfield-wrapper.non-collapsible.toolbar-searchfield-wrapper.has-categories .btn-icon.close svg.close {\n background-color: transparent !important;\n}\n\n.header.is-personalizable button:not(:disabled) .app-header.icon > span {\n background-color: ${colors.contrast} !important;\n opacity: 1;\n}\n\n.header.is-personalizable .go-button.is-personalizable {\n background-color: ${colors.lightest};\n border-color:${colors.lightest};\n color: ${colors.contrast};\n}\n\n.header.is-personalizable.has-tabs .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab.is-selected:not(.is-disabled),\n.personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab.is-selected:not(.is-disabled) {\n color: ${colors.contrast} !important;\n opacity: 1;\n}\n\n//xxxx\n\n.header.is-personalizable .flex-toolbar .has-collapse-button .collapse-button {\n background-color: transparent;\n border-color: ${colors.contrast};\n}\n\n.header.is-personalizable .flex-toolbar .has-collapse-button .collapse-button:focus:not(.hide-focus) {\n box-shadow: none !important;\n border-color: ${colors.contrast};\n}\n\nhtml.theme-new-dark .header.is-personalizable.has-tabs .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab.is-selected:not(.is-disabled) {\n color: ${colors.contrast} !important;\n}\n\n.header.is-personalizable .tab-container.header-tabs:not(.alternate) .tab-more::before,\n.tab-container.is-personalizable.header-tabs:not(.alternate) .tab-more::before {\n background-color: ${colors.contrast};\n}\n\n.is-personalizable .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled),\n.header.is-personalizable .tab-container.header-tabs:not(.alternate) .tab-more svg.icon,\n.tab-container.is-personalizable .tab-more svg.icon,\n.is-personalizable.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled),\n.personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled) {\n color: ${colors.contrast} !important;\n opacity: .8;\n}\n\nhtml.theme-new-dark .personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled),\nhtml.theme-classic-dark .personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled) {\n color: #fff !important;\n opacity: .8;\n}\n\n.header.is-personalizable.has-tabs .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab,\n.is-personalizable.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab,\n.personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab {\n color: ${colors.contrast} !important;\n opacity: .8;\n}\n\n.header.is-personalizable.has-tabs .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:hover:not(.is-disabled),\n.is-personalizable.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:hover:not(.is-disabled),\n.personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:hover:not(.is-disabled) {\n color: ${colors.contrast} !important;\n opacity: 1;\n}\n\nhtml.theme-new-dark .header.is-personalizable.has-tabs .tab-container.header-tabs > .tab-list-container .tab:hover:not(.is-disabled),\n.is-personalizable.tab-container.header-tabs > .tab-list-container .tab:hover:not(.is-disabled) {\n color: ${colors.contrast} !important;\n opacity: 1;\n}\n\nhtml[class*=\"theme-new-\"] .header.is-personalizable.has-tabs .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled),\nhtml[class*=\"theme-new-\"] .is-personalizable.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled),\nhtml[class*=\"theme-new-\"] .personalize-header.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab:not(.is-disabled) {\n opacity: 1;\n}\n\n.is-personalizable .count-container .instance-count .count {\n border-color: ${colors.contrast} !important;\n color: ${colors.contrast} !important;\n}\n\nhtml[class*=\"theme-new-\"] .is-personalizable .count-container .instance-count .title {\n color: ${colors.contrast} !important;\n}\n\nhtml.theme-classic-dark .is-personalizable .count-container .instance-count .count {\n border-color: ${colors.subtext} !important;\n color: ${colors.subtext} !important;\n}\n\nhtml.theme-classic-dark .hero-widget.is-personalizable .hero-content .circlepager.is-active .controls .control-button:hover::before,\nhtml.theme-classic-dark .hero-widget.is-personalizable .hero-content .circlepager.is-active .controls .control-button::before {\n border-color: ${colors.subtext};\n}\n\nhtml.theme-classic-dark .hero-widget.is-personalizable .hero-content .circlepager.is-active .controls .control-button.is-active::before {\n background-color: ${colors.subtext};\n}\n\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):hover span {\n color: ${colors.contrast} !important;\n}\n\n.header.is-personalizable .page-title {\n color: ${colors.contrast};\n}\n\n.header.is-personalizable .flex-toolbar [class^='btn'][disabled] .icon,\n.header.is-personalizable .btn:not(.searchfield-category-button)[disabled] span {\n color: ${colors.btnDisabledColor};\n}\n\n.header.is-personalizable button:not(:disabled),\n.subheader.is-personalizable button:not(:disabled):not(.searchfield-category-button) {\n color: ${colors.contrast};\n}\n\n.header.is-personalizable .flex-toolbar [class^='btn']:focus:not(.hide-focus) .icon {\n color: ${colors.contrast} !important;\n}\n\n.header.is-personalizable.has-tabs .tab-container.header-tabs > .tab-list-container .tab:hover:not(.is-disabled)::before {\n background-color: ${colors.contrast};\n}\n\n.header.is-personalizable.has-tabs .animated-bar {\n background-color: ${colors.contrast};\n}\n\n.header.is-personalizable.has-tabs .tab-list-container .tab.is-selected:not(.is-disabled):hover::before {\n background-color: ${colors.contrast} !important;\n}\n\n.header.is-personalizable .buttonset .toolbar-searchfield-wrapper:not(.is-open) svg.icon:not(.close),\n.header.is-personalizable .toolbar-searchfield-wrapper:not(.is-open) .icon:not(.close),\n.header .toolbar-searchfield-wrapper .searchfield,\n.masthead .toolbar-searchfield-wrapper .searchfield,\n.header.is-personalizable .toolbar [class^='btn']:focus .icon {\n color: ${colors.contrast};\n}\n\n.header .toolbar [class^='btn']:hover:not(.go-button):not(.close):not(.searchfield-category-button) {\n background-color: ${colors.base};\n}\n\n.header.alabaster .toolbar [class^='btn']:hover:not(.go-button):not(.searchfield-category-button) {\n background-color: #E6F1FD !important;\n}\n\n.personalize-subheader.scrollable-flex-header .breadcrumb:not(.alternate) .hyperlink,\n.personalize-subheader..scrollable-flex-header .breadcrumb:not(.alternate) .breadcrumb-text,\n.personalize-subheader.scrollable-flex-header .breadcrumb:not(.alternate) .hyperlink,\n.personalize-subheader.scrollable-flex-header .breadcrumb:not(.alternate) .breadcrumb-list li::after,\n.personalize-subheader.scrollable-flex-header .breadcrumb:not(.alternate) ol li::after {\n color: ${colors.contrast};\n}\n\n.personalize-subheader.scrollable-flex-header .breadcrumb .hyperlink:focus:not([disabled]) {\n border: 1px solid ${colors.contrast};\n}\n\n.personalize-subheader.scrollable-flex-header .breadcrumb:not(.alternate) .hyperlink:hover:not([disabled]) {\n border-bottom: 1px solid ${colors.contrast};\n}\n\n.header .toolbar-searchfield-wrapper.active.has-focus .searchfield {\n border-color: ${colors.darkest};\n}\n\nhtml[class*=\"-dark\"] .header .toolbar-searchfield-wrapper.active.has-focus .searchfield {\n border-color: ${colors.contrast};\n}\n\n.header.is-personalizable .btn-actions:not(.btn-primary):not(.btn-secondary).is-open .icon,\n.header.is-personalizable .wizard-header .tick,\n.header.is-personalizable .wizard-header .tick .label,\n.header.is-personalizable .wizard-header a.tick:hover:not(.current):not(.is-disabled):not([disabled]) .label {\n color: ${colors.contrast};\n}\n\n.header.is-personalizable .wizard-header .tick:focus .label {\n border-color: ${colors.lighter};\n}\n\n.header.is-personalizable .wizard-header .tick.current::before {\n border-color: ${colors.light};\n border-color: ${colors.contrast};\n}\n\n.is-personalizable.hero-widget .hero-footer .hero-footer-nav-title {\n color: #ffffff;\n}\n\n.is-personalizable .personalize-text {\n color: ${colors.contrast};\n}\n\nhtml[class*='classic-'] .is-personalizable .tab-container.header-tabs.alternate > .tab-list-container .tab.is-selected:not(.is-disabled) {\n border-color: ${colors.contrast};\n}\n\n.tab-container.header-tabs.alternate > .tab-list-container .tab.is-disabled {\n color: ${colors.contrast};\n opacity: 0.6;\n}\n\n.is-personalizable .tab-container.header-tabs > .tab-list-container .tab.is-disabled {\n color: ${colors.contrast};\n opacity: 0.6;\n}\n\n.is-personalizable .label + .personalize-text.data {\n color: ${colors.contrast};\n}\n\nhtml[class*='new-'] .is-personalizable.hero-widget .hero-header .toolbar .buttonset .btn-tertiary.btn-menu:not(:disabled):hover,\nhtml[class*='new-'] .is-personalizable.hero-widget .hero-header .toolbar .buttonset .btn-tertiary.btn-menu:not(:disabled):hover svg.icon,\nhtml[class*='new-'] .is-personalizable.hero-widget .hero-header .toolbar .buttonset .btn-tertiary.btn-menu:not(:disabled).is-open svg.icon {\n background-color: transparent;\n color: ${colors.contrast};\n}\n\n.header.is-personalizable .wizard-header .tick::after {\n background-color: ${colors.base};\n border: 2px solid ${colors.contrast};\n}\n\n.header.is-personalizable .wizard-header .bar,\n.header.is-personalizable .wizard-header .tick.complete {\n background-color: ${colors.darkest};\n}\n\n.header.is-personalizable .wizard-header .tick.complete::after,\n.header.is-personalizable .wizard-header .completed-range {\n background-color: ${colors.contrast};\n}\n\n.header.is-personalizable .wizard-header .tick.complete .label {\n color: ${colors.contrast};\n}\n\n.header.is-personalizable .wizard-header .tick.current {\n box-shadow: none;\n}\n.header.is-personalizable .wizard-header .tick.current::after {\n background-color: ${colors.contrast};\n border-color: ${colors.contrast};\n}\n\n.subheader.is-personalizable .go-button.is-personalizable {\n background-color: ${colors.dark};\n border-color: ${colors.dark};\n color: ${colors.contrast};\n}\n\n.subheader.is-personalizable .go-button.is-personalizable:not(.hide-focus) {\n box-shadow: 0 0 0 1px #FAFAFA, 0 0 0 2px ${colors.darkest}, 0 0 4px 2px rgba(41, 41, 41, 0.3);\n}\n\n.is-personalizable .breadcrumb .hyperlink,\n.breadcrumb.is-personalizable .hyperlink {\n color: ${colors.theme.text};\n}\n.is-personalizable .breadcrumb .hyperlink:hover,\n.breadcrumb.is-personalizable .hyperlink:hover {\n color: ${colors.theme.text};\n}\n.is-personalizable .breadcrumb .hyperlink:focus:not(.hide-focus),\n.breadcrumb.is-personalizable .hyperlink:focus:not(.hide-focus) {\n box-shadow: none;\n}\n.is-personalizable .breadcrumb .hyperlink[disabled],\n.breadcrumb.is-personalizable .hyperlink[disabled],\n.is-personalizable .breadcrumb .hyperlink[disabled]:hover,\n.breadcrumb.is-personalizable .hyperlink[disabled]:hover {\n color: ${colors.theme.disabledText};\n}\n\n.is-personalizable .scrollable-flex-header .breadcrumb:not(.alternate),\n.scrollable-flex-header.is-personalizable .breadcrumb:not(.alternate) {\n background-color: ${colors.base};\n}\n.is-personalizable .scrollable-flex-header .breadcrumb.truncated:not(.alternate) .breadcrumb-list::before,\n.scrollable-flex-header.is-personalizable .breadcrumb.truncated:not(.alternate) .breadcrumb-list::before {\n background-image: linear-gradient(to right, ${colors.base}, ${colorUtils.hexToRgba(colors.base, 0)});\n}\nhtml[dir='rtl'] .is-personalizable .scrollable-flex-header .breadcrumb.truncated:not(.alternate) .breadcrumb-list::before,\nhtml[dir='rtl'] .scrollable-flex-header.is-personalizable .breadcrumb.truncated:not(.alternate) .breadcrumb-list::before {\n background-image: linear-gradient(to right, ${colorUtils.hexToRgba(colors.base, 0)}, ${colors.base});\n}\n.is-personalizable .scrollable-flex-header .breadcrumb:not(.alternate) .hyperlink,\n.scrollable-flex-header.is-personalizable .breadcrumb:not(.alternate) .hyperlink {\n color: ${colors.contrast};\n}\n.is-personalizable .scrollable-flex-header .breadcrumb:not(.alternate) .hyperlink:hover,\n.scrollable-flex-header.is-personalizable .breadcrumb:not(.alternate) .hyperlink:hover {\n color: ${colors.contrast};\n}\n\n.is-personalizable .scrollable-flex-header .breadcrumb:not(.alternate) .btn-actions.is-open .icon,\n.scrollable-flex-header.is-personalizable .breadcrumb:not(.alternate) .btn-actions.is-open .icon {\n color: ${colors.contrast};\n}\n.is-personalizable .scrollable-flex-header .breadcrumb:not(.alternate) .btn-actions:focus:not(.hide-focus),\n.scrollable-flex-header.is-personalizable .breadcrumb:not(.alternate) .btn-actions:focus:not(.hide-focus) {\n box-shadow: 0 0 0 2px transparent, 0 0 0 1px ${colors.contrast}, 0 0 1px 2px ${colorUtils.hexToRgba(colors.contrast, 0.3)} !important;\n}\n\n.module-tabs.is-personalizable .tab-more {\n border-left-color: ${colors.darkest} !important;\n}\n\n.module-tabs.is-personalizable .tab-more:hover {\n background-color: ${colors.hover} !important;\n color: ${colors.contrast};\n}\n\n.module-tabs.is-personalizable .tab-more.is-open {\n background-color: ${colors.hover} !important;\n}\n\n.module-tabs.is-personalizable .tab-more.is-selected {\n background-color: ${colors.base} !important;\n}\n\n.header .toolbar > .toolbar-searchfield-wrapper.active .searchfield {\n background-color: ${colors.hover} !important;\n border-bottom-color: ${colors.hover} !important;\n}\n\n.header .toolbar > .toolbar-searchfield-wrapper.active .searchfield-category-button {\n background-color: ${colors.hover} !important;\n border-bottom-color: ${colors.hover} !important;\n}\n\n.subheader.is-personalizable {\n background-color: ${colors.lighter} !important;\n}\n\n.builder .sidebar .header {\n border-right: 1px solid ${colors.hover} !important;\n}\n\n.module-tabs.is-personalizable .tab:not(.is-selected):hover, \n.module-tabs.is-personalizable .tab-more:hover {\n background-color: ${colors.tabHoverColor} !important;\n color: ${colors.contrast};\n}\n\n.is-personalizable.tab-container.module-tabs .tab .icon {\n color: ${colors.contrast};\n}\n\n.module-tabs.has-toolbar.is-personalizable .tab-list-container + .toolbar {\n border-left-color: ${colors.darkest} !important;\n}\n\n.module-tabs.is-personalizable [class^=\"btn\"] {\n background-color: transparent;\n color: ${colors.contrast} !important;\n}\n\n.module-tabs.is-personalizable .tab.is-disabled {\n background-color: ${colors.darker} !important;\n color: ${colors.contrast} !important;\n}\n\n.module-tabs.is-personalizable .tab.is-disabled > svg {\n fill: ${colors.contrast} !important;\n}\n\n.module-tabs.is-personalizable .add-tab-button {\n border-left-color: ${colors.darkest} !important;\n}\n\n.module-tabs.is-personalizable .add-tab-button:hover {\n background-color: ${colors.darker} !important;\n}\n\n.module-tabs.is-personalizable .toolbar-searchfield-wrapper > .searchfield {\n color: ${colors.contrast};\n}\n\n.module-tabs.is-personalizable .toolbar-searchfield-wrapper > svg {\n fill: ${colors.contrast} !important;\n}\n\n.header.is-personalizable .toolbar [class^='btn'][disabled],\n.header.is-personalizable .toolbar [class^='btn'][disabled] .icon,\n.header.is-personalizable .toolbar [class^='btn'][disabled] span {\n color: ${colors.btnDisabledColor};\n}\n\n.header .toolbar [class^='btn']:focus:not(.hide-focus):not(.collapse-button) {\n border-color: ${colors.contrast};\n}\n\n.is-personalizable .tab-container.header-tabs:not(.alternate)::before,\n.is-personalizable.tab-container.header-tabs:not(.alternate)::before {\n background-image: linear-gradient(to right, ${colors.base} , ${colorUtils.hexToRgba(colors.base, 0)}) !important;\n}\n\nhtml[dir='rtl'] .is-personalizable .tab-container.header-tabs:not(.alternate)::before,\nhtml[dir='rtl'] .is-personalizable.tab-container.header-tabs:not(.alternate)::before {\n background-image: linear-gradient(to left, ${colors.base}, ${colorUtils.hexToRgba(colors.base, 0)}) !important;\n}\n\n.is-personalizable .tab-container.header-tabs:not(.alternate)::after,\n.is-personalizable.tab-container.header-tabs:not(.alternate)::after {\n background-image: linear-gradient(to right, ${colorUtils.hexToRgba(colors.base, 0)}, ${colors.base}) !important;\n}\n\nhtml[dir='rtl'] .is-personalizable .tab-container.header-tabs:not(.alternate)::after,\nhtml[dir='rtl'] .is-personalizable.tab-container.header-tabs:not(.alternate)::after {\n background-image: linear-gradient(to left, ${colorUtils.hexToRgba(colors.base, 0)}, ${colors.base}) !important;\n}\n\n.is-personalizable.header .section-title {\n color: ${colors.contrast};\n}\n\n.is-personalizable.header button.application-menu-trigger:hover:not(:disabled) .icon.app-header.go-back > span {\n background-color: ${colors.contrast} !important;\n}\n\n.is-personalizable .tab-container.header-tabs:not(.alternate),\n.is-personalizable.tab-container.header-tabs:not(.alternate) {\n border-bottom-color: ${colors.tabBottomBorderColor};\n}\n\n.tab-container.is-personalizable.module-tabs .tab a {\n font-weight: ${colors.tabTextWeight} !important;\n}\n\n.tab-container.is-personalizable.module-tabs .tab.is-selected a {\n font-weight: ${colors.tabTextSelectedWeight} !important;\n}\n\n.hero-widget.is-personalizable {\n background-color: ${colors.darkestPalette};\n}\n\n.hero-widget.is-personalizable .hero-content .circlepager.is-active .controls .control-button.is-active::before,\n.hero-widget.is-personalizable .hero-content .circlepager.is-active .controls .control-button.is-active:hover::before {\n background-color: ${colors.contrast};\n border-color: ${colors.contrast};\n}\n\n.hero-widget.is-personalizable .hero-content .circlepager.is-active .controls .control-button:hover::before,\n.hero-widget.is-personalizable .hero-content .circlepager.is-active .controls .control-button::before {\n border-color: ${colors.contrast};\n}\n\n.hero-widget.is-personalizable .hero-bottom {\n background-color: ${colors.base};\n}\n\n.hero-widget.is-personalizable .hero-footer .hero-footer-nav li::before {\n color: ${colors.light};\n}\n\n.hero-widget.is-personalizable .chart-container .arc {\n stroke: ${colors.lighter};\n}\n\n.hero-widget.is-personalizable .chart-container .bar {\n stroke: ${colors.lighter};\n}\n\n.hero-widget.is-personalizable .chart-container.line-chart .dot {\n stroke: ${colors.lighter};\n}\n\n\n.hero-widget.is-personalizable .count-container .instance-count svg.icon {\n background-color: ${colors.darkestPalette} !important;\n}\n\nhtml[class*=\"-dark\"] .is-personalizable .btn-tertiary:not(.destructive):not(:disabled):hover,\nhtml[class*=\"-dark\"] .is-personalizable .btn-link:not(:disabled):hover,\nhtml[class*=\"-dark\"] .is-personalizable:not(.header) .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):hover:not(:disabled) {\n background-color: #28282A;\n}\n\n.is-personalizable.hero-widget .hero-bottom .hero-footer .hero-footer-nav a.btn-tertiary:not(:disabled):hover {\n border-radius: 0;\n border-bottom: 4px solid ${colors.contrast};\n background-color: ${colors.darker};\n color: ${colors.contrast} !important;\n}\n\nhtml[class*=\"theme-new-\"] .application-menu.is-personalizable .accordion.panel.inverse .accordion-pane .accordion-header {\n border: 1px solid ${colors.lighter};\n}\n\nhtml[class*=\"theme-new-\"] .application-menu.is-personalizable button:focus:not(.hide-focus),\nhtml[class*=\"theme-new-\"] .application-menu.is-personalizable .hyperlink:focus:not(.hide-focus)::after {\n border-color: ${colors.contrast} !important;\n box-shadow: none !important;\n}\n\n.application-menu.is-personalizable .accordion-header.has-filtered-children > a,\n.application-menu.is-personalizable .accordion.panel .accordion-header.has-filtered-children.is-focused {\n color: ${colors.contrast} !important;\n}\n\nhtml[dir='rtl'] .application-menu.is-personalizable {\n background-color: ${colors.lighter};\n border-left: ${colors.light};\n}\n\nhtml[class*=\"theme-new-\"] .application-menu.is-personalizable button svg.ripple-effect {\n background-color: ${colors.contrast} !important;\n}\n\nhtml[class*=\"theme-new\"] .application-menu.is-personalizable .accordion.panel.inverse > .accordion-header.is-focused.is-expanded {\n border-color: transparent !important;\n}\n\nhtml[class*=\"theme-new-\"] .application-menu.is-personalizable .accordion.panel.inverse > .accordion-header.is-expanded.is-focused::before {\n border-color: ${colors.contrast} !important;\n}\n\n.is-personalizable.tab-container {\n background-color: ${colors.tabColor} !important;\n}\n\n.is-personalizable.tab-container.vertical,\n.is-personalizable.tab-container.vertical > .tab-list-container {\n background-color: ${colors.tabVerticalColor} !important;\n}\n\n.is-personalizable .tab-container.header-tabs:not(.alternate) > .tab-list-container .tab-list .tab.is-disabled,\n.is-personalizable.tab-container.header-tabs:not(.alternate) > .tab-list-container .tab-list .tab.is-disabled {\n opacity: .4;\n}\n\nhtml[class*=\"theme-classic-\"] .is-personalizable .tab-container .tab-list-container .tab-list .tab.is-disabled,\nhtml[class*=\"theme-classic-\"] .tab-container.is-personalizable .tab-list-container .tab-list .tab.is-disabled {\n opacity: .4 !important;\n}\n\n.is-personalizable .personalize-header:not(.header-tabs.alternate) {\n background-color: ${colors.base} !important;\n}\n\n.is-personalizable .personalize-subheader {\n background-color: ${colors.lighter} !important;\n}\n\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb {\n background-color: ${colors.lighter};\n}\n\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb.truncated .breadcrumb-list::before,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb.truncated ol::before {\n background-image: linear-gradient(to right, ${colors.lighter}, ${colors.lighter}80);\n}\n\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .hyperlink,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .btn-actions .icon,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .breadcrumb-overflow-container::after,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .breadcrumb-list li::after,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb ol li::after {\n color: ${colors.contrast};\n}\n\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb {\n background-color: ${colors.lighter};\n}\n\n.is-personalizable .section-wizard {\n background-color: ${colors.lighter} !important;\n}\n\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb.truncated .breadcrumb-list::before,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb.truncated ol::before {\n background-image: linear-gradient(to right, ${colors.lighter}, ${colors.lighter}80);\n}\n\n.is-personalizable .section-wizard {\n background-color: ${colors.lighter} !important;\n}\n\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .hyperlink,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .btn-actions .icon,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .breadcrumb-overflow-container::after,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .breadcrumb-list li::after,\n.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb ol li::after {\n color: ${colors.contrast};\n}\n\n.is-personalizable .section-wizard {\n background-color: ${colors.lighter} !important;\n}\n\n.is-personalizable .personalize-subheader p:not(.notification-text),\n.personalize-subheader.is-personalizable p:not(.notification-text) {\n color: ${colors.contrast} !important;\n}\n\n.is-personalizable .personalize-subheader h1,\n.personalize-subheader.is-personalizable h1,\n.is-personalizable .personalize-subheader h2,\n.personalize-subheader.is-personalizable h2 {\n color: ${colors.contrast} !important;\n}\n\nhtml[class*=\"-dark\"] .is-personalizable .personalize-subheader h1,\nhtml[class*=\"-dark\"] .is-personalizable .personalize-subheader h2 {\n color: #fff !important;\n}\n\nhtml[class*=\"-dark\"] .is-personalizable .subheader h1,\nhtml[class*=\"-dark\"] .is-personalizable .subheader h2 {\n color: #fff !important;\n}\n\n.is-personalizable .personalize-subheader button:not(.notification-close) .icon:not(.notification-icon),\n.personalize-subheader.is-personalizable button:not(.notification-close) .icon:not(.notification-icon) {\n color: ${colors.contrast} !important;\n}\n\n.is-personalizable .personalize-subheader .toolbar-searchfield-wrapper.active.is-open *,\n.personalize-subheader.is-personalizable .toolbar-searchfield-wrapper.active.is-open * {\n color: ${colors.theme.text} !important;\n}\n\n.is-personalizable .personalize-actionable,\n.is-personalizable .personalize-actionable svg,\n.is-personalizable .personalize-actionable .icon,\n.is-personalizable .personalize-header .personalize-chart-targeted .label,\n.is-personalizable .personalize-header .info-message .icon,\n.is-personalizable .personalize-header .info-message p,\n.is-personalizable .personalize-header .btn-icon .icon {\n color: ${colors.contrast} !important;\n}\n\nhtml[class*=\"theme-new-\"] .is-personalizable .personalize-actionable:hover:not([disabled]):not(.personalize-actionable-disabled):not(a) ,\nhtml[class*=\"theme-new-\"] .is-personalizable .personalize-actionable:hover:not([disabled]):not(.personalize-actionable-disabled) svg,\nhtml[class*=\"theme-new-\"] .is-personalizable .personalize-actionable:hover:not([disabled]):not(.personalize-actionable-disabled) .icon {\n color: ${colors.btnHoverColor} !important;\n opacity: 1;\n}\n\nhtml[class*=\"theme-new-\"] .is-personalizable .personalize-actionable:hover:not([disabled]):not(.personalize-actionable-disabled):not(a) {\n color: ${colors.btnHoverColor} !important;\n background-color: ${colors.btnBgHoverColor};\n opacity: 1;\n}\n\n.is-personalizable .btn-icon.personalize-actionable {\n height: 34px;\n width: 34px;\n}\n\nhtml[class*=\"theme-new-\"] .is-personalizable .btn-icon.personalize-actionable:hover {\n background-color: ${colors.btnBgHoverColor} !important;\n}\n\n.is-personalizable .personalize-actionable.is-focused:not(.hide-focus),\n.is-personalizable .personalize-actionable:focus:not(.hide-focus) {\n border-color: ${colors.btnFocusBorderColor} !important;\n box-shadow: 0 0 4px 3px rgba(255, 255, 255, 0.2);\n}\n\n.is-personalizable .personalize-actionable.hyperlink:focus:not(.hide-focus)::after {\n border-color: ${colors.contrast} !important;\n opacity: 1;\n box-shadow: 0 0 4px 3px rgba(255, 255, 255, 0.2);\n}\n\n.is-personalizable .personalize-vertical-border {\n border-color: ${colors.light};\n}\n\n.is-personalizable .personalize-horizontal-top-border {\n border-top: 1px solid: ${colors.darkest};\n}\n\n.is-personalizable .personalize-chart-targeted .total.bar {\n background-color: ${colors.btnDisabledColor};\n}\n\n.is-personalizable .personalize-chart-targeted .chart-percent-text,\n.is-personalizable .personalize-chart-targeted .label {\n color: ${colors.contrast};\n}\n\n.is-personalizable .info-message,\n.is-personalizable .info-message .icon,\n.is-personalizable .info-message p {\n color: ${colors.contrast} !important;\n}\n\n.is-personalizable .personalize-actionable-disabled,\n.is-personalizable .personalize-actionable-disabled:hover {\n opacity: .4 !important;\n cursor: default;\n}\n\n.hero-widget.is-personalizable .hero-header .chart-container .arc,\n.hero-widget.is-personalizable .hero-header .chart-container .bar,\n.hero-widget.is-personalizable .hero-header .chart-container.line-chart .dot,\n.hero-widget.is-personalizable .hero-content .chart-container .arc,\n.hero-widget.is-personalizable .hero-content .chart-container .bar,\n.hero-widget.is-personalizable .hero-content .chart-container.line-chart .dot,\n.hero-widget.is-personalizable .hero-footer .chart-container .arc,\n.hero-widget.is-personalizable .hero-footer .chart-container .bar,\n.hero-widget.is-personalizable .hero-footer .chart-container.line-chart .dot {\n stroke: ${colors.lighter} !important;\n}\n\n.hero-widget.is-personalizable .hero-header .chart-container text,\n.hero-widget.is-personalizable .hero-content .chart-container text,\n.hero-widget.is-personalizable .hero-footer .chart-container text {\n fill: ${colors.text} !important;\n}\n\n.hero-widget.is-personalizable .hero-header .chart-container .chart-legend-item-text,\n.hero-widget.is-personalizable .hero-content .chart-container .chart-legend-item-text,\n.hero-widget.is-personalizable .hero-footer .chart-container .chart-legend-item-text {\n color: ${colors.text};\n fill: ${colors.text};\n}\n\n.hero-widget.is-personalizable .hero-header .title,\n.hero-widget.is-personalizable .hero-content .title,\n.hero-widget.is-personalizable .hero-footer .title {\n color: ${colors.subtext};\n}\n\n.hero-widget.is-personalizable .hero-header .btn-tertiary,\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span,\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary),\n.hero-widget.is-personalizable .hero-content .btn-tertiary,\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span,\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary),\n.hero-widget.is-personalizable .hero-footer .btn-tertiary,\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span,\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary),\n.hero-widget.is-personalizable .hero-header .btn-tertiary .icon,\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span .icon,\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary) .icon,\n.hero-widget.is-personalizable .hero-content .btn-tertiary .icon,\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span .icon,\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary) .icon,\n.hero-widget.is-personalizable .hero-footer .btn-tertiary .icon,\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span .icon,\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary) .icon\n {\n color: ${colors.subtext};\n}\n\n.hero-widget.is-personalizable .hero-header .btn-tertiary:hover,\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:hover,\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):hover,\n.hero-widget.is-personalizable .hero-content .btn-tertiary:hover,\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:hover,\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):hover,\n.hero-widget.is-personalizable .hero-footer .btn-tertiary:hover,\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:hover,\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):hover,\n.hero-widget.is-personalizable .hero-header .btn-tertiary:hover .icon,\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:hover .icon,\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):hover .icon,\n.hero-widget.is-personalizable .hero-content .btn-tertiary:hover .icon,\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:hover .icon,\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):hover .icon,\n.hero-widget.is-personalizable .hero-footer .btn-tertiary:hover .icon,\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:hover .icon,\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):hover .icon\n {\n color: ${colors.text};\n}\n\n.hero-widget.is-personalizable .hero-header .btn-tertiary:focus:not(.hide-focus),\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:focus:not(.hide-focus),\n.hero-widget.is-personalizable .hero-header .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):focus:not(.hide-focus),\n.hero-widget.is-personalizable .hero-content .btn-tertiary:focus:not(.hide-focus),\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:focus:not(.hide-focus),\n.hero-widget.is-personalizable .hero-content .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):focus:not(.hide-focus),\n.hero-widget.is-personalizable .hero-footer .btn-tertiary:focus:not(.hide-focus),\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary).is-open span:focus:not(.hide-focus),\n.hero-widget.is-personalizable .hero-footer .btn-menu:not(.btn):not(.btn-primary):not(.btn-secondary):not(.btn-tertiary):focus:not(.hide-focus) {\n box-shadow: ${colors.focusBoxShadow};\n}\n\nhtml.theme-new-dark .hero-widget.is-personalizable .hero-header .title {\n color: #fff !important;\n}\n\n.subheader.is-personalizable .toolbar [class^='btn']:focus:not(.hide-focus),\n.subheader.is-personalizable .flex-toolbar [class^='btn']:focus:not(.hide-focus) {\n box-shadow: ${colors.focusBoxShadow};\n}\n\n.header.is-personalizable .toolbar [class^='btn']:focus:not(.hide-focus),\n.header.is-personalizable .flex-toolbar [class^='btn']:focus:not(.hide-focus) {\n box-shadow: ${colors.focusBoxShadow}\n}\n\nhtml.theme-new-dark .header.alabaster.is-personalizable .flex-toolbar [class^='btn']:focus:not(.hide-focus) .icon {\n color: #fff !important;\n}\n\nhtml.theme-new-dark .header.alabaster.is-personalizable .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled),\nhtml.theme-new-dark .header.alabaster.is-personalizable .btn-tertiary:not(.destructive):not(.is-select):not(.is-select-month-pane):not(.is-cancel):not(.is-cancel-month-pane):not(:disabled) svg.icon {\n color: #fff !important;\n}\n\nhtml[class*=\"theme-new-\"]:not(.theme-new-dark) .header.alabaster.is-personalizable button:not(:disabled) {\n color: #000 !important;\n}\n\nhtml[class*=\"theme-new-\"]:not(.theme-new-dark) .header.alabaster.is-personalizable .flex-toolbar [class^='btn'][disabled] span,\nhtml[class*=\"theme-new-\"]:not(.theme-new-dark) .header.alabaster.is-personalizable .flex-toolbar [class^='btn'][disabled] .icon {\n color: #C5C5C9 !important;\n}\n\nhtml[class*=\"theme-classic-\"]:not(.theme-classic-dark) .header.alabaster.is-personalizable .flex-toolbar [class^='btn'][disabled] span,\nhtml[class*=\"theme-classic-\"]:not(.theme-classic-dark) .header.alabaster.is-personalizable .flex-toolbar [class^='btn'][disabled] .icon {\n color: #888B94 !important;\n}\n\nhtml[class*=\"theme-classic-\"]:not(.theme-classic-dark) .header.alabaster.is-personalizable button:not(:disabled) {\n color: #000 !important;\n}\n\nhtml[class*=\"theme-new-\"]:not(.theme-new-dark) .alabaster.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .btn-actions .icon,\nhtml[class*=\"theme-new-\"]:not(.theme-new-dark) .alabaster.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .hyperlink,\nhtml[class*=\"theme-new-\"]:not(.theme-new-dark) .alabaster.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .breadcrumb-overflow-container::after,\nhtml[class*=\"theme-new-\"]:not(.theme-new-dark) .alabaster.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb .breadcrumb-list li::after,\nhtml[class*=\"theme-new-\"]:not(.theme-new-dark) .alabaster.header.is-personalizable + .page-container .scrollable-flex-header.personalize-subheader .breadcrumb ol li::after {\n color: #000 !important;\n}\n`;\n}\n\nexport { personalizeStyles };\n","import * as debug from '../../utils/debug';\nimport { utils } from '../../utils/utils';\nimport { colorUtils } from '../../utils/color';\nimport { xssUtils } from '../../utils/xss';\nimport { theme } from '../theme/theme';\nimport { personalizeStyles } from './personalize.styles';\n\n// Component name as referenced by jQuery/event namespace/etc\nconst COMPONENT_NAME = 'personalize';\n\n// Component Defaults\nconst PERSONALIZE_DEFAULTS = {\n colors: '',\n theme: '',\n font: '',\n blockUI: true,\n noInit: false\n};\n\n/**\n * The personalization routines for setting custom company colors.\n *\n * @class Personalize\n * @param {HTMLElement|jQuery[]} element The base element\n * @param {object} [settings] Incoming settings\n * @param {string} [settings.colors] The list of colors\n * @param {string} [settings.theme] The theme name (light, dark or high-contrast)\n * @param {string} [settings.font] Use the newer source sans font\n * @param {boolean} [settings.blockUI=true] Cover the UI and animate when changing theme.\n * @param {boolean} [settings.noInit=false] If true, prevents automatic setup of personalized theme/colors/font, allowing for manual triggering at a more convenient time.\n */\nfunction Personalize(element, settings) {\n this.element = $(element);\n this.settings = utils.mergeSettings(this.element[0], settings, PERSONALIZE_DEFAULTS);\n\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n}\n\n// Plugin Methods\nPersonalize.prototype = {\n\n /**\n * Runs on each initialization\n * @private\n * @returns {this} component instance\n */\n init() {\n this.handleEvents();\n\n // Skip automatic setup of theme/colors/font.\n if (this.settings.noInit) {\n return this;\n }\n\n if (this.settings.theme) {\n this.setTheme(this.settings.theme);\n } else {\n this.setTheme(this.getThemeFromStylesheet());\n }\n\n if (this.settings.colors) {\n this.setColors(this.settings.colors);\n }\n\n if (this.settings.font) {\n this.setFont(this.settings.font);\n }\n\n return this;\n },\n\n /**\n * Sets up event handlers for this control and its sub-elements\n * @private\n * @returns {this} component instance\n */\n handleEvents() {\n const self = this;\n\n this.element\n .off(`updated.${COMPONENT_NAME}`)\n .on(`updated.${COMPONENT_NAME}`, () => {\n self.updated();\n });\n\n return this;\n },\n\n /**\n * Create new CSS rules in head and override any existing\n * @private\n * @param {object} cssRules The rules to append.\n */\n appendStyleSheet(cssRules) {\n let sheet = document.getElementById('soho-personalization');\n if (sheet) {\n sheet.parentNode.removeChild(sheet);\n }\n\n // Create the `).appendTo('body');\n }\n\n if (s.layout) {\n this.setLayout(s.layout);\n }\n\n // Automation attributes\n this.setAutomationAttributes();\n },\n\n /**\n * Set automation attributes.\n * @private\n * @returns {void}\n */\n setAutomationAttributes() {\n const leaves = [].slice.call(this.element[0].querySelectorAll('.leaf'));\n leaves.forEach((leaf) => {\n const leafJq = $(leaf);\n const d = leafJq.data();\n if (d && d.attributes) {\n const btnToggle = leafJq.find('.btn-expand, .btn-collapse');\n utils.addAttributes(leafJq, this, d.attributes, 'hierarchy-leaf');\n utils.addAttributes(btnToggle, this, d.attributes, 'hierarchy-btn-toggle');\n }\n });\n },\n /**\n * Setup the hierarchy layout.\n * @private\n * @param {string} layout The layout to display\n * @returns {void}\n */\n setLayout(layout) {\n if (this.isPagingLayout()) {\n layout = 'paging';\n }\n\n if (this.isMobileOnly()) {\n layout = 'mobile-only';\n }\n\n switch (layout) {\n case 'horizontal':\n this.element.addClass('layout-is-horizontal');\n break;\n case 'stacked':\n this.element.addClass('layout-is-stacked');\n break;\n case 'paging':\n this.element.addClass('layout-is-paging');\n break;\n case 'mobile-only':\n this.element.addClass('layout-is-mobile-only');\n break;\n default:\n this.element.addClass('layout-is-horizontal');\n }\n },\n\n /**\n * Attach all event handlers\n * @private\n * @returns {void}\n */\n handleEvents() {\n const self = this;\n const s = this.settings;\n\n // Expand or Collapse\n self.element.off('click.hierarchy').on('click.hierarchy', '.btn', function (e) {\n // Stacked layout doesn't expand/collapse\n if (self.isStackedLayout()) {\n return;\n }\n\n if (s.newData.length > 0) {\n s.newData = [];\n }\n\n const nodeId = $(this).closest('.leaf').attr('id');\n const nodeData = $(`#${nodeId}`).data();\n const domObject = {\n branch: $(this).closest('li'),\n leaf: $(this).closest('.leaf'),\n button: $(this)\n };\n\n if (nodeData.isExpanded) {\n self.collapse(e, nodeData, domObject);\n } else {\n self.expand(e, nodeData, domObject);\n }\n });\n\n this.element.on('keypress', '.leaf', function (e) {\n const nodeId = $(this).attr('id');\n const nodeData = $(`#${nodeId}`);\n\n if (e.which === 13) {\n if (nodeData.isExpanded) {\n self.collapse(e, nodeData);\n } else {\n self.expand(e, nodeData);\n }\n }\n });\n\n self.element.off('dblclick.hierarchy').on('dblclick.hierarchy', '.leaf', (e) => {\n const nodeId = e.currentTarget.id;\n const nodeData = $(`#${nodeId}`).data();\n const dblClickEvent = { event: e, data: nodeData };\n e.stopImmediatePropagation();\n\n this.element.trigger('dblclick', dblClickEvent);\n });\n\n /**\n * Fires when node is selected\n * @event selected\n * @memberof Hierarchy\n * @param {object} event - The jquery event object\n * @param {object} eventInfo - More info to identify the node.\n */\n self.element.on('mouseup', '.leaf, .back button', function (e) {\n const leaf = $(this);\n const target = $(e.target);\n const hierarchy = leaf.closest('.hierarchy').data('hierarchy');\n const nodeData = leaf.data();\n const nodeId = $(this).attr('id');\n const targetInfo = { target: e.target, pageX: e.pageX, pageY: e.pageY };\n const isButton = target.is('button');\n const isNotBack = !target.hasClass('btn-back');\n const isBack = target.is('.btn-back');\n const svgHref = target.find('use').prop('href');\n const isCollapseButton = svgHref ? svgHref.baseVal === '#icon-caret-up' : false;\n const isExpandButton = svgHref ? svgHref.baseVal === '#icon-caret-down' : false;\n const isForward = svgHref ? svgHref.baseVal === '#icon-caret-right' : false;\n const isActions = target.hasClass('btn-actions');\n const isAction = target.is('a') && target.parent().parent().is('ul.popupmenu');\n const isAncestor = leaf.hasClass('ancestor');\n let eventType = 'selected';\n\n e.stopImmediatePropagation();\n\n if (isAction && $(target).parent().data('disabled')) {\n return;\n }\n\n $('.leaf.is-selected').removeClass('is-selected');\n $(`#${nodeId}`).addClass('is-selected');\n\n // Is collapse event\n if (isButton && isCollapseButton && isNotBack) {\n eventType = isAncestor ? 'back' : 'collapse';\n }\n\n // Is expand event\n if (isButton && isExpandButton && isNotBack) {\n eventType = 'expand';\n }\n\n if (isBack) {\n eventType = 'back';\n }\n\n if (isActions) {\n eventType = 'actions';\n hierarchy.buildActionsMenu(nodeData, leaf);\n }\n\n if (isAction) {\n eventType = 'action';\n }\n\n if (isButton && isForward && isNotBack) {\n eventType = 'forward';\n }\n\n // Is right click event\n if (e.which === 3) {\n eventType = 'rightClick';\n }\n\n if (!hierarchy) {\n return;\n }\n\n const eventInfo = {\n id: nodeId,\n data: nodeData,\n actionReference: isAction ? target.data('actionReference') : null,\n targetInfo,\n eventType,\n isForwardEvent: hierarchy.isForwardEvent(eventType),\n isBackEvent: hierarchy.isBackEvent(eventType),\n isAddEvent: hierarchy.isAddEvent(eventType),\n isExpandEvent: hierarchy.isExpandEvent(eventType),\n isCollapseEvent: hierarchy.isCollapseEvent(eventType),\n isSelectedEvent: hierarchy.isSelectedEvent(eventType),\n isActionsEvent: hierarchy.isActionsEvent(eventType),\n isActionEvent: hierarchy.isActionEvent(eventType),\n allowLazyLoad: hierarchy.allowLazyLoad(nodeData, eventType)\n };\n\n leaf.trigger('selected', eventInfo);\n });\n },\n\n /**\n * Manually set selection on a leaf\n * @public\n * @param {string} nodeId id used to find leaf\n */\n selectLeaf(nodeId) {\n const leaf = $(`#${nodeId}`);\n $('.leaf.is-selected').removeClass('is-selected');\n leaf.addClass('is-selected');\n\n const eventInfo = {\n data: leaf.data(),\n actionReference: null,\n isForwardEvent: false,\n isBackEvent: false,\n isAddEvent: false,\n isExpandEvent: false,\n isCollapseEvent: false,\n isSelectedEvent: true,\n isActionsEvent: false,\n isActionEvent: false,\n allowLazyLoad: false\n };\n\n leaf.trigger('selected', eventInfo);\n },\n\n /**\n * Update existing leaf actions with new actions\n * @public\n * @param {object} eventInfo eventType, target, data, ect..\n * @param {array} updatedActions -actions to be appended to the menu\n */\n updateActions(eventInfo, updatedActions) {\n const leaf = $(eventInfo.targetInfo.target).closest('.leaf');\n const nodeData = eventInfo.data;\n const popupMenu = $(leaf).find('.popupmenu');\n const popupMenuControl = popupMenu.data('trigger').data().popupmenu;\n const lineItemsToRemove = popupMenu.find('li').not(':eq(0)');\n\n $(lineItemsToRemove).each((idx, item) => {\n $(item).remove();\n });\n\n nodeData.menu.actions = updatedActions;\n popupMenu.append(this.getActionMenuItems(nodeData));\n\n // Setup flag to prevent double-open\n popupMenuControl.keydownThenClick = true;\n popupMenuControl.open();\n\n popupMenuControl.handleAfterPlace(null, {\n element: popupMenu.parent(),\n parent: $(leaf).find('.btn-actions'),\n placement: 'bottom'\n });\n },\n\n /**\n * @private\n * @param {object} data associated with leaf\n * @param {leaf} leaf jQuery reference in DOM\n */\n buildActionsMenu(data, leaf) {\n const popupMenu = $(leaf).find('.popupmenu');\n const template = [];\n\n // Safety\n if (data.menu === undefined) {\n return;\n }\n\n // Reset & rebuild\n popupMenu.empty();\n\n if (data.menu.details) {\n popupMenu.addClass('has-detail-fields');\n template.push(`
  • ${data.menu.details.map(v => `
    ${v.key}
    ${v.value}
    `).join('')}
  • `);\n }\n\n if (data.menu.actions) {\n template.push(this.getActionMenuItems(data));\n }\n\n template.forEach((i) => { popupMenu.append(i); });\n },\n\n /**\n * @private\n * @param {object} data the data to be iterated\n * @returns {string} returns list items as a string\n */\n getActionMenuItems(data) {\n const actions = data.menu.actions.map((a) => {\n if (a.disabled === undefined) {\n a.disabled = false;\n }\n\n return a;\n });\n\n let opAttr;\n const opAttrStr = idx => utils.stringAttributes(this, opAttr, `option-${idx}`);\n if (data.attributes) {\n opAttr = this.getAutomationAttributes(data, '-hierarchy-popupmenu');\n }\n\n // Ignoring next line. Eslint expects template literals vs string concat.\n // However template literals break JSON.stringify() in this case\n /* eslint-disable */\n const actionMarkup = actions.map((a, idx) => {\n if (a.hasOwnProperty('data')) {\n if (a.data.type === 'separator') {\n return `
  • `\n }\n }\n return `\n
  • \n \n ${a.value}\n ${a.menu ? '' : ''}\n \n ${a.menu ? `
    \n
      \n ${a.menu.map((x, idx2) => `\n
    • \n ${x.value}\n
    • `).join('')}\n
    \n
    ` : ''}\n
  • `\n }).join('');\n /* eslint-enable */\n\n return actionMarkup;\n },\n\n /**\n * Check if event is back\n * @private\n * @param {string} eventType is back\n * @returns {boolean} true if back event\n */\n isBackEvent(eventType) {\n return eventType === 'back';\n },\n\n /**\n * Check if event is forward\n * @private\n * @param {string} eventType is forward\n * @returns {boolean} true if forward event\n */\n isForwardEvent(eventType) {\n return eventType === 'forward';\n },\n\n /**\n * Check if event is add\n * @private\n * @param {string} eventType is add\n * @returns {boolean} true if add event\n */\n isAddEvent(eventType) {\n return eventType === 'add';\n },\n\n /**\n * Check if event is expand\n * @private\n * @param {string} eventType is expand\n * @returns {boolean} true if expand event\n */\n isExpandEvent(eventType) {\n return eventType === 'expand';\n },\n\n /**\n * Check if event is collapse\n * @private\n * @param {string} eventType is collapse\n * @returns {boolean} true if collapse event\n */\n isCollapseEvent(eventType) {\n return eventType === 'collapse';\n },\n\n /**\n * Check if event is selected\n * @private\n * @param {string} eventType is selected\n * @returns {boolean} true if selected event\n */\n isSelectedEvent(eventType) {\n return eventType === 'selected';\n },\n\n /**\n * Checks if is actions event\n * @private\n * @param {string} eventType is actions\n * @returns {boolean} true if actions event\n */\n isActionsEvent(eventType) {\n return eventType === 'actions';\n },\n\n /**\n * @private\n * @param {string} evenType is action\n * @returns {boolean} true if action\n */\n isActionEvent(evenType) {\n return evenType === 'action';\n },\n\n /**\n * Check to see if lazy load is allowed\n * @private\n * @param {object} data contains info\n * @param {string} eventType is expand\n * @returns {boolean} true if lazy load is allowed\n */\n allowLazyLoad(data, eventType) {\n if (data === undefined || eventType === undefined) {\n return false;\n }\n return !data.isLoaded && !data.isLeaf && eventType === 'expand';\n },\n\n /**\n * Process data attached through jquery data\n * @private\n * @param {string} nodeId .\n * @param {string} currentDataObject .\n * @param {string} newDataObject .\n * @param {string} params .\n * @returns {object} data\n */\n data(nodeId, currentDataObject, newDataObject, params) {\n /* eslint-disable no-use-before-define */\n if (params === undefined) {\n params = {};\n }\n\n const s = this.settings;\n const obj = currentDataObject.isRootNode ? currentDataObject : currentDataObject[0];\n const nodeData = [];\n\n if (s.newData.length > 0) {\n s.newData = [];\n }\n\n function addChildrenToObject(thisObj, thisParams) {\n if (thisParams.insert) {\n delete thisObj.isLeaf;\n thisObj.isExpanded = true;\n }\n if (newDataObject.length !== 0 && thisParams.insert) {\n thisObj.children = [newDataObject];\n } else {\n thisObj.children = newDataObject;\n }\n }\n\n function checkForChildren(self, thisObj, thisNewDataObject) {\n Object.keys(thisObj).forEach((prop) => {\n if (prop === 'id' && nodeId === thisObj.id) {\n if (!thisObj.isLoaded && !thisObj.isRootNode) {\n addChildrenToObject(thisObj, params);\n }\n nodeData.push(thisObj);\n }\n });\n if (thisObj.children) {\n processData(self, thisObj.children, thisNewDataObject); // eslint-disable-line\n }\n }\n\n function processData(self, thisObj, thisNewDataObject) {\n if (thisObj.length === undefined) {\n checkForChildren(self, thisObj, thisNewDataObject);\n } else {\n for (let i = 0, l = thisObj.length; i < l; i++) {\n checkForChildren(self, thisObj[i], thisNewDataObject);\n }\n }\n }\n\n if (newDataObject !== undefined) {\n processData(this, obj, newDataObject);\n }\n\n if (nodeData.length !== 0) {\n $(`#${nodeData[0].id}`).data(nodeData[0]);\n }\n\n return nodeData[0];\n /* eslint-enable no-use-before-define */\n },\n\n /**\n * Add data as children for the given nodeId.\n * @private\n * @param {string} nodeId .\n * @param {object} currentDataObject info\n * @param {object} newDataObject .\n * @returns {void}\n */\n add(nodeId, currentDataObject, newDataObject) {\n const s = this.settings;\n const id = currentDataObject.id !== undefined ? currentDataObject.id : nodeId;\n const node = $(`#${id}`);\n const parentContainer = node.parent().hasClass('leaf-container') ? node.parent().parent() : node.parent();\n const selectorObject = {};\n const isSubLevelChild = parentContainer.parent().attr('class') !== 'sub-level';\n const subListExists = parentContainer.children('.sublist').length === 1;\n\n if (isSubLevelChild) {\n if (subListExists) {\n selectorObject.element = parentContainer.children('.sublist');\n } else {\n selectorObject.el = parentContainer.append('
      ');\n selectorObject.element = $(selectorObject.el).find('.sublist');\n }\n } else {\n selectorObject.el = parentContainer.children('ul');\n selectorObject.element = $(selectorObject.el);\n }\n\n if (selectorObject.element.length === 0) {\n selectorObject.el = parentContainer.append('
        ');\n selectorObject.element = $(selectorObject.el).find('ul');\n }\n\n if (!currentDataObject.isRootNode) {\n for (let i = 0, l = newDataObject.length; i < l; i++) {\n s.newData.push(newDataObject[i]);\n }\n this.createLeaf(newDataObject, selectorObject.element);\n }\n\n this.updateState(node, false, null, 'add');\n },\n\n /**\n * Closes popupmenu\n * @private\n * @param {object} node leaf containing btn-actions\n */\n closePopupMenu(node) {\n const actionButton = node.find('.btn-actions');\n\n if (actionButton.length !== 0) {\n actionButton.data('popupmenu').close();\n }\n },\n\n /**\n * Expand the nodes until nodeId is displayed on the page.\n * @private\n * @param {object} event .\n * @param {object} nodeData info\n * @param {object} domObject .\n * @returns {void}\n */\n expand(event, nodeData, domObject) {\n const s = this.settings;\n const node = domObject.leaf;\n let nodeTopLevel = node.next();\n\n // close popupmenu if open\n this.closePopupMenu(node);\n\n nodeTopLevel.animateOpen();\n /**\n * Fires when leaf expanded.\n *\n * @event expanded\n * @memberof Hierarchy\n * @type {object}\n * @param {object} event - The jquery event object\n * @param {array} args [nodeData, dataset]\n */\n this.element.trigger('expanded', [nodeData, s.dataset]);\n\n if (node.hasClass('root')) {\n nodeTopLevel = nodeTopLevel.next('ul');\n nodeTopLevel.animateOpen();\n }\n\n node.parent().removeClass('branch-collapsed').addClass('branch-expanded');\n this.updateState(node, false, null, 'expand');\n },\n\n /**\n * Collapse the passed in nodeId.\n * @private\n * @param {object} event .\n * @param {object} nodeData info\n * @param {object} domObject .\n * @returns {void}\n */\n collapse(event, nodeData, domObject) {\n const s = this.settings;\n const node = domObject.leaf;\n let nodeTopLevel = node.next();\n\n // close popupmenu if open\n this.closePopupMenu(node);\n\n nodeTopLevel.animateClosed().on('animateclosedcomplete', () => {\n /**\n * Fires when leaf collapsed.\n *\n * @event collapsed\n * @memberof Hierarchy\n * @type {object}\n * @param {object} event - The jquery event object\n * @param {array} args [nodeData, dataset]\n */\n this.element.trigger('collapsed', [nodeData, s.dataset]);\n });\n\n if (node.hasClass('root')) {\n nodeTopLevel = nodeTopLevel.next('ul');\n nodeTopLevel.animateClosed();\n }\n\n node.parent().removeClass('branch-expanded').addClass('branch-collapsed');\n this.updateState(node, false, null, 'collapse');\n },\n\n /**\n * Main render method\n * @private\n * @param {object} data info.\n * @returns {void}\n */\n render(data) {\n /* eslint-disable no-use-before-define */\n const s = this.settings;\n const thisLegend = s.legend;\n const thisChildren = data.children;\n const rootNodeHTML = [];\n const uniqueId = `${utils.uniqueId(this.element, 'hierarchy')}`;\n const structure = {\n legend: '
          ',\n chart: '
          ',\n toplevel: this.isPagingLayout() ? '
            ' : '
              ',\n sublevel: this.isPagingLayout() ? '' : '
                '\n };\n\n // Append chart structure to hierarchy container\n $(`#${this.settings.rootId}`).append(structure.chart);\n\n const chart = $(`#${this.settings.rootId} .chart`);\n\n if (thisLegend.length !== 0) {\n $(`#${this.settings.rootId}`).prepend(structure.legend);\n const element = $(`#${this.settings.rootId} legend`);\n this.createLegend(element);\n }\n\n // check to see how many children are not leafs and have children\n if (this.isSingleChildWithChildren()) {\n $(chart).addClass('has-single-child');\n }\n\n // Create root node\n this.setColor(data);\n\n if (this.isPagingLayout() && data.parentDataSet) {\n const backAutomationAttr = ` id=\"${uniqueId}-back-button\" data-automation-id=\"automation-id-${uniqueId}-back-button\"`;\n const backMarkup = `
                \n
                `;\n\n // Append back button to chart to go back to view previous level\n const backButton = $(backMarkup).appendTo(chart);\n\n // Wrap back button and leaf after leaf has been rendered\n setTimeout(() => backButton.next($('.leaf')).addBack($('.back')).wrapAll('
                '));\n\n // Attach data reference to back button\n backButton.children('button').data(data);\n\n // Class used to adjust heights and account for back button\n $(chart).addClass('has-back');\n }\n\n if (data.isMultiRoot) {\n let multiRootHTML = `

                ${data.multiRootText}

                `;\n multiRootHTML = xssUtils.sanitizeHTML(multiRootHTML);\n rootNodeHTML.push(multiRootHTML);\n $(rootNodeHTML[0]).addClass('root').appendTo(chart);\n } else if (data.ancestorPath !== null && data.ancestorPath !== undefined) {\n data.ancestorPath.push(data.centeredNode);\n let ancestorHTML = `${data.ancestorPath.map(a => ` ${this.getTemplate(a)} `).join('')}`;\n ancestorHTML = xssUtils.sanitizeHTML(ancestorHTML);\n rootNodeHTML.push(ancestorHTML);\n $(rootNodeHTML[0]).addClass('root ancestor').appendTo(chart);\n\n const roots = this.element.find('.leaf.root');\n\n roots.each((index, root) => {\n this.updateState(root, false, data.ancestorPath[index], 'add');\n\n if (index === roots.length - 1) {\n $(root).addClass('is-selected');\n }\n });\n } else {\n const centeredNode = data.centeredNode;\n let leaf;\n\n if (this.isStackedLayout() && centeredNode !== null) {\n leaf = this.getTemplate(centeredNode);\n } else if (!this.isStackedLayout()) {\n leaf = this.getTemplate(data);\n }\n\n if (leaf) {\n leaf = xssUtils.sanitizeHTML(leaf);\n rootNodeHTML.push(leaf);\n $(rootNodeHTML[0]).addClass('root is-selected').appendTo(chart);\n }\n\n if (centeredNode && centeredNode !== null) {\n this.updateState(this.element.find('.leaf.root'), true, centeredNode, undefined);\n } else {\n this.updateState(this.element.find('.leaf.root'), true, data, undefined);\n }\n }\n\n function renderSubChildren(self, subArray, thisData) {\n if (subArray !== null && subArray !== undefined) {\n for (let i = 0, l = subArray.length; i < l; i++) {\n const obj = subArray[i];\n subArrayChildren(self, obj, thisData); // eslint-disable-line\n }\n }\n }\n\n // Create children nodes\n if (thisChildren && thisChildren.length > 0) {\n for (let i = 0, l = thisChildren.length; i < l; i++) {\n const childObject = data.children[i].children;\n\n // If child has no children then render the element in the top level\n // If paging then render all children in the top level\n // If not paging and child has children then render in the sub level\n if (this.isLeaf(thisChildren[i]) && !this.isPagingLayout() && s.renderSubLevel) {\n this.createLeaf(data.children[i], $(structure.toplevel));\n } else if (this.isPagingLayout()) {\n this.createLeaf(data.children[i], $(structure.toplevel));\n } else {\n this.createLeaf(data.children[i], $(structure.sublevel));\n }\n\n if (childObject !== undefined && childObject !== null) {\n const subArray = data.children[i].children;\n const self = this;\n renderSubChildren(self, subArray, data);\n }\n }\n }\n\n function subArrayChildren(self, obj, thisData) {\n Object.keys(obj).forEach((prop) => {\n if (prop === 'children') {\n const nodeId = obj.id;\n const currentDataObject = obj;\n const newDataObject = obj.children;\n\n if (newDataObject !== null && newDataObject !== undefined) {\n if (newDataObject.length > 0) {\n self.add(nodeId, currentDataObject, newDataObject);\n }\n }\n return renderSubChildren(self, newDataObject, thisData);\n }\n return true;\n });\n }\n\n const containerWidth = this.element.find('.container').outerWidth();\n const windowWidth = $(window).width();\n const center = (containerWidth - windowWidth) / 2;\n this.element.scrollLeft(center);\n\n // Add a no-sublevel class if only two levels (to remove extra border)\n const topLevel = this.element.find('.top-level');\n if (this.element.find('.sub-level').length === 0 && topLevel.length === 1) {\n topLevel.addClass('no-sublevel');\n }\n\n /* eslint-enable no-use-before-define */\n },\n\n /**\n * @private\n * @returns {boolean} true if paging layout\n */\n isPagingLayout() {\n return this.settings.layout && this.settings.layout === 'paging';\n },\n\n /**\n * @private\n * @returns {boolean} true if mobile only\n */\n isMobileOnly() {\n return this.settings.layout && this.settings.layout === 'mobile-only';\n },\n\n /**\n * @private\n * @returns {boolean} true if stacked layout\n */\n isStackedLayout() {\n return this.settings.layout && this.settings.layout === 'stacked';\n },\n\n /**\n * Checks to see if children have children\n * @private\n * @returns {boolean} true if have children\n */\n isSingleChildWithChildren() {\n if (this.isStackedLayout()) {\n return false;\n }\n\n const s = this.settings;\n if (s.dataset && (s.dataset[0] && s.dataset[0].children)) {\n let i = s.dataset[0].children.length;\n let count = 0;\n\n while (i--) {\n if (!s.dataset[0].children[i].isLeaf) {\n count++;\n }\n }\n\n return count === 1;\n }\n return false;\n },\n\n /**\n * Get append suffix with automation attributes\n * @private\n * @param {array} data for attributes to use\n * @param {string} suffix to use\n * @returns {object|array} attributes with suffix\n */\n getAutomationAttributes(data, suffix) {\n let attributes = data.attributes;\n if (data.attributes && typeof suffix === 'string' && suffix !== '') {\n if (Array.isArray(data.attributes)) {\n attributes = [];\n data.attributes.forEach((item) => {\n const value = typeof item.value === 'function' ? item.value(this) : item.value;\n attributes.push({ name: item.name, value: (value + suffix) });\n });\n } else {\n const value = typeof data.attributes.value === 'function' ? data.attributes.value(this) : data.attributes.value;\n attributes = { name: data.attributes.name, value: (value + suffix) };\n }\n }\n return attributes;\n },\n\n /**\n * Builds leaf template\n * @private\n * @param {object} data leaf data\n * @returns {string} compiled template as HTML string\n */\n getTemplate(data) {\n const template = Tmpl.compile(`{{#dataset}}${$(`#${xssUtils.stripTags(this.settings.templateId)}`).html()}{{/dataset}}`, { dataset: data });\n\n // Init popupmenu after rendered in DOM\n setTimeout(() => {\n const actionButton = $(`#btn-${xssUtils.stripTags(data.id)}`);\n if (actionButton.length !== 0) {\n const attributes = this.getAutomationAttributes(data, '-hierarchy-popupmenu');\n actionButton.hideFocus().popupmenu({ attachToBody: false, attributes });\n }\n }, 1);\n\n return $(template).prop('outerHTML');\n },\n\n /**\n * Add the legend from the Settings\n * @private\n * @param {object} element .\n * @returns {void}\n */\n createLegend(element) {\n const s = this.settings;\n const mod = 4;\n let index = 0;\n\n for (let i = 0, l = s.legend.length; i < l; i++) {\n const thislabel = s.legend[i].label;\n const color = s.colorClass[i];\n\n if ((i - (1 % mod)) + 1 === mod) {\n element.append('
                  ');\n index++;\n }\n\n element.children('ul').eq(index).append('' +\n `
                • \n \n ${thislabel}\n
                • `);\n }\n },\n\n /**\n * Creates a leaf node under element for nodeData\n * @private\n * @param {object} nodeData contains info.\n * @param {object} container .\n * @returns {void}\n */\n createLeaf(nodeData, container) {\n const self = this;\n // Needs to be unique in the chance of multiple charts\n const chart = $(`#${self.settings.rootId} .chart`, self.container);\n const elClassName = container.attr('class');\n const el = elClassName !== undefined ? $(`#${self.settings.rootId} .${elClassName}`) : container;\n\n if (el.length < 1) {\n if (elClassName === 'top-level') {\n container.insertAfter('.root');\n } else {\n container.appendTo(chart);\n }\n }\n\n function processDataForLeaf(thisNodeData) {\n self.setColor(thisNodeData);\n\n const leaf = self.getTemplate(thisNodeData);\n const rootId = self.settings.rootId;\n\n let parent = el.length === 1 ? el : container;\n let branchState = thisNodeData.isExpanded || thisNodeData.isExpanded === undefined ? 'branch-expanded' : 'branch-collapsed';\n\n if (thisNodeData.isLeaf) {\n branchState = '';\n }\n\n if ($(`#${rootId} #${thisNodeData.id}`).length === 1) {\n return;\n }\n\n parent.append(`
                • ${$(leaf)[0].outerHTML}
                • `);\n\n if (thisNodeData.children) {\n let childrenNodes = '';\n\n for (let j = 0, l = thisNodeData.children.length; j < l; j++) {\n self.setColor(thisNodeData.children[j]);\n const childLeaf = self.getTemplate(thisNodeData.children[j]);\n\n if (j === thisNodeData.children.length - 1) {\n childrenNodes += `
                • ${$(childLeaf)[0].outerHTML}
                • `;\n } else {\n childrenNodes += `
                • ${$(childLeaf)[0].outerHTML}
                • `;\n }\n }\n\n parent = $(`#${rootId} #${xssUtils.stripTags(thisNodeData.id)}`).parent();\n parent.append(`
                    ${childrenNodes}
                  `);\n\n let childLength = thisNodeData.children.length;\n while (childLength--) {\n const lf = $(`#${rootId} #${xssUtils.stripTags(thisNodeData.children[childLength].id)}`);\n self.updateState(lf, false, thisNodeData.children[childLength], undefined);\n }\n }\n }\n\n if (nodeData.length) {\n for (let i = 0, l = nodeData.length; i < l; i++) {\n const isLast = (i === (nodeData.length - 1));\n processDataForLeaf(nodeData[i], isLast);\n self.updateState($(`#${self.settings.rootId} #${xssUtils.stripTags(nodeData[i].id)}`), false, nodeData[i], undefined);\n }\n } else {\n processDataForLeaf(nodeData, true);\n self.updateState($(`#${self.settings.rootId} #${xssUtils.stripTags(nodeData.id)}`), false, nodeData, undefined);\n }\n },\n\n /**\n * Set leaf colors matching data to key in legend\n * @private\n * @param {object} data contains info.\n * @returns {void}\n */\n setColor(data) {\n const s = this.settings;\n this.setRootColor(data);\n\n if (this.isStackedLayout()) {\n if (data.ancestorPath && data.ancestorPath !== null) {\n data.ancestorPath.forEach((d) => {\n this.setRootColor(d);\n });\n }\n\n if (data.centeredNode && data.centeredNode !== null) {\n this.setRootColor(data.centeredNode);\n }\n }\n\n if (data.children && !data.isRootNode) {\n for (let k = 0, ln = data.children.length; k < ln; k++) {\n for (let j = 0, x = s.legend.length; j < x; j++) {\n if (data.children[k][s.legendKey] === s.legend[j].value) {\n data.children[k].colorClass = s.colorClass[j];\n }\n }\n }\n }\n },\n\n /**\n * Set the color of the root element.\n * @private\n * @param {object} data The data object to use.\n */\n setRootColor(data) {\n const s = this.settings;\n for (let i = 0, l = s.legend.length; i < l; i++) {\n if (data[s.legendKey] === s.legend[i].value) {\n data.colorClass = s.colorClass[i];\n break;\n } else if (data[s.legendKey] === '') {\n data.colorClass = 'default-color';\n }\n }\n },\n\n /**\n * Check to see if particular node is a leaf\n * @private\n * @param {object} dataNode contains data info\n * @returns {boolean} whether or not a particular node is a leaf\n */\n isLeaf(dataNode) {\n const s = this.settings;\n if (dataNode.isLeaf) {\n return dataNode.isLeaf;\n }\n\n if (s.beforeExpand) {\n return dataNode.isLeaf;\n }\n\n // Node is not a leaf and should display and expand/collapse icon\n if ((dataNode.children && dataNode.children.length > 0)) {\n return false;\n }\n\n return true;\n },\n\n /**\n * Handle all leaf state here,\n * get the current state via .data() and re-attach the new state\n * @private\n * @param {string} leaf .\n * @param {boolean} isRoot .\n * @param {object} nodeData .\n * @param {string} eventType .\n * @returns {void}\n */\n updateState(leaf, isRoot, nodeData, eventType) {\n // set data if it has not been set already\n if ($.isEmptyObject($(leaf).data()) && nodeData) {\n const d = nodeData === undefined ? {} : nodeData;\n $(leaf).data(d);\n }\n\n const s = this.settings;\n const btn = $(leaf).find('.btn');\n const expandCaret = this.isPagingLayout() ? 'caret-right' : 'caret-up';\n let data = $(leaf).data();\n\n if (data === undefined && nodeData !== undefined) {\n data = nodeData;\n }\n\n // data has been loaded if it has children\n if ((data.children && data.children.length !== 0) || eventType === 'add') {\n data.isExpanded = true;\n data.isLoaded = true;\n }\n\n if (isRoot) {\n data.isRootNode = true;\n data.isLoaded = true;\n }\n\n if ((data.isExpanded === undefined && data.children) || eventType === 'expand') {\n data.isExpanded = true;\n }\n\n // defaults to collapsed state\n if (data.isExpanded === undefined || eventType === 'collapse') {\n data.isExpanded = false;\n }\n\n if (data.isExpanded) {\n btn.find('svg.icon').changeIcon(expandCaret);\n btn.addClass('btn-expand').removeClass('btn-collapse');\n } else {\n btn.find('svg.icon').changeIcon('caret-down');\n btn.addClass('btn-collapse').removeClass('btn-expand');\n }\n\n if (data.isLeaf || data.isRootNode) {\n btn.addClass('btn-hidden');\n }\n\n if (data.isLeaf) {\n data.isLoaded = false;\n data.isExpanded = false;\n }\n\n // Keep reference of the parent dataset for paging\n if (this.isPagingLayout()) {\n data.parentDataSet = s.dataset;\n }\n\n // Reset data\n $(leaf).data(data);\n },\n\n /**\n * Reloads hierarchy control with new dataset\n * @private\n * @param {object} options hierarchy\n * @returns {void}\n */\n reload(options) {\n this.destroy();\n this.element.hierarchy(options);\n },\n\n /**\n * Removes event bindings from the instance.\n * @private\n * @returns {void}\n */\n unbind() {\n this.element.empty();\n },\n\n /**\n * Resync the UI and Settings.\n * @param {object} settings The settings to apply.\n * @returns {object} The api\n */\n updated(settings) {\n if (typeof settings !== 'undefined') {\n this.settings = utils.mergeSettings(this.element, settings, HIERARCHY_DEFAULTS);\n }\n return this\n .unbind()\n .init();\n },\n\n /**\n * Removes the component from existence\n * @returns {void}\n */\n destroy() {\n this.unbind();\n this.element.removeData(COMPONENT_NAME);\n }\n\n};\n\nexport { Hierarchy, COMPONENT_NAME };\n","import { Hierarchy, COMPONENT_NAME } from './hierarchy';\n\n/**\n * jQuery Component Wrapper for Hierarchy\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.hierarchy = function (settings) {\n return this.each(function () {\n let instance = $.data(this, COMPONENT_NAME);\n if (instance) {\n instance.updated(settings);\n } else {\n instance = $.data(this, COMPONENT_NAME, new Hierarchy(this, settings));\n }\n });\n};\n","import * as debug from '../../utils/debug';\nimport { utils } from '../../utils/utils';\nimport { stringUtils as str } from '../../utils/string';\nimport { Tmpl } from '../tmpl/tmpl';\nimport { Locale } from '../locale/locale';\n\n// jQuery components\nimport '../dropdown/dropdown.jquery';\n\n// Component Name\nconst COMPONENT_NAME = 'fieldfilter';\n\n/**\n * Ability to have a dropdown next to the field.\n *\n * @class FieldFilter\n * @constructor\n *\n * @param {jQuery[]|HTMLElement} element The component element.\n * @param {object} [settings] The component settings.\n * @param {array} [settings.dataset] Array of data\n * @param {object} [settings.dropdownOpts] Gets passed to this control's dropdown\n * @param {string} [settings.template] An Html String with the mustache template for the view.\n */\nconst FIELDFILTER_DEFAULTS = {\n dataset: [],\n dropdownOpts: {}, // Dropdown custom settings\n template: '' +\n `\n `\n};\nfunction FieldFilter(element, settings) {\n this.element = $(element);\n this.settings = utils.mergeSettings(this.element[0], settings, FIELDFILTER_DEFAULTS);\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n}\n\n// FieldFilter Methods\nFieldFilter.prototype = {\n\n init() {\n this.render();\n this.handleEvents();\n this.setFiltered();\n },\n\n /**\n * Render the template against the dataset.\n * @private\n * @param {array} dataset The dataset to use\n * @returns {void}\n */\n render(dataset) {\n const s = this.settings;\n dataset = dataset || s.dataset;\n // Render \"mustache\" Template\n if (typeof Tmpl === 'object' && dataset && s.template) {\n // create a copy of an inlined template\n if (s.template instanceof $) {\n s.template = `${s.template.html()}`;\n } else if (typeof s.template === 'string') {\n // If a string doesn't contain HTML elments,\n // assume it's an element ID string and attempt to select with jQuery\n if (!str.containsHTML(s.template)) {\n s.template = $(`#${s.template}`).html();\n }\n }\n\n const renderedTmpl = Tmpl.compile(s.template, { dataset: !s.dropdownOpts.source ? dataset : [] }); // eslint-disable-line\n const emptyTmpl = '' +\n `\n `;\n\n if (dataset.length > 0) {\n this.element.before(renderedTmpl);\n } else if (dataset.length === 0) {\n this.element.before(renderedTmpl || emptyTmpl);\n }\n\n // Set element id\n let id = this.element.attr('id') || this.element.attr('name');\n if (typeof id === 'undefined') {\n id = utils.uniqueId(this.element, 'fieldfilter-');\n this.element[0].setAttribute('id', id);\n }\n const ffId = `${id}-ff`;\n\n // Set Field\n this.field = this.element.closest('.field, .field-short');\n\n // RTL list x-position\n const isRTL = Locale.isRTL();\n s.dropdownOpts = s.dropdownOpts || {};\n if (isRTL && typeof s.dropdownOpts === 'object') {\n if (s.dropdownOpts.placementOpts) {\n s.dropdownOpts.placementOpts.x = this.element.outerWidth();\n } else {\n s.dropdownOpts.placementOpts = { x: this.element.outerWidth() };\n }\n }\n\n // Set Dropdown\n s.dropdownOpts.cssClass = s.dropdownOpts.cssClass ? `${s.dropdownOpts.cssClass} ffdropdown` : 'ffdropdown';\n s.dropdownOpts.noSearch = true;\n\n // Find the field filter dropdown\n this.ffdropdown = this.field.find('select.dropdown.field-filter-dropdown');\n this.ffdropdown\n .dropdown(s.dropdownOpts)\n .prev('label')\n .addClass('audible');\n\n this.ffdropdown[0].setAttribute('id', ffId);\n this.ffdropdown[0].setAttribute('name', ffId);\n this.ffdropdown.prev('label')[0].setAttribute('for', ffId);\n\n // Add css classes\n const labelText = this.ffdropdown.prev('label').prev('label').text();\n this.field.addClass('fieldfilter-wrapper');\n this.field.find('div.dropdown span.audible').text(labelText);\n\n // Dropdown api\n this.ddApi = this.ffdropdown.data('dropdown');\n if (this.ddApi && this.ddApi.icon) {\n this.ddApi.icon.addClass('ffdropdown-icon');\n }\n\n // Add test automation ids\n utils.addAttributes(this.field, this, this.settings.attributes, '', true);\n utils.addAttributes(this.field.find('label:not(.audible)'), this, this.settings.attributes, 'label', true);\n utils.addAttributes(this.field.find('select'), this, this.settings.attributes, 'select', true);\n }\n },\n\n /**\n * Set currently filtered item\n * @private\n * @returns {object} The api\n */\n setFiltered() {\n if (this.ddApi) {\n const item = this.ddApi.element.find('option:selected');\n this.filtered = this.getTriggerData(item);\n }\n return this;\n },\n\n /**\n * Get currently triggerData for given item args\n * @private\n * @param {object} args selected item.\n * @returns {object} The api\n */\n getTriggerData(args) {\n const s = this.settings;\n const dataset = s.dropdownOpts.source && this.ddApi ? this.ddApi.dataset : s.dataset;\n return { idx: args.index(), item: args, data: dataset[args.index()] };\n },\n\n /**\n * Get current filter type\n * @returns {object} The current filter type\n */\n getFilterType() {\n this.setFiltered();\n return this.filtered;\n },\n\n /**\n * Set filter type to given value\n * @param {number|string} value to be set, index or value.\n * @returns {void}\n */\n setFilterType(value) {\n if (this.ddApi) {\n let newIdx = -1;\n const s = this.settings;\n const dataset = s.dropdownOpts.source && this.ddApi ? this.ddApi.dataset : s.dataset;\n\n if (typeof value === 'number' && value > -1 && value < dataset.length) {\n newIdx = value;\n } else if (typeof value === 'string') {\n let option = this.ffdropdown.find(`option[value=\"${value}\"]`);\n if (!option.length) {\n option = this.ffdropdown.find('option').filter(function () {\n return $(this).text() === value;\n });\n }\n if (option.length) {\n newIdx = option.index();\n }\n }\n\n // Make filtered\n if (newIdx !== -1 && newIdx !== this.ffdropdown[0].selectedIndex) {\n this.ffdropdown[0].selectedIndex = newIdx;\n this.ddApi.updated();\n this.ffdropdown.triggerHandler('change');\n this.setFiltered();\n this.element.triggerHandler('filtered', [this.filtered]);\n }\n }\n },\n\n /**\n * Attach Events used by the Control\n * @private\n * @returns {object} The api\n */\n handleEvents() {\n this.ffdropdown\n .on(`listopened.${COMPONENT_NAME}`, () => {\n // drowpdownWidth - border (52)\n const extra = this.field.is('.field-short') ? 42 : 52;\n $('#dropdown-list ul').width(this.element.outerWidth() + extra);\n })\n .on(`selected.${COMPONENT_NAME}`, (e, args) => {\n /**\n * Fires after the value in the dropdown is selected.\n * @event filtered\n * @memberof FieldFilter\n * @property {object} event The jquery event object.\n * @property {object} data for selected item.\n */\n const triggerData = this.getTriggerData(args);\n this.element.triggerHandler('filtered', [triggerData]);\n });\n\n return this;\n }, // END: Handle Events -------------------------------------------------\n\n /**\n * Set component to readonly.\n * @returns {object} The api\n */\n readonly() {\n this.ffdropdown.readonly();\n return this;\n },\n\n /**\n * Set component to enabled.\n * @returns {object} The api\n */\n enable() {\n this.ffdropdown.enable();\n return this;\n },\n\n /**\n * Set component to disabled.\n * @returns {object} The api\n */\n disable() {\n this.ffdropdown.disable();\n return this;\n },\n\n /**\n * Removes event bindings from the instance.\n * @private\n * @returns {object} The api\n */\n unbind() {\n this.ffdropdown.off(`.${COMPONENT_NAME}`);\n\n // Remove Dropdown\n if (this.ddApi && typeof this.ddApi.destroy === 'function') {\n this.ddApi.destroy();\n }\n this.ffdropdown.add(this.ffdropdown.prev('label')).remove();\n\n return this;\n },\n\n /**\n * Resync the UI and Settings.\n * @param {object} settings The settings to apply.\n * @returns {object} The api\n */\n updated(settings) {\n if (typeof settings !== 'undefined') {\n this.settings = utils.mergeSettings(this.element[0], settings, FIELDFILTER_DEFAULTS);\n }\n return this\n .unbind()\n .init();\n },\n\n /**\n * Teardown process for this plugin\n * @returns {void}\n */\n destroy() {\n this.unbind();\n $.removeData(this.element[0], COMPONENT_NAME);\n }\n};\n\nexport { FieldFilter, COMPONENT_NAME };\n","import { FieldFilter, COMPONENT_NAME } from './field-filter';\n\n/**\n * jQuery Component Wrapper for FieldFilter\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.fieldfilter = function (settings) {\n return this.each(function () {\n let instance = $.data(this, COMPONENT_NAME);\n if (instance) {\n instance.updated(settings);\n } else {\n instance = $.data(this, COMPONENT_NAME, new FieldFilter(this, settings));\n }\n });\n};\n","import * as debug from '../../utils/debug';\nimport { utils } from '../../utils/utils';\nimport { Environment as env } from '../../utils/environment';\nimport { warnAboutRemoval } from '../../utils/deprecated';\n\n// Component Name\nconst COMPONENT_NAME = 'fieldoptions';\n\n/**\n* A control bind next to another component to add some extra functionality.\n* @class FieldOptions\n* @deprecated as of v4.20.0. This component is no longer supported by the IDS team.\n* @constructor\n*\n* @param {jQuery[]|HTMLElement} element The component element.\n* @param {object} [settings] The component settings.\n*/\nconst FIELDOPTIONS_DEFAULTS = {\n};\n\nfunction FieldOptions(element, settings) {\n this.element = $(element);\n this.settings = utils.mergeSettings(this.element[0], settings, FIELDOPTIONS_DEFAULTS);\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n warnAboutRemoval('FieldOptions');\n}\n\n// FieldOptions Methods\nFieldOptions.prototype = {\n\n init() {\n this.setElements();\n this.handleEvents();\n },\n\n /**\n * Set all elements used by the Control\n * @private\n * @returns {object} The api\n */\n setElements() {\n this.isFirefox = env.browser.name === 'firefox';\n this.isSafari = env.browser.isSafari();\n\n this.field = this.element.closest('.field, .radio-group');\n this.targetElem = this.element;\n\n const label = this.field.find('label');\n if (label) {\n this.label = label;\n }\n\n // In some cases, adjust the target element\n if (this.element[0].className.match(/(dropdown|multiselect)/)) {\n this.targetElem = this.element.data('dropdown').pseudoElem;\n }\n if (this.element[0].className.match(/(fileupload)/)) {\n this.targetElem = this.field.find('.fileupload[type=\"text\"]');\n }\n\n this.field.addClass('is-fieldoptions');\n\n this.fieldParent = this.element.closest('.field').parent();\n this.trigger = this.field.find('.btn-actions');\n\n // Fix: Some reason firfox \"event.relatedTarget\" not working\n // with un-focusable elements(ie.. div) on focusout, use \"contentEditable\"\n // https://stackoverflow.com/a/43010274\n if (this.isFirefox && this.trigger.length) {\n this.trigger[0].contentEditable = true;\n this.trigger.on(`keydown.${COMPONENT_NAME}`, (e) => {\n const key = e.which || e.keyCode || e.charCode || 0;\n if (key !== 9) {\n e.preventDefault();\n e.stopPropagation();\n }\n });\n }\n\n // Adjust some setting for popupmenu this trigger(action button)\n setTimeout(() => {\n this.popupmenuApi = this.trigger.data('popupmenu');\n if (this.popupmenuApi) {\n this.popupmenuApi.settings.returnFocus = false;\n this.popupmenuApi.settings.offset.y = 10;\n }\n }, 100);\n\n return this;\n },\n\n /**\n * Attach Events used by the Control\n * @private\n * @returns {object} The api\n */\n handleEvents() {\n const self = this;\n const datepicker = this.element.data('datepicker');\n const timepicker = this.element.data('timepicker');\n const dropdown = this.element.data('dropdown');\n const lookup = this.element.data('lookup') || this.element.hasClass('lookup');\n const isCheckbox = this.element.is('.checkbox');\n const isFileupload = this.element.is('.fileupload');\n const isSearchfield = this.element.is('.searchfield');\n const isColorpicker = this.element.is('.colorpicker');\n const isRadio = this.element.closest('.radio-group').length > 0;\n const isFieldset = this.element.is('.data') && this.element.closest('.summary-form').length > 0;\n\n // Helper functions\n const isFocus = elem => $(':focus').is(elem);\n const addFocused = (elem) => {\n (elem || this.element).addClass('is-focused');\n };\n const removeFocused = (elem) => {\n (elem || this.element).removeClass('is-focused');\n };\n const canActive = () => {\n let r = isFocus(this.element);\n r = datepicker && datepicker.isOpen() ? false : r;\n r = timepicker && timepicker.isOpen() ? false : r;\n r = dropdown && dropdown.isOpen() ? false : r;\n return r;\n };\n const doActive = () => {\n self.element.add(self.trigger).add(self.field).add(self.fieldParent)\n .addClass('is-active');\n };\n const doUnactive = () => {\n self.element.add(self.trigger).add(self.field).add(self.fieldParent)\n .removeClass('is-active');\n };\n const canUnactive = (e) => {\n let r = !isFocus(this.element);\n r = this.trigger.is(e.relatedTarget) ? false : r;\n r = this.trigger.is('.is-open') ? false : r;\n r = datepicker && datepicker.isOpen() ? false : r;\n r = timepicker && timepicker.isOpen() ? false : r;\n r = $(e.relatedTarget).prev().is(this.element) ? false : r;\n r = dropdown && dropdown.isOpen() ? false : r;\n r = lookup && lookup.modal && lookup.modal.isOpen() ? false : r;\n r = isColorpicker && this.element.is('.is-open') ? false : r;\n return r;\n };\n const onPopupToggle = (elem) => {\n if (elem.trigger) {\n elem.trigger\n .off(`show.${COMPONENT_NAME}`).on(`show.${COMPONENT_NAME}`, () => {\n doActive();\n })\n .off(`hide.${COMPONENT_NAME}`).on(`hide.${COMPONENT_NAME}`, (e) => {\n if (canUnactive(e)) {\n doUnactive();\n this.element.removeClass('is-open');\n }\n });\n }\n };\n const getTriggerTopVal = () => {\n const height = this.element.height();\n let returns;\n\n if (isFieldset) {\n const lineHeight = parseInt(this.element.css('line-height'), 10);\n if (height > lineHeight) {\n this.element.removeClass('is-singleline');\n returns = (((this.element.outerHeight() - this.trigger.height()) / 2) + 0) * -1;\n const diff = (lineHeight - 16);\n returns += diff ? (diff / 2) : 0;\n } else {\n this.element.addClass('is-singleline');\n returns = 2;\n }\n } else if (isRadio) {\n returns = (((height - 10) - this.trigger.height()) / 2) * -1;\n }\n return returns;\n };\n const setTriggerCssTop = () => {\n this.trigger.css({ top: `${getTriggerTopVal() - 1}px` });\n };\n\n // Set field-options visibility.\n // In touch environments, the button should always be visible.\n // In desktop environments, the button should only display when the field is in use.\n if (env.features.touch) {\n this.field.addClass('visible');\n this.trigger.on(`beforeopen.${COMPONENT_NAME}`, (e) => {\n if (!canActive(e)) {\n return;\n }\n doActive();\n }).on(`close.${COMPONENT_NAME}`, (e) => {\n if (!canUnactive(e)) {\n return;\n }\n doUnactive();\n });\n } else {\n this.field.removeClass('visible');\n this.field\n .on(`mouseover.${COMPONENT_NAME}`, () => {\n if (self.element.prop('disabled') || self.element.closest('is-disabled').length) {\n return;\n }\n\n if (self.field[0].className.indexOf('visible') < 0) {\n self.field[0].classList.add('visible');\n }\n })\n .on(`mouseout.${COMPONENT_NAME}`, () => {\n if (self.field[0].className.indexOf('visible') > -1) {\n self.field[0].classList.remove('visible');\n }\n });\n }\n\n // Adjust stack order for dropdown\n if (dropdown) {\n setTimeout(() => {\n const popupmenu = this.trigger.data('popupmenu');\n if (popupmenu) {\n popupmenu.menu.closest('.popupmenu-wrapper').css({ 'z-index': '4502' });\n }\n }, 0);\n }\n // Bind active/unactive on show datepicker or timepicker\n if (datepicker || timepicker) {\n if (datepicker) {\n onPopupToggle(datepicker);\n } else {\n onPopupToggle(timepicker);\n }\n }\n // Adjust return focus for timepicker\n if (timepicker) {\n timepicker.settings.returnFocus = false;\n }\n // Move trigger(action-button) in to lookup-wrapper\n if (lookup || isColorpicker) {\n this.field.on(`click.${COMPONENT_NAME}`, '.lookup-wrapper .trigger, .colorpicker-container .trigger', () => {\n doActive();\n });\n\n if (isColorpicker) {\n this.element\n .on(`beforeopen.${COMPONENT_NAME}`, () => {\n doActive();\n });\n }\n }\n // Checkbox add parent css class\n if (isCheckbox) {\n this.trigger.addClass('is-checkbox');\n if (!env.features.touch && this.isSafari) {\n this.field.on(`click.${COMPONENT_NAME}`, '.checkbox-label', () => {\n doActive();\n }).on(`mouseout.${COMPONENT_NAME}`, '.checkbox-label', () => {\n doUnactive();\n });\n }\n }\n // Bind fileupload events\n if (isFileupload) {\n this.element.on(`change.${COMPONENT_NAME}`, () => {\n this.targetElem.focus();\n });\n this.field.on(`click.${COMPONENT_NAME}`, '.trigger, .trigger-close', () => {\n doActive();\n });\n }\n // Move trigger(action-button) in to searchfield-wrapper\n if (isSearchfield) {\n setTimeout(() => {\n this.trigger.add(this.trigger.next('.popupmenu'))\n .appendTo(this.element.closest('.searchfield-wrapper'));\n }, 0);\n }\n // Fieldset - set trigger(action-button) top value and bind events\n if (isFieldset) {\n setTriggerCssTop();\n this.targetElem.add(this.trigger).on(`keydown.${COMPONENT_NAME}`, (e) => {\n const key = e.which || e.keyCode || e.charCode || 0;\n if (key === 13) {\n setTimeout(() => {\n doActive();\n }, 0);\n }\n });\n this.targetElem.attr('tabindex', 0)\n .on(`click.${COMPONENT_NAME}`, () => {\n doActive();\n });\n $(document).on(`click.${COMPONENT_NAME}`, (e) => {\n if (!$(e.target).is(this.element)) {\n doUnactive();\n }\n });\n $('body').on(`resize.${COMPONENT_NAME}`, () => {\n setTriggerCssTop();\n });\n }\n // Radio group - set trigger(action-button) top value and bind events\n if (isRadio) {\n setTriggerCssTop();\n this.element\n .on(`focusin.${COMPONENT_NAME}`, '.radio', () => {\n const delay = this.isSafari ? 200 : 0;\n addFocused();\n setTimeout(() => {\n doActive();\n }, delay);\n })\n .on(`focusout.${COMPONENT_NAME}`, '.radio', () => {\n removeFocused();\n });\n $('body').on(`resize.${COMPONENT_NAME}`, () => {\n setTriggerCssTop();\n });\n }\n\n // Element events\n this.targetElem\n .on(`focusin.${COMPONENT_NAME}`, () => {\n doActive();\n if (isRadio && this.isSafari) {\n addFocused();\n }\n })\n .on(`focusout.${COMPONENT_NAME}`, (e) => {\n const delay = this.isSafari ? 200 : 0;\n if (isRadio && this.isSafari) {\n removeFocused();\n }\n setTimeout(() => {\n if (canUnactive(e)) {\n doUnactive();\n }\n }, delay);\n });\n\n // Trigger(action button) events\n this.trigger\n .on(`focusin.${COMPONENT_NAME} click.${COMPONENT_NAME}`, () => {\n doActive();\n })\n .on(`focusout.${COMPONENT_NAME}`, (e) => {\n if (canUnactive(e)) {\n doUnactive();\n }\n })\n .on(`selected.${COMPONENT_NAME}`, () => {\n this.popupmenuApi.settings.returnFocus = true;\n })\n .on(`close.${COMPONENT_NAME}`, (e) => {\n if (canUnactive(e)) {\n doUnactive();\n }\n });\n\n // FIX: Safari - by default does not get focus on some elements while using tab key\n // https://stackoverflow.com/a/29106095\n if (this.isSafari || isFileupload) {\n if (isRadio) {\n this.element.attr('tabindex', 0);\n }\n this.targetElem.on(`keydown.${COMPONENT_NAME}`, (e) => {\n const key = e.which || e.keyCode || e.charCode || 0;\n if (key === 9 && !e.shiftKey) {\n if (isRadio) {\n this.targetElem.find(':checked, .radio:first').not(':disabled').focus();\n this.targetElem.find('.radio')\n .off(`keydown.${COMPONENT_NAME}`).on(`keydown.${COMPONENT_NAME}`, (e2) => {\n const key2 = e2.which || e2.keyCode || e2.charCode || 0;\n if (key2 === 9 && !e.shiftKey) {\n setTimeout(() => {\n this.trigger.focus();\n }, 0);\n }\n });\n } else {\n this.trigger.focus();\n }\n doActive();\n e.preventDefault();\n e.stopPropagation();\n }\n });\n }\n\n this.element\n .on(`listopened.${COMPONENT_NAME}`, () => {\n doActive();\n })\n .on(`listclosed.${COMPONENT_NAME}`, () => {\n doUnactive();\n });\n\n return this;\n }, // END: Handle Events -------------------------------------------------\n\n /**\n * Set component to enabled.\n * @returns {object} The api\n */\n enable() {\n this.trigger.prop('disabled', false);\n return this;\n },\n\n /**\n * Set component to disabled.\n * @returns {object} The api\n */\n disable() {\n this.trigger.prop('disabled', true);\n return this;\n },\n\n /**\n * Removes event bindings from the instance.\n * @private\n * @returns {object} The api\n */\n unbind() {\n this.field.off([\n `click.${COMPONENT_NAME}`,\n `mouseover.${COMPONENT_NAME}`,\n `mouseout.${COMPONENT_NAME}`\n ].join(' '));\n\n this.element.off([\n `beforeopen.${COMPONENT_NAME}`,\n `change.${COMPONENT_NAME}`,\n `focusin.${COMPONENT_NAME}`,\n `focusout.${COMPONENT_NAME}`,\n `listclosed.${COMPONENT_NAME}`,\n `listopened.${COMPONENT_NAME}`\n ].join(' '));\n\n this.trigger.off([\n `beforeopen.${COMPONENT_NAME}`,\n `click.${COMPONENT_NAME}`,\n `focusin.${COMPONENT_NAME}`,\n `focusout.${COMPONENT_NAME}`,\n `selected.${COMPONENT_NAME}`,\n `close.${COMPONENT_NAME}`\n ].join(' '));\n\n this.targetElem.off([\n `click.${COMPONENT_NAME}`,\n `keydown.${COMPONENT_NAME}`\n ].join(' '));\n\n $('body').off([\n `resize.${COMPONENT_NAME}`\n ].join(' '));\n\n $(document).off([\n `click.${COMPONENT_NAME}`\n ].join(' '));\n\n return this;\n },\n\n /**\n * Resync the UI and Settings.\n * @param {object} settings The settings to apply.\n * @returns {object} The api\n */\n updated(settings) {\n if (typeof settings !== 'undefined') {\n this.settings = utils.mergeSettings(this.element, settings, FIELDOPTIONS_DEFAULTS);\n }\n return this\n .unbind()\n .init();\n },\n\n /**\n * Teardown process for this plugin\n * @returns {void}\n */\n destroy() {\n this.unbind();\n $.removeData(this.element[0], COMPONENT_NAME);\n }\n};\n\nexport { FieldOptions, COMPONENT_NAME };\n","import { FieldOptions, COMPONENT_NAME } from './field-options';\n\n/**\n * jQuery Component Wrapper for FieldOptions\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.fieldoptions = function (settings) {\n return this.each(function () {\n let instance = $.data(this, COMPONENT_NAME);\n if (instance) {\n instance.updated(settings);\n } else {\n instance = $.data(this, COMPONENT_NAME, new FieldOptions(this, settings));\n }\n });\n};\n","import { Environment as env } from '../../utils/environment';\nimport * as debug from '../../utils/debug';\nimport { utils } from '../../utils/utils';\nimport { Locale } from '../locale/locale';\n\n// Component Name\nconst COMPONENT_NAME = 'fileupload';\n\n/**\n* A list of items with add/remove/delete and sort functionality.\n* @class FileUpload\n* @constructor\n*\n* @param {jQuery[]|HTMLElement} element The component element.\n* @param {object} [settings] The component settings.\n*/\n\nconst FILEUPLOAD_DEFAULTS = {\n};\n\nfunction FileUpload(element, settings) {\n this.element = $(element);\n this.settings = utils.mergeSettings(this.element[0], settings, FILEUPLOAD_DEFAULTS);\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n}\n\n// FileUpload Methods\nFileUpload.prototype = {\n\n init() {\n this.build();\n },\n\n // Example Method\n build() {\n const self = this;\n const elem = this.element;\n const hasInlineLabel = !elem.is('input.fileupload');\n\n this.fileInput = hasInlineLabel ? elem.find('input') : elem;\n\n elem.closest('.field, .field-short').addClass('field-fileupload');\n\n // append markup\n let id = elem.find('input').attr('name');\n if (!hasInlineLabel) {\n id = elem.attr('id') || elem.attr('name');\n }\n\n let elemClass = !hasInlineLabel ? elem.attr('class') : elem.find('input').attr('class');\n elemClass = elemClass ? ` ${elemClass}` : '';\n\n const instructions = Locale.translate('FileUpload');\n this.shadowLabel = $(``);\n this.shadowField = $(``);\n const svg = `${$.createIcon('folder')}`;\n const svgClose = `${$.createIcon('close')}`;\n\n if (!hasInlineLabel) {\n let orgLabel = elem.prev('label');\n\n // Could be wrapped (angular)\n if (orgLabel.length === 0) {\n orgLabel = elem.parent().prev('label');\n }\n\n if (orgLabel.hasClass('required')) {\n this.shadowLabel.addClass('required');\n }\n\n this.shadowLabel.html(`${orgLabel.text()} ${instructions}`);\n orgLabel.addClass('audible').add(this.fileInput).attr('tabindex', '-1').attr('aria-hidden', 'true');\n }\n\n if (elem.parent().find('input[type=\"text\"]').length === 0) {\n elem.before(this.shadowLabel, this.shadowField);\n this.fileInput.after(svg, svgClose);\n }\n\n // if there is a value attribute, then this will be used as the current value since unable to set files[0].name\n // move it to the text input and remove it off the file input\n const fileInputValue = this.fileInput.attr('value');\n if (fileInputValue && fileInputValue.length > 0) {\n this.shadowField.val(fileInputValue);\n this.fileInput.attr('value', '');\n }\n\n this.textInput = this.shadowField;\n this.svg = elem.parent().find('.trigger');\n this.svgClose = elem.parent().find('.trigger-close');\n\n /*\n * Added Keydown for Keyboard Backspace and remove Keypress because it doesn't detect Backspace\n */\n this.textInput.on('keydown.fileupload', (e) => {\n let handle = false;\n if (e.which === 13 || e.which === 32) {\n elem.parent().find('[type=\"file\"]').trigger('click');\n handle = true;\n } else if (e.which === 8) {\n if (env.browser.isIE11()) e.preventDefault();\n this.clearUploadFile();\n handle = true;\n }\n if (handle) {\n e.stopPropagation();\n }\n });\n\n this.svg.on('click.fileupload', (e) => {\n this.fileInput.trigger('click');\n if (hasInlineLabel) {\n this.fileInput.data(`handleEvent${[(e.type || '')]}`, e.handleObj);\n }\n });\n\n this.svgClose.on('click.fileupload', (e) => {\n this.clearUploadFile();\n this.svgClose.removeClass('is-visible');\n if (hasInlineLabel) {\n this.fileInput.data(`handleEvent +${[(e.type || '')]}`, e.handleObj);\n }\n });\n\n if (this.fileInput.is(':disabled')) {\n this.textInput.prop('disabled', true);\n }\n\n if (elem.hasClass('required')) {\n this.shadowLabel.addClass('required');\n elem.removeClass('required');\n }\n\n if (this.fileInput.attr('data-validate')) {\n this.textInput.attr('data-validate', this.fileInput.attr('data-validate'));\n this.textInput.validate();\n this.shadowLabel.addClass(this.fileInput.attr('data-validate'));\n }\n\n if (this.fileInput.attr('readonly')) {\n this.textInput.prop('disabled', false);\n this.textInput[0].classList.remove('fileupload-background-transparent');\n this.fileInput.attr('disabled', 'disabled');\n }\n\n /*\n * New Event for File Upload Change\n */\n this.fileInput.on('change.fileupload', function () {\n if (this.files.length > 0) {\n self.textInput.val(this.files[0].name).trigger('change');\n self.svgClose.addClass('is-visible');\n } else if (!self.clearing) {\n self.clearUploadFile();\n }\n });\n\n // Fix - Not to bubble events when clicked on trigger/close icons\n this.fileInput.on('click.fileupload', (e) => {\n const handleEventData = this.fileInput.data(`handleEvent${[(e.type || '')]}`);\n if (handleEventData &&\n handleEventData.type === e.type &&\n e.handleObj.namespace === 'fileupload') {\n this.fileInput.data(`handleEvent${[(e.type || '')]}`, null);\n e.preventDefault();\n }\n });\n\n // Support Drag and Drop\n this.textInput.on('dragenter.fileupload', () => {\n this.fileInput.css('z-index', '1');\n });\n\n this.textInput.on('dragleave.fileupload, dragend.fileupload, drop.fileupload', () => {\n setTimeout(() => {\n this.fileInput.css('z-index', '-1');\n }, 1);\n });\n\n // Add test automation ids\n utils.addAttributes(elem, this, this.settings.attributes);\n utils.addAttributes(this.svg, this, this.settings.attributes, 'btn-trigger');\n utils.addAttributes(this.svgClose, this, this.settings.attributes, 'btn-trigger-close');\n },\n\n /*\n * Clear the Input Upload File\n */\n clearUploadFile() {\n this.clearing = true;\n this.fileInput.add(this.textInput).val('');\n this.svgClose.removeClass('is-visible');\n this.fileInput.triggerHandler('change');\n this.clearing = false;\n },\n\n // Unbind all events\n unbind() {\n this.svg.add(this.svgClose).off('click.fileupload');\n this.fileInput.off('change.fileupload');\n this.fileInput.prev('label');\n this.textInput.off();\n\n this.element.closest('.field-fileupload')\n .removeClass('field-fileupload')\n .find('>label:first, >[type=\"text\"]:first, .trigger, .trigger-close, .icon-dirty, .msg-dirty').remove();\n\n return this;\n },\n\n /**\n * Resync the UI and Settings.\n * @param {object} settings The settings to apply.\n * @returns {object} The api\n */\n updated(settings) {\n if (typeof settings !== 'undefined') {\n this.settings = utils.mergeSettings(this.element, settings, FILEUPLOAD_DEFAULTS);\n }\n // Nothing to do here as there are no settings.\n return this;\n },\n\n /**\n * Teardown process for this plugin\n * @returns {void}\n */\n destroy() {\n this.unbind();\n this.shadowField.remove();\n this.shadowLabel.remove();\n $.removeData(this.element[0], COMPONENT_NAME);\n },\n\n /**\n * Disable the input and button.\n * @returns {void}\n */\n disable() {\n this.textInput.prop('disabled', true);\n this.fileInput.prop('disabled', true);\n },\n\n /**\n * Enable the input and button.\n * @returns {void}\n */\n enable() {\n this.textInput.prop('disabled', false).prop('readonly', false);\n this.fileInput.removeAttr('disabled');\n },\n\n /**\n * Make the input readonly and disable the button.\n * @returns {void}\n */\n readonly() {\n this.textInput.prop('readonly', true);\n this.fileInput.prop('disabled', true);\n\n this.textInput.prop('disabled', false);\n this.textInput.removeClass('fileupload-background-transparent');\n }\n\n};\n\nexport { FileUpload, COMPONENT_NAME };\n","import { FileUpload, COMPONENT_NAME } from './fileupload';\n\n/**\n * jQuery Component Wrapper for FileUpload\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.fileupload = function (settings) {\n return this.each(function () {\n let instance = $.data(this, COMPONENT_NAME);\n\n if (instance) {\n instance.updated(settings);\n } else {\n instance = $.data(this, COMPONENT_NAME, new FileUpload(this, settings));\n }\n });\n};\n","import * as debug from '../../utils/debug';\nimport { utils } from '../../utils/utils';\nimport { DOM } from '../../utils/dom';\nimport { Locale } from '../locale/locale';\n\n// Component Name\nconst COMPONENT_NAME = 'fileuploadadvanced';\n\n/**\n* A trigger field for uploading a single file.\n* @class FileUploadAdvanced\n* @constructor\n*\n* @param {jQuery[]|HTMLElement} element The component element.\n* @param {object} [settings] The component settings.\n* @param {boolean} [settings.isStandalone=true] On page(true)|on modal(false), used for some visual style only.\n* @param {string} [settings.standaloneClass='standalone'] Css class if on page.\n* @param {string} [settings.allowedTypes='*'] Restrict file types(ie. 'jpg|png|gif') ['*' all types]\n* @param {number} [settings.maxFiles=99999] Max number of files can be uploaded\n* @param {number} [settings.maxFilesInProcess=99999] Max number of files can be uploaded while in process\n* @param {number} [settings.maxFileSize=-1] Max file size in bytes, -1 for unlimited\n* @param {string} [settings.fileName='myfile'] Variable name to read from server\n* @param {boolean} [settings.isDisabled=false] Make control disabled\n* @param {boolean} [settings.showBrowseButton=true] Add way to browse files to upload\n* @param {Function} [settings.send] Method for send file to upload\n* @param {string} [settings.textDropArea] Text to show in drop area\n* @param {string} [settings.textDropAreaWithBrowse] Text to show in drop area when browse option true\n* @param {string} [settings.textBtnCancel] Hidden text for cancel button\n* @param {string} [settings.textBtnCloseError] Hidden text for error close button\n* @param {string} [settings.textBtnRemove] Hidden text for remove button\n* @param {string} [settings.errorAllowedTypes] Error text for allowed types\n* @param {string} [settings.errorMaxFileSize] Error text for max file size\n* @param {string} [settings.errorMaxFiles] Error text for max files to upload\n* @param {string} [settings.errorMaxFilesInProcess] Error text for max files in process\n*/\n\nconst FILEUPLOADADVANCED_DEFAULTS = {\n isStandalone: true, //\n standaloneClass: 'standalone', // css class if on page\n allowedTypes: '*', // restrict file types(ie. 'jpg|png|gif') ['*' all types]\n maxFiles: 99999, // max files can be upload\n maxFilesInProcess: 99999, // max files can be upload while in process\n maxFileSize: -1, // max file size in bytes, -1 for unlimited\n fileName: 'myfile', // variable name to read from server\n isDisabled: false, // Disabled\n showBrowseButton: true, // Browse files to upload\n send: null, // Function to send files to server\n\n // Text strings\n textDropArea: null,\n textDropAreaWithBrowse: null,\n textBtnCancel: null,\n textBtnCloseError: null,\n textBtnRemove: null,\n\n // Error strings\n errorAllowedTypes: null,\n errorMaxFileSize: null,\n errorMaxFiles: null,\n errorMaxFilesInProcess: null\n};\n\nfunction FileUploadAdvanced(element, settings) {\n this.element = $(element);\n this.settings = utils.mergeSettings(this.element[0], settings, FILEUPLOADADVANCED_DEFAULTS);\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n}\n\n// FileUploadAdvanced Methods\nFileUploadAdvanced.prototype = {\n\n init() {\n this.build();\n this.handleEvents();\n return this;\n },\n\n /**\n * Add markup\n * @private\n * @returns {void}\n */\n build() {\n const s = this.settings;\n let html;\n let cssClassList = s.isStandalone ? s.standaloneClass : '';\n\n // Re-evaluate strings\n s.textDropArea = s.textDropArea || Locale.translate('TextDropArea');\n s.textDropAreaWithBrowse = s.textDropAreaWithBrowse || Locale.translate('TextDropAreaWithBrowse');\n s.textBtnCancel = s.textBtnCancel || Locale.translate('TextBtnCancel');\n s.textBtnCloseError = s.textBtnCloseError || Locale.translate('TextBtnCloseError');\n s.textBtnRemove = s.textBtnRemove || Locale.translate('TextBtnRemove');\n s.errorAllowedTypes = s.errorAllowedTypes || `${Locale.translate('Error')}: ${Locale.translate('ErrorAllowedTypes')}`;\n s.errorMaxFileSize = s.errorMaxFileSize || `${Locale.translate('Error')}: ${Locale.translate('ErrorMaxFileSize')}`;\n s.errorMaxFiles = s.errorMaxFiles || `${Locale.translate('Error')}: ${Locale.translate('ErrorMaxFiles')}`;\n s.errorMaxFiles = s.errorMaxFiles.replace('{n}', s.maxFiles);\n s.errorMaxFilesInProcess = s.errorMaxFilesInProcess || `${Locale.translate('Error')}: ${Locale.translate('ErrorMaxFilesInProcess')}`;\n\n // Disabled\n if (this.element.is('.is-disabled')) {\n s.isDisabled = true;\n }\n if (s.isDisabled) {\n cssClassList += ' is-disabled';\n }\n\n // Browse files option\n if (s.showBrowseButton) {\n let types = '';\n const id = utils.uniqueId(this.element, 'fileupload-adv-');\n const fileExtensions = s.allowedTypes.split(/[\\s|]+/g);\n let isExtra = s.maxFilesInProcess > 1 ? ' multiple' : '';\n isExtra += s.isDisabled ? ' disabled' : '';\n\n if (fileExtensions.length === 1) {\n if (fileExtensions[0] !== '*') {\n types = `.${fileExtensions[0]}`;\n }\n } else {\n for (let i = 0, l = fileExtensions.length; i < l; i++) {\n types += `.${(fileExtensions[i] + (i !== (l - 1) ? ',' : ''))}`;\n }\n }\n\n html = '' +\n `
                  \n
                  \n ${$.createIcon('upload')}\n \n
                  \n
                  `;\n } else {\n // Without browse files option\n\n html = '' +\n `
                  \n
                  \n ${$.createIcon('upload')}\n

                  ${s.textDropArea}

                  \n
                  \n
                  `;\n }\n\n DOM.append(this.element, html, '
                  ');\n this.dropArea = $('.drop-area', this.element);\n\n // Add test automation ids\n utils.addAttributes(this.dropArea, this, this.settings.attributes);\n utils.addAttributes(this.dropArea.find('svg'), this, this.settings.attributes, 'icon');\n utils.addAttributes(this.dropArea.find('label'), this, this.settings.attributes, 'label');\n },\n\n /**\n * Attach Events used by the Control\n * @private\n * @returns {void}\n */\n handleEvents() {\n const self = this;\n const s = this.settings;\n\n this.dropArea\n // Drag enter\n .on('dragenter.fileuploadadvanced', function (e) {\n self.element.triggerHandler('filesdragenter');\n e.stopPropagation();\n e.preventDefault();\n\n if (s.isDisabled) {\n return;\n }\n $(this).addClass('hover');\n })\n\n // Drag over\n .on('dragover.fileuploadadvanced', (e) => {\n e.stopPropagation();\n e.preventDefault();\n })\n\n // Drop\n .on('drop.fileuploadadvanced', function (e) {\n const files = e.originalEvent.dataTransfer.files;\n e.preventDefault();\n if (s.isDisabled) {\n return;\n }\n\n /**\n * Fires when file/s drag and droped to drop area.\n *\n * @event filesdroped\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {array} files - List of files droped\n */\n self.element.triggerHandler('filesdroped', [files]);\n\n $(this).removeClass('hover is-focus');\n self.handleFileUpload(files);\n });\n\n if (s.showBrowseButton && !s.isDisabled) {\n const label = this.dropArea.find('.fileupload-adv-browse-lbl');\n const input = label.find('input[type=\"file\"]');\n\n // Only let open dialog if clicked on link or input\n label.click((e) => {\n if (!$(e.target).is('.hyperlink, input[type=\"file\"]')) {\n e.preventDefault();\n }\n });\n\n input.hideFocus();\n input\n .on('hidefocusremove.fileuploadadvanced', (e) => {\n e.stopPropagation();\n this.dropArea.addClass('is-focus');\n })\n .on('hidefocusadd.fileuploadadvanced', (e) => {\n e.stopPropagation();\n this.dropArea.removeClass('is-focus');\n })\n .on('change.fileuploadadvanced', function (e) {\n e.stopPropagation();\n self.handleFileUpload(this.files);\n this.value = '';\n });\n }\n\n // If the files are dropped outside the div, files will open in the browser window.\n // To avoid this prevent 'drop' event on document.\n $(document).on('dragenter.fileuploadadvanced dragover.fileuploadadvanced drop.fileuploadadvanced', (e) => {\n e.stopPropagation();\n e.preventDefault();\n\n if (e.type === 'dragover') {\n self.dropArea.removeClass('hover');\n }\n });\n },\n\n /**\n * Read the file contents using HTML5 FormData()\n * @param {object} files File object containing uploaded files.\n * @returns {void}\n */\n handleFileUpload(files) {\n if (!files?.length) {\n return;\n }\n\n const s = this.settings;\n\n // Clear previous errors in general area\n $('span.msg', this.element).closest('.error').remove();\n\n // Total files completed\n this.totalCompleted = this.totalCompleted || 0;\n\n // File list\n this.totalFileList = this.totalFileList || [];\n\n this.counter = this.counter || 0;\n\n // Currently files in progress\n const totalInProgress = $('.progress', this.element).length;\n\n // Max files can be upload\n const filesLen = this.totalCompleted + files.length + totalInProgress;\n if (filesLen > s.maxFiles) {\n this.showError(s.errorMaxFiles);\n return;\n }\n if (totalInProgress >= s.maxFilesInProcess) {\n this.showError(s.errorMaxFilesInProcess);\n return;\n }\n const fileName = s.fileName.replace('[]', '');\n\n /* eslint-disable no-continue */\n for (let i = 0, l = files.length; i < l; i++) {\n // Check if file type allowed\n if (!this.isFileTypeAllowed(files[i].name)) {\n this.showError(s.errorAllowedTypes, files[i]);\n continue;\n }\n\n // Check for max file size\n if (s.maxFileSize !== -1 && files[i].size > s.maxFileSize) {\n this.showError(s.errorMaxFileSize, files[i]);\n continue;\n }\n\n /**\n * Fires before create the progress status object.\n *\n * @event beforecreatestatus\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} file - file to set the status\n */\n this.element.triggerHandler('beforecreatestatus', [files[i]]);\n\n // use FormData API\n const fd = new FormData();\n fd.append(`${fileName}[]`, files[i]);\n\n const status = this.createStatus(files[i]);\n status.container.find('.status-icon .action').focus();\n\n this.totalFileList.push(status);\n\n /**\n * Fires after create the progress status object.\n *\n * @event aftercreatestatus\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} file - file to set the status\n */\n this.element.triggerHandler('aftercreatestatus', [files[i]]);\n\n if (typeof s.send === 'function') {\n s.send(fd, status);\n } else {\n this.sendFileToServer(fd, status);\n }\n }\n /* eslint-enable no-continue */\n\n if (s.showBrowseButton) {\n // Clear browse file input\n this.dropArea.find('.fileupload-adv-browse-lbl input[type=\"file\"]').val('');\n }\n },\n\n /**\n * Create status object\n * @param {object} file to create progress status.\n * @returns {object} contains file and status methods to access.\n */\n createStatus(file) {\n const self = this;\n const s = this.settings;\n const container = $('' +\n `
                  \n
                  \n \n \n \n ${file.name}\n
                  \n ${this.formatFileSize(file.size)} | 0%\n
                  \n
                  \n
                  \n \n \n \n
                  \n
                  `);\n\n const btnCancel = $('.action', container).button();\n const rightSide = $('.l-pull-right', container);\n const progressBar = $('.progress-bar', container).progress({ animationLength: 10 });\n const percent = $('.percent', container);\n const bar = $('#bar', container);\n\n const index = this.counter;\n\n // Add this container\n this.dropArea.after(container);\n\n const removeFromList = () => {\n if (this.totalFileList) {\n const idx = this.totalFileList.findIndex(element => element.index === index);\n if (idx >= 0) {\n this.totalFileList.splice(idx, 1);\n }\n }\n };\n\n // Update progress-bar\n const setProgress = (progress) => {\n /**\n * Fires when file progress status changes.\n *\n * @event fileprogress\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} status - `{ file, progress }`\n */\n this.element.triggerHandler('fileprogress', [{ file, progress }]);\n\n percent.text(progress <= 100 ? `${progress}%` : '');\n if (progress > 100) {\n bar.text('');\n }\n\n progressBar.attr('data-value', progress).triggerHandler('updated');\n };\n\n // Set abort action\n const setAbort = (jqxhr) => {\n btnCancel.on('click.fileuploadadvanced', () => {\n /**\n * Fires when file aborted.\n *\n * @event fileaborted\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} file - aborted\n */\n this.element.triggerHandler('fileaborted', [file]);\n if (jqxhr && typeof jqxhr.abort === 'function') {\n jqxhr.abort();\n }\n btnCancel.off('click.fileuploadadvanced');\n container.remove();\n self.totalCompleted--;\n removeFromList();\n });\n };\n\n // Set completed state\n const setCompleted = function (data) {\n data = data && typeof data.remove === 'function' ? data : { remove: () => {} };\n container.addClass('completed');\n\n // Add \"Completed\" icon\n btnCancel.after($.createIcon('check'));\n\n // Add \"Remove from server\" button\n rightSide.append('' +\n ``);\n\n // Set \"Remove from server\" button action\n $('.action', rightSide).button().on('click.fileuploadadvanced', function () {\n $(this).off('click.fileuploadadvanced');\n\n /**\n * Fires before the attached file is removed.\n *\n * @event beforefileremove\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} file uploaded\n */\n self.element.triggerHandler('beforefileremove', [file]);\n\n if (container.hasClass('completed')) {\n self.totalCompleted--;\n }\n\n container.remove();\n\n // TODO: server call for removing data\n data.remove();\n\n /**\n * Fires when attached file removed.\n *\n * @event fileremoved\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} file uploaded\n */\n self.element.triggerHandler('fileremoved', [file]);\n });\n\n // Remove Cancel button and progress-bar area\n progressBar.destroy();\n btnCancel.off('click.fileuploadadvanced');\n btnCancel.add(progressBar.closest('.progress-row')).remove();\n\n // Increment to total files completed\n self.totalCompleted++;\n removeFromList();\n /**\n * Fires when file complete uploading.\n *\n * @event filecompleteuploading\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} file uploaded\n */\n self.element.triggerHandler('filecompleteuploading', [file]);\n };\n\n const setFailed = function (error) {\n /**\n * Fires when file status is failed.\n *\n * @event filefailed\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} file - aborted\n */\n self.element.triggerHandler('filefailed', [{ error, file }]);\n removeFromList();\n container.remove();\n self.showError(error, file);\n };\n\n this.counter++;\n\n return { file, container, index, setProgress, setAbort, setCompleted, setFailed };\n },\n\n /**\n * Function you can implement to send data to the server.\n * @param {object} formData - Contains the form data / file data.\n * @param {object} status - Status of the upload operation\n * @returns {void}\n */\n sendFileToServer(formData, status) {\n const jqXHR = { abort: () => {} };\n const tempData = { remove: () => {} };\n let percent = 0;\n const total = parseFloat(status.file.size);\n /* eslint-disable new-cap */\n const timer = new $.fn.timer(() => {\n status.setCompleted(tempData);\n }, total);\n /* eslint-enable new-cap */\n\n $(timer.event)\n .on('update', (e, data) => {\n percent = Math.ceil((data.counter / total) * 100);\n status.setProgress(percent);\n });\n\n status.setAbort(jqXHR);\n },\n\n /**\n * Set status of a file in progress to failed\n * @public\n * @param {string} error Error message\n * @param {number} fileIndex Index of file in file list\n */\n setFailed(error, fileIndex = 0) {\n if (this.totalFileList && this.totalFileList.length > 0) {\n this.totalFileList[fileIndex].setFailed(error);\n }\n },\n\n /**\n * Show error on ui\n * @private\n * @param {string} error to display\n * @param {object} file contains the error.\n * @returns {void}\n */\n showError(error, file) {\n let container;\n const s = this.settings;\n\n if (error === s.errorMaxFiles || error === s.errorMaxFilesInProcess) {\n // This error show without file name or size in general area\n container = $('' +\n `
                  \n
                  \n \n ${$.createIcon({ classes: ['icon-error'], icon: 'error' })}\n \n ${error}\n
                  \n \n
                  \n
                  \n
                  `);\n } else {\n container = $('' +\n `
                  \n
                  \n \n ${$.createIcon({ classes: ['icon-error'], icon: 'error' })}\n \n ${file.name}\n
                  \n ${this.formatFileSize(file.size)}\n \n
                  \n
                  \n
                  \n

                  ${error}

                  \n
                  \n
                  `);\n }\n\n $('.action', container).button().on('click.fileuploadadvanced', () => {\n /**\n * Fires before the error message closes.\n *\n * @event beforeerrormessageremove\n * @memberof FileUploadAdvanced\n * @property {object} event - The jquery event object\n * @property {object} status - `{ error, file }`\n */\n this.element.triggerHandler('beforeerrormessageremove', [{ error, file }]);\n\n container.remove();\n });\n\n // Add this container\n this.dropArea.after(container);\n },\n\n /**\n * Check if file type allowed\n * @private\n * @param {string} fileName to check types\n * @returns {boolean} true if allowed to uploaded\n */\n isFileTypeAllowed(fileName) {\n const fileExtensions = this.settings.allowedTypes.toLowerCase().split(/[\\s|]+/g);\n const ext = fileName.split('.').pop().toLowerCase();\n if (this.settings.allowedTypes !== '*' && $.inArray(ext, fileExtensions) < 0) {\n return false;\n }\n return true;\n },\n\n /**\n * Helper function that formats the file sizes\n * @private\n * @param {number} bytes to be formated\n * @returns {string} formated to use in ui\n */\n formatFileSize(bytes) {\n const scale = {\n GB: 1000000000,\n MB: 1000000,\n KB: 1000\n };\n if (typeof bytes !== 'number') {\n return '';\n }\n if (bytes >= scale.GB) {\n return `${(bytes / scale.GB).toFixed(2)} GB`;\n }\n if (bytes >= scale.MB) {\n return `${(bytes / scale.MB).toFixed(2)} MB`;\n }\n return `${(bytes / scale.KB).toFixed(2)} KB`;\n },\n\n /**\n * Set component to enabled.\n * @returns {void}\n */\n enable() {\n this.settings.isDisabled = false;\n this.element\n .find('.fileupload-wrapper').removeClass('is-disabled')\n .find('.fileupload-adv-browse-lbl input[type=\"file\"]').removeAttr('disabled');\n },\n\n /**\n * Set component to disabled.\n * @returns {void}\n */\n disable() {\n this.settings.isDisabled = true;\n this.element\n .find('.fileupload-wrapper').addClass('is-disabled')\n .find('.fileupload-adv-browse-lbl input[type=\"file\"]').attr('disabled', 'disabled');\n },\n\n /**\n * Removes event bindings from the instance.\n * @private\n * @returns {void}\n */\n unbind() {\n this.dropArea.find('.fileupload-adv-browse-lbl input[type=\"file\"]').off('hidefocusremove.fileuploadadvanced hidefocusadd.fileuploadadvanced change.fileuploadadvanced');\n\n this.dropArea.off('dragenter.fileuploadadvanced dragover.fileuploadadvanced drop.fileuploadadvanced');\n $(document).off('dragenter.fileuploadadvanced dragover.fileuploadadvanced drop.fileuploadadvanced');\n $('.action', this.element).off('click.fileuploadadvanced');\n\n const wrapper = $('.fileupload-wrapper', this.element);\n wrapper.off().find('*').off();\n wrapper.remove();\n return this;\n },\n\n /**\n * Resync the UI and Settings.\n * @param {object} settings The settings to apply.\n * @returns {object} The api\n */\n updated(settings) {\n if (typeof settings !== 'undefined') {\n this.settings = utils.mergeSettings(this.element, settings, FILEUPLOADADVANCED_DEFAULTS);\n }\n return this\n .unbind()\n .init();\n },\n\n /**\n * Destroy and remove added markup, all events\n * @returns {void}\n */\n destroy() {\n this.unbind();\n $('.fileupload-wrapper', this.element).remove();\n $.removeData(this.element[0], COMPONENT_NAME);\n }\n};\n\nexport { FileUploadAdvanced, COMPONENT_NAME };\n\n/*\nERROR\n--------\nhttps://social.technet.microsoft.com/Forums/ie/en-US/ec3c0be0-0834-4873-8e94-700e9df9c822/edge-browser-drag-and-drop-files-not-working?forum=ieitprocurrentver\n\n*/\n","import { FileUploadAdvanced, COMPONENT_NAME } from './fileupload-advanced';\n\n/**\n * jQuery Component Wrapper for FileUpload Advanced\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.fileuploadadvanced = function (settings) {\n return this.each(function () {\n let instance = $.data(this, COMPONENT_NAME);\n if (instance) {\n instance.updated(settings);\n } else {\n instance = $.data(this, COMPONENT_NAME, new FileUploadAdvanced(this, settings));\n }\n });\n};\n","import * as debug from '../../utils/debug';\nimport { utils } from '../../utils/utils';\nimport { Locale } from '../locale/locale';\n\n// Default Settings\nconst COMPONENT_NAME = 'homepage';\n\n/**\n* The Homepage handles card layout at multiple breakpoints.\n*\n* @class Homepage\n* @constructor\n* @param {HTMLElement} element The component element.\n* @param {object} [settings] The component settings.\n* @param {boolean} [settings.animate] Disable animation during resize\n* @param {number} [settings.columns] Display in 3 (default) or 4 column layout\n* @param {string} [settings.easing]\n* @param {number} [settings.gutterSize]\n* @param {number} [settings.widgetWidth]\n* @param {number} [settings.widgetHeight]\n* @param {number} [settings.timeout]\n*/\nconst HOMEPAGE_DEFAULTS = {\n animate: true,\n columns: 3,\n editing: false,\n easing: 'blockslide',\n gutterSize: 16,\n widgetWidth: 360,\n widgetHeight: 368,\n useSmall: false,\n timeout: 100\n};\n\nfunction Homepage(element, settings) {\n this.settings = utils.mergeSettings(element, settings, HOMEPAGE_DEFAULTS);\n this.element = $(element);\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n}\n\n// Homepage Methods\nHomepage.prototype = {\n\n /**\n * @name Homepage#rowsAndCols\n * @type Array\n * @default []\n * @readonly\n * Stores information about the current number of rows and columns that make up the Homepage layout.\n * Each entry in the array\n */\n\n /**\n * @returns {object} containing information about the current state of the Homepages component.\n */\n get state() {\n let rows = this.rowsAndCols.length;\n const cols = rows ? this.rowsAndCols[0].length : 0;\n const settings = this.settings;\n\n const lastRow = this.rowsAndCols[rows - 1];\n if (lastRow.indexOf(false) === -1) {\n rows -= 1;\n }\n\n function getContainerHeight() {\n const topGutter = settings.gutterSize;\n return topGutter + ((settings.gutterSize) + (settings.widgetHeight)) * rows;\n }\n\n if (this.editing === undefined) {\n this.editing = settings.editing;\n }\n\n return {\n rows,\n cols,\n containerHeight: getContainerHeight(),\n matrix: this.rowsAndCols,\n blocks: this.blocks,\n editing: this.editing\n };\n },\n\n /**\n * @private\n * @returns {void}\n */\n init() {\n this.setSmall();\n this.setColumns();\n this.initHeroWidget();\n this.handleEvents();\n this.initEdit();\n\n // Initial Sizing\n setTimeout(() => { // Timeout to let rtl load first before the render of the resize\n this.resize(this, false);\n }, 100);\n this.element.parent().addClass('homepage-background');\n },\n\n /**\n * Set max number of columns can be shown.\n * @private\n * @returns {void}\n */\n setColumns() {\n let columns = this.settings.columns;\n const columnsByAttr = parseInt(this.element.attr('data-columns'), 10);\n if (!isNaN(columnsByAttr) && (columns !== columnsByAttr && columns === HOMEPAGE_DEFAULTS.columns)) {\n columns = columnsByAttr;\n }\n this.columns = columns;\n },\n\n /**\n * Set widget/card default height and width.\n * @private\n * @returns {void}\n */\n setSmall() {\n const s = this.settings;\n const smAttr = this.element.attr('data-use-small');\n if (s.useSmall || (smAttr && smAttr.toString().toLowerCase() === 'true')) {\n const d = HOMEPAGE_DEFAULTS;\n const small = { gutterSize: 24, widgetWidth: 260, widgetHeight: 260 };\n s.useSmall = true;\n\n if (s.gutterSize === d.gutterSize) {\n s.gutterSize = small.gutterSize;\n }\n if (s.widgetWidth === d.widgetWidth) {\n s.widgetWidth = small.widgetWidth;\n }\n if (s.widgetHeight === d.widgetHeight) {\n s.widgetHeight = small.widgetHeight;\n }\n }\n },\n\n /**\n * Initialize columns.\n * @private\n * @param {number} row to be initialize.\n * @returns {void}\n */\n initColumns(row) {\n row = row || 0;\n this.rowsAndCols[row] = [];\n\n for (let i = 0, l = this.columns; i < l; i++) {\n this.rowsAndCols[row][i] = true;// Make all columns available in first row[true]\n }\n },\n\n /**\n * Initialize hero widget.\n * @private\n * @returns {void}\n */\n initHeroWidget() {\n let heroWidget = $('.hero-widget');\n if (heroWidget.length > 1) {\n heroWidget = heroWidget.not(':first').remove();\n }\n this.heroWidget = heroWidget;\n },\n\n /**\n * Initialize rows and cols.\n * @private\n * @returns {void}\n */\n initRowsAndCols() {\n this.rowsAndCols = [];// Keeping all blocks as rows and columns\n this.initColumns();\n },\n\n /**\n * Initialize guide and drag event listeners.\n * @private\n * @returns {void}\n */\n initEdit() {\n const homepage = this;\n const cards = homepage.element.find('.card, .widget, .small-widget');\n if (homepage.editing === undefined) {\n homepage.editing = homepage.settings.editing;\n }\n if (homepage.editing) {\n cards.attr('draggable', true);\n cards.css('cursor', 'move');\n\n homepage.guide = $('
                  ').addClass('drop-indicator').append(`\n
                  \n
                  \n
                  \n `);\n\n cards.each((index, element) => {\n const card = $(element);\n const removeButton = $('\n `;\n\n return html;\n }\n\n // Remove all existing buttons\n $(this.numberButtons).remove();\n\n // First Button\n if (doRenderFirstButton) {\n buttonHTML += renderButton($.createIcon({ icon: 'first-page' }), Locale.translate('FirstPage'), this.settings.firstPageTooltip, null, 'pager-first', false, disableFirstButton, false);\n }\n\n // Previous Button\n if (doRenderPreviousButton) {\n buttonHTML += renderButton($.createIcon({ icon: 'previous-page' }), Locale.translate('PreviousPage'), this.settings.previousPageTooltip, null, 'pager-prev', false, disablePreviousButton, false);\n }\n\n // Draw all relevant page numbers, if applicable\n // Page Number Buttons are only rendered if there is visible space available to fit them.\n if (!this.isTable && hasDataset && !this.settings.showPageSelectorInput) {\n let numberButtonHTML = '';\n buttonsToRender.forEach((i) => {\n if (i === (activePage || 1)) {\n numberButtonHTML += renderButton(i, Locale.translate('PageOn'), null, i, 'pager-no', true, false, false);\n } else {\n numberButtonHTML += renderButton(i, Locale.translate('Page'), null, i, 'pager-no', false, false, false);\n }\n });\n buttonHTML += numberButtonHTML;\n }\n\n // Next Button\n if (doRenderNextButton) {\n buttonHTML += renderButton($.createIcon({ icon: 'next-page' }), Locale.translate('NextPage'), this.settings.nextPageTooltip, null, 'pager-next', false, disableNextButton, false);\n }\n\n // Last Button\n if (doRenderLastButton) {\n buttonHTML += renderButton($.createIcon({ icon: 'last-page' }), Locale.translate('LastPage'), this.settings.lastPageTooltip, null, 'pager-last', false, disableLastButton, false);\n }\n\n // Render all elements into the pager container element\n this.pagerBar[0].innerHTML = buttonHTML;\n\n if (!doRenderLastButton && !doRenderFirstButton && !this.settings.showPageSizeSelector) {\n this.pagerBar[0].classList.add('two-button');\n } else {\n this.pagerBar[0].classList.remove('two-button');\n }\n\n // Invoke all sub-components\n this.pagerBar.children('li').find('> .btn-icon').button().tooltip();\n this.pagerBar.children('li').find('> .btn-icon[disabled] .disabled-tooltip').tooltip();\n },\n\n /**\n * Renders an input-field based page selector (Datagrid only)\n * @returns {void}\n */\n renderPageSelectorInput() {\n if ((!this.isTable || this.settings.indeterminate) && !this.settings.showPageSelectorInput) {\n return;\n }\n\n let activePage = this.activePage;\n let totalPages = this.state.pages || 1;\n\n // If this is a filtered dataset, use the `filteredTotal` instead\n if (this.state.filteredPages) {\n activePage = this.state.filteredActivePage;\n totalPages = this.state.filteredPages;\n }\n\n if (!this.pageSelectorInput) {\n let text = this.settings.pageSelectorInputText || Locale.translate('PageOf');\n text = text.replace('{0}', ``);\n text = text.replace('{1}', `${totalPages}`);\n $(`
                • `).insertAfter(this.pagerBar.find('.pager-prev'));\n } else {\n // Update the total number of pages\n if (totalPages > 1) {\n this.pagerBar.find('.pager-total-pages').text(totalPages);\n }\n // Update the input field's number\n this.pagerBar.find('.pager-count input').val(activePage);\n }\n\n let lastValue = null;\n const pattern = (`${totalPages}`).replace(/\\d/g, '#');\n const self = this;\n const maskSettings = {\n pattern,\n mode: 'number',\n processOnInitialize: false\n };\n\n function update(elem) {\n const newValue = self.adjustPageCount(parseInt(elem.val(), 10));\n if (lastValue === newValue) {\n elem.val(lastValue);\n return;\n }\n\n let currentPage = self.activePage;\n if (self.state.filteredPages) {\n currentPage = self.state.filteredActivePage;\n }\n elem.val(self.setActivePage(newValue, false, 'page'));\n self.triggerPagingEvents(currentPage);\n }\n\n $(this.pageSelectorInput)\n .mask(maskSettings)\n .on('focus', function () {\n lastValue = parseInt($(this).val(), 10);\n })\n .on('blur', function () {\n update($(this));\n lastValue = null;\n })\n .on('keydown', function (e) {\n if (e.which === 13) {\n update($(this));\n e.stopPropagation();\n e.preventDefault();\n return false;\n }\n return true;\n });\n },\n\n /**\n * Displays a Page Size Selector button as part of the pager bar\n * @returns {void}\n */\n renderPageSizeSelectorButton() {\n if (!this.settings.showPageSizeSelector || this.settings.pagesizes.length < 2) {\n return;\n }\n\n if (!this.pageSizeSelectorButton) {\n const pageSizeLi = $('
                • ');\n const dropdownIcon = $.createIcon({ icon: 'dropdown' });\n let translatedText = Locale.translate(this.settings.pageSizeSelectorText).replace('{0}', this.settings.pagesize);\n let isAudible = '';\n let recordHtml = `${translatedText}`;\n\n // Change to the condensed layout, if applicable\n if (this.showSmallPageSizeSelector) {\n isAudible = ' class=\"audible\"';\n translatedText = Locale.translate(this.settings.pageSizeSelectorTextNoToken);\n recordHtml = `${this.settings.pagesize}\n ${translatedText}`;\n }\n\n // Render the button\n const pageSizeButton = $(``).appendTo(pageSizeLi);\n pageSizeLi.appendTo(this.pagerBar);\n\n // Render menu items that render available records per page\n let menuItems = '';\n if (this.showSmallPageSizeSelector) {\n menuItems = `
                • ${translatedText}
                • `;\n }\n for (let k = 0; k < this.settings.pagesizes.length; k++) {\n const size = this.settings.pagesizes[k];\n menuItems += `
                • ${size}
                • `;\n }\n const menu = $(`
                    ${menuItems}
                  `);\n pageSizeButton.after(menu);\n }\n\n const $pageSizeSelectorButton = $(this.pageSizeSelectorButton);\n\n // Invoke/Update the popupmenu instance with new settings\n const popupOpts = utils.extend({}, {\n placementOpts: {\n parent: $pageSizeSelectorButton,\n parentXAlignment: (Locale.isRTL() ? 'left' : 'right'),\n strategies: ['flip']\n }\n }, this.settings.pageSizeMenuSettings);\n $pageSizeSelectorButton.popupmenu(popupOpts);\n $pageSizeSelectorButton.on('selected.pager', (e, args) => {\n this.changePageSize(args);\n });\n\n if (this.settings.tabbable) {\n const popupmenuApi = $pageSizeSelectorButton.data('popupmenu');\n this.pagerBar\n .off(`keydown.${COMPONENT_NAME}`, '.pager-pagesize button')\n .on(`keydown.${COMPONENT_NAME}`, '.pager-pagesize button', (e) => {\n const key = e.which || e.keyCode || e.charCode;\n if (key === 40 && popupmenuApi && !popupmenuApi.isOpen) {\n popupmenuApi.open();\n e.preventDefault();\n e.stopPropagation();\n }\n });\n }\n },\n\n /**\n * Renders the contents of the pager bar\n * @returns {void}\n */\n render() {\n // Adjust Page count numbers\n const state = this.state;\n let totalPages = state.pages;\n\n // Preserve the focus before render, if focus on pagesize popupmenu\n this.preserveFocus = this.preserveFocus || false;\n const isPagesizeBtnFocused = $(':focus').closest('.pager-pagesize').length > 0;\n if (!this.preserveFocus && isPagesizeBtnFocused) {\n this.preserveFocus = true;\n }\n\n if (state.filteredPages) {\n totalPages = state.filteredPages;\n }\n this.pageCount(totalPages);\n\n if (this.pageSizeSelectorButton) {\n this.teardownPageSizeSelector();\n }\n\n this.renderButtons();\n this.renderPageSelectorInput();\n this.renderPageSizeSelectorButton();\n this.renderBar();\n\n // Apply focus after render, if preserve focus was on pagesize popupmenu\n if (this.preserveFocus) {\n this.element.find('.pager-pagesize button').focus();\n }\n },\n\n /**\n * Renders the pager bar based on derived or forced settings.\n * @private\n * @param {SohoPagingInfo} pagingInfo - an object containing information on how to render the pager.\n * @returns {undefined}\n */\n renderBar(pagingInfo) {\n const self = this;\n\n if (!pagingInfo) {\n pagingInfo = this.state;\n } else {\n pagingInfo = utils.extend({}, this.state, pagingInfo);\n }\n\n let activePage = pagingInfo.activePage;\n if (pagingInfo.filteredActivePage) {\n activePage = pagingInfo.filteredActivePage;\n }\n\n // hide buttons feature\n if (!this.settings.showFirstButton) {\n this.showButton('first', false);\n }\n\n if (!this.settings.enableFirstButton) {\n this.enableButton('first', false);\n }\n\n if (!this.settings.showPreviousButton) {\n this.showButton('previous', false);\n }\n\n if (!this.settings.enablePreviousButton) {\n this.enableButton('previous', false);\n }\n\n if (!this.settings.showNextButton) {\n this.showButton('next', false);\n }\n\n if (!this.settings.enableNextButton) {\n this.enableButton('next', false);\n }\n\n if (!this.settings.showLastButton) {\n this.showButton('last', false);\n }\n\n if (!this.settings.enableLastButton) {\n this.enableButton('last', false);\n }\n\n if (this.settings.showPageSizeSelector) {\n this.showPageSizeSelector(true);\n }\n\n // Explicit true/false when using `firstPage` or `lastPage` will force the state\n // on the specified set of buttons to change.\n if (pagingInfo.firstPage !== undefined) {\n // First/Prev page\n if (pagingInfo.firstPage === false) {\n this.enableButton('first', true);\n this.enableButton('previous', true);\n }\n if (pagingInfo.firstPage === true || activePage === 1) {\n this.enableButton('first', false);\n this.enableButton('previous', false);\n }\n }\n\n if (pagingInfo.lastPage !== undefined) {\n // Next/Last Page\n if (pagingInfo.lastPage === false) {\n this.enableButton('next', true);\n this.enableButton('last', true);\n }\n if (pagingInfo.lastPage === true || activePage === this.pageCount()) {\n this.enableButton('next', false);\n this.enableButton('last', false);\n }\n }\n\n const classList = this.pagerBar[0] ? this.pagerBar[0].classList : null;\n const pagerClassList = ['.pager-first', '.pager-prev', '.pager-next', '.pager-last', '.pager-count'];\n\n const toggleHide = (classNames = [], addHide = true) => {\n classNames.forEach((className) => {\n if (addHide) {\n self.pagerBar.find(className).addClass('hidden');\n } else {\n self.pagerBar.find(className).removeClass('hidden');\n }\n });\n };\n\n if (this.settings.hideOnOnePage && pagingInfo.total <= pagingInfo.pagesize) {\n toggleHide(pagerClassList);\n } else if (this.hidePagerBar(pagingInfo) && classList) {\n classList.add('hidden');\n } else if (this.settings.hideOnOnePage && classList && classList.contains('hidden')) {\n classList.remove('hidden');\n toggleHide(pagerClassList, false);\n }\n\n this.initTabIndexes();\n\n // Add id's to everything\n utils.addAttributes(this.pagerBar, this, this.settings.attributes);\n utils.addAttributes(this.pagerBar.find('.pager-first button'), this, this.settings.attributes, 'btn-first');\n utils.addAttributes(this.pagerBar.find('.pager-prev button'), this, this.settings.attributes, 'btn-prev');\n utils.addAttributes(this.pagerBar.find('.pager-next button'), this, this.settings.attributes, 'btn-next');\n utils.addAttributes(this.pagerBar.find('.pager-last button'), this, this.settings.attributes, 'btn-last');\n utils.addAttributes(this.pagerBar.find('.pager-pagesize button'), this, this.settings.attributes, 'btn-pagesize');\n utils.addAttributes(this.pagerBar.find('input'), this, this.settings.attributes, 'pagesize-input');\n\n this.pagerBar.find('.pager-pagesize .popupmenu li').each(function () {\n const link = $(this).find('a');\n const value = link.text();\n utils.addAttributes(link, self, self.settings.attributes, `pagesize-opt-${value}`);\n });\n\n // Append if using attach to body\n const menu = this.pagerBar.find('.btn-menu')?.data('popupmenu')?.menu;\n if (menu) {\n menu.find('li').each(function () {\n const link = $(this).find('a');\n const value = link.text();\n utils.addAttributes(link, self, self.settings.attributes, `pagesize-opt-${value}`);\n });\n }\n },\n\n /**\n * Sync the tabindexes\n * @private\n */\n initTabIndexes() {\n if (this.settings.tabbable) {\n return;\n }\n const tabbables = $(this.focusableElements);\n tabbables.attr('tabindex', '-1');\n tabbables.filter(':not([disabled])').first().removeAttr('tabindex');\n },\n\n /**\n * Fires when the first page button is clicked.\n * @event firstpage\n * @memberof Pager\n * @property {object} event - The jquery event object\n * @property {function} request - Various paging info\n */\n /**\n * Fires when the previous page button is clicked.\n * @event previouspage\n * @memberof Pager\n * @property {object} event - The jquery event object\n * @property {function} request - Various paging info\n */\n /**\n * Fires when the next page button is clicked.\n * @event nextpage\n * @memberof Pager\n * @property {object} event - The jquery event object\n * @property {function} request - Various paging info\n */\n /**\n *Fires when the last page button is clicked.\n * @event lastpage\n * @memberof Pager\n * @property {object} event - The jquery event object\n * @property {function} request - Various paging info\n */\n /**\n * @private\n * Triggers the `page` event, along with other special events. Also runs associated callbacks.\n * @param {number} [previousActivePage=undefined] if defined, sets a previous page value for determining some event triggers\n * @returns {void}\n */\n triggerPagingEvents(previousActivePage) {\n const state = this.state;\n if (!previousActivePage) {\n previousActivePage = this.state.activePage;\n }\n\n // Trigger events for specific special pages, and always trigger the `page` event\n // containing the new pager state.\n if (state.type === 'first') {\n // First Page\n if (this.settings.onFirstPage) {\n this.settings.onFirstPage(this, state);\n }\n this.element.trigger('firstpage', state);\n }\n if (state.type === 'prev') {\n // Previous Page\n if (this.settings.onPreviousPage) {\n this.settings.onPreviousPage(this, state);\n }\n this.element.trigger('previouspage', state);\n }\n if (state.type === 'next') {\n // Next Page\n if (this.settings.onNextPage) {\n this.settings.onNextPage(this, state);\n }\n this.element.trigger('nextpage', state);\n }\n if (state.type === 'last') {\n // Last Page\n if (this.settings.onLastPage) {\n this.settings.onLastPage(this, state);\n }\n this.element.trigger('lastpage', state);\n }\n\n if (state.type === 'pageinfo') {\n return;\n }\n\n this.element.trigger('page', state);\n },\n\n /**\n * Update the component and optionally apply new settings.\n * @param {object} settings the settings to update to.\n * @returns {object} The plugin api for chaining.\n */\n updated(settings) {\n if (settings) {\n this.settings = utils.mergeSettings(this.element, settings, this.settings);\n }\n if (Array.isArray(settings.pagesizes) && settings.pagesizes.length) {\n this.settings.pagesizes = settings.pagesizes;\n }\n\n // Limit updated paging info to a specific subset\n const pagingInfo = {\n activePage: this.settings.activePage,\n indeterminate: this.settings.indeterminate,\n pagesize: this.settings.pagesize\n };\n\n this.handleDeprecatedSettings();\n this.updatePagingInfo(pagingInfo);\n return this;\n },\n\n /**\n * Changes the size of the visible page\n * @param {jQuery} anchor containing a reference to the jQuery-wrapped popupmenu menu item element that was chosen.\n * @returns {void}\n */\n changePageSize(anchor) {\n const tag = anchor;\n tag.closest('.popupmenu').find('.is-checked').removeClass('is-checked');\n tag.parent('li').addClass('is-checked');\n this.settings.pagesize = parseInt(tag.text(), 10);\n\n if (this.settings.componentAPI) {\n this.settings.componentAPI.settings.pagesize = this.settings.pagesize;\n }\n this.setActivePage(1, true, 'first');\n\n if (this.settings.onPageSizeChange) {\n this.settings.onPageSizeChange(this, {\n tag: anchor,\n pagesize: this.settings.pagesize,\n settings: this.settings\n });\n }\n\n this.element.trigger('pagesizechange', {\n tag: anchor,\n pagesize: this.settings.pagesize,\n settings: this.settings\n });\n },\n\n /**\n * Updates this instance of pager with externally-provided settings.\n * @param {object} pagingInfo - contains settings that will change buttons on the pager.\n * @param {number} pagingInfo.pagesize - the number of items visible per page\n * @param {number} pagingInfo.total - the total number of pages\n * @param {number} pagingInfo.activePage - the currently visible page\n * @param {boolean} [pagingInfo.firstPage=false] - passed if the currently visible page is the\n * first one\n * @param {boolean} [pagingInfo.lastPage=false] - passed if the currently visible page is the\n * last one\n * @param {boolean} [pagingInfo.hideDisabledPagers=false] - causes the pager to become completely\n * hidden if all buttons are disabled\n * @param {boolean} [isResponse=false] if true, causes events not to be triggered (avoids infinite loops)\n * @returns {void}\n */\n updatePagingInfo(pagingInfo, isResponse) {\n if (!pagingInfo) {\n return;\n }\n\n // Grab and retain the pagesize\n if (pagingInfo.pagesize) {\n this.settings.pagesize = pagingInfo.pagesize;\n if (this.isTable && this.settings.componentAPI) {\n this.settings.componentAPI.settings.pagesize = pagingInfo.pagesize;\n }\n }\n\n // Detect client-side filtering in the other component's API\n if (pagingInfo.isFilteredClientside) {\n this.isFilteredClientside = true;\n delete this.serverDatasetTotal;\n } else if (this.isFilteredClientside) {\n delete this.isFilteredClientside;\n }\n\n // Explicitly setting `firstPage` or `lastPage` to true/false will cause pager buttons\n // to be forced enabled/disabled\n delete this.firstPage;\n delete this.lastPage;\n if (pagingInfo.firstPage !== undefined) {\n this.firstPage = pagingInfo.firstPage;\n this.settings.enableFirstButton = !pagingInfo.firstPage;\n this.settings.enablePreviousButton = !pagingInfo.firstPage;\n }\n if (pagingInfo.lastPage !== undefined) {\n this.lastPage = pagingInfo.lastPage;\n this.settings.enableNextButton = !pagingInfo.lastPage;\n this.settings.enableLastButton = !pagingInfo.lastPage;\n }\n\n // Track \"grandTotal\" for all records, including filtered-out, if applicable\n if (!isNaN(pagingInfo.grandTotal)) {\n this.grandTotal = pagingInfo.grandTotal;\n }\n\n // For server-side paging, retain a separate \"total\" for the server dataset.\n if (!this.isFilteredClientside) {\n this.serverDatasetTotal = pagingInfo.total;\n }\n\n // If the dataset is filtered, store some extra meta-data for the state.\n if (!isNaN(pagingInfo.filteredTotal)) {\n this.filteredTotal = pagingInfo.filteredTotal;\n this.filteredActivePage = pagingInfo.searchActivePage || pagingInfo.filteredActivePage || 1;\n } else if (this.filteredTotal || this.filteredActivePage) {\n delete this.filteredTotal;\n delete this.filteredActivePage;\n }\n\n if (!pagingInfo.type) {\n pagingInfo.type = 'pageinfo';\n }\n\n if (this.settings.source || this.settings.dataset) {\n // Set first and last page if passed\n // If we get a page number as a result, rendering has already happened and\n // we should not attempt to re-render.\n this.setActivePage(pagingInfo, false, pagingInfo.type);\n if (pagingInfo.type !== 'initial' && !isResponse) {\n this.triggerPagingEvents();\n }\n return;\n }\n\n this.teardown();\n this.render();\n this.handleEvents();\n },\n\n /**\n * Reclaim the pager height so that datagrid can use it's full container, if only one page.\n * @private\n * @param {object} pagingInfo The pager states.\n * @returns {void}\n */\n hidePagerBar(pagingInfo) {\n if (pagingInfo && (pagingInfo.firstPage === true && pagingInfo.lastPage === true) &&\n pagingInfo.hideDisabledPagers) {\n return true;\n }\n\n return false;\n },\n\n /**\n * Removes all event listeners and generated HTML markup from the pager instance\n * @returns {void}\n */\n teardown() {\n if (this.numberButtons) {\n this.numberButtons.forEach((li) => {\n const btn = li.querySelector('.btn-icon');\n const buttonAPI = $(btn).data('button');\n const tooltipAPI = $(btn).data('tooltip');\n\n if (buttonAPI) {\n buttonAPI.destroy();\n }\n if (tooltipAPI) {\n tooltipAPI.destroy();\n }\n });\n }\n\n this.pagerBar.off([\n `click.${COMPONENT_NAME}`,\n `keydown.${COMPONENT_NAME}`\n ].join(' '));\n\n if (this.pageSelectorInput) {\n $(this.pageSelectorInput).off([\n `focus.${COMPONENT_NAME}`,\n `blur.${COMPONENT_NAME}`,\n `keydown.${COMPONENT_NAME}`\n ].join(' '));\n $(this.pageSelectorInput).data('mask')?.destroy();\n }\n\n if (this.pageSizeSelectorButton) {\n $(this.pageSizeSelectorButton).off(`selected.${COMPONENT_NAME}`);\n this.teardownPageSizeSelector();\n }\n\n this.pagerBar[0].innerHTML = '';\n\n delete this.firstPage;\n delete this.lastPage;\n },\n\n /**\n * Tears down the Popupmenu associated with the page size selector.\n * This happens here because the Popupmenu component does not remove its own\n * menu markup when being destroyed.\n * @private\n * @returns {void}\n */\n teardownPageSizeSelector() {\n const $pageSizeSelectorButton = $(this.pageSizeSelectorButton);\n const api = $pageSizeSelectorButton.data('popupmenu');\n\n if (!api || !api.menu) {\n return;\n }\n\n const pageSizeSelectorMenu = api.menu;\n api.destroy();\n pageSizeSelectorMenu.remove();\n },\n\n /**\n * Tear down and detatch all events\n */\n destroy() {\n this.teardown();\n if (this.pagerBar) {\n this.pagerBar.remove();\n }\n $.removeData(this.element[0], COMPONENT_NAME);\n }\n};\n\nexport { Pager, COMPONENT_NAME };\n","import { Pager, COMPONENT_NAME } from './pager';\n\n/**\n * jQuery Component Wrapper for pager\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.pager = function (settings) {\n return this.each(function () {\n let instance = $.data(this, COMPONENT_NAME);\n if (!instance) {\n instance = $.data(this, COMPONENT_NAME, new Pager(this, settings));\n } else {\n instance.updated(settings);\n }\n });\n};\n","/* eslint-disable no-underscore-dangle, no-continue, no-nested-ternary */\nimport * as debug from '../../utils/debug';\nimport { deprecateMethod } from '../../utils/deprecated';\nimport { utils } from '../../utils/utils';\nimport { DOM } from '../../utils/dom';\nimport { Environment as env } from '../../utils/environment';\nimport { stringUtils as str } from '../../utils/string';\nimport { Tmpl } from '../tmpl/tmpl';\nimport { ListFilter } from '../listfilter/listfilter';\nimport { Locale } from '../locale/locale';\n\n// jQuery Components\nimport '../../utils/animations';\nimport '../pager/pager.jquery';\nimport '../popupmenu/popupmenu.jquery';\nimport '../searchfield/searchfield.jquery';\nimport '../emptymessage/emptymessage.jquery';\n\nconst COMPONENT_NAME = 'listview';\n\n/**\n * Creates lists of small pieces of relevant, actionable information.\n * @class ListView\n * @constructor\n *\n * @param {jquery[]|HTMLelement} element the base element\n * @param {object} [settings] incoming settings\n * @param {array} [settings.dataset] Array of data to feed the template\n * @param {string} [settings.template] Html Template String\n * @param {string} [settings.description] Audible Label (or use parent title)\n * @param {boolean} [settings.paging=false] If true, activates paging\n * @param {number} [settings.pagesize=10] If paging is activated, sets the number of listview items available per page\n * @param {boolean} [settings.searchable=false] If true, associates itself with a Searchfield/Autocomplete and allows itself to be filtered\n * @param {boolean} [settings.highlight=true] If false the highlighting of text when using searchable is disabled. You may want to disable this on larger lists.\n * @param {string|boolean} [settings.selectable='single'] selection mode, can be false, 'single', 'multiple' or 'mixed'\n * @param {boolean} [settings.allowDeselect=true] If using single select you can set this if you want the rows to not be deSelected.\n * @param {boolean} [settings.selectOnFocus=true] If true the first item in the list will be selected as it is focused.\n * @param {boolean} [settings.showCheckboxes=true] If false will not show checkboxes used with multiple selection mode only\n * @param {boolean} [settings.hoverable=true] If true the list element will show a hover action to indicate its actionable.\n * @param {string} [settings.emptyMessage] Text to go in emptyMessage.\n * @param {function|string} [settings.source] If source is a string then it serves as\n the url for an ajax call that returns the dataset. If its a function it is a call back for getting the data asyncronously.\n * @param {boolean} [settings.forceToRenderOnEmptyDs=false] If true list will render as an empty list with ul tag, but not any li tags in it.\n * @param {boolean} [settings.disableItemDeactivation=false] If true when an item is\n activated the user should not be able to deactivate it by clicking on the activated item. They can only select another row.\n * @param {boolean} [settings.showPageSizeSelector=false] If true the page size select will be shown when paging.\n * @param {object} [settings.listFilterSettings=null] If defined as an object, passes settings into the internal ListFilter component\n * @param {object} [settings.pagerSettings=null] If defined as an object, passes settings into the internal Pager component\n * @param {object} [settings.searchTermMinSize=1] The search term will trigger filtering only when its length is greater than or equals to the value.\n * @param {object} [settings.initializeContents=false] If true the initializer will be run on all internal contents.\n * @param {string} [settings.attributes] Add extra attributes like id's to the listview. For example `attributes: { name: 'id', value: 'my-unique-id' }`\n * @param {boolean} [settings.attributesOverride=true] if true, will override existing the attributes key/value.\n */\nconst LISTVIEW_DEFAULTS = {\n dataset: [],\n template: null,\n description: null,\n paging: false,\n pagesize: 10,\n searchable: false,\n highlight: true,\n selectable: 'single',\n selectOnFocus: true,\n showCheckboxes: true,\n hoverable: true,\n emptyMessage: null,\n source: null,\n forceToRenderOnEmptyDs: false,\n disableItemDeactivation: false,\n allowDeselect: true,\n showPageSizeSelector: false,\n listFilterSettings: null,\n pagerSettings: {\n showFirstButton: false,\n showLastButton: false\n },\n searchTermMinSize: 1,\n initializeContents: false,\n attributes: null,\n attributesOverride: null\n};\n\nfunction ListView(element, settings) {\n this.element = $(element);\n this.settings = utils.mergeSettings(this.element[0], settings, LISTVIEW_DEFAULTS);\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n\n return this;\n}\n\nListView.prototype = {\n\n /**\n * @returns {Pager|undefined} Pager component instance, if one exists\n */\n get pagerAPI() {\n return this.element.data('pager');\n },\n\n /**\n * @returns {object} containing valid Pager Component settings\n */\n get pagerSettings() {\n let pagerSettings = {};\n if (this.settings.pagerSettings) {\n pagerSettings = this.settings.pagerSettings;\n }\n pagerSettings.dataset = this.settings.dataset;\n pagerSettings.source = this.settings.source;\n pagerSettings.type = 'list';\n\n // Backwards compatibility for direct pager settings\n const oldSettingTypes = ['pagesize', 'showPageSizeSelector'];\n for (let i = 0; i < oldSettingTypes.length; i++) {\n if (this.settings[oldSettingTypes[i]] !== undefined && !pagerSettings[oldSettingTypes[i]]) {\n pagerSettings[oldSettingTypes[i]] = this.settings[oldSettingTypes[i]];\n }\n }\n\n return pagerSettings;\n },\n\n /**\n * Initialize this component.\n * @private\n * @returns {void}\n */\n init() {\n this.setup();\n this.handleEvents();\n this.refresh();\n this.selectedItems = [];\n this.lastSelectedItem = 0; // Rember index to use shift key\n this.isSelectedAll = false; // Rember if all selected or not\n this.sortInit('listview', 'click.listview', 'data-sortlist');\n this.handleResize();\n },\n\n /**\n * Do initial dom and settings setup.\n * @private\n * @returns {void}\n */\n setup() {\n const self = this;\n const card = this.element.closest('.card, .widget');\n const selectable = this.element.attr('data-selectable');\n const selectOnFocus = this.element.attr('data-select-onfocus');\n\n if ($('.listview-filter-wrapper').length > 0) {\n const filterWrapper = $('.listview-filter-wrapper');\n const searchFieldWrapper = filterWrapper.siblings('.searchfield-wrapper');\n searchFieldWrapper.addClass('no-animate');\n const filterWidth = filterWrapper.css('width');\n searchFieldWrapper.addClass('has-listview-filters');\n searchFieldWrapper.css('width', `calc(100% - ${filterWidth})`);\n }\n\n // Check for legacy data attributes\n if (this.element.attr('data-pagesize')) {\n const pagesize = Number(this.element.attr('data-pagesize'));\n if (!isNaN(pagesize)) {\n this.settings.pagesize = pagesize;\n }\n this.element.removeAttr('data-pagesize');\n }\n\n if (this.element.hasClass('card-list')) {\n this.element.find('li .card').selectable();\n }\n\n // Convert a DOM-based list into a stored dataset (legacy)\n if (!this.settings.dataset.length) {\n if (this.element.is('ul') && this.element.children('li').length) {\n const items = this.element.children('li');\n if (!this.settings.template) {\n this.settings.template = '{{#dataset}}
                • {{text}}
                • {{/dataset}}';\n }\n items.each((i, item) => {\n this.settings.dataset.push({ text: $(item).text() });\n });\n items.remove();\n }\n }\n\n // Search the global variable space for a dataset variable name, if provided.\n if (this.settings.dataset && typeof this.settings.dataset === 'string') {\n const globalDataset = window[this.settings.dataset];\n if (globalDataset && globalDataset.length) {\n this.settings.dataset = globalDataset;\n }\n }\n\n if (selectable && selectable.length) {\n this.settings.selectable = selectable;\n }\n\n if (selectOnFocus && selectOnFocus.length) {\n this.settings.selectOnFocus = JSON.parse(selectOnFocus);\n }\n\n self.actionButton = card.find('.btn-actions');\n\n if (self.actionButton.length > 0) {\n // Action Buttons may already be invoked via initialize.js.\n if (!(self.actionButton.data('popupmenu'))) {\n self.actionButton.popupmenu();\n }\n }\n\n this.element.attr({ tabindex: '-1', 'x-ms-format-detection': 'none' });\n\n // Add user-defined attributes\n if (this.settings.attributes) {\n utils.addAttributes(this.element, this, this.settings.attributes, 'listview', this.settings.attributesOverride);\n }\n\n // Configure Paging\n if (this.element.is('.paginated') || this.settings.paging === true) {\n this.element.pager(this.pagerSettings);\n }\n\n const cardWidgetContent = this.element.parents('.card-content, .widget-content');\n\n if (this.element.prev().is('.listview-search') && cardWidgetContent[0]) {\n cardWidgetContent.css({ overflow: 'visible' });\n }\n\n // Associate with an existing searchfield, if applicable\n if (this.settings.searchable) {\n this.searchfield = this.element.parent().find('.searchfield, .autocomplete');\n\n if (!this.searchfield.length) {\n // TODO: Create Searchfield somehow\n }\n\n // Setup the ListFilter with externally-defined settings, if applicable\n let listFilterSettings = {\n filterMode: 'contains'\n };\n if (typeof this.settings.listFilterSettings === 'object') {\n listFilterSettings = utils.extend({}, listFilterSettings, this.settings.listFilterSettings);\n }\n\n this.listfilter = new ListFilter(listFilterSettings);\n }\n\n if (this.settings.emptyMessage) {\n // Object { title: \"No data available\", info: \"\", icon: \"icon-empty-no-data-new\" }\n self.emptyMessageContainer = $('
                  ').emptymessage(this.settings.emptyMessage);\n }\n },\n\n /**\n * Calculate the totals for totalling examples.\n * This is displayed in the template by referencing {{totals}}.\n * @private\n * @param {array} dataset the incoming dataset\n * @returns {number} the total number of listview items.\n */\n getTotals(dataset) {\n const totals = { count: dataset.length };\n let property;\n\n if (!dataset[0]) {\n return undefined;\n }\n\n for (property in dataset[0]) { //eslint-disable-line\n totals[property] = 0;\n }\n\n for (let i = 0; i < dataset.length; i++) {\n for (property in dataset[i]) { //eslint-disable-line\n totals[property] += parseFloat(dataset[i][property]);\n }\n }\n return totals;\n },\n\n /**\n * Render the template against the dataset.\n * @private\n * @param {array} dataset The dataset to use\n * @param {object} pagerInfo Pager instructions\n */\n render(dataset, pagerInfo) {\n const self = this;\n const isServerSide = typeof this.settings.source === 'function';\n let totals = {};\n let displayedDataset = dataset;\n let firstRecordIdx = 0;\n let lastRecordIdx = displayedDataset ? displayedDataset.length : 0;\n let pagesize = this.settings.pagesize;\n let setSize = dataset.length;\n\n if (pagerInfo) {\n pagesize = pagerInfo.pagesize || pagesize;\n setSize = pagerInfo.filteredTotal || setSize;\n }\n\n if (!isServerSide && this.pagerAPI) {\n this.renderPager(pagerInfo, true);\n pagerInfo = this.pagerAPI.state;\n }\n\n // If the paging information sets limits on the dataset, customize the\n // displayed dataset to fit the conditions.\n if (setSize > pagesize) {\n const pages = this.filteredDataset ? pagerInfo.filteredPages : pagerInfo.pages;\n if (pages > 1) {\n let trueActivePage = pagerInfo.activePage > 0 ? pagerInfo.activePage - 1 : 0;\n if (this.filteredDataset) {\n trueActivePage = pagerInfo.filteredActivePage - 1;\n }\n firstRecordIdx = pagerInfo.pagesize * trueActivePage;\n lastRecordIdx = pagerInfo.pagesize * (trueActivePage + 1);\n displayedDataset = dataset.slice(firstRecordIdx, lastRecordIdx);\n }\n }\n\n // Render \"mustache\" Template\n if (typeof Tmpl === 'object' && displayedDataset && this.settings.template) {\n // create a copy of an inlined template\n if (this.settings.template instanceof $) {\n this.settings.template = `${this.settings.template.html()}`;\n } else if (typeof this.settings.template === 'string') {\n // If a string doesn't contain HTML elments,\n // assume it's an element ID string and attempt to select with jQuery\n if (!str.containsHTML(this.settings.template)) {\n this.settings.template = $(`#${this.settings.template}`).html();\n }\n }\n\n if (this.settings.template.indexOf('{{#totals}}') > -1) {\n totals = this.getTotals(dataset);\n }\n\n const renderedTmpl = Tmpl.compile(this.settings.template, {\n dataset: displayedDataset,\n totals\n });\n\n if (this.element.parent().is('.scrollable-flex-content')) {\n this.element.parent().find('.empty-message').remove();\n }\n\n if (displayedDataset.length > 0 || this.settings.forceToRenderOnEmptyDs) {\n this.element.html(renderedTmpl);\n } else if (self.emptyMessageContainer && this.element.parent().is('.scrollable-flex-content')) {\n this.element.empty();\n DOM.append(this.element.parent(), this.emptyMessageContainer[0].outerHTML, '
                  ');\n } else if (self.emptyMessageContainer) {\n this.element.empty();\n DOM.append(this.element, this.emptyMessageContainer[0].outerHTML, '
                  ');\n } else if (displayedDataset.length === 0) {\n this.element.html(renderedTmpl || '
                    ');\n }\n }\n\n // Add Aria\n const card = this.element.closest('.card, .widget');\n $('ul', this.element).attr({\n role: 'listbox',\n 'aria-label': this.settings.description || card.find('.card-title, .widget-title').text() || 'List'\n });\n\n // Add Checkboxes\n const first = this.element.find('li, tbody > tr').first();\n const items = this.element.find('li, tr');\n const isMultiselect = (this.settings.selectable === 'multiple' || this.settings.selectable === 'mixed');\n\n // Set Initial Tab Index\n this.focusItem = first.attr('tabindex', 0);\n\n // Let the link be focus'd\n if (!this.settings.selectable && first.find('a').length === 1) {\n first.removeAttr('tabindex');\n }\n\n // When DOM items are not rendered with \"mustache\" template, filtered items\n // have to be hidden specifically.\n const hideFlag = items.length > displayedDataset.length;\n\n items.each(function (i) {\n const item = $(this);\n\n item.attr('role', 'option');\n\n // Add css class `is-touch` for touch devices\n if (env.features.touch) {\n item.addClass('is-touch');\n }\n\n // Add user-defined attributes\n if (self.settings.attributes) {\n utils.addAttributes(item, self, self.settings.attributes, `listview-item-${i}`, self.settings.attributesOverride);\n }\n\n if (isMultiselect) {\n // Add Selection Checkboxes\n self.element.addClass('is-multiselect');\n\n // Create a Toolbar for the \"Selected Items\" area\n const selectedToolbar = self.element.prevAll('.toolbar, .contextual-toolbar');\n if (selectedToolbar.length && selectedToolbar.data('toolbar')) {\n selectedToolbar.data('toolbar').toggleMoreMenu();\n }\n\n if (self.settings.showCheckboxes) {\n // Only need one checkbox\n if (item.children('.listview-selection-checkbox').length === 0) {\n // For mixed selection mode primarily append a checkbox object\n item.prepend(``);\n }\n }\n } else {\n self.element.removeClass('is-multiselect');\n item.find('.listview-selection-checkbox').remove();\n }\n\n // Hide filtered items\n if (hideFlag) {\n const n = firstRecordIdx + i;\n if (n < self.settings.dataset.length) {\n const data = self.settings.dataset[n];\n item.css('display', (data._isFilteredOut === undefined || data._isFilteredOut) ? '' : 'none');\n }\n } else {\n item.css('display', '');\n }\n\n // Add Aria\n item.attr({ 'aria-posinset': (firstRecordIdx + i + 1), 'aria-setsize': setSize });\n\n // Add Aria disabled\n if (item.hasClass('is-disabled')) {\n item.attr('aria-disabled', 'true');\n }\n\n // If this dataset is filtered, hightlight the relevant search term inside the element.\n if (self.settings.highlight && self.searchTerm) {\n item.highlight(self.searchTerm);\n }\n });\n\n // Invoke all elements within the list view\n if (self.settings.initializeContents) {\n this.element.find('ul').initialize();\n }\n\n /**\n * Fires after the listbox is fully rendered.\n *\n * @event rendered\n * @memberof ListView\n * @property {object} event - The jquery event object\n * @property {array} dataset .\n */\n this.element.trigger('rendered', [displayedDataset]);\n\n // Handle refresh\n this.element.off('updated.listview').on('updated.listview', (e, settings) => {\n self.updated(settings);\n });\n },\n\n /**\n * Add and update the pager (if used)\n * @private\n * @param {object} updatedPagerInfo contains updated paging settings\n * @param {boolean} isResponse represents whether or not this render call was caused by an AJAX response\n * @returns {void}\n */\n renderPager(updatedPagerInfo, isResponse) {\n if (!this.pagerAPI) {\n return;\n }\n\n this.pagerAPI.updatePagingInfo(updatedPagerInfo, isResponse);\n },\n\n /**\n * Reliably gets all the pre-rendered elements in the container and returns them for use.\n * @private\n * @returns {array} TThe pagable items\n */\n getPageableElements() {\n let elements = this.element.children();\n\n // Adjust for cases where the root is a