1{"version":3,"sources":["../../src/emoji/picker.js"],"names":["ROW_RENDER_BUFFER_COUNT","RECENT_EMOJIS_STORAGE_KEY","ROW_HEIGHT_RAW","EMOJIS_PER_ROW","ROW_TYPE","EMOJI","HEADER","SELECTORS","CATEGORY_SELECTOR","EMOJIS_CONTAINER","EMOJI_PREVIEW","EMOJI_SHORT_NAME","ROW_CONTAINER","SEARCH_INPUT","SEARCH_RESULTS_CONTAINER","createRowDataForCategory","categoryName","categoryDisplayName","emojis","totalRowCount","rowData","push","index","length","type","data","name","displayName","i","rowEmojis","slice","addIndexesToRowData","map","getCategoryScrollPositionsFromRowData","reduce","carry","row","createHeaderRow","rowIndex","context","text","html","temp","document","createElement","innerHTML","firstChild","createEmojiRow","emojiData","charCodes","unified","split","code","emojiText","String","fromCodePoint","apply","shortnames","join","spacer","spacers","Array","fill","isEmojiElement","element","getAttribute","findCategorySelectorFromElement","parentElement","getCategorySelectorByCategoryName","root","querySelector","setCategorySelectorActive","allCategorySelectors","querySelectorAll","selector","classList","remove","add","getCategoryByScrollPosition","position","categoryScrollPositions","positions","categoryPosition","sort","a","b","candidate","previousPosition","currentPosition","nextPosition","getRecentEmojis","storedData","LocalStorage","get","JSON","parse","saveRecentEmoji","recentEmojis","set","stringify","addRecentEmoji","recentEmojiRowCount","newEmoji","newRecentEmojis","filter","emoji","newRecentEmojiRowData","concat","getRowsToRender","scrollPosition","visibleRowCount","minVisibleRow","Math","floor","start","rows","createRowElement","style","left","right","top","doRowsMatch","renderRows","rowContainer","currentRows","nextRows","toAdd","nextRow","some","currentRow","toKeep","toRemove","toRemoveElements","Promise","all","forEach","nextRowIndex","nextRowData","nextRowNode","insertBefore","splice","appendChild","removeChild","generateRenderRowsAtPositionFunction","rowCount","isRendering","renderNextRows","nextRowsToRender","rowLimit","height","showSearchResults","emojiContainer","searchResultsContainer","clearSearch","searchInput","value","getHandleMouseEnter","emojiPreview","emojiShortName","e","target","textContent","getHandleMouseLeave","getHandleClick","selectCallback","renderAtPosition","newRowData","newCategoryScrollPositions","replace","currentScrollTop","scrollTop","isRecentEmojiRowVisible","categorySelector","selectedCategory","getHandleScroll","currentVisibleRowScrollPosition","initialCategoryScrollPositions","currentCategoryElement","previousCategoryPosition","nextCategoryPosition","newScrollPosition","upperScrollBound","lowerScrollBound","updateActiveCategory","requestAnimationFrame","getHandleSearch","renderSearchResultsAtPosition","searchTerm","toLowerCase","matchingEmojis","Object","keys","EmojiData","byShortName","shortName","includes","searchResultsString","registerEventListeners","clickHandler","scrollHandler","searchHandler","addEventListener","allData","byCategory","category","title","categoryRowData"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Emoji picker.\n *\n * @module core/emoji/picker\n * @copyright  2019 Ryan Wyllie <ryan@moodle.com>\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport LocalStorage from 'core/localstorage';\nimport * as EmojiData from 'core/emoji/data';\nimport {throttle, debounce} from 'core/utils';\nimport {get_string as getString} from 'core/str';\nimport {render as renderTemplate} from 'core/templates';\n\nconst VISIBLE_ROW_COUNT = 10;\nconst ROW_RENDER_BUFFER_COUNT = 5;\nconst RECENT_EMOJIS_STORAGE_KEY = 'moodle-recent-emojis';\nconst ROW_HEIGHT_RAW = 40;\nconst EMOJIS_PER_ROW = 7;\nconst MAX_RECENT_COUNT = EMOJIS_PER_ROW * 3;\nconst ROW_TYPE = {\n    EMOJI: 0,\n    HEADER: 1\n};\nconst SELECTORS = {\n    CATEGORY_SELECTOR: '[data-action=\"show-category\"]',\n    EMOJIS_CONTAINER: '[data-region=\"emojis-container\"]',\n    EMOJI_PREVIEW: '[data-region=\"emoji-preview\"]',\n    EMOJI_SHORT_NAME: '[data-region=\"emoji-short-name\"]',\n    ROW_CONTAINER: '[data-region=\"row-container\"]',\n    SEARCH_INPUT: '[data-region=\"search-input\"]',\n    SEARCH_RESULTS_CONTAINER: '[data-region=\"search-results-container\"]'\n};\n\n/**\n * Create the row data for a category.\n *\n * @method\n * @param {String} categoryName The category name\n * @param {String} categoryDisplayName The category display name\n * @param {Array} emojis The emoji data\n * @param {Number} totalRowCount The total number of rows generated so far\n * @return {Array}\n */\nconst createRowDataForCategory = (categoryName, categoryDisplayName, emojis, totalRowCount) => {\n    const rowData = [];\n    rowData.push({\n        index: totalRowCount + rowData.length,\n        type: ROW_TYPE.HEADER,\n        data: {\n            name: categoryName,\n            displayName: categoryDisplayName\n        }\n    });\n\n    for (let i = 0; i < emojis.length; i += EMOJIS_PER_ROW) {\n        const rowEmojis = emojis.slice(i, i + EMOJIS_PER_ROW);\n        rowData.push({\n            index: totalRowCount + rowData.length,\n            type: ROW_TYPE.EMOJI,\n            data: rowEmojis\n        });\n    }\n\n    return rowData;\n};\n\n/**\n * Add each row's index to it's value in the row data.\n *\n * @method\n * @param {Array} rowData List of emoji row data\n * @return {Array}\n */\nconst addIndexesToRowData = (rowData) => {\n    return rowData.map((data, index) => {\n        return {...data, index};\n    });\n};\n\n/**\n * Calculate the scroll position for the beginning of each category from\n * the row data.\n *\n * @method\n * @param {Array} rowData List of emoji row data\n * @return {Object}\n */\nconst getCategoryScrollPositionsFromRowData = (rowData) => {\n    return rowData.reduce((carry, row, index) => {\n        if (row.type === ROW_TYPE.HEADER) {\n            carry[row.data.name] = index * ROW_HEIGHT_RAW;\n        }\n        return carry;\n    }, {});\n};\n\n/**\n * Create a header row element for the category name.\n *\n * @method\n * @param {Number} rowIndex Index of the row in the row data\n * @param {String} name The category display name\n * @return {Element}\n */\nconst createHeaderRow = async(rowIndex, name) => {\n    const context = {\n        index: rowIndex,\n        text: name\n    };\n    const html = await renderTemplate('core/emoji/header_row', context);\n    const temp = document.createElement('div');\n    temp.innerHTML = html;\n    return temp.firstChild;\n};\n\n/**\n * Create an emoji row element.\n *\n * @method\n * @param {Number} rowIndex Index of the row in the row data\n * @param {Array} emojis The list of emoji data for the row\n * @return {Element}\n */\nconst createEmojiRow = async(rowIndex, emojis) => {\n    const context = {\n        index: rowIndex,\n        emojis: emojis.map(emojiData => {\n            const charCodes = emojiData.unified.split('-').map(code => `0x${code}`);\n            const emojiText = String.fromCodePoint.apply(null, charCodes);\n            return {\n                shortnames: `:${emojiData.shortnames.join(': :')}:`,\n                unified: emojiData.unified,\n                text: emojiText,\n                spacer: false\n            };\n        }),\n        spacers: Array(EMOJIS_PER_ROW - emojis.length).fill(true)\n    };\n    const html = await renderTemplate('core/emoji/emoji_row', context);\n    const temp = document.createElement('div');\n    temp.innerHTML = html;\n    return temp.firstChild;\n};\n\n/**\n * Check if the element is an emoji element.\n *\n * @method\n * @param {Element} element Element to check\n * @return {Bool}\n */\nconst isEmojiElement = element => element.getAttribute('data-short-names') !== null;\n\n/**\n * Search from an element and up through it's ancestors to fine the category\n * selector element and return it.\n *\n * @method\n * @param {Element} element Element to begin searching from\n * @return {Element|null}\n */\nconst findCategorySelectorFromElement = element => {\n    if (!element) {\n        return null;\n    }\n\n    if (element.getAttribute('data-action') === 'show-category') {\n        return element;\n    } else {\n        return findCategorySelectorFromElement(element.parentElement);\n    }\n};\n\nconst getCategorySelectorByCategoryName = (root, name) => {\n    return root.querySelector(`[data-category=\"${name}\"]`);\n};\n\n/**\n * Sets the given category selector element as active.\n *\n * @method\n * @param {Element} root The root picker element\n * @param {Element} element The category selector element to make active\n */\nconst setCategorySelectorActive = (root, element) => {\n    const allCategorySelectors = root.querySelectorAll(SELECTORS.CATEGORY_SELECTOR);\n\n    for (let i = 0; i < allCategorySelectors.length; i++) {\n        const selector = allCategorySelectors[i];\n        selector.classList.remove('selected');\n    }\n\n    element.classList.add('selected');\n};\n\n/**\n * Get the category selector element and the scroll positions for the previous and\n * next categories for the given scroll position.\n *\n * @method\n * @param {Element} root The picker root element\n * @param {Number} position The position to get the category for\n * @param {Object} categoryScrollPositions Set of scroll positions for all categories\n * @return {Array}\n */\nconst getCategoryByScrollPosition = (root, position, categoryScrollPositions) => {\n    let positions = [];\n\n    if (position < 0) {\n        position = 0;\n    }\n\n    // Get all of the category positions.\n    for (const categoryName in categoryScrollPositions) {\n        const categoryPosition = categoryScrollPositions[categoryName];\n        positions.push([categoryPosition, categoryName]);\n    }\n\n    // Sort the positions in ascending order.\n    positions.sort(([a], [b]) => {\n        if (a < b) {\n            return -1;\n        } else if (a > b) {\n            return 1;\n        } else {\n            return 0;\n        }\n    });\n\n    // Get the current category name as well as the previous and next category\n    // positions from the sorted list of positions.\n    const {categoryName, previousPosition, nextPosition} = positions.reduce(\n        (carry, candidate) => {\n            const [categoryPosition, categoryName] = candidate;\n\n            if (categoryPosition <= position) {\n                carry.categoryName = categoryName;\n                carry.previousPosition = carry.currentPosition;\n                carry.currentPosition = position;\n            } else if (carry.nextPosition === null) {\n                carry.nextPosition = categoryPosition;\n            }\n\n            return carry;\n        },\n        {\n            categoryName: null,\n            currentPosition: null,\n            previousPosition: null,\n            nextPosition: null\n        }\n    );\n\n    return [getCategorySelectorByCategoryName(root, categoryName), previousPosition, nextPosition];\n};\n\n/**\n * Get the list of recent emojis data from local storage.\n *\n * @method\n * @return {Array}\n */\nconst getRecentEmojis = () => {\n    const storedData = LocalStorage.get(RECENT_EMOJIS_STORAGE_KEY);\n    return storedData ? JSON.parse(storedData) : [];\n};\n\n/**\n * Save the list of recent emojis in local storage.\n *\n * @method\n * @param {Array} recentEmojis List of emoji data to save\n */\nconst saveRecentEmoji = (recentEmojis) => {\n    LocalStorage.set(RECENT_EMOJIS_STORAGE_KEY, JSON.stringify(recentEmojis));\n};\n\n/**\n * Add an emoji data to the set of recent emojis. This function will update the row\n * data to ensure that the recent emoji rows are correct and all of the rows are\n * re-indexed.\n *\n * The new set of recent emojis are saved in local storage and the full set of updated\n * row data and new emoji row count are returned.\n *\n * @method\n * @param {Array} rowData The emoji rows data\n * @param {Number} recentEmojiRowCount Count of the recent emoji rows\n * @param {Object} newEmoji The emoji data for the emoji to add to the recent emoji list\n * @return {Array}\n */\nconst addRecentEmoji = (rowData, recentEmojiRowCount, newEmoji) => {\n    // The first set of rows is always the recent emojis.\n    const categoryName = rowData[0].data.name;\n    const categoryDisplayName = rowData[0].data.displayName;\n    const recentEmojis = getRecentEmojis();\n    // Add the new emoji to the start of the list of recent emojis.\n    let newRecentEmojis = [newEmoji, ...recentEmojis.filter(emoji => emoji.unified != newEmoji.unified)];\n    // Limit the number of recent emojis.\n    newRecentEmojis = newRecentEmojis.slice(0, MAX_RECENT_COUNT);\n    const newRecentEmojiRowData = createRowDataForCategory(categoryName, categoryDisplayName, newRecentEmojis);\n\n    // Save the new list in local storage.\n    saveRecentEmoji(newRecentEmojis);\n\n    return [\n        // Return the new rowData and re-index it to make sure it's all correct.\n        addIndexesToRowData(newRecentEmojiRowData.concat(rowData.slice(recentEmojiRowCount))),\n        newRecentEmojiRowData.length\n    ];\n};\n\n/**\n * Calculate which rows should be visible based on the given scroll position. Adds a\n * buffer to amount to either side of the total number of requested rows so that\n * scrolling the emoji rows container is smooth.\n *\n * @method\n * @param {Number} scrollPosition Scroll position within the emoji container\n * @param {Number} visibleRowCount How many rows should be visible\n * @param {Array} rowData The emoji rows data\n * @return {Array}\n */\nconst getRowsToRender = (scrollPosition, visibleRowCount, rowData) => {\n    const minVisibleRow = scrollPosition > ROW_HEIGHT_RAW ? Math.floor(scrollPosition / ROW_HEIGHT_RAW) : 0;\n    const start = minVisibleRow >= ROW_RENDER_BUFFER_COUNT ? minVisibleRow - ROW_RENDER_BUFFER_COUNT : minVisibleRow;\n    const end = minVisibleRow + visibleRowCount + ROW_RENDER_BUFFER_COUNT;\n    const rows = rowData.slice(start, end);\n    return rows;\n};\n\n/**\n * Create a row element from the row data.\n *\n * @method\n * @param {Object} rowData The emoji row data\n * @return {Element}\n */\nconst createRowElement = async(rowData) => {\n    let row = null;\n    if (rowData.type === ROW_TYPE.HEADER) {\n        row = await createHeaderRow(rowData.index, rowData.data.displayName);\n    } else {\n        row = await createEmojiRow(rowData.index, rowData.data);\n    }\n\n    row.style.position = 'absolute';\n    row.style.left = 0;\n    row.style.right = 0;\n    row.style.top = `${rowData.index * ROW_HEIGHT_RAW}px`;\n\n    return row;\n};\n\n/**\n * Check if the given rows match.\n *\n * @method\n * @param {Object} a The first row\n * @param {Object} b The second row\n * @return {Bool}\n */\nconst doRowsMatch = (a, b) => {\n    if (a.index !== b.index) {\n        return false;\n    }\n\n    if (a.type !== b.type) {\n        return false;\n    }\n\n    if (typeof a.data != typeof b.data) {\n        return false;\n    }\n\n    if (a.type === ROW_TYPE.HEADER) {\n        return a.data.name === b.data.name;\n    } else {\n        if (a.data.length !== b.data.length) {\n            return false;\n        }\n\n        for (let i = 0; i < a.data.length; i++) {\n            if (a.data[i].unified != b.data[i].unified) {\n                return false;\n            }\n        }\n    }\n\n    return true;\n};\n\n/**\n * Update the visible rows. Deletes any row elements that should no longer\n * be visible and creates the newly visible row elements. Any rows that haven't\n * changed visibility will be left untouched.\n *\n * @method\n * @param {Element} rowContainer The container element for the emoji rows\n * @param {Array} currentRows List of row data that matches the currently visible rows\n * @param {Array} nextRows List of row data containing the new list of rows to be made visible\n */\nconst renderRows = async(rowContainer, currentRows, nextRows) => {\n    // We need to add any rows that are in nextRows but not in currentRows.\n    const toAdd = nextRows.filter(nextRow => !currentRows.some(currentRow => doRowsMatch(currentRow, nextRow)));\n    // Remember which rows will still be visible so that we can insert our element in the correct place in the DOM.\n    let toKeep = currentRows.filter(currentRow => nextRows.some(nextRow => doRowsMatch(currentRow, nextRow)));\n    // We need to remove any rows that are in currentRows but not in nextRows.\n    const toRemove = currentRows.filter(currentRow => !nextRows.some(nextRow => doRowsMatch(currentRow, nextRow)));\n    const toRemoveElements = toRemove.map(rowData => rowContainer.querySelectorAll(`[data-row=\"${rowData.index}\"]`));\n\n    // Render all of the templates first.\n    const rows = await Promise.all(toAdd.map(rowData => createRowElement(rowData)));\n\n    rows.forEach((row, index) => {\n        const rowData = toAdd[index];\n        let nextRowIndex = null;\n\n        for (let i = 0; i < toKeep.length; i++) {\n            const candidate = toKeep[i];\n            if (candidate.index > rowData.index) {\n                nextRowIndex = i;\n                break;\n            }\n        }\n\n        // Make sure the elements get added to the DOM in the correct order (ascending by row data index)\n        // so that they appear naturally in the tab order.\n        if (nextRowIndex !== null) {\n            const nextRowData = toKeep[nextRowIndex];\n            const nextRowNode = rowContainer.querySelector(`[data-row=\"${nextRowData.index}\"]`);\n\n            rowContainer.insertBefore(row, nextRowNode);\n            toKeep.splice(nextRowIndex, 0, toKeep);\n        } else {\n            toKeep.push(rowData);\n            rowContainer.appendChild(row);\n        }\n    });\n\n    toRemoveElements.forEach(rows => {\n        for (let i = 0; i < rows.length; i++) {\n            const row = rows[i];\n            rowContainer.removeChild(row);\n        }\n    });\n};\n\n/**\n * Build a function to render the visible emoji rows for a given scroll\n * position.\n *\n * @method\n * @param {Element} rowContainer The container element for the emoji rows\n * @return {Function}\n */\nconst generateRenderRowsAtPositionFunction = (rowContainer) => {\n    let currentRows = [];\n    let nextRows = [];\n    let rowCount = 0;\n    let isRendering = false;\n    const renderNextRows = async() => {\n        if (!nextRows.length) {\n            return;\n        }\n\n        if (isRendering) {\n            return;\n        }\n\n        isRendering = true;\n        const nextRowsToRender = nextRows.slice();\n        nextRows = [];\n\n        await renderRows(rowContainer, currentRows, nextRowsToRender);\n        currentRows = nextRowsToRender;\n        isRendering = false;\n        renderNextRows();\n    };\n\n    return (scrollPosition, rowData, rowLimit = VISIBLE_ROW_COUNT) => {\n        nextRows = getRowsToRender(scrollPosition, rowLimit, rowData);\n        renderNextRows();\n\n        if (rowCount !== rowData.length) {\n            // Adjust the height of the container to match the number of rows.\n            rowContainer.style.height = `${rowData.length * ROW_HEIGHT_RAW}px`;\n        }\n\n        rowCount = rowData.length;\n    };\n};\n\n/**\n * Show the search results container and hide the emoji container.\n *\n * @method\n * @param {Element} emojiContainer The emojis container\n * @param {Element} searchResultsContainer The search results container\n */\nconst showSearchResults = (emojiContainer, searchResultsContainer) => {\n    searchResultsContainer.classList.remove('hidden');\n    emojiContainer.classList.add('hidden');\n};\n\n/**\n * Hide the search result container and show the emojis container.\n *\n * @method\n * @param {Element} emojiContainer The emojis container\n * @param {Element} searchResultsContainer The search results container\n * @param {Element} searchInput The search input\n */\nconst clearSearch = (emojiContainer, searchResultsContainer, searchInput) => {\n    searchResultsContainer.classList.add('hidden');\n    emojiContainer.classList.remove('hidden');\n    searchInput.value = '';\n};\n\n/**\n * Build function to handle mouse hovering an emoji. Shows the preview.\n *\n * @method\n * @param {Element} emojiPreview The emoji preview element\n * @param {Element} emojiShortName The emoji short name element\n * @return {Function}\n */\nconst getHandleMouseEnter = (emojiPreview, emojiShortName) => {\n    return (e) => {\n        const target = e.target;\n        if (isEmojiElement(target)) {\n            emojiShortName.textContent = target.getAttribute('data-short-names');\n            emojiPreview.textContent = target.textContent;\n        }\n    };\n};\n\n/**\n * Build function to handle mouse leaving an emoji. Removes the preview.\n *\n * @method\n * @param {Element} emojiPreview The emoji preview element\n * @param {Element} emojiShortName The emoji short name element\n * @return {Function}\n */\nconst getHandleMouseLeave = (emojiPreview, emojiShortName) => {\n    return (e) => {\n        const target = e.target;\n        if (isEmojiElement(target)) {\n            emojiShortName.textContent = '';\n            emojiPreview.textContent = '';\n        }\n    };\n};\n\n/**\n * Build the function to handle a user clicking something in the picker.\n *\n * The function currently handles clicking on the category selector or selecting\n * a specific emoji.\n *\n * @method\n * @param {Number} recentEmojiRowCount Number of rows of recent emojis\n * @param {Element} emojiContainer Container element for the visible of emojis\n * @param {Element} searchResultsContainer Contaienr element for the search results\n * @param {Element} searchInput Search input element\n * @param {Function} selectCallback Callback function to execute when a user selects an emoji\n * @param {Function} renderAtPosition Render function to display current visible emojis\n * @return {Function}\n */\nconst getHandleClick = (\n    recentEmojiRowCount,\n    emojiContainer,\n    searchResultsContainer,\n    searchInput,\n    selectCallback,\n    renderAtPosition\n) => {\n    return (e, rowData, categoryScrollPositions) => {\n        const target = e.target;\n        let newRowData = rowData;\n        let newCategoryScrollPositions = categoryScrollPositions;\n\n        // Hide the search results if they are visible.\n        clearSearch(emojiContainer, searchResultsContainer, searchInput);\n\n        if (isEmojiElement(target)) {\n            // Emoji selected.\n            const unified = target.getAttribute('data-unified');\n            const shortnames = target.getAttribute('data-short-names').replace(/:/g, '').split(' ');\n            // Build the emoji data from the selected element.\n            const emojiData = {unified, shortnames};\n            const currentScrollTop = emojiContainer.scrollTop;\n            const isRecentEmojiRowVisible = emojiContainer.querySelector(`[data-row=\"${recentEmojiRowCount - 1}\"]`) !== null;\n            // Save the selected emoji in the recent emojis list.\n            [newRowData, recentEmojiRowCount] = addRecentEmoji(rowData, recentEmojiRowCount, emojiData);\n            // Re-index the category scroll positions because the additional recent emoji may have\n            // changed their positions.\n            newCategoryScrollPositions = getCategoryScrollPositionsFromRowData(newRowData);\n\n            if (isRecentEmojiRowVisible) {\n                // If the list of recent emojis is currently visible then we need to re-render the emojis\n                // to update the display and show the newly selected recent emoji.\n                renderAtPosition(currentScrollTop, newRowData);\n            }\n\n            // Call the client's callback function with the selected emoji.\n            selectCallback(target.textContent);\n            // Return the newly calculated row data and scroll positions.\n            return [newRowData, newCategoryScrollPositions];\n        }\n\n        const categorySelector = findCategorySelectorFromElement(target);\n        if (categorySelector) {\n            // Category selector.\n            const selectedCategory = categorySelector.getAttribute('data-category');\n            const position = categoryScrollPositions[selectedCategory];\n            // Scroll the container to the selected category. This will trigger the\n            // on scroll handler to re-render the visibile emojis.\n            emojiContainer.scrollTop = position;\n        }\n\n        return [newRowData, newCategoryScrollPositions];\n    };\n};\n\n/**\n * Build the function that handles scrolling of the emoji container to display the\n * correct emojis.\n *\n * We render the emoji rows as they are needed rather than all up front so that we\n * can avoid adding tends of thousands of elements to the DOM unnecessarily which\n * would bog down performance.\n *\n * @method\n * @param {Element} root The picker root element\n * @param {Number} currentVisibleRowScrollPosition The current scroll position of the container\n * @param {Element} emojiContainer The emojis container element\n * @param {Object} initialCategoryScrollPositions Scroll positions for each category\n * @param {Function} renderAtPosition Function to render the appropriate emojis for a scroll position\n * @return {Function}\n */\nconst getHandleScroll = (\n    root,\n    currentVisibleRowScrollPosition,\n    emojiContainer,\n    initialCategoryScrollPositions,\n    renderAtPosition\n) => {\n    // Scope some local variables to track the scroll positions of the categories. We need to\n    // recalculate these because adding recent emojis can change those positions by adding\n    // additional rows.\n    let [\n        currentCategoryElement,\n        previousCategoryPosition,\n        nextCategoryPosition\n    ] = getCategoryByScrollPosition(root, emojiContainer.scrollTop, initialCategoryScrollPositions);\n\n    return (categoryScrollPositions, rowData) => {\n        const newScrollPosition = emojiContainer.scrollTop;\n        const upperScrollBound = currentVisibleRowScrollPosition + ROW_HEIGHT_RAW;\n        const lowerScrollBound = currentVisibleRowScrollPosition - ROW_HEIGHT_RAW;\n        // We only need to update the active category indicator if the user has scrolled into a\n        // new category scroll position.\n        const updateActiveCategory = (newScrollPosition >= nextCategoryPosition) ||\n                       (newScrollPosition < previousCategoryPosition);\n        // We only need to render new emoji rows if the user has scrolled far enough that a new row\n        // would be visible (i.e. they've scrolled up or down more than 40px - the height of a row).\n        const updateRenderRows = (newScrollPosition < lowerScrollBound) || (newScrollPosition > upperScrollBound);\n\n        if (updateActiveCategory) {\n            // New category is visible so update the active category selector and re-index the\n            // positions incase anything has changed.\n            [\n                currentCategoryElement,\n                previousCategoryPosition,\n                nextCategoryPosition\n            ] = getCategoryByScrollPosition(root, newScrollPosition, categoryScrollPositions);\n            setCategorySelectorActive(root, currentCategoryElement);\n        }\n\n        if (updateRenderRows) {\n            // A new row should be visible so re-render the visible emojis at this new position.\n            // We request an animation frame from the browser so that we're not blocking anything.\n            // The animation only needs to occur as soon as the browser is ready not immediately.\n            requestAnimationFrame(() => {\n                renderAtPosition(newScrollPosition, rowData);\n                // Remember the updated position.\n                currentVisibleRowScrollPosition = newScrollPosition;\n            });\n        }\n    };\n};\n\n/**\n * Build the function that handles search input from the user.\n *\n * @method\n * @param {Element} searchInput The search input element\n * @param {Element} searchResultsContainer Container element to display the search results\n * @param {Element} emojiContainer Container element for the emoji rows\n * @return {Function}\n */\nconst getHandleSearch = (searchInput, searchResultsContainer, emojiContainer) => {\n    const rowContainer = searchResultsContainer.querySelector(SELECTORS.ROW_CONTAINER);\n    // Build a render function for the search results.\n    const renderSearchResultsAtPosition = generateRenderRowsAtPositionFunction(rowContainer);\n    searchResultsContainer.appendChild(rowContainer);\n\n    return async() => {\n        const searchTerm = searchInput.value.toLowerCase();\n\n        if (searchTerm) {\n            // Display the search results container and hide the emojis container.\n            showSearchResults(emojiContainer, searchResultsContainer);\n\n            // Find which emojis match the user's search input.\n            const matchingEmojis = Object.keys(EmojiData.byShortName).reduce((carry, shortName) => {\n                if (shortName.includes(searchTerm)) {\n                    carry.push({\n                        shortnames: [shortName],\n                        unified: EmojiData.byShortName[shortName]\n                    });\n                }\n                return carry;\n            }, []);\n\n            const searchResultsString = await getString('searchresults', 'core');\n            const rowData = createRowDataForCategory(searchResultsString, searchResultsString, matchingEmojis, 0);\n            // Show the emoji rows for the search results.\n            renderSearchResultsAtPosition(0, rowData, rowData.length);\n        } else {\n            // Hide the search container and show the emojis container.\n            clearSearch(emojiContainer, searchResultsContainer, searchInput);\n        }\n    };\n};\n\n/**\n * Register the emoji picker event listeners.\n *\n * @method\n * @param {Element} root The picker root element\n * @param {Element} emojiContainer Root element containing the list of visible emojis\n * @param {Function} renderAtPosition Function to render the visible emojis at a given scroll position\n * @param {Number} currentVisibleRowScrollPosition What is the current scroll position\n * @param {Function} selectCallback Function to execute when the user picks an emoji\n * @param {Object} categoryScrollPositions Scroll positions for where each of the emoji categories begin\n * @param {Array} rowData Data representing each of the display rows for hte emoji container\n * @param {Number} recentEmojiRowCount Number of rows of recent emojis\n */\nconst registerEventListeners = (\n    root,\n    emojiContainer,\n    renderAtPosition,\n    currentVisibleRowScrollPosition,\n    selectCallback,\n    categoryScrollPositions,\n    rowData,\n    recentEmojiRowCount\n) => {\n    const searchInput = root.querySelector(SELECTORS.SEARCH_INPUT);\n    const searchResultsContainer = root.querySelector(SELECTORS.SEARCH_RESULTS_CONTAINER);\n    const emojiPreview = root.querySelector(SELECTORS.EMOJI_PREVIEW);\n    const emojiShortName = root.querySelector(SELECTORS.EMOJI_SHORT_NAME);\n    // Build the click handler function.\n    const clickHandler = getHandleClick(\n        recentEmojiRowCount,\n        emojiContainer,\n        searchResultsContainer,\n        searchInput,\n        selectCallback,\n        renderAtPosition\n    );\n    // Build the scroll handler function.\n    const scrollHandler = getHandleScroll(\n        root,\n        currentVisibleRowScrollPosition,\n        emojiContainer,\n        categoryScrollPositions,\n        renderAtPosition\n    );\n    const searchHandler = getHandleSearch(searchInput, searchResultsContainer, emojiContainer);\n\n    // Mouse enter/leave events to show the emoji preview on hover or focus.\n    root.addEventListener('focus', getHandleMouseEnter(emojiPreview, emojiShortName), true);\n    root.addEventListener('blur', getHandleMouseLeave(emojiPreview, emojiShortName), true);\n    root.addEventListener('mouseenter', getHandleMouseEnter(emojiPreview, emojiShortName), true);\n    root.addEventListener('mouseleave', getHandleMouseLeave(emojiPreview, emojiShortName), true);\n    // User selects an emoji or clicks on one of the emoji category selectors.\n    root.addEventListener('click', e => {\n        // Update the row data and category scroll positions because they may have changes if the\n        // user selects an emoji which updates the recent emojis list.\n        [rowData, categoryScrollPositions] = clickHandler(e, rowData, categoryScrollPositions);\n    });\n    // Throttle the scroll event to only execute once every 50 milliseconds to prevent performance issues\n    // in the browser when re-rendering the picker emojis. The scroll event fires a lot otherwise.\n    emojiContainer.addEventListener('scroll', throttle(() => scrollHandler(categoryScrollPositions, rowData), 50));\n    // Debounce the search input so that it only executes 200 milliseconds after the user has finished typing.\n    searchInput.addEventListener('input', debounce(searchHandler, 200));\n};\n\n/**\n * Initialise the emoji picker.\n *\n * @method\n * @param {Element} root The root element for the picker\n * @param {Function} selectCallback Callback for when the user selects an emoji\n */\nexport default (root, selectCallback) => {\n    const emojiContainer = root.querySelector(SELECTORS.EMOJIS_CONTAINER);\n    const rowContainer = emojiContainer.querySelector(SELECTORS.ROW_CONTAINER);\n    const recentEmojis = getRecentEmojis();\n    // Add the recent emojis category to the list of standard categories.\n    const allData = [{\n        name: 'Recent',\n        emojis: recentEmojis\n    }, ...EmojiData.byCategory];\n    let rowData = [];\n    let recentEmojiRowCount = 0;\n\n    /**\n     * Split categories data into rows which represent how they will be displayed in the\n     * picker. Each category will add a row containing the display name for the category\n     * and a row for every 9 emojis in the category. The row data will be used to calculate\n     * which emojis should be visible in the picker at any given time.\n     *\n     * E.g.\n     * input = [\n     *  {name: 'example1', emojis: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]},\n     *  {name: 'example2', emojis: [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]},\n     * ]\n     * output = [\n     *      {type: 'categoryName': data: 'Example 1'},\n     *      {type: 'emojiRow': data: [1, 2, 3, 4, 5, 6, 7, 8, 9]},\n     *      {type: 'emojiRow': data: [10, 11, 12]},\n     *      {type: 'categoryName': data: 'Example 2'},\n     *      {type: 'emojiRow': data: [13, 14, 15, 16, 17, 18, 19, 20, 21]},\n     *      {type: 'emojiRow': data: [22, 23]},\n     * ]\n     */\n    allData.forEach(category => {\n        const categorySelector = getCategorySelectorByCategoryName(root, category.name);\n        // Get the display name from the category selector button so that we don't need to\n        // send an ajax request for the string.\n        const categoryDisplayName = categorySelector.title;\n        const categoryRowData = createRowDataForCategory(category.name, categoryDisplayName, category.emojis, rowData.length);\n\n        if (category.name === 'Recent') {\n            // Remember how many recent emoji rows there are because it needs to be used to\n            // re-index the row data later when we're adding more recent emojis.\n            recentEmojiRowCount = categoryRowData.length;\n        }\n\n        rowData = rowData.concat(categoryRowData);\n    });\n\n    // Index the row data so that we can calculate which rows should be visible.\n    rowData = addIndexesToRowData(rowData);\n    // Calculate the scroll positions for each of the categories within the emoji container.\n    // These are used to know where to jump to when the user selects a specific category.\n    const categoryScrollPositions = getCategoryScrollPositionsFromRowData(rowData);\n    const renderAtPosition = generateRenderRowsAtPositionFunction(rowContainer);\n    // Display the initial set of emojis.\n    renderAtPosition(0, rowData);\n\n    registerEventListeners(\n        root,\n        emojiContainer,\n        renderAtPosition,\n        0,\n        selectCallback,\n        categoryScrollPositions,\n        rowData,\n        recentEmojiRowCount\n    );\n};\n"],"file":"picker.min.js"}