{"version":3,"file":"sohoxi.js.map","sources":["../../components/utils/debug.js","../../components/utils/dom.js","../../components/utils/behaviors.js","../../components/utils/breakpoints.js","../../components/utils/debounced-resize.js","../../components/utils/debounced-resize.jquery.js","../../components/utils/environment.js","../../components/utils/utils.js","../../components/locale/locale.js","../../components/utils/highlight.js","../../components/arrange/arrange.js","../../components/arrange/arrange.jquery.js","../../components/drag/drag.js","../../components/drag/drag.jquery.js","../../components/place/place.js","../../components/place/place.jquery.js","../../components/personalize/personalize.js","../../components/personalize/personalize.bootstrap.js","../../components/personalize/personalize.jquery.js","../../components/icons/icons.js","../../components/icons/icons.jquery.js","../../components/button/button.js","../../components/button/button.jquery.js","../../components/hyperlinks/hyperlinks.js","../../components/hyperlinks/hyperlinks.jquery.js","../../components/about/about.js","../../components/about/about.jquery.js","../../components/utils/animations.js","../../components/accordion/accordion.js","../../components/accordion/accordion.jquery.js","../../components/utils/lifecycle.js","../../components/listfilter/listfilter.js","../../components/tmpl/tmpl.js","../../components/popupmenu/popupmenu.js","../../components/popupmenu/popupmenu.jquery.js","../../components/autocomplete/autocomplete.js","../../components/autocomplete/autocomplete.jquery.js","../../components/searchfield/searchfield.js","../../components/searchfield/searchfield.jquery.js","../../components/applicationmenu/applicationmenu.js","../../components/applicationmenu/applicationmenu.jquery.js","../../components/blockgrid/blockgrid.js","../../components/blockgrid/blockgrid.jquery.js","../../components/busyindicator/busyindicator.js","../../components/busyindicator/busyindicator.jquery.js","../../components/charts/charts.js","../../components/bullet/bullet.js","../../components/completion-chart/completion-chart.js","../../components/sparkline/sparkline.js","../../components/line/line.js","../../components/column/column.js","../../components/bar/bar.js","../../components/pie/pie.js","../../components/radar/radar.js","../../components/charts/charts.jquery.js","../../components/circlepager/circlepager.js","../../components/circlepager/circlepager.jquery.js","../../components/colorpicker/colorpicker.js","../../components/colorpicker/colorpicker.jquery.js","../../components/expandablearea/expandablearea.js","../../components/expandablearea/expandablearea.jquery.js","../../components/compositeform/compositeform.js","../../components/compositeform/compositeform.jquery.js","../../components/contextualactionpanel/contextualactionpanel.js","../../components/contextualactionpanel/contextualactionpanel.jquery.js","../../components/utils/string.js","../../components/mask/masks.js","../../components/mask/mask-api.js","../../components/mask/mask-input.js","../../components/mask/mask-input.jquery.js","../../components/tooltip/tooltip.js","../../components/tooltip/tooltip.jquery.js","../../components/popover/popover.jquery.js","../../components/dropdown/dropdown.js","../../components/dropdown/dropdown.jquery.js","../../components/timepicker/timepicker.js","../../components/timepicker/timepicker.jquery.js","../../components/validation/validation.js","../../components/utils/renderloop.js","../../components/toast/toast.js","../../components/toast/toast.jquery.js","../../components/validation/validator.js","../../components/validation/validation.jquery.js","../../components/validation/validation.utils.js","../../components/datepicker/datepicker.js","../../components/datepicker/datepicker.jquery.js","../../components/editor/editor.js","../../components/editor/editor.jquery.js","../../components/emptymessage/emptymessage.js","../../components/emptymessage/emptymessage.jquery.js","../../components/form/form.js","../../components/hierarchy/hierarchy.js","../../components/hierarchy/hierarchy.jquery.js","../../components/field-filter/field-filter.js","../../components/field-filter/field-filter.jquery.js","../../components/field-options/field-options.js","../../components/field-options/field-options.jquery.js","../../components/fileupload/fileupload.js","../../components/fileupload/fileupload.jquery.js","../../components/fileupload-advanced/fileupload-advanced.js","../../components/fileupload-advanced/fileupload-advanced.jquery.js","../../components/homepage/homepage.js","../../components/homepage/homepage.jquery.js","../../components/pager/pager.js","../../components/pager/pager.jquery.js","../../components/listview/listview.js","../../components/listview/listview.jquery.js","../../components/listbuilder/listbuilder.js","../../components/listbuilder/listbuilder.jquery.js","../../components/modal/modal.js","../../components/message/message.js","../../components/message/message.jquery.js","../../components/modal/modal.jquery.js","../../components/multiselect/multiselect.js","../../components/multiselect/multiselect.jquery.js","../../components/progress/progress.js","../../components/progress/progress.jquery.js","../../components/popdown/popdown.js","../../components/popdown/popdown.jquery.js","../../components/rating/rating.js","../../components/rating/rating.jquery.js","../../components/signin/signin.js","../../components/signin/signin.jquery.js","../../components/slider/slider.js","../../components/slider/slider.jquery.js","../../components/spinbox/spinbox.js","../../components/spinbox/spinbox.jquery.js","../../components/splitter/splitter.js","../../components/splitter/splitter.jquery.js","../../components/swaplist/swaplist.js","../../components/swaplist/swaplist.jquery.js","../../components/scrollaction/scrollaction.js","../../components/scrollaction/scrollaction.jquery.js","../../components/stepchart/stepchart.js","../../components/stepchart/stepchart.jquery.js","../../components/stepprocess/stepprocess.js","../../components/stepprocess/stepprocess.jquery.js","../../components/tabs/tabs.js","../../components/tabs/tabs.jquery.js","../../components/tag/tag.js","../../components/tag/tag.jquery.js","../../components/textarea/textarea.js","../../components/textarea/textarea.jquery.js","../../components/toolbarsearchfield/toolbarsearchfield.js","../../components/toolbarsearchfield/toolbarsearchfield.jquery.js","../../components/toolbar/toolbar.js","../../components/toolbar/toolbar.jquery.js","../../components/trackdirty/trackdirty.js","../../components/trackdirty/trackdirty.jquery.js","../../components/tree/tree.js","../../components/tree/tree.jquery.js","../../components/wizard/wizard.js","../../components/wizard/wizard.jquery.js","../../components/zoom/zoom.js","../../components/zoom/zoom.jquery.js","../../components/datagrid/datagrid.formatters.js","../../components/utils/excel.js","../../components/datagrid/datagrid.groupby.js","../../components/datagrid/datagrid.editors.js","../../components/datagrid/datagrid.js","../../components/datagrid/datagrid.jquery.js","../../components/header/header.js","../../components/header/header.jquery.js","../../components/list-detail/list-detail.js","../../components/list-detail/list-detail.jquery.js","../../components/lookup/lookup.js","../../components/lookup/lookup.jquery.js","../../components/tabs-multi/multi-tabs.js","../../components/tabs-multi/multi-tabs.jquery.js","../../components/components.jquery.js","../../components/initialize/initialize.js","../../components/initialize/initialize.jquery.js","../../components/utils/base.js","../../components/personalize/personalize.hooks.js","../../components/components.js","../../components/index.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 (!console) { // eslint-disable-line\n return;\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","\nconst DOM = {};\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.classNameExists = function classNameExists(element) {\n const cn = element.className;\n return cn && cn.length > 0;\n};\n\n/**\n * Checks the contents of a string for the existence of a particular substring.\n * @param {string} classNameString a string to test\n * @param {string} targetContents the contents that need to exist inside the `classNameString`\n * @returns {boolean} whether or not a className exists\n */\nDOM.classNameHas = function has(classNameString, targetContents) {\n return classNameString.indexOf(targetContents) > -1;\n};\n\n/**\n * @param {HTMLElement} el 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(el, className) {\n return el.classList ? el.classList.contains(className) : new RegExp(`\\\\b${className}\\\\b`).test(el.className);\n};\n\n/**\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.classList) {\n el.classList.add(className);\n } else if (!DOM.hasClass(el, className)) {\n el.className += ` ${className}`;\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\nexport { DOM };\n","import { DOM } from './dom';\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 = $(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 $el.addClass('hide-focus')\n .on('mousedown.hide-focus touchstart.hide-focus', (e) => {\n isClick = true;\n $el.addClass('hide-focus');\n $el.triggerHandler('hidefocusadd', [e]);\n })\n .on('focusin.hide-focus', (e) => {\n if (!isClick && !isFocused) {\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\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 $(this.element).off(elemEvents.join(' '));\n\n return this;\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 instance.destroy = function destroy() {\n this.teardown();\n $.removeData(this, 'hidefocus');\n };\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","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 * 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 const afterElement = window.getComputedStyle ? window.getComputedStyle(document.body, ':after') : false;\n if (!afterElement) {\n return '';\n }\n return (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 { version as SOHO_XI_VERSION } from '../../package.json';\nimport { breakpoints } from './breakpoints';\n\n// jQuery Components\nimport './debounced-resize.jquery';\n\n/**\n * @class {Environment}\n */\nconst Environment = {\n\n browser: {},\n\n os: {},\n\n rtl: $('html').attr('dir') === 'rtl',\n\n /**\n * Builds run-time environment settings\n */\n set() {\n $('html').attr('data-sohoxi-version', SOHO_XI_VERSION);\n this.addBrowserClasses();\n this.addGlobalResize();\n },\n\n /**\n * Global Classes for browser, version and device as needed.\n */\n addBrowserClasses() {\n const ua = navigator.userAgent || navigator.vendor || window.opera;\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 if (ua.indexOf('Chrome') !== -1) {\n cssClasses += 'is-chrome ';\n this.browser.name = 'chrome';\n }\n\n if (ua.indexOf('Mac OS X') !== -1) {\n cssClasses += 'is-mac ';\n this.os.name = 'Mac OS X';\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(/Edge\\//)) {\n cssClasses += 'ie ie-edge ';\n this.browser.name = 'edge';\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)) {\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 html.addClass(cssClasses);\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/**\n *\n */\nEnvironment.pasteEvent = (function getPasteEvent() {\n const el = document.createElement('input');\n const name = 'onpaste';\n el.setAttribute(name, '');\n return ((typeof el[name] === 'function') ? 'paste' : 'input');\n}());\n\n/**\n * Automatically set up the environment by virtue of including this script\n */\nEnvironment.set();\n\nexport { Environment };\n","import { 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 = 0; // eslint-disable-line\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 {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 */\n$.fn.uniqueId = function (className, prefix, suffix) {\n const predefinedId = $(this).attr('id');\n\n if (predefinedId && $(`#${predefinedId}`).length < 2) {\n return predefinedId;\n }\n\n prefix = (!prefix ? '' : `${prefix}-`);\n suffix = (!suffix ? '' : `-${suffix}`);\n className = (!className ? $(this).attr('class') : className);\n\n const str = prefix + className + uniqueIdCount + suffix;\n uniqueIdCount += 1;\n return str;\n};\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 * 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 * @private\n * @deprecated\n * TODO: Remove in 4.4.1 ?\n */\nutils.parseOptions = utils.parseSettings;\n\n/**\n* jQuery Behavior Wrapper for `utils.parseOptions`.\n* @deprecated\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 * 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 } else 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 * Escapes HTML, replacing special characters with encoded symbols.\n * @private\n * @param {string} value HTML in string form\n * @returns {string} the modified value\n */\n$.escapeHTML = function (value) {\n let newValue = value;\n if (typeof value === 'string') {\n newValue = newValue.replace(/&/g, '&');\n newValue = newValue.replace(//g, '>');\n }\n return newValue;\n};\n\n/**\n * Un-escapes HTML, replacing encoded symbols with special characters.\n * @private\n * @param {string} value HTML in string form\n * @returns {string} the modified value\n */\n$.unescapeHTML = function (value) {\n let newValue = value;\n if (typeof value === 'string') {\n newValue = newValue.replace(/</g, '<').replace(/>/g, '>');\n newValue = newValue.replace(/&/g, '&');\n }\n return newValue;\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 */\n$.sanitizeHTML = function (html) {\n let santizedHtml = html.replace(/)<[^<]*)*<\\/script>/g, '');\n santizedHtml = santizedHtml.replace(/<[^>]+/g, match => match.replace(/(\\/|\\s)on\\w+=(\\'|\")?[^\"]*(\\'|\")?/g, '')); // eslint-disable-line\n\n return santizedHtml;\n};\n\n/**\n * Clearable (Shows an X to clear)\n * @private\n */\n$.fn.clearable = function () {\n const self = this;\n this.element = $(this);\n\n // Create an X icon button styles in icons.scss\n this.xButton = $.createIconElement({ classes: 'close is-empty', icon: 'close' }).icon();\n\n // Create a function\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\n // Handle Events\n this.xButton\n .off()\n .on('click.clearable', () => {\n self.element.val('').trigger('change').focus().trigger('cleared');\n self.checkContents();\n });\n\n this.element.on('change.clearable, blur.clearable, keyup.clearable', () => {\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 ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'].indexOf(String.fromCharCode(key)) === -1) {\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 &&\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 * Object deep copy.\n * For now, alias jQuery.extend\n * Eventually we'll replace this with a non-jQuery extend method.\n * @private\n */\nutils.extend = $.extend;\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 if (rootElement instanceof $) {\n if (!rootElement.length) {\n return;\n }\n\n rootElement = rootElement[0];\n }\n\n setTimeout(() => {\n const uses = rootElement.getElementsByTagName('use');\n for (let i = 0; i < uses.length; i++) {\n const attr = uses[i].getAttribute('xlink:href');\n uses[i].setAttribute('xlink:href', 'x');\n uses[i].setAttribute('xlink: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 = $.extend({}, 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 */\nutils.safeSetSelection = function safeSetSelection(element, startPos, endPos) {\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 // Actually get ready to merge incoming options if we get to this point.\n return utils.extend(\n true, {},\n resolveFunctionBasedSettings(defaultOptions || {}),\n resolveFunctionBasedSettings(incomingOptions),\n (element !== undefined ? utils.parseSettings(element) : {})\n ); // possible to run this without an element present -- will simply skip this part\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\nexport { utils, math };\n","/* eslint-disable no-nested-ternary, no-useless-escape */\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 = '';\nif (typeof window.SohoConfig === 'object' && typeof window.SohoConfig.culturesPath === 'string') {\n existingCulturePath = window.SohoConfig.culturesPath;\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*/\nconst Locale = { // eslint-disable-line\n\n currentLocale: { name: '', data: {} }, // default\n cultures: {},\n culturesPath: existingCulturePath,\n\n /**\n * Sets the Lang in the Html Header\n * @returns {void}\n */\n updateLang() {\n const html = $('html');\n\n html.attr('lang', this.currentLocale.name);\n if (this.isRTL()) {\n html.attr('dir', 'rtl');\n } else {\n html.removeAttr('dir');\n }\n },\n\n /**\n * Get the path to the directory with the cultures\n * @returns {string} path containing culture files.\n */\n getCulturesPath() {\n if (!this.culturesPath) {\n const scripts = document.getElementsByTagName('script');\n const partialPathMin = 'sohoxi.min.js';\n const partialPath = 'sohoxi.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.indexOf(partialPathMin) > -1) {\n this.culturesPath = `${src.replace(partialPathMin, '')}cultures/`;\n }\n if (src.indexOf(partialPath) > -1) {\n this.culturesPath = `${src.replace(partialPath, '')}cultures/`;\n }\n }\n }\n return this.culturesPath;\n },\n\n /**\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 * @param {string} locale the 4-character Locale ID\n * @param {object} data translation data and locale-specific functions, such as calendars.\n * @returns {void}\n */\n addCulture(locale, data) {\n this.cultures[locale] = data;\n },\n\n /**\n * Set the currently used 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 this.dff = $.Deferred();\n\n // Map incorrect java locale to correct locale\n if (locale === 'in-ID') {\n locale = 'id-ID';\n }\n\n if (locale && !this.cultures[locale] && this.currentLocale.name !== locale) {\n this.setCurrentLocale(locale);\n\n // fetch the local and cache it\n $.ajax({\n url: `${this.getCulturesPath() + this.currentLocale.name}.js`,\n dataType: 'script',\n error() {\n self.dff.reject();\n }\n }).done(() => {\n self.setCurrentLocale(locale, self.cultures[locale]);\n self.addCulture(locale, self.currentLocale.data);\n\n if (locale && (locale === 'en-US' || self.cultures['en-US'])) {\n self.dff.resolve(self.currentLocale.name);\n }\n });\n }\n\n if (locale && locale !== 'en-US' && !this.cultures['en-US']) {\n // fetch the english local and cache it from translation defaults\n $.ajax({\n url: `${this.getCulturesPath()}en-US.js`,\n dataType: 'script',\n error() {\n self.dff.reject();\n }\n }).done(() => {\n self.addCulture(locale, self.currentLocale.data);\n self.dff.resolve(self.currentLocale.name);\n });\n }\n\n if (locale && self.currentLocale.data && self.currentLocale.dataName === locale) {\n self.dff.resolve(self.currentLocale.name);\n }\n\n self.setCurrentLocale(locale, self.cultures[locale]);\n\n if (self.cultures[locale] && this.cultureInHead()) {\n self.dff.resolve(self.currentLocale.name);\n }\n return this.dff.promise();\n },\n\n /**\n * Chooses a stored locale dataset and sets it as \"current\"\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 this.currentLocale.name = name;\n\n if (data) {\n this.currentLocale.data = data;\n this.currentLocale.dataName = name;\n }\n this.updateLang();\n },\n\n /**\n * Formats a Date Object and return it parsed in the current locale.\n * @param {date} value The date to show in the current locale.\n * @param {object} attribs Additional formatting settings.\n * @returns {string} the formatted date.\n */\n formatDate(value, attribs) {\n // We will use http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table\n if (!attribs) {\n attribs = { date: 'short' }; // can be date, time, datetime or pattern\n }\n\n if (!value) {\n return undefined;\n }\n\n // Convert if a timezone string.\n if (!(value instanceof Date) && typeof value === 'string' && value.indexOf('Z') > -1) {\n const tDate1 = new Date(value);\n value = tDate1;\n }\n\n if (!(value instanceof Date) && typeof value === 'string' && value.indexOf('T') > -1) {\n const tDate1 = new Date(value);\n value = tDate1;\n }\n\n // Convert if a string..\n if (!(value instanceof Date) && typeof value === 'string') {\n let tDate2 = Locale.parseDate(value, attribs);\n if (isNaN(tDate2) && attribs.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 // TODO: Can we handle this if (this.dff.state()==='pending')\n const data = this.currentLocale.data;\n let pattern;\n let ret = '';\n const cal = (data.calendars ? data.calendars[0] : null);\n\n if (attribs.pattern) {\n pattern = attribs.pattern;\n }\n\n if (attribs.date) {\n pattern = cal.dateFormat[attribs.date];\n }\n\n const day = value.getDate();\n const month = value.getMonth();\n const year = value.getFullYear();\n const mins = value.getMinutes();\n const hours = value.getHours();\n const seconds = value.getSeconds();\n\n // Special\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;\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(value.getMilliseconds(), 0));\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 (cal) {\n ret = ret.replace(' a', ` ${hours >= 12 ? cal.dayPeriods[1] : cal.dayPeriods[0]}`);\n ret = ret.replace('EEEE', cal.days.wide[value.getDay()]); // Day of Week\n }\n\n // Day of Week\n if (cal) {\n ret = ret.replace('EEEE', cal.days.wide[value.getDay()]); // Day of Week\n }\n ret = ret.replace('nnnn', 'ngày');\n ret = ret.replace('t1áng', 'tháng');\n ret = ret.replace('nnn', 'den');\n\n return ret.trim();\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 * 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 * Take a date string written in the current locale and parse it into a Date Object\n * @param {string} dateString The string to parse in the current format\n * @param {string} dateFormat The source format fx yyyy-MM-dd\n * @param {boolean} isStrict If true missing date parts will be considered\n * invalid. If false the current month/day.\n * @returns {date|undefined} updated date object, or nothing\n */\n parseDate(dateString, dateFormat, isStrict) {\n const thisLocaleCalendar = this.calendar();\n const orgDatestring = dateString;\n\n if (!dateString) {\n return undefined;\n }\n\n if (!dateFormat) {\n dateFormat = this.calendar().dateFormat.short;\n }\n\n if (dateFormat.pattern) {\n dateFormat = dateFormat.pattern;\n }\n\n if (typeof dateFormat === 'object' && dateFormat.date) {\n dateFormat = this.calendar().dateFormat[dateFormat.date];\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\n if (isDateTime) {\n // replace [space & colon & dot] with \"/\"\n dateFormat = dateFormat.replace(/[T\\s:.-]/g, '/').replace(/z/i, '');\n dateString = dateString.replace(/[T\\s:.-]/g, '/').replace(/z/i, '');\n }\n\n if (dateFormat === 'Mdyyyy' || dateFormat === 'dMyyyy') {\n dateString = `${dateString.substr(0, dateString.length - 4)}/${dateString.substr(dateString.length - 4, dateString.length)}`;\n dateString = `${dateString.substr(0, dateString.indexOf('/') / 2)}/${dateString.substr(dateString.indexOf('/') / 2)}`;\n }\n\n if (dateFormat === 'Mdyyyy') {\n dateFormat = 'M/d/yyyy';\n }\n\n if (dateFormat === 'dMyyyy') {\n dateFormat = 'd/M/yyyy';\n }\n\n if (dateFormat.indexOf(' ') !== -1) {\n dateFormat = dateFormat.replace(/[\\s:.]/g, '/');\n dateString = dateString.replace(/[\\s:.]/g, '/');\n }\n\n if (dateFormat.indexOf(' ') === -1 && dateFormat.indexOf('.') === -1 && dateFormat.indexOf('/') === -1 && dateFormat.indexOf('-') === -1) {\n let lastChar = dateFormat[0];\n let newFormat = '';\n let newDateString = '';\n\n for (i = 0, l = dateFormat.length; i < l; i++) {\n newFormat += (dateFormat[i] !== lastChar ? `/${dateFormat[i]}` : dateFormat[i]);\n newDateString += (dateFormat[i] !== lastChar ? `/${dateString[i]}` : dateString[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');\n const year = this.getDatePart(formatParts, dateStringParts, 'yy', 'yyyy');\n let hasDays = false;\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().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().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 '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 = value;\n break;\n case 'hh':\n if (numberValue < 0 || numberValue > 12) {\n return undefined;\n }\n 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 = value;\n break;\n case 'HH':\n if (numberValue < 0 || numberValue > 24) {\n return undefined;\n }\n dateObj.h = value.length === 1 ? `0${value}` : value;\n break;\n\n case 'ss':\n if (numberValue < 0 || numberValue > 60) {\n dateObj.ss = 0;\n break;\n }\n dateObj.ss = value;\n break;\n\n case 'SSS':\n dateObj.ms = value;\n break;\n\n case 'mm':\n if (numberValue < 0 || numberValue > 60) {\n dateObj.mm = 0;\n break;\n }\n dateObj.mm = value;\n break;\n\n case 'a':\n if ((value.toLowerCase() === thisLocaleCalendar.dayPeriods[0]) ||\n (value.toUpperCase() === thisLocaleCalendar.dayPeriods[0])) {\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() === thisLocaleCalendar.dayPeriods[1]) ||\n (value.toUpperCase() === thisLocaleCalendar.dayPeriods[1])) {\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 dateObj.return = undefined;\n dateObj.leapYear = ((dateObj.year % 4 === 0) &&\n (dateObj.year % 100 !== 0)) || (dateObj.year % 400 === 0);\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 dateObj.year = (new Date()).getFullYear();\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 // TODO: Need to find solution for three digit year\n // http://jira/browse/SOHO-4691\n // if (dateObj.year && dateObj.year.length === 3) {\n // dateObj.year = '2' + 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 return (this.isValidDate(dateObj.return) ? dateObj.return : undefined);\n },\n\n twoToFourDigitYear(twoDigitYear) {\n return parseInt((twoDigitYear > 39 ? '19' : '20') + twoDigitYear, 10);\n },\n\n getDatePart(formatParts, dateStringParts, filter1, filter2, filter3) {\n let ret = 0;\n\n $.each(dateStringParts, (i) => {\n if (filter1 === formatParts[i] || filter2 === formatParts[i] || filter3 === formatParts[i]) {\n ret = dateStringParts[i];\n }\n });\n\n return ret;\n },\n\n /**\n * Format a decimal with thousands and padding in the current locale.\n * http://mzl.la/1MUOEWm\n * @param {number} number The source number.\n * @param {object} options Additional options.style can be decimal, currency,\n percent and integer options.percentSign, options.minusSign, options.decimal,\n options.group options.minimumFractionDigits (0), options.maximumFractionDigits (3)\n * @returns {string} the formatted number.\n */\n formatNumber(number, options) {\n // Lookup , decimals, decimalSep, thousandsSep\n let formattedNum;\n let curFormat;\n let percentFormat;\n const decimal = options && options.decimal ? options.decimal : this.numbers().decimal;\n const group = options && options.group !== undefined ? options.group : this.numbers().group;\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 :\n this.currentLocale.data.currencySign;\n const format = options && options.currencyFormat ? options.currencyFormat :\n this.currentLocale.data.currencyFormat;\n\n curFormat = format.replace('¤', sign);\n }\n\n if (options && options.style === 'percent') {\n const percentSign = this.currentLocale.data.numbers.percentSign;\n\n percentFormat = this.currentLocale.data.numbers.percentFormat;\n percentFormat = percentFormat.replace('¤', percentSign);\n }\n\n if (typeof number === 'string') {\n if (decimal !== '.') {\n number = number.replace(decimal, '.');\n }\n number = Locale.parseNumber(number);\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 parts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, group);\n formattedNum = parts.join(decimal);\n\n // Position the negative at the front - There is no CLDR info for this.\n const minusSign = (this.currentLocale.data && this.currentLocale.data.numbers &&\n this.currentLocale.data.numbers.minusSign) ? this.currentLocale.data.numbers.minusSign : '-';\n const isNegative = (formattedNum.indexOf(minusSign) > -1);\n formattedNum = formattedNum.replace(minusSign, '');\n\n if (minimumFractionDigits === 0) { // Not default\n formattedNum = formattedNum.replace(/(\\.[0-9]*?)0+$/, '$1'); // remove trailing zeros\n formattedNum = formattedNum.replace(/\\.$/, ''); // remove trailing dot\n }\n\n if (minimumFractionDigits === 0 && decimal !== '.') { // Not default\n formattedNum = formattedNum.replace(/(\\,[0-9]*?)0+$/, '$1'); // remove trailing zeros\n formattedNum = formattedNum.replace(/\\,$/, ''); // remove trailing dot\n }\n\n if (minimumFractionDigits > 0) {\n const expr = new RegExp(`(\\\\..{${minimumFractionDigits}}[0-9]*?)0+$`);\n formattedNum = formattedNum.replace(expr, '$1'); // remove trailing zeros\n formattedNum = formattedNum.replace(/\\.$/, ''); // remove trailing dot\n }\n\n // Confirm Logic After All Locales are added.\n if (options && options.style === 'currency') {\n formattedNum = curFormat.replace('#,##0.00', formattedNum);\n formattedNum = formattedNum.replace('#,##0.00', formattedNum);\n }\n\n if (options && options.style === 'percent') {\n formattedNum = percentFormat.replace('#,##0', formattedNum);\n formattedNum = formattedNum.replace('#.##0', formattedNum);\n }\n\n if (isNegative) {\n formattedNum = minusSign + formattedNum;\n }\n return formattedNum;\n },\n\n decimalPlaces(number) {\n const result = /^-?[0-9]+\\.([0-9]+)$/.exec(number);\n return result === null ? 0 : result[1].length;\n },\n\n truncateDecimals(number, minDigits, maxDigits, round) {\n let multiplier = Math.pow(10, maxDigits);\n let adjustedNum = number * multiplier;\n let truncatedNum;\n\n // Round Decimals\n const decimals = this.decimalPlaces(number);\n\n // Handle larger numbers\n if (number.toString().length - decimals - 1 >= 10 ||\n (decimals === minDigits && decimals === maxDigits) || (decimals < maxDigits)) {\n multiplier = Math.pow(100, maxDigits);\n adjustedNum = number * multiplier;\n }\n\n truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);\n\n if (round && decimals >= maxDigits && adjustedNum > 0) {\n truncatedNum = Math.round(adjustedNum);\n }\n\n if (round && decimals <= maxDigits && decimals > 0) {\n truncatedNum = Math.round(adjustedNum);\n }\n\n if (decimals < maxDigits && decimals > 0) {\n truncatedNum = Math.floor(adjustedNum);\n maxDigits = Math.max(decimals, minDigits);\n }\n\n return (truncatedNum / multiplier).toFixed(maxDigits);\n },\n\n /**\n * Take a Formatted Number and return a real number\n * @param {string} input The source number (as a string).\n * @returns {number} the number as an actual Number type.\n */\n parseNumber(input) {\n const numSettings = this.currentLocale.data.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 = numSettings ? numSettings.group : ',';\n const decimal = numSettings ? numSettings.decimal : '.';\n const percentSign = numSettings ? numSettings.percentSign : '%';\n const currencySign = this.currentLocale.data.currencySign || '$';\n\n numString = numString.replace(new RegExp(`\\\\${group}`, 'g'), '');\n numString = numString.replace(decimal, '.');\n numString = numString.replace(percentSign, '');\n numString = numString.replace(currencySign, '');\n numString = numString.replace(' ', '');\n\n return parseFloat(numString);\n },\n\n /**\n * Overridable culture messages\n * @param {string} key The key to search for on the string.\n * @param {boolean} [showAsUndefined] causes a translated phrase to be\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, showAsUndefined) {\n if (this.currentLocale.data === undefined || this.currentLocale.data.messages === undefined) {\n return showAsUndefined ? undefined : `[${key}]`;\n }\n\n if (this.currentLocale.data.messages[key] === undefined) {\n // Substitue English Expression if missing\n if (!this.cultures['en-US'] || this.cultures['en-US'].messages[key] === undefined) {\n return showAsUndefined ? undefined : `[${key}]`;\n }\n return this.cultures['en-US'].messages[key].value;\n }\n\n return this.currentLocale.data.messages[key].value;\n },\n\n /**\n * Translate Day Period\n * @param {string} period should be \"am\", \"pm\", \"AM\", \"PM\", or \"i\"\n * @returns {string} the translated day period.\n */\n translateDayPeriod(period) {\n if (/am|pm|AM|PM/i.test(period)) {\n return Locale.calendar().dayPeriods[/AM|am/i.test(period) ? 0 : 1];\n }\n return period;\n },\n\n /**\n * Shortcut function to get 'first' calendar\n * @returns {object} containing calendar data.\n */\n calendar() {\n if (this.currentLocale.data.calendars) {\n return this.currentLocale.data.calendars[0];\n }\n\n // Defaults to ISO 8601\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 };\n },\n\n /**\n * Access the calendar array\n * @param {string} name the name of the calendar (fx: \"gregorian\", \"islamic-umalqura\")\n * @returns {object} containing calendar data\n */\n getCalendar(name) {\n if (this.currentLocale.data.calendars) {\n for (let i = 0; i < this.currentLocale.data.calendars.length; i++) {\n const calendar = this.currentLocale.data.calendars[i];\n if (calendar.name === name) {\n return calendar;\n }\n }\n }\n\n // Defaults to ISO 8601\n return [{ dateFormat: 'yyyy-MM-dd', timeFormat: 'HH:mm:ss' }];\n },\n\n /**\n * Shortcut function to get numbers\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: '#,##0 %',\n minusSign: '-',\n decimal: '.',\n group: ','\n };\n },\n\n /**\n * TODO: Document this\n * @param {string} n ?\n * @param {number} width ?\n * @param {string} z ?\n * @returns {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 read in \"right-to-left\" fashion.\n * @returns {boolean} whether or not this locale is \"right-to-left\".\n */\n isRTL() {\n return this.currentLocale.data.direction === 'right-to-left';\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 * @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 * @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 * @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 * @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 * Modifies a specified list of icons by flipping them horizontally to make them\n * compatible for RTL-based locales.\n * @returns {void}\n */\n flipIconsHorizontally() {\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 '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 (iconName && $.inArray(iconName, icons) !== -1) {\n $(this).addClass('icon-rtl-rotate');\n }\n });\n }\n\n};\n\n// Has to delay in order to check if no culture in head since scripts load async\n$(() => {\n setTimeout(() => {\n if (Locale && !Locale.cultureInHead() && !Locale.currentLocale.name) {\n Locale.set('en-US');\n }\n\n // ICONS: Right to Left Direction\n if (Locale && Locale.isRTL()) {\n Locale.flipIconsHorizontally();\n }\n }, 50);\n});\n\nexport { Locale };\n","// Text Highlight/Unhighlight Control\n// Originally called \"highlight v5\" by Johann Burkard\n// http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html\n//\n// Modified for SoHo Xi (TODO: bit.ly link to docs)\n\nfunction innerHighlight(node, pat) {\n let skip = 0;\n let pos;\n let spannode;\n let middlebit;\n let middleclone;\n\n if (node.nodeType === 3) {\n pos = node.data.toUpperCase().indexOf(pat);\n pos -= (node.data.substr(0, pos).toUpperCase().length - node.data.substr(0, pos).length);\n\n if (pos >= 0) {\n spannode = document.createElement('mark');\n spannode.className = 'highlight';\n middlebit = node.splitText(pos);\n middleclone = middlebit.cloneNode(true);\n spannode.appendChild(middleclone);\n middlebit.parentNode.replaceChild(spannode, middlebit);\n skip = 1;\n }\n } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {\n for (let i = 0; i < node.childNodes.length; ++i) {\n i += innerHighlight(node.childNodes[i], pat);\n }\n }\n\n return skip;\n}\n\n/**\n * Highlight a portion of text inside an element\n * @param {string} pat portion of text that's being highlighted\n * @returns {this} this\n */\n$.fn.highlight = function (pat) {\n if (this.length && pat && pat.length) {\n return this.each(() => {\n innerHighlight(this, pat.toUpperCase());\n });\n }\n return this;\n};\n\n/**\n * Removes highlighting from portions of text inside an element\n * @returns {this} this\n */\n$.fn.unhighlight = function () {\n return this.find('mark.highlight').each(() => {\n const node = this.parentNode;\n node.replaceChild(this.firstChild, this);\n node.normalize();\n }).end();\n};\n","import * as debug from '../utils/debug';\nimport { utils } from '../utils/utils';\n\n// Component Name\nconst COMPONENT_NAME = 'arrange';\n\n/**\n* The Arrange Component allows touch and drag support to sort UI items.\n* @class Arrange\n* @constructor\n*\n* @param {jQuery[]|HTMLElement} element The component element.\n* @param {object} [settings] The component settings.\n* @param {string} [settings.handle] The CSS class name of the handle element to connect\n* @param {string} [settings.itemsSelector] The CSS selector to match all the sortable elements.\n* @param {string} [settings.connectWith] Optional CSS Selector to connect with when using two lists\n* @param {boolean} [settings.isVisualItems] Use only index of visual items to trigger\n* @param {string} [settings.placeholder] The html for the element that appears while dragging\n* @param {string} [settings.placeholderCssClass='arrange-placeholder'] The class to add to the ghost element that is being dragged.\n*/\nconst ARRANGE_DEFAULTS = {\n handle: null, // The Class of the handle element\n itemsSelector: null,\n connectWith: false,\n isVisualItems: false,\n placeholder: null,\n placeholderCssClass: 'arrange-placeholder'\n};\n\nfunction Arrange(element, settings) {\n this.settings = utils.mergeSettings(element, settings, ARRANGE_DEFAULTS);\n\n this.element = $(element);\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n}\n\n// Arrange Methods\nArrange.prototype = {\n\n // example from: https://github.com/farhadi/html5arrangeable/blob/master/jquery.arrangeable.js\n init() {\n this.isTouch = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n this.handleEvents();\n },\n\n /**\n * Get Element By Touch In List\n * @private\n * @param {object} list element.\n * @param {number} x value.\n * @param {number} y value.\n * @returns {object} item found in list\n */\n getElementByTouchInList(list, x, y) {\n let returns = false;\n const listJq = $(list);\n\n for (let i = 0, l = listJq.length; i < l; i++) {\n const item = $(listJq[i]);\n const offset = item.offset();\n\n if (!(x <= offset.left || x >= offset.left + item.outerWidth() ||\n y <= offset.top || y >= offset.top + item.outerHeight())) {\n returns = item;\n }\n }\n return returns;\n },\n\n /**\n * Dragg touch element\n * @private\n * @param {object} e as event.\n * @param {object} elm as element.\n * @returns {void}\n */\n dragTouchElement(e, elm) {\n const orig = e.originalEvent.changedTouches[0];\n elm[0].style.top = `${(orig.pageY - this.offset.y)}px`;\n elm[0].style.left = `${(orig.pageX - this.offset.x)}px`;\n },\n\n /**\n * Removes event bindings from the instance.\n * @private\n * @returns {object} The api\n */\n unbind() {\n this.items\n .removeClass('draggable')\n .removeAttr('draggable')\n .off(`selectstart.arrange ${this.dragStart} ${this.dragEnd} ${this.dragWhileDragging}`);\n\n $(this.handle, this.items)\n .removeClass('draggable')\n .off('mousedown.arrange mouseup.arrange touchstart.arrange touchend.arrange');\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, ARRANGE_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 /**\n * Find out the visual index to trigger\n * @private\n * @param {object} elem to get index number.\n * @returns {number} the index\n */\n getVisualIndex(elem) {\n const s = this.settings;\n let idx = null;\n\n if (s.isVisualItems) {\n let items = this.element.children().not('[data-arrange-exclude=\"true\"]');\n if (s.itemsSelector) {\n items = $(s.itemsSelector, this.element).not('[data-arrange-exclude=\"true\"]');\n }\n idx = items.index(elem);\n }\n\n return idx;\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 let index;\n let isHandle;\n const status = {};\n let items = this.element.children().not('[data-arrange-exclude=\"true\"]');\n let placeholder = $(`<${(/^(ul|ol)$/i.test(this.element[0].tagName) ? 'li' : 'div')}>`);\n\n if (s.itemsSelector) {\n items = $(s.itemsSelector, this.element).not('[data-arrange-exclude=\"true\"]');\n placeholder = $(`<${items.first()[0].tagName} />`);\n }\n\n if (s.placeholder) {\n placeholder = $(s.placeholder);\n }\n\n this.dragStart = 'dragstart.arrange touchstart.arrange gesturestart.arrange';\n this.dragEnd = 'dragend.arrange touchend.arrange touchcancel.arrange gestureend.arrange';\n this.dragWhileDragging = 'dragover.arrange dragenter.arrange drop.arrange touchmove.arrange gesturechange.arrange';\n\n this.handle = s.handle || this.element.attr('data-arrange-handle');\n this.connectWith = this.element.attr('data-arrange-connectWith');\n this.placeholders = placeholder;\n\n if (!this.isTouch) {\n this.placeholders.addClass(`${s.placeholderCssClass} draggable`);\n }\n\n // Use Handle if available\n $(this.handle, items).addClass('draggable')\n .on('mousedown.arrange touchstart.arrange', () => {\n isHandle = true;\n })\n .on('mouseup.arrange touchend.arrange', () => {\n isHandle = false;\n });\n\n // Add connect with\n if (this.connectWith) {\n items = items\n .add($(this.connectWith).children().not('[data-arrange-exclude=\"true\"]'))\n .data('connectWith', this.connectWith);\n }\n\n this.items = items;\n\n // Draggable Items\n this.items\n .attr('draggable', true).addClass(this.handle ? '' : 'draggable')\n .add([this, placeholder])\n .not('a[href], img')\n .on('selectstart.arrange', function () {\n if (this.dragDrop) {\n this.dragDrop();// ie9\n }\n return false;\n })\n .end()\n\n .each(function () {\n $(this)\n // Drag start --------------------------------------------------------------------------\n .on(self.dragStart, function (e) {\n if (self.handle && !isHandle) {\n if (self.isTouch) {\n return;\n }\n return false;// eslint-disable-line\n }\n isHandle = false;\n self.dragging = $(this);\n\n index = self.dragging.addClass('arrange-dragging').index();\n const idx = s.isVisualItems ?\n self.getVisualIndex(self.dragging) : index;\n\n $.extend(status, { start: self.dragging, startIndex: idx });\n\n /**\n * Fires before moving an element allowing you to access the ui to\n customize the draggable item.\n *\n * @event beforearrange\n * @memberof Arrange\n * @property {object} event - The jquery event object\n * @property {object} status - Status for this item\n */\n const result = self.element.triggerHandler('beforearrange', status);\n if ((typeof result === 'boolean' && !result) || (typeof result === 'string' && result.toLowerCase() === 'false')) {\n self.dragging = null;\n return;\n }\n\n if (self.isTouch) {\n const rect = self.dragging[0].getBoundingClientRect();\n const touch = e.originalEvent.changedTouches[0];\n\n // Save offset\n self.offset = {\n x: touch.pageX - rect.left,\n y: touch.pageY - rect.top\n };\n self.placeholderTouch = self.dragging\n .clone().addClass('is-touch').attr('id', 'arrange-placeholder-touch')\n .insertBefore(self.dragging);\n\n self.dragTouchElement(e, self.placeholderTouch);\n } else {\n const dt = e.originalEvent.dataTransfer;\n dt.effectAllowed = 'move';\n dt.setData('Text', 'dummy');\n }\n })\n\n // Drag end ----------------------------------------------------------\n .on(self.dragEnd, () => {\n if (!self.dragging) {\n return;\n }\n\n if (self.isTouch) {\n self.dragging.css('opacity', 1);\n self.placeholderTouch.remove();\n }\n\n self.placeholders.filter(':visible').after(self.dragging);\n self.dragging.removeClass('arrange-dragging').show();\n self.placeholders.detach();\n\n if (index !== self.dragging.index()) {\n const idx = s.isVisualItems ?\n self.getVisualIndex(self.dragging) : self.dragging.index();\n $.extend(status, { end: self.dragging, endIndex: idx });\n\n /**\n * Fires after moving an element allowing you do any follow up updating.\n *\n * @event arrangeupdate\n * @memberof Arrange\n * @property {object} event - The jquery event object\n * @property {object} status - Status for this item\n */\n self.element.triggerHandler('arrangeupdate', status);\n }\n self.dragging = null;\n self.placeholderTouch = null;\n })\n\n // While dragging ----------------------------------------------------\n .on(self.dragWhileDragging, function (e) {\n if (!self.dragging) {\n return;\n }\n let overItem = this;\n let overIndex;\n e.preventDefault();\n\n /**\n * Fires after finishing an arrange action.\n *\n * @event dragend\n * @memberof ApplicationMenu\n * @param {object} event - The jquery event object\n */\n if (e.type === 'drop') {\n e.stopPropagation();\n self.dragging.trigger('dragend.arrange');\n return false;// eslint-disable-line\n }\n\n if (self.isTouch) {\n const touch = e.originalEvent.touches[0];\n overItem = self.getElementByTouchInList(items, touch.pageX, touch.pageY) || overItem;\n }\n overItem = $(overItem);\n\n if (!self.isTouch) {\n e.originalEvent.dataTransfer.dropEffect = 'move';\n }\n\n if (items.is(overItem) && placeholder.index() !== overItem.index()) {\n if (self.isTouch) {\n self.dragging.css('opacity', 0);\n } else {\n self.dragging.hide();\n }\n\n let idx;\n if (placeholder.index() < (overItem.index())) {\n placeholder.insertAfter(overItem);\n overIndex = overItem.index();\n idx = s.isVisualItems ?\n self.getVisualIndex(overItem) : overIndex;\n } else {\n placeholder.insertBefore(overItem);\n overIndex = placeholder.index();\n idx = s.isVisualItems ?\n self.getVisualIndex(placeholder) : overIndex;\n }\n\n $.extend(status, { over: overItem, overIndex: idx });\n self.element.triggerHandler('draggingarrange', status);\n\n // Fix: IE-11 on windows-10 svg was disappering\n utils.fixSVGIcons(overItem);\n\n self.placeholders.not(placeholder).detach();\n } else if (!self.placeholders.is(this)) {\n self.placeholders.detach();\n self.element.append(placeholder);\n }\n\n if (self.isTouch) {\n self.dragTouchElement(e, self.placeholderTouch);\n return;\n }\n return false;// eslint-disable-line\n });//-----------------------------------------------------------------\n });// end each items\n }\n\n};\n\nexport { Arrange, COMPONENT_NAME };\n","import { Arrange, COMPONENT_NAME } from './arrange';\n\n/**\n * jQuery component wrapper for Arrange\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} jQuery-wrapped components being acted on\n */\n$.fn.arrange = 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 Arrange(this, settings));\n }\n });\n};\n","/* eslint-disable no-cond-assign */\n\nimport * as debug from '../utils/debug';\nimport { utils } from '../utils/utils';\n\n// TODO: Resize: http://stackoverflow.com/questions/8258232/resize-an-html-element-using-touches\n// Similar: https://github.com/desandro/draggabilly\n\n// The name of this plugin\nconst COMPONENT_NAME = 'drag';\n\n/**\n * Drag/Drop functions with touch support.\n * @class Drag\n * @constructor\n *\n * @param {jQuery[]|HTMLElement} element The component element.\n * @param {object} [settings] The component settings.\n * @param {string} [settings.axis] Constrains dragging to either axis. Possible values: null, 'x', 'y'\n * @param {boolean} [settings.clone=false] Set to true to clone the object to drag. In many situations this is\n * needed to break out of layout.\n * @param {string} [settings.cloneCssClass='is-clone'] Css class added to clone element (defaults is 'is-clone')\n * @param {boolean} [settings.clonePosIsFixed=false] If true cloned object will use css style \"position: fixed\"\n * @param {string} [settings.cloneAppendTo] Selector to append to for the clone\n * ['body'|'parent'|'jquery object'] default:'body'\n * @param {boolean} [settings.containment=false] Constrains dragging to within the bounds of the specified element\n * or region. Possible values: \"parent\", \"document\", \"window\".\n * @param {string} [settings.obstacle] jQuery Selector of object(s) that you cannot drag into,\n * @param {boolean} [settings.underElements=false] If set to true will return list of elements that are\n * underneath the drag element\n * @param {object} [settings.containmentOffset={left: 0, top: 0}] How close to the containment object should we be allowed\n * to drag in position form. `{left: 0, top: 0}`\n*/\nconst DRAG_DEFAULTS = {\n axis: null,\n clone: false,\n cloneCssClass: 'is-clone',\n clonePosIsFixed: false,\n cloneAppendTo: null,\n containment: false,\n obstacle: false,\n underElements: false,\n containmentOffset: { left: 0, top: 0 }\n};\n\nfunction Drag(element, settings) {\n this.element = $(element);\n this.settings = utils.mergeSettings(this.element[0], settings, DRAG_DEFAULTS);\n\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n}\n\n// Plugin Methods\nDrag.prototype = {\n\n init() {\n this.handleEvents();\n },\n\n /**\n * Trigger events and remove clone\n * @private\n * @param {number} left Current left position\n * @param {number} top Current top position\n */\n finish(left, top) {\n const pos = { top, left };\n\n this.element.off('mouseup.draggable');\n $(document).off('mousemove.draggable mouseup.draggable');\n if (this.settings.underElements) {\n pos.underElements = this.getElementsFromPoint(pos.left, pos.top);\n }\n\n /**\n * Fires after the drag is completed. Use this to remove / set drag feedback off.\n * @event dragend\n * @memberof Drag\n * @property {object} event - The jquery event object.\n * @property {object} ui - The dialog object\n */\n this.element.trigger('dragend', pos);\n this.element.removeClass('is-dragging');\n\n if (this.clone) {\n if (this.settings.axis === 'x') {\n delete pos.top;\n }\n\n if (this.settings.axis === 'y') {\n delete pos.left;\n }\n // this.element.css(pos);\n this.clone.remove();\n this.clone = null;\n }\n\n // Clear Cached Sizes\n if (this.obstacle) {\n this.obstacle = null;\n }\n if (this.upperYLimit) {\n this.upperYLimit = null;\n }\n if (this.upperXLimit) {\n this.upperXLimit = null;\n }\n $('body').removeClass('disable-select');\n },\n\n // Move the object from the event coords\n move(left, top) {\n const self = this;\n\n const css = {\n left,\n top\n };\n\n // X-Y Axis\n if (this.settings.axis === 'x') {\n delete css.top;\n }\n\n if (this.settings.axis === 'y') {\n delete css.left;\n }\n\n if (this.settings.containment) {\n if (this.settings.containment === 'parent') {\n this.container = this.element.parent();\n } else if (this.settings.containment === 'window') {\n this.container = $(window);\n } else if (this.settings.containment === 'container') {\n this.container = this.element.closest('.page-container');\n } else {\n this.container = $(document);\n }\n\n if (!this.upperXLimit) {\n this.upperXLimit = (this.container.width() - this.element.outerWidth()) +\n this.settings.containmentOffset.left;\n }\n if (!this.upperYLimit) {\n this.upperYLimit = (this.container.height() - this.element.outerHeight()) +\n this.settings.containmentOffset.top;\n }\n if (css.top > this.upperYLimit) {\n css.top = this.upperYLimit;\n }\n\n if (css.left > this.upperXLimit) {\n css.left = this.upperXLimit;\n }\n\n if (css.top < 0) {\n css.top = 0;\n }\n\n if (css.left < 0) {\n css.left = 0;\n }\n\n if (this.settings.containment === 'container' && css.left <= 1) {\n css.left = 1;\n }\n }\n\n if (this.settings.obstacle) {\n const elemOffset = (this.clone ? this.clone.offset() : this.element.offset());\n const elemWidth = (this.clone ? this.clone.outerWidth() : this.element.outerWidth());\n const movingRight = css.left > elemOffset.left;\n\n // Caching this so drag is not jaggie\n if (!this.obstacle) {\n this.obstacle = $(this.settings.obstacle).not(this.element);\n const obstacleOffset = $(this.obstacle).offset();\n\n this.constraints = {\n top: obstacleOffset.top,\n left: obstacleOffset.left,\n bottom: obstacleOffset.top + this.obstacle.outerHeight(),\n right: obstacleOffset.left + this.obstacle.outerWidth()\n };\n }\n\n if (!movingRight && self.originalPos.left > this.constraints.left &&\n css.left <= this.constraints.right) {\n css.left = this.constraints.right;\n }\n\n if (movingRight && self.originalPos.left + elemWidth <= this.constraints.left &&\n css.left + elemWidth >= this.constraints.left) {\n css.left = (this.constraints.left - this.obstacle.outerWidth());\n }\n\n // TODO: Moving Down\n }\n\n const applyCssStyle = function (el, applyCss, prop) {\n if (typeof applyCss[prop] !== 'undefined') {\n el[0].style[prop] = `${applyCss[prop]}px`;\n }\n };\n\n applyCssStyle((this.clone || this.element), css, 'top');\n applyCssStyle((this.clone || this.element), css, 'left');\n\n if (this.settings.underElements) {\n css.underElements = this.getElementsFromPoint(css.left, css.top);\n }\n\n /**\n * Fires (many times) while dragging is occuring. Use this for DOM feedback but\n * be careful about what you do in here for performance.\n * @event drag\n * @memberof Drag\n * @property {object} event - The jquery event object.\n * @property {object} ui - The dialog object\n */\n this.element.trigger('drag', css);\n },\n\n /**\n * Get elements from given point.\n * @param {number} x The x-coordinate of the Point.\n * @param {number} y The y-coordinate of the Point.\n * @Returns {array} List of all elements at the given point.\n */\n getElementsFromPoint(x, y) {\n let elements = [];\n\n if (document.elementsFromPoint) {\n elements = document.elementsFromPoint(x, y);\n } else if (document.msElementsFromPoint) {\n elements = document.msElementsFromPoint(x, y);\n } else {\n let i;\n let l;\n let d;\n let current;\n let max = 999;\n const pointerEvents = [];\n\n while ((current = document.elementFromPoint(x, y)) && elements.indexOf(current) === -1 &&\n current !== null && max > -1) {\n max--;\n\n // push the element and its current style\n elements.push(current);\n pointerEvents.push({\n value: current.style.getPropertyValue('pointer-events') || '',\n priority: current.style.getPropertyPriority('pointer-events')\n });\n // add \"pointer-events: none\", to get to the underlying element\n current.style.setProperty('pointer-events', 'none', 'important');\n }\n // restore the previous pointer-events values\n for (i = 0, l = elements.length; i < l; i++) {\n d = pointerEvents[i];\n elements[i].style.setProperty('pointer-events', d.value, d.priority);\n }\n }\n return elements;\n },\n\n /**\n * Update the component and optionally apply new settings.\n * @param {object} settings the settings to update to.\n */\n updated(settings) {\n if (settings) {\n this.settings = utils.mergeSettings(this.element[0], settings, this.settings);\n }\n },\n\n /**\n * Detach all functionality and events.\n */\n destroy() {\n $.removeData(this.element[0], COMPONENT_NAME);\n this.element.off('touchstart.draggable MSPointerDown.draggable pointerdown.draggable touchmove.draggable touchend.draggable touchcancel.draggable mousedown.draggable');\n },\n\n handleEvents() {\n const self = this;\n self.offset = null;\n\n // Touch and Drag Support\n self.element.attr('draggable', false);\n\n if ('onpointerdown' in window || 'onmspointerdown' in window) {\n // TODO: Setup Pointer Events for 11 - pointerdown MSPointerDown, pointermove,\n // MSPointerMove, pointerup MSPointerUp\n } else {\n // Touch-only Drag Support\n self.element.on('touchstart.draggable gesturestart.draggable', function (e) {\n const pos = $(this).position();\n const orig = e.originalEvent;\n\n self.offset = {\n x: orig.changedTouches[0].pageX - pos.left,\n y: orig.changedTouches[0].pageY - pos.top\n };\n\n self.originalPos = pos;\n self.element.addClass('is-dragging');\n\n /**\n * When the dragging is initiated. Use this to customize/style\n * the drag/drop objects in the DOM.\n * @event dragstart\n * @memberof Drag\n * @property {object} event - The jquery event object.\n * @property {object} ui - The dialog object\n */\n self.element.trigger('dragstart', pos);\n })\n // Move\n .on('touchmove.draggable gesturechange.draggable', (e) => {\n e.preventDefault();\n const orig = e.originalEvent;\n\n // do now allow two touch points to drag the same element\n if (orig.targetTouches.length > 1) {\n return;\n }\n\n const xpos = orig.changedTouches[0].pageX - self.offset.x;\n const ypos = orig.changedTouches[0].pageY - self.offset.y;\n self.move(xpos, ypos);\n })\n // Finish Touch Dragging\n .on('touchend.draggable gestureend.draggable touchcancel.draggable', (e) => {\n e.preventDefault();\n const touch = e.originalEvent.changedTouches[0];\n self.finish(touch.pageX - self.offset.x, touch.pageY - self.offset.y);\n });\n }\n\n // Always bind mousedown in either scenario, in the event that a mouse is used\n self.element.on('mousedown.draggable', (e) => {\n e.preventDefault();\n\n const pos = self.settings.clonePosIsFixed ?\n self.element[0].getBoundingClientRect() : self.element.position();\n\n // Save offset\n self.offset = {\n x: e.pageX - pos.left,\n y: e.pageY - pos.top\n };\n\n self.originalPos = pos;\n\n // Prevent Text Selection\n $('body').addClass('disable-select');\n\n // Handle Mouse Press over draggable element\n $(document).on('mousemove.draggable', (mouseMoveEvent) => {\n mouseMoveEvent.preventDefault();\n self.move(mouseMoveEvent.pageX - self.offset.x, mouseMoveEvent.pageY - self.offset.y);\n });\n\n // Handle Mouse release over draggable element close out events and trigger\n $(document).on('mouseup.draggable', (docMouseUpEvent) => {\n docMouseUpEvent.preventDefault();\n self.finish(e.pageX - self.offset.x, docMouseUpEvent.pageY - self.offset.y);\n });\n\n self.element.on('mouseup.draggable', (mouseUpEvent) => {\n mouseUpEvent.preventDefault();\n self.finish(mouseUpEvent.pageX - self.offset.x, mouseUpEvent.pageY - self.offset.y);\n });\n\n // Trigger dragging\n // Clone\n if (!self.clone && self.settings.clone) {\n self.clone = self.element.clone(true);\n if (self.settings.cloneAppendTo === 'parent') {\n self.settings.cloneAppendTo = self.element.parent();\n }\n self.clone\n .addClass(self.settings.cloneCssClass)\n .appendTo(self.settings.cloneAppendTo || 'body');\n }\n\n self.element.addClass('is-dragging');\n self.element.trigger('dragstart', [pos, self.clone]);\n });\n }\n};\n\nexport { Drag, COMPONENT_NAME };\n","import { Drag, COMPONENT_NAME } from './drag';\n\n/**\n * jQuery Component Wrapper for Drag\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.drag = 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 Drag(this, settings));\n }\n });\n};\n","import { utils } from '../utils/utils';\nimport { DOM } from '../utils/dom';\n\n// Component Name\nconst COMPONENT_NAME = 'place';\n\n// Default Component Options\nconst DEFAULT_PLACE_SETTINGS = {\n bleedFromContainer: false,\n callback: null,\n container: null,\n parent: null,\n parentXAlignment: 'center',\n parentYAlignment: 'center',\n placement: 'bottom',\n strategies: ['nudge']\n};\n\n// Constants used throughout\nconst PLACE_STRATEGIES = ['nudge', 'clockwise', 'flip', 'shrink', 'shrink-x', 'shrink-y'];\nconst PLACE_POSITIONS = ['top', 'left', 'right', 'bottom', 'center'];\nconst PLACE_X_ALIGNMENTS = ['left', 'center', 'right'];\nconst PLACE_Y_ALIGNMENTS = ['top', 'center', 'bottom'];\nconst PLACEMENT_OBJECT_SETTING_KEYS = [\n 'x', 'y',\n 'container', 'containerOffsetX', 'containerOffsetY',\n 'callback',\n 'parent', 'parentXAlignment', 'parentYAlignment',\n 'useParentWidth', 'useParentHeight',\n 'placement',\n 'strategies'\n];\n\n/**\n * Object that contains coordinates along with temporary, changeable properties.\n * This object gets passed around the Place Behavior and modified during each phase of positioning.\n * This object is also passed to all callbacks and event listeners for further modification.\n * @private\n * @param {object} [placementOptions] object containing settings for placement\n * @returns {void}\n */\nfunction PlacementObject(placementOptions) {\n const self = this;\n\n PLACEMENT_OBJECT_SETTING_KEYS.forEach((val) => {\n if (placementOptions[val] === null) {\n return;\n }\n\n if (val === 'x' || val === 'y') {\n self.setCoordinate(val, placementOptions[val]);\n self[`original${val}`] = placementOptions[val];\n return;\n }\n\n self[val] = placementOptions[val];\n });\n\n this.modified = false;\n\n return this.sanitize();\n}\n\nPlacementObject.prototype = {\n isReasonableDefault(setting, limits) {\n return $.inArray(setting, limits) > -1;\n },\n\n sanitize() {\n const self = this;\n\n this.bleedFromContainer = this.bleedFromContainer === true;\n this.callback = (typeof this.callback === 'function') ? this.callback : DEFAULT_PLACE_SETTINGS.callback;\n this.container = (this.container instanceof $ && this.container.length) ?\n this.container : DEFAULT_PLACE_SETTINGS.container;\n this.containerOffsetX = !isNaN(parseInt(this.containerOffsetX, 10)) ?\n this.containerOffsetX : 0;\n this.containerOffsetY = !isNaN(parseInt(this.containerOffsetY, 10)) ?\n this.containerOffsetY : 0;\n this.parent = (this.parent instanceof $ && this.parent.length) ?\n this.parent : DEFAULT_PLACE_SETTINGS.parent;\n this.parentXAlignment = this.isReasonableDefault(this.parentXAlignment, PLACE_X_ALIGNMENTS) ?\n this.parentXAlignment : DEFAULT_PLACE_SETTINGS.parentXAlignment;\n this.parentYAlignment = this.isReasonableDefault(this.parentYAlignment, PLACE_Y_ALIGNMENTS) ?\n this.parentYAlignment : DEFAULT_PLACE_SETTINGS.parentYAlignment;\n this.placement = this.isReasonableDefault(this.placement, PLACE_POSITIONS) ?\n this.placement : DEFAULT_PLACE_SETTINGS.placement;\n this.useParentHeight = this.useParentHeight === true;\n this.useParentWidth = this.useParentWidth === true;\n\n if (!$.isArray(this.strategies) || !this.strategies.length) {\n this.strategies = ['nudge'];\n }\n this.strategies.forEach((strat, i) => {\n self.strategies[i] = self.isReasonableDefault(strat, PLACE_STRATEGIES) ?\n strat : self.strategies[i];\n });\n },\n\n setCoordinate(coordinate, value) {\n const coordinates = ['x', 'y'];\n if (!this.isReasonableDefault(coordinate, coordinates)) {\n return;\n }\n\n if (isNaN(value)) {\n value = 0;\n }\n\n this[coordinate] = Math.round(value);\n }\n};\n\n/**\n * The Place API which handles internal placement of popups, menus ect.\n * @class Place\n * @param {HTMLElement|jQuery[]} element the base element being placed\n * @param {object} [settings] incoming settings\n * @param {boolean} [settings.bleedFromContainer = false] If true, allows positioned content to bleed\n * outside of a defined container.\n * @param {function} [settings.callback] If defined, provides extra placement adjustments\n * after the main calculation is performed.\n * @param {HTMLElement} [settings.container] If defined, contains the placement of the\n * element to the boundaries of a specific container element.\n * @param {HTMLElement} [settings.parent] If defined, will be used as the reference\n * element for placement this element.\n * @param {string} [settings.parentXAlignment = 'center'] Only used for parent-based placement.\n * Determines the X-coordinate alignment of the placed element against its parent.\n * @param {string} [settings.parentYAlignment = 'center'] Only used for parent-based placement.\n * Determines the Y-coordinate alignment of the placed element against its parent.\n * @param {string} [settings.placement = 'bottom'] If defined, changes the direction in which\n * placement of the element happens\n * @param {string[]} [settings.strategies = ['nudge']] Determines the \"strategy\" for alternatively\n * placing the element if it doesn't fit in the defined boundaries. Only matters\n * when \"parent\" is a defined setting. It's possible to define multiple strategies\n * and execute them in order.\n */\nfunction Place(element, settings) {\n this.settings = utils.mergeSettings(element, settings, DEFAULT_PLACE_SETTINGS);\n this.element = $(element);\n this.init();\n}\n\nPlace.prototype = {\n\n /**\n * Do other init (change/normalize settings, load externals, etc)\n * @private\n * @returns {this} component instance\n */\n init() {\n return this\n .build()\n .handleEvents();\n },\n\n /**\n * Add markup to the control\n * @private\n * @returns {this} component instance\n */\n build() {\n if (!this.element.hasClass('placeable')) {\n this.element.addClass('placeable');\n }\n\n // Setup a hash of original styles that will retain width/height whenever\n // the placement for this element is recalculated.\n this.originalStyles = {};\n const h = this.element[0].style.height;\n const w = this.element[0].style.width;\n\n if (h) {\n this.originalStyles.height = h;\n }\n if (w) {\n this.originalStyles.width = w;\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.on(`place.${COMPONENT_NAME}`, (e, x, y) => {\n self.place(new PlacementObject({ x, y }));\n }).on(`updated.${COMPONENT_NAME}`, () => {\n self.updated();\n });\n\n return this;\n },\n\n /**\n * Actually renders an element with coordinates inside the DOM\n * @param {PlacementObject} placementObj settings for the placement routine\n * @returns {void}\n */\n render(placementObj) {\n const unitRegex = /(px|%)/i;\n\n this.element.offset({\n left: placementObj.x,\n top: placementObj.y\n });\n\n if (placementObj.height) {\n this.element[0].style.height = placementObj.height + (unitRegex.test(`${placementObj.height}`) ? '' : 'px');\n }\n if (placementObj.width) {\n this.element[0].style.width = placementObj.width + (unitRegex.test(`${placementObj.width}`) ? '' : 'px');\n }\n },\n\n /**\n * Main placement API Method (external)\n * Can either take a PlacementObject as a single argument, or can take 2 coordinates (x, y) and\n * will use the pre-defined settings.\n * @param {PlacementObject} placementObj settings for the placement routine\n * @returns {void}\n */\n place(placementObj) {\n const curr = [\n this.element[0].style.left,\n this.element[0].style.top,\n ];\n\n // Cancel placement with return:false; from a \"beforeplace\" event\n const canBePlaced = this.element.trigger('beforeplace', [curr]);\n if (!canBePlaced) {\n return curr;\n }\n\n if (!(placementObj instanceof PlacementObject)) {\n placementObj = new PlacementObject(placementObj);\n }\n\n // If no values are defined, simply return the current coordinates with a warning.\n if (placementObj.x == null && placementObj.y == null) {\n // TODO: Log a warning about not positioning stuff?\n return curr;\n }\n\n // Remove any previous placement styles\n this.clearOldStyles();\n\n // Use different methods if placement against a parent, versus straight-up coordinate placement\n if (placementObj.parent) {\n return this.placeWithParent(placementObj);\n }\n\n return this.placeWithCoords(placementObj);\n },\n\n /**\n * Placement Routine that expects a parent to be used as a base placement marking.\n * In this case, \"x\" and \"y\" integers are \"relative\" adjustments to the original\n * numbers generated by the parent. Can be modified by using a callback in the settings.\n * @private\n * @param {PlacementObject} placementObj settings for the placement routine\n * @returns {PlacementObject} modified placementObject with updated settings\n */\n placeWithParent(placementObj) {\n if (!placementObj.parent || !placementObj.parent.length) {\n // can't simply return x and y here because if there is no parent element,\n // these numbers are not coordinates, they are offsets.\n return [undefined, undefined];\n }\n\n const self = this;\n const parentRect = DOM.getDimensions(placementObj.parent[0]);\n const elRect = DOM.getDimensions(this.element[0]);\n const container = this.getContainer(placementObj);\n const containerIsBody = container.length && container[0] === document.body;\n // NOTE: Usage of $(window) instead of $('body') is deliberate here - http://stackoverflow.com/a/17776759/4024149.\n // Firefox $('body').scrollTop() will always return zero.\n const scrollX = containerIsBody ? $(window).scrollLeft() : container.scrollLeft();\n const scrollY = containerIsBody ? $(window).scrollTop() : container.scrollTop();\n\n if (placementObj.useParentWidth) {\n placementObj.width = parentRect.width;\n }\n if (placementObj.useParentHeight) {\n placementObj.height = parentRect.height;\n }\n\n function getCoordsFromPlacement(incomingPlacementObj) {\n const p = incomingPlacementObj.placement;\n const aX = incomingPlacementObj.parentXAlignment;\n const aY = incomingPlacementObj.parentYAlignment;\n let cX;\n let cY;\n\n // Set initial placements\n switch (p) {\n case 'top':\n cY = parentRect.top - elRect.height - incomingPlacementObj.y +\n (containerIsBody ? scrollY : 0);\n break;\n case 'left':\n cX = parentRect.left - elRect.width - incomingPlacementObj.x +\n (containerIsBody ? scrollX : 0);\n break;\n case 'right':\n cX = parentRect.right + incomingPlacementObj.x + (containerIsBody ? scrollX : 0);\n break;\n default: // Bottom\n cY = parentRect.bottom + incomingPlacementObj.y + (containerIsBody ? scrollY : 0);\n break;\n }\n\n // Set X alignments on bottom/top placements\n if (p === 'top' || p === 'bottom') {\n switch (aX) {\n case 'left':\n cX = parentRect.left - incomingPlacementObj.x + (containerIsBody ? scrollX : 0);\n break;\n case 'right':\n cX = (parentRect.right - elRect.width) +\n incomingPlacementObj.x + (containerIsBody ? scrollX : 0);\n break;\n default: // center\n cX = (parentRect.left + ((parentRect.width - elRect.width) / 2)) +\n incomingPlacementObj.x + (containerIsBody ? scrollX : 0);\n break;\n }\n }\n\n // Set Y alignments on left/right placements\n if (p === 'right' || p === 'left') {\n switch (aY) {\n case 'top':\n cY = parentRect.top - incomingPlacementObj.y + (containerIsBody ? scrollY : 0);\n break;\n case 'bottom':\n cY = (parentRect.bottom - elRect.height) +\n incomingPlacementObj.y + (containerIsBody ? scrollY : 0);\n break;\n default: // center\n cY = (parentRect.top + ((parentRect.height - elRect.height) / 2)) +\n incomingPlacementObj.y + (containerIsBody ? scrollY : 0);\n break;\n }\n }\n\n return [cX, cY];\n }\n\n function doPlacementAgainstParent(incomingPlacementObj) {\n const coords = getCoordsFromPlacement(incomingPlacementObj);\n incomingPlacementObj.setCoordinate('x', coords[0]);\n incomingPlacementObj.setCoordinate('y', coords[1]);\n self.render(incomingPlacementObj);\n incomingPlacementObj = self.handlePlacementCallback(incomingPlacementObj);\n return incomingPlacementObj;\n }\n\n // Simple placement logic\n placementObj = doPlacementAgainstParent(placementObj);\n\n // Adjusts the placement coordinates based on a defined strategy\n // Will only adjust the current strategy if bleeding outside the\n // viewport/container are detected.\n placementObj.strategies.forEach((strat) => {\n placementObj = self.checkBleeds(placementObj);\n\n if (placementObj.bleeds) {\n placementObj = (function () {\n switch (strat) {\n case 'nudge':\n return self.nudge(placementObj);\n case 'clockwise':\n return self.clockwise(placementObj);\n case 'flip':\n placementObj = self.flip(placementObj);\n placementObj.setCoordinate('x', placementObj.originalx);\n placementObj.setCoordinate('y', placementObj.originaly);\n placementObj = doPlacementAgainstParent(placementObj);\n return placementObj;\n case 'shrink':\n return self.shrink(placementObj);\n case 'shrink-x':\n return self.shrink(placementObj, 'x');\n case 'shrink-y':\n return self.shrink(placementObj, 'y');\n default:\n return placementObj;\n }\n }(self));\n\n self.render(placementObj);\n }\n });\n\n // Trigger an event to notify placement has ended\n this.element.trigger('afterplace', [placementObj]);\n\n return placementObj;\n },\n\n /**\n * Basic Placement Routine that simply accepts X and Y coordinates.\n * In this case, \"x\" and \"y\" integers are \"absolute\" and will be the base point for placement.\n * Can be modified by using a callback in the settings.\n * @private\n * @param {PlacementObject} placementObj settings for the placement routine\n * @returns {PlacementObject} modified placementObject with updated settings\n */\n placeWithCoords(placementObj) {\n this.render(placementObj);\n\n placementObj = this.handlePlacementCallback(placementObj);\n\n this.render(placementObj);\n\n // Coordinate placement can only be \"nudged\" (strategy is not used in this style of placement).\n placementObj = this.checkBleeds(placementObj);\n if (placementObj.bleeds) {\n placementObj = this.nudge(placementObj);\n }\n\n // Place again\n this.render(placementObj);\n\n placementObj = this.checkBleeds(placementObj);\n if (placementObj.bleeds) {\n placementObj = this.shrink(placementObj);\n }\n\n this.render(placementObj);\n\n this.element.trigger('afterplace', [\n placementObj\n ]);\n\n return placementObj;\n },\n\n /**\n * Perform callback, if it exists.\n * Callback should return an array containing the modified coordinate values: [x, y];\n * NOTE: These are actual coordinates in all cases.\n * NOTE: They are not relative values - they are absolute.\n * @private\n * @param {PlacementObject} placementObj settings for the placement routine\n * @returns {PlacementObject} modified placementObject with updated settings\n */\n handlePlacementCallback(placementObj) {\n const cb = placementObj.callback || this.settings.callback;\n\n if (cb && typeof cb === 'function') {\n placementObj = cb(placementObj);\n }\n\n this.render(placementObj);\n return placementObj;\n },\n\n /**\n * Detects for elements with fixed positioning, or an absolutely-positioned containment.\n * If either condition is true, this placement should not account for container scrolling.\n * @private\n * @param {PlacementObject} placementObj settings for the placement routine\n * @returns {boolean} whether or not the values calculated should account for scrolling.\n */\n accountForScrolling(placementObj) {\n let container = placementObj.container;\n let pos = window.getComputedStyle(this.element[0]).position;\n\n // fixed-positoned, placed elements don't account for scrolling\n if (pos === 'fixed') {\n return false;\n }\n\n // Check the container element.\n // If we can't find a valid container element, do account for scrolling.\n if (!container || !container.length) {\n container = this.element.parents().filter(function () {\n const containerPos = window.getComputedStyle(this).position;\n return containerPos === 'absolute' || pos === 'fixed';\n });\n }\n if (!container || !container.length) {\n return true;\n }\n\n if (container[0] === document.body) {\n return false;\n }\n\n const containerStyle = window.getComputedStyle(container[0]);\n pos = containerStyle.position;\n if (pos === 'fixed') {\n return false;\n }\n if (pos === 'absolute' && containerStyle.overflow === 'hidden') {\n return false;\n }\n return true;\n },\n\n /**\n * Gets a parent container element.\n * @param {PlacementObject} placementObj settings for the placement routine\n * @returns {HTMLElement|jQuery[]} container element\n */\n getContainer(placementObj) {\n if (placementObj.container instanceof $ && placementObj.container.length) {\n return placementObj.container;\n }\n\n const modalParent = this.element.parents('.modal');\n if (modalParent.length) {\n return modalParent;\n }\n\n return $(document.body);\n },\n\n /**\n * Re-adjust a previously-placed element to account for bleeding off the edges.\n * Element must fit within the boundaries of the page or it's current scrollable pane.\n * @param {PlacementObject} placementObj settings for the placement routine.\n * @returns {PlacementObject} modified placementObject with updated settings.\n */\n checkBleeds(placementObj) {\n const containerBleed = this.settings.bleedFromContainer;\n const container = this.getContainer(placementObj);\n const containerIsBody = container.length && container[0] === document.body;\n const BoundingRect = this.element[0].getBoundingClientRect();\n const rect = {};\n const containerRect = container ? container[0].getBoundingClientRect() : {};\n // NOTE: Usage of $(window) instead of $('body') is deliberate here - http://stackoverflow.com/a/17776759/4024149.\n // Firefox $('body').scrollTop() will always return zero.\n const scrollX = containerIsBody ? $(window).scrollLeft() : container.scrollLeft();\n const scrollY = containerIsBody ? $(window).scrollTop() : container.scrollTop();\n const windowH = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);\n const windowW = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);\n let d;\n\n rect.width = BoundingRect.width;\n rect.height = BoundingRect.height;\n rect.top = BoundingRect.top;\n rect.right = BoundingRect.right;\n rect.bottom = BoundingRect.bottom;\n rect.left = BoundingRect.left;\n\n function getBoundary(edge) {\n switch (edge) {\n case 'top':\n return (containerBleed ? 0 : containerRect.top) -\n (!containerIsBody ? 0 : scrollY * -1); // 0 === top edge of viewport\n case 'left':\n return (containerBleed ? 0 : containerRect.left) -\n (!containerIsBody ? 0 : scrollX * -1); // 0 === left edge of viewport\n case 'right':\n return (containerBleed ? windowW : containerRect.right) -\n (!containerIsBody ? 0 : scrollX * -1);\n default: // bottom\n return (containerBleed ? windowH : containerRect.bottom) -\n (!containerIsBody ? 0 : scrollY * -1);\n }\n }\n\n // If element width is greater than window width, shrink to fit\n const rightViewportEdge = getBoundary('right');\n if (rect.width >= rightViewportEdge) {\n d = rect.width - rightViewportEdge;\n const newWidth = rect.width - d;\n placementObj.width = newWidth;\n\n this.element[0].style.width = `${newWidth}px`;\n rect.width = newWidth; // reset the rect because the size changed\n }\n\n // If element height is greater than window height, shrink to fit\n const bottomViewportEdge = getBoundary('bottom');\n if (rect.height >= bottomViewportEdge) {\n d = rect.height - bottomViewportEdge;\n const newHeight = rect.height - d;\n placementObj.height = newHeight;\n\n this.element[0].style.height = `${newHeight}px`;\n rect.height = newHeight; // reset the rect because the size changed\n }\n\n // build conditions\n const offRightEdge = rect.right > getBoundary('right');\n const offLeftEdge = rect.left < getBoundary('left');\n const offTopEdge = rect.top < getBoundary('top');\n const offBottomEdge = rect.bottom > getBoundary('bottom');\n\n // Return if no bleeding is detected (no need to fix anything!)\n if (!offRightEdge && !offLeftEdge && !offTopEdge && !offBottomEdge) {\n placementObj.bleeds = undefined;\n return placementObj;\n }\n\n // Keep a record of bleeds that need to be adjusted, and by what values\n placementObj.bleeds = {};\n placementObj.bleeds.right = offRightEdge ? (rect.right - getBoundary('right')) : null;\n placementObj.bleeds.left = offLeftEdge ? -(rect.left - getBoundary('left')) : null;\n placementObj.bleeds.top = offTopEdge ? -(rect.top - getBoundary('top')) : null;\n placementObj.bleeds.bottom = offBottomEdge ? (rect.bottom - getBoundary('bottom')) : null;\n\n return placementObj;\n },\n\n // Bumps the element around in each direction\n nudge(placementObj) {\n if (!placementObj.nudges) {\n placementObj.nudges = { x: 0, y: 0 };\n }\n\n let d = 0;\n if (placementObj.bleeds.right) {\n d = Math.abs(placementObj.bleeds.right) + Math.abs(placementObj.containerOffsetX);\n placementObj.setCoordinate('x', placementObj.x - d);\n placementObj.nudges.x -= d;\n }\n if (placementObj.bleeds.left) {\n d = Math.abs(placementObj.bleeds.left) + Math.abs(placementObj.containerOffsetX);\n placementObj.setCoordinate('x', placementObj.x + d);\n placementObj.nudges.x += d;\n }\n if (placementObj.bleeds.top) {\n d = Math.abs(placementObj.bleeds.top) + Math.abs(placementObj.containerOffsetY);\n placementObj.setCoordinate('y', placementObj.y + d);\n placementObj.nudges.y += d;\n }\n if (placementObj.bleeds.bottom) {\n d = Math.abs(placementObj.bleeds.bottom) + Math.abs(placementObj.containerOffsetY);\n placementObj.setCoordinate('y', placementObj.y - d);\n placementObj.nudges.y -= d;\n }\n\n placementObj.wasNudged = true;\n placementObj.bleeds = undefined;\n\n return placementObj;\n },\n\n flip(placementObj) {\n // Don't attempt to flip if there was no bleeding on the edge we're attempting to leave from.\n if (!placementObj.bleeds[placementObj.placement]) {\n return placementObj;\n }\n\n if (!placementObj.attemptedFlips) {\n placementObj.attemptedFlips = [];\n }\n placementObj.attemptedFlips.push(placementObj.placement);\n\n // If we've tried flipping in all directions, give up and use the default placement.\n if (placementObj.attemptedFlips.length > 3) {\n placementObj = this.giveup(placementObj);\n return placementObj;\n }\n\n const accountForScrolling = this.accountForScrolling(placementObj);\n const isXCoord = ['left', 'right'].indexOf(placementObj.placement) > -1;\n const containerBleed = this.settings.bleedFromContainer;\n const container = this.getContainer(placementObj);\n const containerIsBody = container.length && container[0] === document.body;\n const containerRect = container ? container[0].getBoundingClientRect() : {};\n const parentRect = placementObj.parent[0].getBoundingClientRect();\n // NOTE: Usage of $(window) instead of $('body') is deliberate here - http://stackoverflow.com/a/17776759/4024149.\n // Firefox $('body').scrollTop() will always return zero.\n const scrollX = containerIsBody ? $(window).scrollLeft() : container.scrollLeft();\n const scrollY = containerIsBody ? $(window).scrollTop() : container.scrollTop();\n const windowH = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);\n const windowW = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);\n\n function getOppositeDir(dir) {\n switch (dir) {\n case 'left':\n return 'right';\n case 'right':\n return 'left';\n case 'top':\n return 'bottom';\n default: // bottom\n return 'top';\n }\n }\n\n // Gets the distance between an edge on the target element, and its opposing viewport border\n function getDistance(dir) {\n let d = 0;\n\n switch (dir) {\n case 'left':\n d = (containerBleed ? 0 : containerRect.left) -\n (accountForScrolling ? scrollX : 0) - parentRect.left + placementObj.containerOffsetX;\n break;\n case 'right':\n d = ((containerBleed ? windowW : containerRect.right) -\n (accountForScrolling ? scrollX : 0)) - parentRect.right - placementObj.containerOffsetX;\n break;\n case 'top':\n d = (containerBleed ? 0 : containerRect.top) -\n (accountForScrolling ? scrollY : 0) - parentRect.top + placementObj.containerOffsetY;\n break;\n default: // bottom\n d = ((containerBleed ? windowH : containerRect.bottom) -\n (accountForScrolling ? scrollY : 0)) - parentRect.bottom -\n placementObj.containerOffsetY;\n break;\n }\n\n return Math.abs(d);\n }\n\n function tried(placement) {\n return $.inArray(placement, placementObj.attemptedFlips) > -1;\n }\n\n function performFlip(originalDir) {\n const newDir = getOppositeDir(originalDir);\n const perpendicularDir = isXCoord ? 'top' : 'left';\n const oppPerpendicularDir = getOppositeDir(perpendicularDir);\n const originalDistance = getDistance(originalDir);\n const targetDistance = getDistance(newDir);\n\n if (!tried(newDir)) {\n if (originalDistance >= targetDistance) {\n return originalDir;\n }\n\n placementObj.wasFlipped = true;\n return newDir;\n }\n\n // switch the coordinate definitions\n // since the axis for placement is flipped, our coordinate offsets should also flip\n const tmp = placementObj.originalx;\n placementObj.originalx = placementObj.originaly;\n placementObj.originaly = tmp;\n\n const perpendicularDistance = getDistance(perpendicularDir);\n const oppPerpendicularDistance = getDistance(oppPerpendicularDir);\n\n if (!tried(perpendicularDir)) {\n if (perpendicularDistance >= oppPerpendicularDistance) {\n return perpendicularDir;\n }\n\n if (!tried(oppPerpendicularDir)) {\n return oppPerpendicularDir;\n }\n }\n\n return originalDir;\n }\n\n placementObj.placement = performFlip(placementObj.placement);\n\n return placementObj;\n },\n\n // TODO: Move Clockwise\n clockwise(placementObj) {\n return placementObj;\n },\n\n // If element height/width is greater than window height/width, shrink to fit\n shrink(placementObj, dimension) {\n const accountForScrolling = this.accountForScrolling(placementObj);\n const containerBleed = this.settings.bleedFromContainer;\n const container = this.getContainer(placementObj);\n const containerRect = container ? container[0].getBoundingClientRect() : {};\n const containerIsBody = container.length && container[0] === document.body;\n const rect = this.element[0].getBoundingClientRect();\n const useX = dimension === undefined || dimension === null || dimension === 'x';\n const useY = dimension === undefined || dimension === null || dimension === 'y';\n // NOTE: Usage of $(window) instead of $('body') is deliberate here - http://stackoverflow.com/a/17776759/4024149.\n // Firefox $('body').scrollTop() will always return zero.\n const scrollX = containerIsBody ? $(window).scrollLeft() : container.scrollLeft();\n const scrollY = containerIsBody ? $(window).scrollTop() : container.scrollTop();\n const windowH = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);\n const windowW = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);\n const leftViewportEdge = (accountForScrolling ? scrollX : 0) +\n (containerBleed ? 0 : containerRect.left) + placementObj.containerOffsetX;\n const topViewportEdge = (accountForScrolling ? scrollY : 0) +\n (containerBleed ? 0 : containerRect.top) + placementObj.containerOffsetY;\n const rightViewportEdge = (accountForScrolling ? scrollX : 0) +\n (containerBleed ? windowW : containerRect.right) - placementObj.containerOffsetX;\n const bottomViewportEdge = (accountForScrolling ? scrollY : 0) +\n (containerBleed ? windowH : containerRect.bottom) - placementObj.containerOffsetY;\n let d;\n\n // Shrink in each direction.\n // The value of the \"containerOffsets\" is \"factored out\" of each calculation,\n // if for some reason the element is larger than the viewport/container space allowed.\n placementObj.nudges = placementObj.nudges || {};\n\n if (useX) {\n // Left\n if (rect.left < leftViewportEdge) {\n d = Math.abs(leftViewportEdge - rect.left);\n if (rect.right >= rightViewportEdge) {\n d -= placementObj.containerOffsetX;\n }\n placementObj.width = rect.width - d;\n placementObj.setCoordinate('x', placementObj.x + d);\n placementObj.nudges.x += d;\n }\n\n // Right\n if (rect.right > rightViewportEdge) {\n d = Math.abs(rect.right - rightViewportEdge);\n if (rect.left <= leftViewportEdge) {\n d -= placementObj.containerOffsetX;\n }\n placementObj.width = rect.width - d;\n }\n }\n\n if (useY) {\n // Top\n if (rect.top < topViewportEdge) {\n d = Math.abs(topViewportEdge - rect.top);\n if (rect.bottom >= bottomViewportEdge) {\n d -= placementObj.containerOffsetY;\n }\n placementObj.height = rect.height - d;\n placementObj.setCoordinate('y', placementObj.y + d);\n placementObj.nudges.y += d;\n }\n\n // Bottom\n if (rect.bottom > bottomViewportEdge) {\n d = Math.abs(rect.bottom - bottomViewportEdge);\n if (rect.top <= topViewportEdge) {\n d -= placementObj.containerOffsetY;\n }\n placementObj.height = rect.height - d;\n }\n }\n\n return placementObj;\n },\n\n // Giving up causes all the placementObj settings to revert\n giveup(placementObj) {\n placementObj.giveup = true;\n placementObj.strategy = this.settings.strategy;\n placementObj.placement = this.settings.placement;\n return placementObj;\n },\n\n // Clears the old styles that may be present\n clearOldStyles() {\n this.element[0].style.left = '';\n this.element[0].style.top = '';\n this.element[0].style.width = '';\n this.element[0].style.height = '';\n\n const os = this.originalStyles;\n if (os) {\n if (os.width) {\n this.element[0].style.width = os.width;\n }\n\n if (os.height) {\n this.element[0].style.height = os.height;\n }\n }\n\n return this;\n },\n\n // Built-in method for handling positon of optional arrow elements.\n // Used for tooltip/popovers/popupmenus\n setArrowPosition(e, placementObj, element) {\n let target = placementObj.parent;\n const arrow = element.find('div.arrow');\n const dir = placementObj.placement;\n const isXCoord = ['left', 'right'].indexOf(dir) > -1;\n let targetRect = {};\n const elementRect = element[0].getBoundingClientRect();\n let arrowRect = {};\n let newArrowRect = {};\n let hideArrow = false;\n\n if (!target || !target.length || !arrow.length) {\n return;\n }\n\n arrow[0].removeAttribute('style');\n\n // if (placementObj.attemptedFlips) { TJM Removed for pager bug. Seems to work.\n element.removeClass('top right bottom left').addClass(dir);\n // }\n\n // Flip the arrow if we're in RTL mode\n if (this.isRTL && isXCoord) {\n const opposite = dir === 'right' ? 'left' : 'right';\n element.removeClass('right left').addClass(opposite);\n }\n\n // Custom target for some scenarios\n if (target.is('.colorpicker')) {\n target = target.next('.trigger');\n }\n if (target.is('.datepicker, .timepicker')) {\n target = target.next('.icon');\n }\n if (target.is('.btn-split-menu, .btn-menu, .btn-actions, .btn-filter, .tab, .tab-more')) {\n target = target.find('.icon').last();\n }\n if (target.is('.searchfield-category-button')) {\n target = target.find('.icon.icon-dropdown');\n }\n if (target.is('.colorpicker-editor-button')) {\n target = target.find('.trigger .icon');\n }\n\n // reset if we borked the target\n if (!target.length) {\n target = placementObj.parent;\n }\n\n targetRect = target.length ? target[0].getBoundingClientRect() : targetRect;\n arrowRect = arrow.length ? arrow[0].getBoundingClientRect() : arrowRect;\n newArrowRect = {};\n\n function getMargin(placement) {\n return (placement === 'right' || placement === 'left') ? 'margin-top' : 'margin-left';\n }\n\n function getDistance() {\n let targetCenter = 0;\n let currentArrowCenter = 0;\n let d = 0;\n\n if (dir === 'left' || dir === 'right') {\n targetCenter = targetRect.top + (targetRect.height / 2);\n currentArrowCenter = arrowRect.top + (arrowRect.height / 2);\n d = targetCenter - currentArrowCenter;\n newArrowRect.top = arrowRect.top + d;\n newArrowRect.bottom = arrowRect.bottom + d;\n\n if (newArrowRect.top <= elementRect.top || newArrowRect.bottom >= elementRect.bottom) {\n hideArrow = true;\n }\n }\n if (dir === 'top' || dir === 'bottom') {\n targetCenter = targetRect.left + (targetRect.width / 2);\n currentArrowCenter = arrowRect.left + (arrowRect.width / 2);\n d = targetCenter - currentArrowCenter;\n newArrowRect.left = arrowRect.left + d;\n newArrowRect.right = arrowRect.right + d;\n\n if (newArrowRect.left <= elementRect.left || newArrowRect.right >= elementRect.right) {\n hideArrow = true;\n }\n }\n\n return d;\n }\n\n // line the arrow up with the target element's \"dropdown icon\", if applicable\n const positionOpts = {};\n positionOpts[getMargin(dir)] = getDistance();\n if (hideArrow) {\n positionOpts.display = 'none';\n }\n arrow.css(positionOpts);\n },\n\n // Handle Updating Settings\n updated(settings) {\n if (settings) {\n this.settings = utils.mergeSettings(this.element[0], settings, this.settings);\n }\n\n return this\n .teardown()\n .init();\n },\n\n // Simple Teardown - remove events & rebuildable markup.\n teardown() {\n this.clearOldStyles();\n this.element.removeClass('placeable');\n\n this.element.off(`updated.${COMPONENT_NAME} place.${COMPONENT_NAME}`);\n\n this.element.trigger('afterteardown');\n return this;\n },\n\n // Teardown - Remove added markup and events\n destroy() {\n this.teardown();\n $.removeData(this.element[0], COMPONENT_NAME);\n }\n};\n\nexport { PlacementObject, Place, COMPONENT_NAME };\n","import { Place, COMPONENT_NAME } from './place';\n\n/**\n * jQuery Component Wrapper for Place\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.place = 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 Place(this, settings));\n }\n });\n};\n","import * as debug from '../utils/debug';\nimport { utils } from '../utils/utils';\n\n// Current \"theme\" string\nlet theme = 'light'; //eslint-disable-line\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};\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='light'] The theme name (light, dark or high-contrast)\n * @param {string} [settings.font='Helvetica'] Use the newer source sans font\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 // Set the default theme, or grab the theme from an external CSS stylesheet.\n const cssTheme = this.getThemeFromStylesheet();\n this.currentTheme = cssTheme || this.settings.theme;\n this.setTheme(this.currentTheme);\n\n if (this.settings.colors) {\n this.setColors(this.settings.colors);\n }\n\n if (this.settings.font) {\n $('html').addClass(`font-${this.settings.font}`);\n }\n\n this.handleEvents();\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.on(`updated.${COMPONENT_NAME}`, () => {\n self.updated();\n }).on(`changecolors.${COMPONENT_NAME}`, (e, newColor, noAnimate) => {\n self.setColors(newColor, noAnimate);\n }).on(`changetheme.${COMPONENT_NAME}`, (e, thisTheme) => {\n self.setTheme(thisTheme);\n });\n\n return this;\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 */\n 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 * Validates a string containing a hexadecimal number\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\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 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 /**\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('mousedown', '.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 let eventType = 'selected';\n\n $('.is-selected').removeClass('is-selected');\n $(`#${nodeId}`).addClass('is-selected');\n\n // Is collapse event\n if (isButton && isCollapseButton && isNotBack) {\n eventType = '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 // Is right click event\n if (e.which === 3) {\n eventType = 'rightClick';\n }\n\n const eventInfo = {\n data: nodeData,\n targetInfo,\n eventType,\n isAddEvent: hierarchy.isAddEvent(eventType),\n isExpandEvent: hierarchy.isExpandEvent(),\n isCollapseEvent: hierarchy.isCollapseEvent(),\n isSelectedEvent: hierarchy.isSelectedEvent(),\n allowLazyLoad: hierarchy.allowLazyLoad(nodeData, eventType)\n };\n\n leaf.trigger('selected', eventInfo);\n });\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 select\n * @private\n * @param {string} eventType is select\n * @returns {boolean} true if select event\n */\n isSelectedEvent(eventType) {\n return eventType === 'select';\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 (!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 * 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 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 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 structure = {\n legend: '',\n chart: s.paging ? '' : '',\n toplevel: s.paging ? '' : '',\n sublevel: s.paging ? '' : ''\n };\n\n const chartContainer = this.element.append(structure.chart);\n const chart = $('.chart', chartContainer);\n\n if (thisLegend.length !== 0) {\n this.element.prepend(structure.legend);\n const element = $('legend', chartContainer);\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 (s.paging && data.parentDataSet) {\n const backMarkup = '' +\n '
' +\n '' +\n '
';\n\n // Append back button to chart to go back to view previous level\n const backButton = $(backMarkup).appendTo(chart);\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 const multiRootHTML = `

${data.multiRootText}

`;\n\n rootNodeHTML.push(multiRootHTML);\n $(rootNodeHTML[0]).addClass('root').appendTo(chart);\n } else {\n const leafTemplate = Tmpl.compile(`{{#dataset}}${$(`#${s.templateId}`).html()}{{/dataset}}`);\n const leaf = leafTemplate.render({ dataset: data });\n rootNodeHTML.push(leaf);\n\n $(rootNodeHTML[0]).addClass('root').appendTo(chart);\n this.updateState($('.leaf.root'), true, data);\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.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]) && !s.paging) {\n this.createLeaf(data.children[i], $(structure.toplevel));\n } else if (s.paging) {\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 * Checks to see if children have children\n * @private\n * @returns {boolean} true if have children\n */\n isSingleChildWithChildren() {\n const s = this.settings;\n if (s.dataset && 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 * 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 ${thislabel}\n \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 const s = this.settings;\n const chartClassName = self.element.attr('class');\n const chart = $(`.${chartClassName} .chart`, self.container);\n const elClassName = container.attr('class');\n const el = elClassName !== undefined ? $(`.${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 /* global Tmpl */\n self.setColor(thisNodeData);\n\n const leafTemplate = Tmpl.compile(`{{#dataset}}${$(`#${s.templateId}`).html()}{{/dataset}}`);\n const leaf = leafTemplate.render({ dataset: thisNodeData });\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 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 = leafTemplate.render({ dataset: 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 = $(`#${thisNodeData.id}`).parent();\n parent.append(``);\n\n let childLength = thisNodeData.children.length;\n while (childLength--) {\n self.updateState($(`#${thisNodeData.children[childLength].id}`), false, thisNodeData.children[childLength]);\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($(`#${nodeData[i].id}`), false, nodeData[i]);\n }\n } else {\n processDataForLeaf(nodeData, true);\n self.updateState($(`#${nodeData.id}`), false, nodeData);\n }\n },\n\n /**\n * Determine the color from settings\n * @private\n * @param {object} data contains info.\n * @returns {void}\n */\n setColor(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 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 * 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.children === undefined) {\n dataNode.isLeaf = true;\n return dataNode.isLeaf;\n }\n\n if (s.beforeExpand) {\n return dataNode.isLeaf;\n }\n\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 {string} isRoot .\n * @param {string} 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 data = $(leaf).data();\n const expandCaret = s.paging ? 'caret-right' : 'caret-up';\n\n // data has been loaded if it has children\n if ((data.children && data.children.length !== 0) || eventType === 'add') {\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 data.parentDataSet = s.dataset;\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 compiledTmpl = Tmpl.compile(s.template);\n const renderedTmpl = compiledTmpl.render({ dataset: !s.dropdownOpts.source ? dataset : [] });\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 = $.fn.uniqueId('fieldfilter-');\n this.element.attr('id', id);\n }\n const ffId = `${id}-ff`;\n\n // Set Field\n this.field = this.element.closest('.field');\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 this.ffdropdown = this.field.find('select.dropdown');\n this.ffdropdown\n .attr({ id: ffId, named: ffId })\n .dropdown(s.dropdownOpts)\n .prev('label')\n .addClass('audible')\n .attr('for', ffId);\n\n // Add css classes\n this.field.addClass('fieldfilter-wrapper')\n .find('div.dropdown span').addClass('audible');\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 },\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 * 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 $('#dropdown-list ul').width(this.element.outerWidth() + 52);\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';\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* @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}\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.name === 'safari';\n\n this.targetElem = this.element;\n this.hoverElem = this.targetElem;\n this.field = this.element.closest('.field, .radio-group');\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 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 isFileupload = this.element.is('.fileupload');\n const isSearchfield = this.element.is('.searchfield');\n const isSpinbox = this.element.is('.spinbox');\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 doActive = () => {\n this.element.add(this.trigger).add(this.field).add(this.fieldParent)\n .addClass('is-active');\n };\n const doUnactive = () => {\n this.element.add(this.trigger).add(this.field).add(this.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.css({ 'margin-bottom': '', 'padding-bottom': '' });\n returns = ((height - lineHeight) / 2) * -1;\n } else {\n this.element.css({ 'margin-bottom': '8px', 'padding-bottom': '12px' });\n returns = 6;\n }\n } else if (isRadio) {\n returns = ((height - this.trigger.height()) / 2) * -1;\n }\n return returns;\n };\n const setTriggerCssTop = () => {\n this.trigger.css({ top: `${getTriggerTopVal()}px` });\n };\n\n // Update target element\n this.targetElem = dropdown ? dropdown.pseudoElem : this.targetElem;\n this.targetElem = isFileupload ? this.field.find('.fileupload[type=\"text\"]') : this.targetElem;\n\n // Update hover element\n this.hoverElem = isSpinbox ? this.element.add(this.field.find('.down, .up')) : this.targetElem;\n this.hoverElem = isColorpicker ? this.element.add(this.field.find('.colorpicker-container, .swatch, .trigger')) : this.hoverElem;\n\n // Set is-hover for field\n this.hoverElem\n .on(`mouseenter.${COMPONENT_NAME}`, () => {\n this.field.addClass('is-hover');\n })\n .on(`mouseleave.${COMPONENT_NAME}`, () => {\n this.field.removeClass('is-hover');\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.addClass('is-fieldoptions');\n this.field.on(`click.${COMPONENT_NAME}`, '.lookup-wrapper .trigger, .colorpicker-container .trigger', () => {\n doActive();\n });\n\n if (isColorpicker) {\n this.element\n .off(`beforeopen.${COMPONENT_NAME}`)\n .on(`beforeopen.${COMPONENT_NAME}`, () => {\n doActive();\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 // Spinbox add parent css class\n if (isSpinbox) {\n this.field.addClass('is-fieldoptions');\n }\n // Move trigger(action-button) in to searchfield-wrapper\n if (isSearchfield) {\n this.field.addClass('is-fieldoptions');\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.find('.radio')\n .on(`focusin.${COMPONENT_NAME}`, () => {\n const delay = this.isSafari ? 200 : 0;\n addFocused();\n setTimeout(() => {\n doActive();\n }, delay);\n })\n .on(`focusout.${COMPONENT_NAME}`, () => {\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, isCancelled) => {\n if (canUnactive(e) && isCancelled) {\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 this.element\n .on(`listopened.${COMPONENT_NAME}`, () => {\n doActive();\n })\n .on(`listclosed.${COMPONENT_NAME}`, () => {\n doUnactive();\n });\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 $(document)\n .add('body')\n .add(this.field)\n .add(this.element)\n .add(this.trigger)\n .add(this.hoverElem)\n .add(this.targetElem)\n .add(this.element.find('.radio'))\n .off(`.${COMPONENT_NAME}`);\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 * 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').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 let label = $(``);\n const 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 label = $(``);\n elem.before(label, shadowField);\n this.fileInput.after(svgClose);\n this.fileInput.after(svg);\n orgLabel.addClass('audible').append(`${instructions}`);\n } else {\n elem.before(label, shadowField);\n this.fileInput.after(svgClose);\n this.fileInput.after(svg);\n }\n\n this.textInput = elem.parent().find('[type=\"text\"]');\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 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 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 label.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 }\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);\n self.svgClose.show().addClass('is-visible');\n } else {\n self.clearUploadFile();\n }\n });\n\n // Fix - Not to buble 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 // Fix: not sure why, but some browser(ie. safari) need to rerender,\n // some rules were not applying from css file\n self.fileInput.css({ position: 'static', left: 0 });\n setTimeout(() => {\n self.fileInput.css({ position: 'fixed', left: '-10000px' });\n }, 0);\n },\n\n /*\n * Clear the Input Upload File\n */\n clearUploadFile() {\n const val = this.fileInput.val();\n this.fileInput.add(this.textInput).val('');\n this.svgClose.hide().removeClass('is-visible');\n if (val !== '') {\n this.fileInput.triggerHandler('change');\n }\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.textInput.off('keydown.fileupload');\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 $.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 { 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.maxFilesInProcess=99999] Max number of files can be uploaded\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.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 maxFilesInProcess: 99999, // max files can be upload\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 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 },\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.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 = $.fn.uniqueId('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 this.element.append(html);\n this.dropArea = $('.drop-area', this.element);\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\n // Clear previous errors in general area\n $('span.msg', this.element).closest('.error').remove();\n\n // Max files can be upload\n if ((files.length + $('.progress', this.element).length) > s.maxFilesInProcess) {\n self.showError(s.errorMaxFilesInProcess);\n return;\n }\n\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 });\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 const s = this.settings;\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 /* global FormData */\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 /**\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)}\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\n // Add this container\n this.dropArea.after(container);\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 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 jqxhr.abort();\n btnCancel.off('click.fileuploadadvanced');\n container.remove();\n });\n };\n\n // Set completed state\n const setCompleted = function (data) {\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 container.remove();\n\n // TODO: server call for removing data\n data.remove();\n });\n\n // Remove Cancel button and progress-bar area\n btnCancel.off('click.fileuploadadvanced');\n btnCancel.add(progressBar.closest('.progress-row')).remove();\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 return { file, container, setProgress, setAbort, setCompleted };\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 * 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.errorMaxFilesInProcess) {\n // This error show without file name or size in general area\n container = $('' +\n `
    \n
    \n \n \n \n ${error}\n
    \n
    `);\n } else {\n container = $('' +\n `
    \n
    \n \n \n \n ${file.name}\n
    \n ${this.formatFileSize(file.size)}\n
    \n
    \n
    \n

    ${error}

    \n
    \n
    `);\n }\n\n $('.action', container).button().on('click.fileuploadadvanced', () => {\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.unbind();\n this.element\n .find('.fileupload-wrapper').removeClass('is-disabled')\n .find('.fileupload-adv-browse-lbl input[type=\"file\"]').removeAttr('disabled');\n this.handleEvents();\n },\n\n /**\n * Set component to disabled.\n * @returns {void}\n */\n disable() {\n this.settings.isDisabled = true;\n this.unbind();\n this.element\n .find('.fileupload-wrapper').addClass('is-disabled')\n .find('.fileupload-adv-browse-lbl input[type=\"file\"]').attr('disabled', 'disabled');\n this.handleEvents();\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\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 easing: 'blockslide', // Private\n gutterSize: 20, // Private\n widgetWidth: 360, // Private\n widgetHeight: 370, // Private\n timeout: 100 // Private\n};\n\nfunction Homepage(element, settings) {\n this.settings = utils.mergeSettings(element, settings, HOMEPAGE_DEFAULTS);\n\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 * @private\n * @returns {void}\n */\n init() {\n this.isTransitionsSupports = this.supportsTransitions();\n this.initHeroWidget();\n this.handleEvents();\n\n // Initial Sizing\n this.resize(this, false);\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.settings.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 * Get availability where we can fit this given block.\n * @private\n * @param {object} block to get availability.\n * @returns {object} [x and y] where we can fit this block\n */\n getAvailability(block) {\n let abort = false;\n const smallest = {};\n const rows = this.rowsAndCols.length;\n\n // Loop thru each row and column soon it found first available spot\n // Then check for if block's width can fit in(yes), asign to [smallest] and break both loops\n for (let i = 0, l = rows; i < l && !abort; i++) {\n for (\n let j = 0, innerCheck = true, cols = this.rowsAndCols[i].length;\n j < cols && !abort;\n j++\n ) {\n if ((this.rowsAndCols[i][j]) && ((block.w + j) <= cols)) {\n if ((block.w > 1) && (cols > (j + 1))) {\n for (let n = 0; n < block.w; n++) {\n if (!this.rowsAndCols[i][j + n]) {\n innerCheck = false;\n break;\n }\n }\n }\n if ((block.h > 1) && (rows > (i + 1))) {\n for (let n = 0; n < block.h; n++) {\n if (!this.rowsAndCols[i + n][j]) {\n innerCheck = false;\n break;\n }\n }\n }\n if (innerCheck) {\n smallest.row = i;\n smallest.col = j;\n abort = true;\n }\n }\n }\n }\n\n // If did not found any available spot from previous loops\n // Add new row and asign to [smallest] first column in this new row\n if (!Object.getOwnPropertyNames(smallest).length) {\n this.initColumns(rows);\n smallest.row = rows;\n smallest.col = 0;\n }\n\n return smallest; // {x:0, y:0}\n },\n\n /**\n * Make all spots as unavailable, depends on block's width and height\n * Soon we used this block\n * @private\n * @param {number} r as row.\n * @param {number} c as col.\n * @param {number} block to fit.\n * @returns {void}\n */\n fitBlock(r, c, block) {\n let addRow = true;\n\n block.x = c;\n block.y = r;\n\n if ((block.w === 1) && (block.h === 1)) { // Single block can fit anywhere\n this.rowsAndCols[r][c] = false;\n } else if (block.w !== 1) {\n // If more then one row or column then loop thru to block's width and height\n // If height is more then current rows then add new row\n // Mark those spots as unavailable[false]\n\n // Left to right\n for (let i = r, l = block.h + r; i < l; i++) {\n for (let j = c, l2 = block.w + c; j < l2; j++) {\n if (!this.rowsAndCols[i]) {\n this.initColumns(i);\n }\n this.rowsAndCols[i][j] = false;\n }\n }\n } else {\n // Top to bottom\n for (let i = r, l = block.h + r; i < l; i++) {\n for (let j = c, l2 = block.h + c; j < l2; j++) {\n if (!this.rowsAndCols[i]) {\n this.initColumns(i);\n }\n this.rowsAndCols[i][c] = false;\n }\n }\n }\n\n // Check if reach to end of columns then assign flag[addRow]\n for (let i = 0, l = this.rowsAndCols[r].length; i < l; i++) {\n if (this.rowsAndCols[r][i]) {\n addRow = false;\n }\n }\n\n // If reach to end of columns and next row is not avaiable then add new row\n // Make all columns available, if not assigned earlier as unavailable\n if (addRow) {\n if (!this.rowsAndCols[r + 1]) {\n this.initColumns(r + 1);\n }\n }\n },\n\n /**\n * Setup each block sizes, based on classes provided from markup\n * @private\n * @returns {void}\n */\n setBlocks() {\n const cards = this.element.find('.card, .widget');\n this.blocks = [];\n\n for (let i = 0, l = cards.length; i < l; i++) {\n const card = $(cards[i]);\n const h = card.hasClass('double-height') ? 2 : 1;\n let w;\n\n if (card.hasClass('quad-width')) {\n w = 4;\n } else if (card.hasClass('triple-width')) {\n w = 3;\n } else if (card.hasClass('double-width')) {\n w = 2;\n } else {\n w = 1;\n }\n\n this.blocks.push({ w, h, elem: card, text: card.text() });\n }\n\n // Max sized columns brings to top\n if (this.settings.columns > 1) {\n for (let i = 0, j = 0, w = 0, l = this.blocks.length; i < l; i++) {\n if (this.blocks[i].w >= this.settings.columns && i && w) {\n this.arrayIndexMove(this.blocks, i, j);\n }\n w += this.blocks[i].w;\n if (w >= this.settings.columns) {\n w = 0; // reset\n j = (this.blocks[j].w >= this.settings.columns) ? j + 1 : i; // record to move\n }\n }\n }\n },\n\n /**\n * Move an array element position\n * @private\n * @param {array} arr .\n * @param {number} from index.\n * @param {number} to index.\n * @returns {void}\n */\n arrayIndexMove(arr, from, to) {\n arr.splice(to, 0, arr.splice(from, 1)[0]);\n },\n\n /**\n * Resize Method\n * @private\n * @param {object} self .\n * @param {boolean} animate .\n * @returns {void}\n */\n resize(self, animate) {\n // Sizes of \"breakpoints\" is 320, 660, 1000 , 1340 (for 320)\n // or 360, 740, 1120, 1500 or (for 360)\n const bpXL = (self.settings.widgetWidth * 4) + (self.settings.gutterSize * 3);\n const bpDesktop = (self.settings.widgetWidth * 3) + (self.settings.gutterSize * 2);\n const bpTablet = (self.settings.widgetWidth * 2) + self.settings.gutterSize;\n const bpPhone = self.settings.widgetWidth;\n\n let bp = bpXL; // 1340\n // Math min against window.screen.width for single line mobile support\n const elemWidth = self.element.outerWidth();\n\n // elemWidth -= 30; //extra break space\n\n // Find the Breakpoints\n const xl = (elemWidth >= bpXL);\n const desktop = (elemWidth >= bpDesktop && elemWidth <= bpXL);\n const tablet = (elemWidth >= bpTablet && elemWidth <= bpDesktop);\n const phone = (elemWidth <= bpTablet);\n\n const maxAttr = this.element.attr('data-columns');\n const content = self.element.find('> .content');\n this.settings.columns = parseInt((maxAttr || this.settings.columns), 10);\n\n // Assign columns as breakpoint sizes\n if (xl && self.settings.columns === 4) {\n self.settings.columns = 4;\n bp = bpXL;\n }\n if ((desktop) || (xl && self.settings.columns === 3)) {\n self.settings.columns = 3;\n bp = bpDesktop;\n }\n if (tablet) {\n self.settings.columns = 2;\n bp = bpTablet;\n }\n if (phone) {\n self.settings.columns = 1;\n bp = bpPhone;\n }\n\n if (content.length) {\n content[0].style.marginLeft = `-${(bp / 2)}px`;\n }\n\n this.setBlocks(); // setup blocks\n this.initRowsAndCols(); // setup colums\n\n // Loop thru each block, make fit where available and\n // If block more wider than available size, make as available size\n // Assign new left and top css positions\n for (let i = 0, l = self.blocks.length; i < l; i++) {\n // let left, top, pos, available,\n const block = self.blocks[i];\n\n // Remove extra classes if assigned earlier\n block.elem.removeClass('to-single to-double to-triple');\n\n // If block more wider than available size, make as available size\n if (block.w > self.settings.columns) {\n block.w = self.settings.columns;\n\n if (self.settings.columns === 1) {\n block.elem.addClass('to-single');\n } else if (self.settings.columns === 2) {\n block.elem.addClass('to-double');\n } else if (self.settings.columns === 3) {\n block.elem.addClass('to-triple');\n }\n }\n\n // Get Availability\n const available = self.getAvailability(block);\n\n // Set positions\n const box = self.settings.widgetWidth + self.settings.gutterSize;\n const totalWidth = box * self.settings.columns;\n\n const left = Locale.isRTL() ? totalWidth - ((box * block.w) + (box * available.col)) : box * available.col;// eslint-disable-line\n const top = (self.settings.widgetHeight + self.settings.gutterSize) * available.row;\n const pos = { left, top };\n\n if (animate) {\n const easing = self.settings.easing;\n const blockslide = [0.09, 0.11, 0.24, 0.91];\n\n if (easing === 'blockslide') {\n if (self.isTransitionsSupports) {\n self.applyCubicBezier(block.elem, blockslide);\n block.elem[0].style.left = `${pos.left}px`;\n block.elem[0].style.top = `${pos.top}px`;\n } else {\n // IE-9\n block.elem.animate(pos, self.settings.timeout);\n }\n } else {\n // Other easing effects ie (linear, swing)\n block.elem.animate(pos, self.settings.timeout, easing);\n }\n } else {\n block.elem[0].style.left = `${pos.left}px`;\n block.elem[0].style.top = `${pos.top}px`;\n }\n\n // Mark all spots as unavailable for this block, as we just used this one\n self.fitBlock(available.row, available.col, block);\n }\n\n /**\n * Fires after the page is resized and layout is set.\n * Can be used for any special adjustments.\n * @event resize\n * @memberof Homepage\n * @type {object}\n * @param {object} event - The jquery event object\n */\n self.element.triggerHandler('resize', self.settings.columns);\n },\n\n /**\n * Apply cubic-bezier effects\n * @private\n * @param {object} el as element.\n * @param {string} cubicBezier effect to apply.\n * @returns {void}\n */\n applyCubicBezier(el, cubicBezier) {\n const value = `all .3s cubic-bezier(${cubicBezier})`;\n el[0].style['-webkit-transition'] = value;\n el[0].style['-moz-transition'] = value;\n el[0].style['-ms-transition'] = value;\n el[0].style['-o-transition'] = value;\n el[0].style.transition = value;\n },\n\n /**\n * Check if browser supports transitions\n * @private\n * @returns {boolean} true if supports transitions\n */\n supportsTransitions() {\n const s = document.createElement('p').style;\n let p = 'transition';\n\n if (typeof s[p] === 'string') {\n return true;\n }\n\n // Tests for vendor specific prop\n const v = ['Moz', 'webkit', 'Webkit', 'Khtml', 'O', 'ms'];\n p = p.charAt(0).toUpperCase() + p.substr(1);\n\n for (let i = 0, l = v.length; i < l; i++) {\n if (typeof s[v[i] + p] === 'string') {\n return true;\n }\n }\n return false;\n },\n\n /**\n * Detach events\n * @private\n * @returns {void}\n */\n detachEvents() {\n $('body').off('resize.homepage');\n $('.application-menu').off('applicationmenuopen.homepage applicationmenuclose.homepage');\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, HOMEPAGE_DEFAULTS);\n }\n return this\n .detachEvents()\n .init();\n },\n\n /**\n * Destroy this component instance and remove the link from its base element.\n * @returns {void}\n */\n destroy() {\n this.detachEvents();\n $.removeData(this.element[0], COMPONENT_NAME);\n },\n\n /**\n * Attach Events used by the Control\n * @private\n * @returns {void}\n */\n handleEvents() {\n $('body').on('resize.homepage', () => {\n this.resize(this, this.settings.animate);\n });\n\n $('.application-menu').on('applicationmenuopen.homepage applicationmenuclose.homepage', () => {\n this.resize(this, this.settings.animate);\n });\n }\n\n};\n\nexport { Homepage, COMPONENT_NAME };\n","import { Homepage, COMPONENT_NAME } from './homepage';\n\n/**\n * jQuery Component Wrapper for Homepage\n * @param {object} [settings] incoming settings\n * @returns {jQuery[]} elements being acted on\n */\n$.fn.homepage = 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 Homepage(this, settings));\n }\n });\n};\n","/* eslint-disable no-underscore-dangle */\nimport * as debug from '../utils/debug';\nimport { utils } from '../utils/utils';\nimport { Locale } from '../locale/locale';\n\n// jQuery Components\nimport '../button/button.jquery';\nimport '../icons/icons.jquery';\nimport '../popupmenu/popupmenu.jquery';\nimport '../tooltip/tooltip.jquery';\n\n// The name of this component.\nconst COMPONENT_NAME = 'pager';\n\n/**\n* The Pager Component supports paging on lists.\n* @class Pager\n* @param {jQuery[]|HTMLElement} element The component element.\n* @param {object} [settings] The component settings.\n*\n* @param {string} [settings.componentAPI] If defined, becomes the definitive way to call methods on\n* parent component.\n* @param {string} [settings.type = 'list'] Different types of pagers list - just shows next and Previous and a listing of pages table\n* shows next and previous and first and last with a page number input and page size selector used as the default\n* for datagrid pageof - also shows next and previous and first and last with a page number input and page size selector used optionally for\n* lists firstlast - shows next and previous and first and last with option to set showPageSizeSelector\n* @param {string} [settings.position = 'bottom'] Can be on 'bottom' or 'top'.\n* @param {number} [settings.activePage = 1] Start on this page\n* @param {boolean} [settings.hideOnOnePage = false] If true, hides the pager if there is only one page worth of results.\n* @param {Function} [settings.source] Call Back Function for Pager Data Source\n* @param {number} [settings.pagesize = 15] Can be calculated or a specific number\n* @param {array} [settings.pagesizes = [15, 25, 50, 75]] Array of numbers of the page size selector\n* @param {boolean} [settings.showPageSizeSelector = false] If false will not show page size selector\n* @param {boolean} [settings.indeterminate = false] If true will not show anything that lets you go to a specific\n*/\nconst PAGER_DEFAULTS = {\n componentAPI: undefined,\n type: 'list',\n position: 'bottom',\n activePage: 1,\n hideOnOnePage: false,\n source: null,\n pagesize: 15,\n pagesizes: [15, 25, 50, 75],\n showPageSizeSelector: true,\n indeterminate: false\n};\n\nconst PAGER_NON_NUMBER_BUTTON_SELECTOR = 'li:not(.pager-prev):not(.pager-next):not(.pager-first):not(.pager-last)';\nfunction Pager(element, settings) {\n this.settings = utils.mergeSettings(element, settings, PAGER_DEFAULTS);\n this.settings.dataset = settings.dataset; // by pass deep copy\n this.element = $(element);\n debug.logTimeStart(COMPONENT_NAME);\n this.init();\n debug.logTimeEnd(COMPONENT_NAME);\n}\n\nPager.prototype = {\n\n pagerInfo: {},\n\n /**\n * Init the pager.\n * @private\n * @returns {void}\n */\n init() {\n this.setup();\n this.createPagerBar();\n this.setActivePage(this.settings.activePage, true); // Get First Page\n this.renderBar();\n this.renderPages('initial');\n this.handleEvents();\n },\n\n /**\n * Setting the internals of the pager.\n * @private\n * @returns {void}\n */\n setup() {\n // Add [pagesize] if not found in [pagesizes]\n if ($.inArray(this.settings.pagesize, this.settings.pagesizes) === -1) {\n const sortNumber = function (a, b) {\n return a - b;\n };\n this.settings.pagesizes.push(this.settings.pagesize);\n this.settings.pagesizes = this.settings.pagesizes.sort(sortNumber);\n }\n\n const widgetContainer = this.element.parents('.card, .widget');\n\n // Adjust for the possibility of the pager being attached to a Table instead\n // of normal grid markup\n if (this.element.is('tbody')) {\n this.isTable = true;\n this.settings.type = 'table';\n this.mainContainer = this.element.closest('.datagrid-container');\n\n if (!this.settings.componentAPI) {\n this.settings.componentAPI = this.mainContainer.data('datagrid');\n }\n\n if (widgetContainer.length) {\n widgetContainer[0].classList.add('has-datagrid');\n }\n }\n\n // If contained by a widget/card container, build some settings for that\n const listviewContainer = this.element.is('.listview');\n if (listviewContainer) {\n this.isTable = false;\n this.isListView = true;\n this.mainContainer = this.element;\n\n if (!this.settings.componentAPI) {\n this.settings.componentAPI = this.element.data('listview');\n }\n }\n\n this.isRTL = Locale.isRTL();\n\n return this;\n },\n\n /**\n * Add the pager dom elements.\n * @private\n */\n createPagerBar() {\n this.pagerBar = this.element.prev('.pager-toolbar');\n\n if (this.pagerBar.length === 0) {\n this.pagerBar = $('');\n let buttons = `${'
  • ' +\n ''}${$.createIcon({ icon: 'previous-page' })\n }${Locale.translate('PreviousPage')}` +\n '' +\n '
  • ' +\n '
  • ' +\n `${$.createIcon({ icon: 'next-page' })\n }${Locale.translate('NextPage')}` +\n '' +\n '
  • ';\n\n if (this.settings.type === 'table' || this.settings.type === 'pageof' || this.settings.type === 'firstlast') {\n buttons = `${'
  • ' +\n ''}${$.createIcon({ icon: 'first-page' })\n }${Locale.translate('FirstPage')}` +\n '' +\n `
  • ${\n buttons\n }
  • ` +\n `${$.createIcon({ icon: 'last-page' })\n }${Locale.translate('LastPage')}` +\n '' +\n '
  • ';\n }\n\n this.pagerBar.html(buttons);\n\n this.pagerBar.children('li').children('a').button();\n }\n\n if (this.isTable) {\n this.mainContainer.after(this.pagerBar);\n } else if (this.settings.position === 'bottom') {\n this.element.after(this.pagerBar);\n } else {\n this.element.before(this.pagerBar);\n }\n\n if (this.isListView) {\n this.pagerBar.addClass('is-listview');\n }\n\n // Inside of Listviews, place the pager bar inside of the card/widget footer\n const widgetContainer = this.element.closest('.card, .widget');\n if (widgetContainer.length) {\n const self = this;\n const widgetTypes = ['widget', 'card'];\n\n widgetTypes.forEach((type) => {\n const widgetContent = self.element.closest(`.${type}-content`);\n if (!widgetContent.length) {\n return;\n }\n\n let widgetFooter = widgetContent.next(`.${type}-footer`);\n if (!widgetFooter.length) {\n widgetFooter = $(`
    `).insertAfter(widgetContent);\n }\n\n self.pagerBar.appendTo(widgetFooter);\n });\n }\n\n this.pagerBar.find('a').tooltip();\n },\n\n /**\n * Attach All relevant events\n * @private\n */\n handleEvents() {\n const self = this;\n\n // Set element to be focused after paging\n self.element.on('afterpaging.pager', () => {\n const isVisible = $('li[tabindex]:visible, td[tabindex]:visible', self.element);\n if (!isVisible.length) {\n $('li:visible:first, td:visible:first', self.element).attr('tabindex', '0');\n }\n\n // Fix: Firefox by default to not allow keyboard focus on links\n $('li a', self.pagerBar).each(function () {\n const a = $(this);\n const li = a.closest('li');\n\n if (!a.is('[disabled]')) {\n li.attr('tabindex', '0').on('focus.pager', function () {\n $('a', this).focus();\n });\n }\n });\n });\n\n // Attach button click and touch\n this.pagerBar.on('click.pager', 'a', function (e) {\n const li = $(this).parent();\n e.preventDefault();\n\n if ($(this).attr('disabled')) {\n return false;\n }\n\n if (li.is('.pager-prev')) {\n self.setActivePage(self.activePage - 1, false, 'prev');\n return false;\n }\n\n if (li.is('.pager-next')) {\n self.setActivePage((self.activePage === -1 ? 0 : self.activePage) + 1, false, 'next');\n return false;\n }\n\n if (li.is('.pager-first')) {\n self.setActivePage(1, false, 'first');\n return false;\n }\n\n if (li.is('.pager-last')) {\n self.setActivePage(self.pageCount(), false, 'last'); // TODO Calculate Last Page?\n return false;\n }\n\n // Go to the page via the index of the button\n self.setActivePage($(this).parent().index() + (self.settings.type === 'table' ||\n self.settings.type === 'pageof' ? -1 : 0), false, 'page');\n\n return false;\n })\n .on('focus.pager', 'a', function () {\n const li = $(this).parent('li');\n li.addClass('is-focused');\n })\n .on('blur.pager', 'a', function () {\n const li = $(this).parent('li');\n li.removeClass('is-focused');\n });\n\n // Toolbar functionality\n this.pagerBar.on('keydown.pager', 'a', function (e) {\n e = e || window.event;\n const key = e.which || e.keyCode || e.charCode || false;\n const parent = $(this).parent();\n let btn = ((key === 37 || key === 9 && e.shiftKey) ? parent.prev() : //eslint-disable-line\n (key === 39 ? parent.next() : $())); //eslint-disable-line\n\n if (key === 9 && e.shiftKey && parent.prev().is('.pager-prev, .pager-first, .pager-count') ||\n key === 9 && e.shiftKey && parent.is('.pager-prev, .pager-first')) {\n parent.removeAttr('tabindex');\n setTimeout(() => {\n parent.attr('tabindex', '0');\n }, 0);\n // Handle pressing Enter on arrow icons and prevent pagerBar.click from being triggered\n } else if (key === 13) {\n const li = $(this).parent();\n e.preventDefault();\n\n if ($(this).attr('disabled')) {\n return false;\n }\n\n if (li.is('.pager-prev')) {\n self.setActivePage(self.activePage - 1, false, 'prev');\n return false;\n }\n\n if (li.is('.pager-next')) {\n self.setActivePage((self.activePage === -1 ? 0 : self.activePage) + 1, false, 'next');\n return false;\n }\n\n if (li.is('.pager-first')) {\n self.setActivePage(1, false, 'first');\n return false;\n }\n\n if (li.is('.pager-last')) {\n self.setActivePage(self.pageCount(), false, 'last'); // TODO Calculate Last Page?\n return false;\n }\n\n // Go to the page via the index of the button\n if (self.settings.type === 'list') {\n self.setActivePage($(this).parent().index(), false, 'page');\n }\n }\n\n btn = $('a', btn).length ? btn : $(':text', btn);\n if (btn.length && !btn.is('[disabled]')) {\n btn.focus();\n }\n return false;\n });\n },\n\n /**\n * Show page size selector\n * @param {boolean} toggleOption Toggle vs show\n */\n showPageSizeSelector(toggleOption) {\n toggleOption = (`${toggleOption}`).toLowerCase() === 'true';\n this.settings.showPageSizeSelector = toggleOption;\n if (toggleOption) {\n this.isShowPageSizeSelectorCall = toggleOption;\n this.pageCount();\n } else {\n this.pagerBar.find('.pager-pagesize').remove();\n }\n },\n\n /**\n * Set or Get Current Page.\n * @param {object} pagingInfo The paging info object\n * @param {boolean} force Force the update\n * @param {string} op The paging operation type.\n * @returns {void}\n */\n setActivePage(pagingInfo, force, op) {\n const lis = this.pagerBar.find(PAGER_NON_NUMBER_BUTTON_SELECTOR);\n let pageNum;\n\n // Backwards compatibility with having \"pageNum\" as the first argument\n // instead of \"pagingInfo\"\n if (!isNaN(pagingInfo)) {\n pageNum = pagingInfo;\n pagingInfo = {\n activePage: pageNum\n };\n }\n\n // Check to make sure our internal active page is set\n if (!this.activePage || isNaN(this.activePage)) {\n this.activePage = this.settings.activePage;\n }\n\n // If any of the following conditions are met, don't rerender the pages.\n // Only rerender the pager bar.\n if (pageNum === undefined ||\n pageNum === 0 ||\n isNaN(pageNum) ||\n pageNum > this.pageCount() ||\n (pageNum === this.activePage && !force)) {\n this.renderBar(pagingInfo);\n return this.activePage;\n }\n\n this.activePage = pageNum;\n\n // Remove selected\n if (!this.settings.source) {\n lis.filter('.selected').removeClass('selected').removeAttr('aria-selected')\n .find('a')\n .removeAttr('aria-disabled')\n .find('.audible')\n .html(Locale.translate('Page'));\n\n // Set selected Page\n lis.eq(pageNum - 1).addClass('selected').attr('aria-selected', true)\n .find('a')\n .attr('aria-disabled', true)\n .find('.audible')\n .html(Locale.translate('PageOn'));\n }\n\n this.renderBar(pagingInfo);\n this.renderPages(op);\n if (this.settings.componentAPI && this.settings.componentAPI.saveUserSettings) {\n this.settings.componentAPI.saveUserSettings();\n }\n return pageNum;\n },\n\n _pageCount: 0,\n\n /**\n * Get the Total Number of pages\n * @private\n * @param {object} pages The pages to set.\n * @returns {void}\n */\n pageCount(pages) {\n const self = this;\n const isShowPageSizeSelectorCall = this.isShowPageSizeSelectorCall;\n\n // Remove call, after cached\n delete this.isShowPageSizeSelectorCall;\n\n if (pages === undefined && this.settings.indeterminate) {\n this._pageCount = this.settings.pagesize; //eslint-disable-line\n }\n\n if (pages === undefined && !this.settings.source && !isShowPageSizeSelectorCall) {\n return this._pageCount; //eslint-disable-line\n }\n\n if (pages !== undefined) {\n this._pageCount = pages; //eslint-disable-line\n }\n\n // Add in fake pages\n if (!this.isTable) {\n let i;\n let thisClass;\n let thisText;\n let isAriaSelected;\n let isAriaDisabled;\n this.pagerBar.find(PAGER_NON_NUMBER_BUTTON_SELECTOR).remove();\n\n for (i = pages; i > 0; i--) {\n if (i === (this.activePage || 1)) {\n thisClass = 'class=\"selected\"';\n thisText = Locale.translate('PageOn');\n isAriaSelected = 'aria-selected=\"true\"';\n isAriaDisabled = 'aria-disabled=\"true\"';\n } else {\n thisClass = '';\n thisText = Locale.translate('Page');\n isAriaSelected = '';\n isAriaDisabled = '';\n }\n\n $(`
  • ${thisText} ${i}
  • `).insertAfter(this.pagerBar.find('.pager-prev'));\n }\n }\n\n if (this.isTable && !this.settings.indeterminate && this.pagerBar.find('.pager-count').length === 0) {\n let text = Locale.translate('PageOf');\n text = text.replace('{0}', ``);\n text = text.replace('{1}', `${pages || 1}`);\n $(`
  • `).insertAfter(this.pagerBar.find('.pager-prev'));\n\n // Setup interactivty with the numeric page input\n let lastValue = null;\n\n this.pagerBar.find('.pager-count input')\n .on('focus', function () {\n lastValue = $(this).val();\n }).on('blur', function () {\n if (lastValue !== $(this).val()) {\n $(this).val(self.setActivePage(parseInt($(this).val(), 10), false, 'page'));\n }\n }).on('keydown', function (e) {\n if (e.which === 13) {\n self.setActivePage(parseInt($(this).val(), 10), false, 'page');\n\n e.stopPropagation();\n e.preventDefault();\n }\n });\n }\n\n // Add functionality to change page size.\n if (self.settings.showPageSizeSelector && this.pagerBar.find('.btn-menu').length === 0) {\n const pageSize = $('
  • ');\n const pageSizeButton = $(`${'`).appendTo(pageSize);\n\n let last = this.pagerBar.find('.pager-last');\n if (last.length === 0) {\n last = this.pagerBar.find('.pager-next');\n }\n pageSize.insertAfter(last);\n\n const menu = $('');\n\n for (let k = 0; k < self.settings.pagesizes.length; k++) {\n const size = self.settings.pagesizes[k];\n menu.append(`
  • ${size}
  • `);\n }\n\n pageSizeButton.after(menu);\n\n const popupOpts = {\n placementOpts: {\n parent: pageSizeButton,\n parentXAlignment: (this.isRTL ? 'left' : 'right'),\n strategies: ['flip']\n }\n };\n\n pageSizeButton.popupmenu(popupOpts).on('selected.pager', (e, args) => {\n const tag = args;\n tag.closest('.popupmenu').find('.is-checked').removeClass('is-checked');\n tag.parent('li').addClass('is-checked');\n self.settings.pagesize = parseInt(tag.text(), 10);\n\n if (self.settings.componentAPI) {\n self.settings.componentAPI.settings.pagesize = self.settings.pagesize;\n }\n self.setActivePage(1, true, 'first');\n });\n }\n\n const pattern = (`${this._pageCount}`).replace(/\\d/g, '#');\n this.pagerBar.find('.pager-count input').attr('data-mask', '').mask({ pattern, mode: 'number', processOnInitialize: false });\n\n this._pageCount = this._pageCount || 1;\n if (this.settings.indeterminate) {\n return 999999999;\n }\n return this._pageCount;\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().not('.datagrid-expandable-row');\n\n // Adjust for cases where the root is a