diff options
Diffstat (limited to 'client/node_modules/emoji-picker-element/svelte.js')
-rw-r--r-- | client/node_modules/emoji-picker-element/svelte.js | 2294 |
1 files changed, 2294 insertions, 0 deletions
diff --git a/client/node_modules/emoji-picker-element/svelte.js b/client/node_modules/emoji-picker-element/svelte.js new file mode 100644 index 0000000..ddc68ff --- /dev/null +++ b/client/node_modules/emoji-picker-element/svelte.js @@ -0,0 +1,2294 @@ +import { SvelteComponent, init, safe_not_equal, element, text, attr, set_style, insert, append, set_input_value, listen, action_destroyer, set_data, update_keyed_each, destroy_block, noop, detach, run_all, globals, binding_callbacks, src_url_equal } from 'svelte/internal'; +import { onMount, tick } from 'svelte'; +import Database from './database.js'; + +// via https://unpkg.com/browse/emojibase-data@6.0.0/meta/groups.json +const allGroups = [ + [-1, 'β¨', 'custom'], + [0, 'π', 'smileys-emotion'], + [1, 'π', 'people-body'], + [3, 'π±', 'animals-nature'], + [4, 'π', 'food-drink'], + [5, 'π οΈ', 'travel-places'], + [6, 'β½', 'activities'], + [7, 'π', 'objects'], + [8, 'βοΈ', 'symbols'], + [9, 'π', 'flags'] +].map(([id, emoji, name]) => ({ id, emoji, name })); + +const groups = allGroups.slice(1); +const customGroup = allGroups[0]; + +const MIN_SEARCH_TEXT_LENGTH = 2; +const NUM_SKIN_TONES = 6; + +/* istanbul ignore next */ +const rIC = typeof requestIdleCallback === 'function' ? requestIdleCallback : setTimeout; + +// check for ZWJ (zero width joiner) character +function hasZwj (emoji) { + return emoji.unicode.includes('\u200d') +} + +// Find one good representative emoji from each version to test by checking its color. +// Ideally it should have color in the center. For some inspiration, see: +// https://about.gitlab.com/blog/2018/05/30/journey-in-native-unicode-emoji/ +// +// Note that for certain versions (12.1, 13.1), there is no point in testing them explicitly, because +// all the emoji from this version are compound-emoji from previous versions. So they would pass a color +// test, even in browsers that display them as double emoji. (E.g. "face in clouds" might render as +// "face without mouth" plus "fog".) These emoji can only be filtered using the width test, +// which happens in checkZwjSupport.js. +const versionsAndTestEmoji = { + 'π« ': 14, + 'π₯²': 13.1, // smiling face with tear, technically from v13 but see note above + 'π₯»': 12.1, // sari, technically from v12 but see note above + 'π₯°': 11, + 'π€©': 5, + 'π±ββοΈ': 4, + 'π€£': 3, + 'ποΈβπ¨οΈ': 2, + 'π': 1, + 'ποΈ': 0.7, + 'π': 0.6 +}; + +const TIMEOUT_BEFORE_LOADING_MESSAGE = 1000; // 1 second +const DEFAULT_SKIN_TONE_EMOJI = 'ποΈ'; +const DEFAULT_NUM_COLUMNS = 8; + +// Based on https://fivethirtyeight.com/features/the-100-most-used-emojis/ and +// https://blog.emojipedia.org/facebook-reveals-most-and-least-used-emojis/ with +// a bit of my own curation. (E.g. avoid the "OK" gesture because of connotations: +// https://emojipedia.org/ok-hand/) +const MOST_COMMONLY_USED_EMOJI = [ + 'π', + 'π', + 'β₯οΈ', + 'ποΈ', + 'π', + 'π', + 'π', + 'βΊοΈ', + 'π', + 'π©', + 'π', + 'π', + 'π', + 'π' +]; + +// It's important to list Twemoji Mozilla before everything else, because Mozilla bundles their +// own font on some platforms (notably Windows and Linux as of this writing). Typically, Mozilla +// updates faster than the underlying OS, and we don't want to render older emoji in one font and +// newer emoji in another font: +// https://github.com/nolanlawson/emoji-picker-element/pull/268#issuecomment-1073347283 +const FONT_FAMILY = '"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol",' + + '"Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif'; + +/* istanbul ignore next */ +const DEFAULT_CATEGORY_SORTING = (a, b) => a < b ? -1 : a > b ? 1 : 0; + +// Test if an emoji is supported by rendering it to canvas and checking that the color is not black + +const getTextFeature = (text, color) => { + const canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + + const ctx = canvas.getContext('2d'); + ctx.textBaseline = 'top'; + ctx.font = `100px ${FONT_FAMILY}`; + ctx.fillStyle = color; + ctx.scale(0.01, 0.01); + ctx.fillText(text, 0, 0); + + return ctx.getImageData(0, 0, 1, 1).data +}; + +const compareFeatures = (feature1, feature2) => { + const feature1Str = [...feature1].join(','); + const feature2Str = [...feature2].join(','); + // This is RGBA, so for 0,0,0, we are checking that the first RGB is not all zeroes. + // Most of the time when unsupported this is 0,0,0,0, but on Chrome on Mac it is + // 0,0,0,61 - there is a transparency here. + return feature1Str === feature2Str && !feature1Str.startsWith('0,0,0,') +}; + +function testColorEmojiSupported (text) { + // Render white and black and then compare them to each other and ensure they're the same + // color, and neither one is black. This shows that the emoji was rendered in color. + const feature1 = getTextFeature(text, '#000'); + const feature2 = getTextFeature(text, '#fff'); + return feature1 && feature2 && compareFeatures(feature1, feature2) +} + +// rather than check every emoji ever, which would be expensive, just check some representatives from the + +function determineEmojiSupportLevel () { + const entries = Object.entries(versionsAndTestEmoji); + try { + // start with latest emoji and work backwards + for (const [emoji, version] of entries) { + if (testColorEmojiSupported(emoji)) { + return version + } + } + } catch (e) { // canvas error + } finally { + } + // In case of an error, be generous and just assume all emoji are supported (e.g. for canvas errors + // due to anti-fingerprinting add-ons). Better to show some gray boxes than nothing at all. + return entries[0][1] // first one in the list is the most recent version +} + +// Check which emojis we know for sure aren't supported, based on Unicode version level +let promise; +const detectEmojiSupportLevel = () => { + if (!promise) { + // Delay so it can run while the IDB database is being created by the browser (on another thread). + // This helps especially with first load β we want to start pre-populating the database on the main thread, + // and then wait for IDB to commit everything, and while waiting we run this check. + promise = new Promise(resolve => ( + rIC(() => ( + resolve(determineEmojiSupportLevel()) // delay so ideally this can run while IDB is first populating + )) + )); + } + return promise +}; +// determine which emojis containing ZWJ (zero width joiner) characters +// are supported (rendered as one glyph) rather than unsupported (rendered as two or more glyphs) +const supportedZwjEmojis = new Map(); + +const VARIATION_SELECTOR = '\ufe0f'; +const SKINTONE_MODIFIER = '\ud83c'; +const ZWJ = '\u200d'; +const LIGHT_SKIN_TONE = 0x1F3FB; +const LIGHT_SKIN_TONE_MODIFIER = 0xdffb; + +// TODO: this is a naive implementation, we can improve it later +// It's only used for the skintone picker, so as long as people don't customize with +// really exotic emoji then it should work fine +function applySkinTone (str, skinTone) { + if (skinTone === 0) { + return str + } + const zwjIndex = str.indexOf(ZWJ); + if (zwjIndex !== -1) { + return str.substring(0, zwjIndex) + + String.fromCodePoint(LIGHT_SKIN_TONE + skinTone - 1) + + str.substring(zwjIndex) + } + if (str.endsWith(VARIATION_SELECTOR)) { + str = str.substring(0, str.length - 1); + } + return str + SKINTONE_MODIFIER + String.fromCodePoint(LIGHT_SKIN_TONE_MODIFIER + skinTone - 1) +} + +function halt (event) { + event.preventDefault(); + event.stopPropagation(); +} + +// Implementation left/right or up/down navigation, circling back when you +// reach the start/end of the list +function incrementOrDecrement (decrement, val, arr) { + val += (decrement ? -1 : 1); + if (val < 0) { + val = arr.length - 1; + } else if (val >= arr.length) { + val = 0; + } + return val +} + +// like lodash's uniqBy but much smaller +function uniqBy (arr, func) { + const set = new Set(); + const res = []; + for (const item of arr) { + const key = func(item); + if (!set.has(key)) { + set.add(key); + res.push(item); + } + } + return res +} + +// We don't need all the data on every emoji, and there are specific things we need +// for the UI, so build a "view model" from the emoji object we got from the database + +function summarizeEmojisForUI (emojis, emojiSupportLevel) { + const toSimpleSkinsMap = skins => { + const res = {}; + for (const skin of skins) { + // ignore arrays like [1, 2] with multiple skin tones + // also ignore variants that are in an unsupported emoji version + // (these do exist - variants from a different version than their base emoji) + if (typeof skin.tone === 'number' && skin.version <= emojiSupportLevel) { + res[skin.tone] = skin.unicode; + } + } + return res + }; + + return emojis.map(({ unicode, skins, shortcodes, url, name, category }) => ({ + unicode, + name, + shortcodes, + url, + category, + id: unicode || name, + skins: skins && toSimpleSkinsMap(skins), + title: (shortcodes || []).join(', ') + })) +} + +// import rAF from one place so that the bundle size is a bit smaller +const rAF = requestAnimationFrame; + +// Svelte action to calculate the width of an element and auto-update + +let resizeObserverSupported = typeof ResizeObserver === 'function'; + +function calculateWidth (node, onUpdate) { + let resizeObserver; + if (resizeObserverSupported) { + resizeObserver = new ResizeObserver(entries => ( + onUpdate(entries[0].contentRect.width) + )); + resizeObserver.observe(node); + } else { // just set the width once, don't bother trying to track it + rAF(() => ( + onUpdate(node.getBoundingClientRect().width) + )); + } + + // cleanup function (called on destroy) + return { + destroy () { + if (resizeObserver) { + resizeObserver.disconnect(); + } + } + } +} + +// get the width of the text inside of a DOM node, via https://stackoverflow.com/a/59525891/680742 +function calculateTextWidth (node) { + /* istanbul ignore else */ + { + const range = document.createRange(); + range.selectNode(node.firstChild); + return range.getBoundingClientRect().width + } +} + +let baselineEmojiWidth; + +function checkZwjSupport (zwjEmojisToCheck, baselineEmoji, emojiToDomNode) { + for (const emoji of zwjEmojisToCheck) { + const domNode = emojiToDomNode(emoji); + const emojiWidth = calculateTextWidth(domNode); + if (typeof baselineEmojiWidth === 'undefined') { // calculate the baseline emoji width only once + baselineEmojiWidth = calculateTextWidth(baselineEmoji); + } + // On Windows, some supported emoji are ~50% bigger than the baseline emoji, but what we really want to guard + // against are the ones that are 2x the size, because those are truly broken (person with red hair = person with + // floating red wig, black cat = cat with black square, polar bear = bear with snowflake, etc.) + // So here we set the threshold at 1.8 times the size of the baseline emoji. + const supported = emojiWidth / 1.8 < baselineEmojiWidth; + supportedZwjEmojis.set(emoji.unicode, supported); + } +} + +// Measure after style/layout are complete + +const requestPostAnimationFrame = callback => { + rAF(() => { + setTimeout(callback); + }); +}; + +// like lodash's uniq + +function uniq (arr) { + return uniqBy(arr, _ => _) +} + +// Note we put this in its own function outside Picker.js to avoid Svelte doing an invalidation on the "setter" here. +// At best the invalidation is useless, at worst it can cause infinite loops: +// https://github.com/nolanlawson/emoji-picker-element/pull/180 +// https://github.com/sveltejs/svelte/issues/6521 +// Also note tabpanelElement can be null if the element is disconnected immediately after connected +function resetScrollTopIfPossible (element) { + if (element) { + element.scrollTop = 0; + } +} + +/* src/picker/components/Picker/Picker.svelte generated by Svelte v3.55.1 */ + +const { Map: Map_1 } = globals; + +function get_each_context(ctx, list, i) { + const child_ctx = ctx.slice(); + child_ctx[64] = list[i]; + child_ctx[66] = i; + return child_ctx; +} + +function get_each_context_1(ctx, list, i) { + const child_ctx = ctx.slice(); + child_ctx[67] = list[i]; + child_ctx[66] = i; + return child_ctx; +} + +function get_each_context_2(ctx, list, i) { + const child_ctx = ctx.slice(); + child_ctx[64] = list[i]; + child_ctx[66] = i; + return child_ctx; +} + +function get_each_context_3(ctx, list, i) { + const child_ctx = ctx.slice(); + child_ctx[70] = list[i]; + return child_ctx; +} + +function get_each_context_4(ctx, list, i) { + const child_ctx = ctx.slice(); + child_ctx[73] = list[i]; + child_ctx[66] = i; + return child_ctx; +} + +// (43:38) {#each skinTones as skinTone, i (skinTone)} +function create_each_block_4(key_1, ctx) { + let div; + let t_value = /*skinTone*/ ctx[73] + ""; + let t; + let div_id_value; + let div_class_value; + let div_aria_selected_value; + let div_title_value; + let div_aria_label_value; + + return { + key: key_1, + first: null, + c() { + div = element("div"); + t = text(t_value); + attr(div, "id", div_id_value = "skintone-" + /*i*/ ctx[66]); + + attr(div, "class", div_class_value = "emoji hide-focus " + (/*i*/ ctx[66] === /*activeSkinTone*/ ctx[20] + ? 'active' + : '')); + + attr(div, "aria-selected", div_aria_selected_value = /*i*/ ctx[66] === /*activeSkinTone*/ ctx[20]); + attr(div, "role", "option"); + attr(div, "title", div_title_value = /*i18n*/ ctx[0].skinTones[/*i*/ ctx[66]]); + attr(div, "tabindex", "-1"); + attr(div, "aria-label", div_aria_label_value = /*i18n*/ ctx[0].skinTones[/*i*/ ctx[66]]); + this.first = div; + }, + m(target, anchor) { + insert(target, div, anchor); + append(div, t); + }, + p(new_ctx, dirty) { + ctx = new_ctx; + if (dirty[0] & /*skinTones*/ 512 && t_value !== (t_value = /*skinTone*/ ctx[73] + "")) set_data(t, t_value); + + if (dirty[0] & /*skinTones*/ 512 && div_id_value !== (div_id_value = "skintone-" + /*i*/ ctx[66])) { + attr(div, "id", div_id_value); + } + + if (dirty[0] & /*skinTones, activeSkinTone*/ 1049088 && div_class_value !== (div_class_value = "emoji hide-focus " + (/*i*/ ctx[66] === /*activeSkinTone*/ ctx[20] + ? 'active' + : ''))) { + attr(div, "class", div_class_value); + } + + if (dirty[0] & /*skinTones, activeSkinTone*/ 1049088 && div_aria_selected_value !== (div_aria_selected_value = /*i*/ ctx[66] === /*activeSkinTone*/ ctx[20])) { + attr(div, "aria-selected", div_aria_selected_value); + } + + if (dirty[0] & /*i18n, skinTones*/ 513 && div_title_value !== (div_title_value = /*i18n*/ ctx[0].skinTones[/*i*/ ctx[66]])) { + attr(div, "title", div_title_value); + } + + if (dirty[0] & /*i18n, skinTones*/ 513 && div_aria_label_value !== (div_aria_label_value = /*i18n*/ ctx[0].skinTones[/*i*/ ctx[66]])) { + attr(div, "aria-label", div_aria_label_value); + } + }, + d(detaching) { + if (detaching) detach(div); + } + }; +} + +// (53:33) {#each groups as group (group.id)} +function create_each_block_3(key_1, ctx) { + let button; + let div; + let t_value = /*group*/ ctx[70].emoji + ""; + let t; + let button_aria_controls_value; + let button_aria_label_value; + let button_aria_selected_value; + let button_title_value; + let mounted; + let dispose; + + function click_handler() { + return /*click_handler*/ ctx[50](/*group*/ ctx[70]); + } + + return { + key: key_1, + first: null, + c() { + button = element("button"); + div = element("div"); + t = text(t_value); + attr(div, "class", "nav-emoji emoji"); + attr(button, "role", "tab"); + attr(button, "class", "nav-button"); + attr(button, "aria-controls", button_aria_controls_value = "tab-" + /*group*/ ctx[70].id); + attr(button, "aria-label", button_aria_label_value = /*i18n*/ ctx[0].categories[/*group*/ ctx[70].name]); + attr(button, "aria-selected", button_aria_selected_value = !/*searchMode*/ ctx[4] && /*currentGroup*/ ctx[13].id === /*group*/ ctx[70].id); + attr(button, "title", button_title_value = /*i18n*/ ctx[0].categories[/*group*/ ctx[70].name]); + this.first = button; + }, + m(target, anchor) { + insert(target, button, anchor); + append(button, div); + append(div, t); + + if (!mounted) { + dispose = listen(button, "click", click_handler); + mounted = true; + } + }, + p(new_ctx, dirty) { + ctx = new_ctx; + if (dirty[0] & /*groups*/ 4096 && t_value !== (t_value = /*group*/ ctx[70].emoji + "")) set_data(t, t_value); + + if (dirty[0] & /*groups*/ 4096 && button_aria_controls_value !== (button_aria_controls_value = "tab-" + /*group*/ ctx[70].id)) { + attr(button, "aria-controls", button_aria_controls_value); + } + + if (dirty[0] & /*i18n, groups*/ 4097 && button_aria_label_value !== (button_aria_label_value = /*i18n*/ ctx[0].categories[/*group*/ ctx[70].name])) { + attr(button, "aria-label", button_aria_label_value); + } + + if (dirty[0] & /*searchMode, currentGroup, groups*/ 12304 && button_aria_selected_value !== (button_aria_selected_value = !/*searchMode*/ ctx[4] && /*currentGroup*/ ctx[13].id === /*group*/ ctx[70].id)) { + attr(button, "aria-selected", button_aria_selected_value); + } + + if (dirty[0] & /*i18n, groups*/ 4097 && button_title_value !== (button_title_value = /*i18n*/ ctx[0].categories[/*group*/ ctx[70].name])) { + attr(button, "title", button_title_value); + } + }, + d(detaching) { + if (detaching) detach(button); + mounted = false; + dispose(); + } + }; +} + +// (94:100) {:else} +function create_else_block_1(ctx) { + let img; + let img_src_value; + + return { + c() { + img = element("img"); + attr(img, "class", "custom-emoji"); + if (!src_url_equal(img.src, img_src_value = /*emoji*/ ctx[64].url)) attr(img, "src", img_src_value); + attr(img, "alt", ""); + attr(img, "loading", "lazy"); + }, + m(target, anchor) { + insert(target, img, anchor); + }, + p(ctx, dirty) { + if (dirty[0] & /*currentEmojisWithCategories*/ 32768 && !src_url_equal(img.src, img_src_value = /*emoji*/ ctx[64].url)) { + attr(img, "src", img_src_value); + } + }, + d(detaching) { + if (detaching) detach(img); + } + }; +} + +// (94:40) {#if emoji.unicode} +function create_if_block_1(ctx) { + let t_value = /*unicodeWithSkin*/ ctx[27](/*emoji*/ ctx[64], /*currentSkinTone*/ ctx[8]) + ""; + let t; + + return { + c() { + t = text(t_value); + }, + m(target, anchor) { + insert(target, t, anchor); + }, + p(ctx, dirty) { + if (dirty[0] & /*currentEmojisWithCategories, currentSkinTone*/ 33024 && t_value !== (t_value = /*unicodeWithSkin*/ ctx[27](/*emoji*/ ctx[64], /*currentSkinTone*/ ctx[8]) + "")) set_data(t, t_value); + }, + d(detaching) { + if (detaching) detach(t); + } + }; +} + +// (89:53) {#each emojiWithCategory.emojis as emoji, i (emoji.id)} +function create_each_block_2(key_1, ctx) { + let button; + let button_role_value; + let button_aria_selected_value; + let button_aria_label_value; + let button_title_value; + let button_class_value; + let button_id_value; + + function select_block_type(ctx, dirty) { + if (/*emoji*/ ctx[64].unicode) return create_if_block_1; + return create_else_block_1; + } + + let current_block_type = select_block_type(ctx); + let if_block = current_block_type(ctx); + + return { + key: key_1, + first: null, + c() { + button = element("button"); + if_block.c(); + attr(button, "role", button_role_value = /*searchMode*/ ctx[4] ? 'option' : 'menuitem'); + + attr(button, "aria-selected", button_aria_selected_value = /*searchMode*/ ctx[4] + ? /*i*/ ctx[66] == /*activeSearchItem*/ ctx[5] + : ''); + + attr(button, "aria-label", button_aria_label_value = /*labelWithSkin*/ ctx[28](/*emoji*/ ctx[64], /*currentSkinTone*/ ctx[8])); + attr(button, "title", button_title_value = /*emoji*/ ctx[64].title); + + attr(button, "class", button_class_value = "emoji " + (/*searchMode*/ ctx[4] && /*i*/ ctx[66] === /*activeSearchItem*/ ctx[5] + ? 'active' + : '')); + + attr(button, "id", button_id_value = "emo-" + /*emoji*/ ctx[64].id); + this.first = button; + }, + m(target, anchor) { + insert(target, button, anchor); + if_block.m(button, null); + }, + p(new_ctx, dirty) { + ctx = new_ctx; + + if (current_block_type === (current_block_type = select_block_type(ctx)) && if_block) { + if_block.p(ctx, dirty); + } else { + if_block.d(1); + if_block = current_block_type(ctx); + + if (if_block) { + if_block.c(); + if_block.m(button, null); + } + } + + if (dirty[0] & /*searchMode*/ 16 && button_role_value !== (button_role_value = /*searchMode*/ ctx[4] ? 'option' : 'menuitem')) { + attr(button, "role", button_role_value); + } + + if (dirty[0] & /*searchMode, currentEmojisWithCategories, activeSearchItem*/ 32816 && button_aria_selected_value !== (button_aria_selected_value = /*searchMode*/ ctx[4] + ? /*i*/ ctx[66] == /*activeSearchItem*/ ctx[5] + : '')) { + attr(button, "aria-selected", button_aria_selected_value); + } + + if (dirty[0] & /*currentEmojisWithCategories, currentSkinTone*/ 33024 && button_aria_label_value !== (button_aria_label_value = /*labelWithSkin*/ ctx[28](/*emoji*/ ctx[64], /*currentSkinTone*/ ctx[8]))) { + attr(button, "aria-label", button_aria_label_value); + } + + if (dirty[0] & /*currentEmojisWithCategories*/ 32768 && button_title_value !== (button_title_value = /*emoji*/ ctx[64].title)) { + attr(button, "title", button_title_value); + } + + if (dirty[0] & /*searchMode, currentEmojisWithCategories, activeSearchItem*/ 32816 && button_class_value !== (button_class_value = "emoji " + (/*searchMode*/ ctx[4] && /*i*/ ctx[66] === /*activeSearchItem*/ ctx[5] + ? 'active' + : ''))) { + attr(button, "class", button_class_value); + } + + if (dirty[0] & /*currentEmojisWithCategories*/ 32768 && button_id_value !== (button_id_value = "emo-" + /*emoji*/ ctx[64].id)) { + attr(button, "id", button_id_value); + } + }, + d(detaching) { + if (detaching) detach(button); + if_block.d(); + } + }; +} + +// (70:36) {#each currentEmojisWithCategories as emojiWithCategory, i (emojiWithCategory.category)} +function create_each_block_1(key_1, ctx) { + let div0; + + let t_value = (/*searchMode*/ ctx[4] + ? /*i18n*/ ctx[0].searchResultsLabel + : /*emojiWithCategory*/ ctx[67].category + ? /*emojiWithCategory*/ ctx[67].category + : /*currentEmojisWithCategories*/ ctx[15].length > 1 + ? /*i18n*/ ctx[0].categories.custom + : /*i18n*/ ctx[0].categories[/*currentGroup*/ ctx[13].name]) + ""; + + let t; + let div0_id_value; + let div0_class_value; + let div1; + let each_blocks = []; + let each_1_lookup = new Map_1(); + let div1_role_value; + let div1_aria_labelledby_value; + let div1_id_value; + let each_value_2 = /*emojiWithCategory*/ ctx[67].emojis; + const get_key = ctx => /*emoji*/ ctx[64].id; + + for (let i = 0; i < each_value_2.length; i += 1) { + let child_ctx = get_each_context_2(ctx, each_value_2, i); + let key = get_key(child_ctx); + each_1_lookup.set(key, each_blocks[i] = create_each_block_2(key, child_ctx)); + } + + return { + key: key_1, + first: null, + c() { + div0 = element("div"); + t = text(t_value); + div1 = element("div"); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].c(); + } + + attr(div0, "id", div0_id_value = "menu-label-" + /*i*/ ctx[66]); + + attr(div0, "class", div0_class_value = "category " + (/*currentEmojisWithCategories*/ ctx[15].length === 1 && /*currentEmojisWithCategories*/ ctx[15][0].category === '' + ? 'gone' + : '')); + + attr(div0, "aria-hidden", "true"); + attr(div1, "class", "emoji-menu"); + attr(div1, "role", div1_role_value = /*searchMode*/ ctx[4] ? 'listbox' : 'menu'); + attr(div1, "aria-labelledby", div1_aria_labelledby_value = "menu-label-" + /*i*/ ctx[66]); + attr(div1, "id", div1_id_value = /*searchMode*/ ctx[4] ? 'search-results' : ''); + this.first = div0; + }, + m(target, anchor) { + insert(target, div0, anchor); + append(div0, t); + insert(target, div1, anchor); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].m(div1, null); + } + }, + p(new_ctx, dirty) { + ctx = new_ctx; + + if (dirty[0] & /*searchMode, i18n, currentEmojisWithCategories, currentGroup*/ 40977 && t_value !== (t_value = (/*searchMode*/ ctx[4] + ? /*i18n*/ ctx[0].searchResultsLabel + : /*emojiWithCategory*/ ctx[67].category + ? /*emojiWithCategory*/ ctx[67].category + : /*currentEmojisWithCategories*/ ctx[15].length > 1 + ? /*i18n*/ ctx[0].categories.custom + : /*i18n*/ ctx[0].categories[/*currentGroup*/ ctx[13].name]) + "")) set_data(t, t_value); + + if (dirty[0] & /*currentEmojisWithCategories*/ 32768 && div0_id_value !== (div0_id_value = "menu-label-" + /*i*/ ctx[66])) { + attr(div0, "id", div0_id_value); + } + + if (dirty[0] & /*currentEmojisWithCategories*/ 32768 && div0_class_value !== (div0_class_value = "category " + (/*currentEmojisWithCategories*/ ctx[15].length === 1 && /*currentEmojisWithCategories*/ ctx[15][0].category === '' + ? 'gone' + : ''))) { + attr(div0, "class", div0_class_value); + } + + if (dirty[0] & /*searchMode, currentEmojisWithCategories, activeSearchItem, labelWithSkin, currentSkinTone, unicodeWithSkin*/ 402686256) { + each_value_2 = /*emojiWithCategory*/ ctx[67].emojis; + each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value_2, each_1_lookup, div1, destroy_block, create_each_block_2, null, get_each_context_2); + } + + if (dirty[0] & /*searchMode*/ 16 && div1_role_value !== (div1_role_value = /*searchMode*/ ctx[4] ? 'listbox' : 'menu')) { + attr(div1, "role", div1_role_value); + } + + if (dirty[0] & /*currentEmojisWithCategories*/ 32768 && div1_aria_labelledby_value !== (div1_aria_labelledby_value = "menu-label-" + /*i*/ ctx[66])) { + attr(div1, "aria-labelledby", div1_aria_labelledby_value); + } + + if (dirty[0] & /*searchMode*/ 16 && div1_id_value !== (div1_id_value = /*searchMode*/ ctx[4] ? 'search-results' : '')) { + attr(div1, "id", div1_id_value); + } + }, + d(detaching) { + if (detaching) detach(div0); + if (detaching) detach(div1); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].d(); + } + } + }; +} + +// (103:94) {:else} +function create_else_block(ctx) { + let img; + let img_src_value; + + return { + c() { + img = element("img"); + attr(img, "class", "custom-emoji"); + if (!src_url_equal(img.src, img_src_value = /*emoji*/ ctx[64].url)) attr(img, "src", img_src_value); + attr(img, "alt", ""); + attr(img, "loading", "lazy"); + }, + m(target, anchor) { + insert(target, img, anchor); + }, + p(ctx, dirty) { + if (dirty[0] & /*currentFavorites*/ 1024 && !src_url_equal(img.src, img_src_value = /*emoji*/ ctx[64].url)) { + attr(img, "src", img_src_value); + } + }, + d(detaching) { + if (detaching) detach(img); + } + }; +} + +// (103:34) {#if emoji.unicode} +function create_if_block(ctx) { + let t_value = /*unicodeWithSkin*/ ctx[27](/*emoji*/ ctx[64], /*currentSkinTone*/ ctx[8]) + ""; + let t; + + return { + c() { + t = text(t_value); + }, + m(target, anchor) { + insert(target, t, anchor); + }, + p(ctx, dirty) { + if (dirty[0] & /*currentFavorites, currentSkinTone*/ 1280 && t_value !== (t_value = /*unicodeWithSkin*/ ctx[27](/*emoji*/ ctx[64], /*currentSkinTone*/ ctx[8]) + "")) set_data(t, t_value); + }, + d(detaching) { + if (detaching) detach(t); + } + }; +} + +// (99:102) {#each currentFavorites as emoji, i (emoji.id)} +function create_each_block(key_1, ctx) { + let button; + let button_aria_label_value; + let button_title_value; + let button_id_value; + + function select_block_type_1(ctx, dirty) { + if (/*emoji*/ ctx[64].unicode) return create_if_block; + return create_else_block; + } + + let current_block_type = select_block_type_1(ctx); + let if_block = current_block_type(ctx); + + return { + key: key_1, + first: null, + c() { + button = element("button"); + if_block.c(); + attr(button, "role", "menuitem"); + attr(button, "aria-label", button_aria_label_value = /*labelWithSkin*/ ctx[28](/*emoji*/ ctx[64], /*currentSkinTone*/ ctx[8])); + attr(button, "title", button_title_value = /*emoji*/ ctx[64].title); + attr(button, "class", "emoji"); + attr(button, "id", button_id_value = "fav-" + /*emoji*/ ctx[64].id); + this.first = button; + }, + m(target, anchor) { + insert(target, button, anchor); + if_block.m(button, null); + }, + p(new_ctx, dirty) { + ctx = new_ctx; + + if (current_block_type === (current_block_type = select_block_type_1(ctx)) && if_block) { + if_block.p(ctx, dirty); + } else { + if_block.d(1); + if_block = current_block_type(ctx); + + if (if_block) { + if_block.c(); + if_block.m(button, null); + } + } + + if (dirty[0] & /*currentFavorites, currentSkinTone*/ 1280 && button_aria_label_value !== (button_aria_label_value = /*labelWithSkin*/ ctx[28](/*emoji*/ ctx[64], /*currentSkinTone*/ ctx[8]))) { + attr(button, "aria-label", button_aria_label_value); + } + + if (dirty[0] & /*currentFavorites*/ 1024 && button_title_value !== (button_title_value = /*emoji*/ ctx[64].title)) { + attr(button, "title", button_title_value); + } + + if (dirty[0] & /*currentFavorites*/ 1024 && button_id_value !== (button_id_value = "fav-" + /*emoji*/ ctx[64].id)) { + attr(button, "id", button_id_value); + } + }, + d(detaching) { + if (detaching) detach(button); + if_block.d(); + } + }; +} + +function create_fragment(ctx) { + let section; + let div0; + let div4; + let div1; + let input; + let input_placeholder_value; + let input_aria_expanded_value; + let input_aria_activedescendant_value; + let label; + let t0_value = /*i18n*/ ctx[0].searchLabel + ""; + let t0; + let span0; + let t1_value = /*i18n*/ ctx[0].searchDescription + ""; + let t1; + let div2; + let button0; + let t2; + let button0_class_value; + let div2_class_value; + let span1; + let t3_value = /*i18n*/ ctx[0].skinToneDescription + ""; + let t3; + let div3; + let each_blocks_3 = []; + let each0_lookup = new Map_1(); + let div3_class_value; + let div3_aria_label_value; + let div3_aria_activedescendant_value; + let div3_aria_hidden_value; + let div5; + let each_blocks_2 = []; + let each1_lookup = new Map_1(); + let div5_aria_label_value; + let div7; + let div6; + let div8; + let t4; + let div8_class_value; + let div10; + let div9; + let each_blocks_1 = []; + let each2_lookup = new Map_1(); + let div10_class_value; + let div10_role_value; + let div10_aria_label_value; + let div10_id_value; + let div11; + let each_blocks = []; + let each3_lookup = new Map_1(); + let div11_class_value; + let div11_aria_label_value; + let button1; + let section_aria_label_value; + let mounted; + let dispose; + let each_value_4 = /*skinTones*/ ctx[9]; + const get_key = ctx => /*skinTone*/ ctx[73]; + + for (let i = 0; i < each_value_4.length; i += 1) { + let child_ctx = get_each_context_4(ctx, each_value_4, i); + let key = get_key(child_ctx); + each0_lookup.set(key, each_blocks_3[i] = create_each_block_4(key, child_ctx)); + } + + let each_value_3 = /*groups*/ ctx[12]; + const get_key_1 = ctx => /*group*/ ctx[70].id; + + for (let i = 0; i < each_value_3.length; i += 1) { + let child_ctx = get_each_context_3(ctx, each_value_3, i); + let key = get_key_1(child_ctx); + each1_lookup.set(key, each_blocks_2[i] = create_each_block_3(key, child_ctx)); + } + + let each_value_1 = /*currentEmojisWithCategories*/ ctx[15]; + const get_key_2 = ctx => /*emojiWithCategory*/ ctx[67].category; + + for (let i = 0; i < each_value_1.length; i += 1) { + let child_ctx = get_each_context_1(ctx, each_value_1, i); + let key = get_key_2(child_ctx); + each2_lookup.set(key, each_blocks_1[i] = create_each_block_1(key, child_ctx)); + } + + let each_value = /*currentFavorites*/ ctx[10]; + const get_key_3 = ctx => /*emoji*/ ctx[64].id; + + for (let i = 0; i < each_value.length; i += 1) { + let child_ctx = get_each_context(ctx, each_value, i); + let key = get_key_3(child_ctx); + each3_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx)); + } + + return { + c() { + section = element("section"); + div0 = element("div"); + div4 = element("div"); + div1 = element("div"); + input = element("input"); + label = element("label"); + t0 = text(t0_value); + span0 = element("span"); + t1 = text(t1_value); + div2 = element("div"); + button0 = element("button"); + t2 = text(/*skinToneButtonText*/ ctx[21]); + span1 = element("span"); + t3 = text(t3_value); + div3 = element("div"); + + for (let i = 0; i < each_blocks_3.length; i += 1) { + each_blocks_3[i].c(); + } + + div5 = element("div"); + + for (let i = 0; i < each_blocks_2.length; i += 1) { + each_blocks_2[i].c(); + } + + div7 = element("div"); + div6 = element("div"); + div8 = element("div"); + t4 = text(/*message*/ ctx[18]); + div10 = element("div"); + div9 = element("div"); + + for (let i = 0; i < each_blocks_1.length; i += 1) { + each_blocks_1[i].c(); + } + + div11 = element("div"); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].c(); + } + + button1 = element("button"); + button1.textContent = "π"; + attr(div0, "class", "pad-top"); + attr(input, "id", "search"); + attr(input, "class", "search"); + attr(input, "type", "search"); + attr(input, "role", "combobox"); + attr(input, "enterkeyhint", "search"); + attr(input, "placeholder", input_placeholder_value = /*i18n*/ ctx[0].searchLabel); + attr(input, "autocapitalize", "none"); + attr(input, "autocomplete", "off"); + attr(input, "spellcheck", "true"); + attr(input, "aria-expanded", input_aria_expanded_value = !!(/*searchMode*/ ctx[4] && /*currentEmojis*/ ctx[1].length)); + attr(input, "aria-controls", "search-results"); + attr(input, "aria-describedby", "search-description"); + attr(input, "aria-autocomplete", "list"); + + attr(input, "aria-activedescendant", input_aria_activedescendant_value = /*activeSearchItemId*/ ctx[26] + ? `emo-${/*activeSearchItemId*/ ctx[26]}` + : ''); + + attr(label, "class", "sr-only"); + attr(label, "for", "search"); + attr(span0, "id", "search-description"); + attr(span0, "class", "sr-only"); + attr(div1, "class", "search-wrapper"); + attr(button0, "id", "skintone-button"); + attr(button0, "class", button0_class_value = "emoji " + (/*skinTonePickerExpanded*/ ctx[6] ? 'hide-focus' : '')); + attr(button0, "aria-label", /*skinToneButtonLabel*/ ctx[23]); + attr(button0, "title", /*skinToneButtonLabel*/ ctx[23]); + attr(button0, "aria-describedby", "skintone-description"); + attr(button0, "aria-haspopup", "listbox"); + attr(button0, "aria-expanded", /*skinTonePickerExpanded*/ ctx[6]); + attr(button0, "aria-controls", "skintone-list"); + + attr(div2, "class", div2_class_value = "skintone-button-wrapper " + (/*skinTonePickerExpandedAfterAnimation*/ ctx[19] + ? 'expanded' + : '')); + + attr(span1, "id", "skintone-description"); + attr(span1, "class", "sr-only"); + attr(div3, "id", "skintone-list"); + + attr(div3, "class", div3_class_value = "skintone-list " + (/*skinTonePickerExpanded*/ ctx[6] + ? '' + : 'hidden no-animate')); + + set_style(div3, "transform", "translateY(" + (/*skinTonePickerExpanded*/ ctx[6] + ? 0 + : 'calc(-1 * var(--num-skintones) * var(--total-emoji-size))') + ")"); + + attr(div3, "role", "listbox"); + attr(div3, "aria-label", div3_aria_label_value = /*i18n*/ ctx[0].skinTonesLabel); + attr(div3, "aria-activedescendant", div3_aria_activedescendant_value = "skintone-" + /*activeSkinTone*/ ctx[20]); + attr(div3, "aria-hidden", div3_aria_hidden_value = !/*skinTonePickerExpanded*/ ctx[6]); + attr(div4, "class", "search-row"); + attr(div5, "class", "nav"); + attr(div5, "role", "tablist"); + set_style(div5, "grid-template-columns", "repeat(" + /*groups*/ ctx[12].length + ", 1fr)"); + attr(div5, "aria-label", div5_aria_label_value = /*i18n*/ ctx[0].categoriesLabel); + attr(div6, "class", "indicator"); + set_style(div6, "transform", "translateX(" + (/*isRtl*/ ctx[24] ? -1 : 1) * /*currentGroupIndex*/ ctx[11] * 100 + "%)"); + attr(div7, "class", "indicator-wrapper"); + attr(div8, "class", div8_class_value = "message " + (/*message*/ ctx[18] ? '' : 'gone')); + attr(div8, "role", "alert"); + attr(div8, "aria-live", "polite"); + + attr(div10, "class", div10_class_value = "tabpanel " + (!/*databaseLoaded*/ ctx[14] || /*message*/ ctx[18] + ? 'gone' + : '')); + + attr(div10, "role", div10_role_value = /*searchMode*/ ctx[4] ? 'region' : 'tabpanel'); + + attr(div10, "aria-label", div10_aria_label_value = /*searchMode*/ ctx[4] + ? /*i18n*/ ctx[0].searchResultsLabel + : /*i18n*/ ctx[0].categories[/*currentGroup*/ ctx[13].name]); + + attr(div10, "id", div10_id_value = /*searchMode*/ ctx[4] + ? '' + : `tab-${/*currentGroup*/ ctx[13].id}`); + + attr(div10, "tabindex", "0"); + attr(div11, "class", div11_class_value = "favorites emoji-menu " + (/*message*/ ctx[18] ? 'gone' : '')); + attr(div11, "role", "menu"); + attr(div11, "aria-label", div11_aria_label_value = /*i18n*/ ctx[0].favoritesLabel); + set_style(div11, "padding-inline-end", /*scrollbarWidth*/ ctx[25] + "px"); + attr(button1, "aria-hidden", "true"); + attr(button1, "tabindex", "-1"); + attr(button1, "class", "abs-pos hidden emoji"); + attr(section, "class", "picker"); + attr(section, "aria-label", section_aria_label_value = /*i18n*/ ctx[0].regionLabel); + attr(section, "style", /*pickerStyle*/ ctx[22]); + }, + m(target, anchor) { + insert(target, section, anchor); + append(section, div0); + append(section, div4); + append(div4, div1); + append(div1, input); + set_input_value(input, /*rawSearchText*/ ctx[2]); + append(div1, label); + append(label, t0); + append(div1, span0); + append(span0, t1); + append(div4, div2); + append(div2, button0); + append(button0, t2); + append(div4, span1); + append(span1, t3); + append(div4, div3); + + for (let i = 0; i < each_blocks_3.length; i += 1) { + each_blocks_3[i].m(div3, null); + } + + /*div3_binding*/ ctx[49](div3); + append(section, div5); + + for (let i = 0; i < each_blocks_2.length; i += 1) { + each_blocks_2[i].m(div5, null); + } + + append(section, div7); + append(div7, div6); + append(section, div8); + append(div8, t4); + append(section, div10); + append(div10, div9); + + for (let i = 0; i < each_blocks_1.length; i += 1) { + each_blocks_1[i].m(div9, null); + } + + /*div10_binding*/ ctx[51](div10); + append(section, div11); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].m(div11, null); + } + + append(section, button1); + /*button1_binding*/ ctx[52](button1); + /*section_binding*/ ctx[53](section); + + if (!mounted) { + dispose = [ + listen(input, "input", /*input_input_handler*/ ctx[48]), + listen(input, "keydown", /*onSearchKeydown*/ ctx[30]), + listen(button0, "click", /*onClickSkinToneButton*/ ctx[35]), + listen(div3, "focusout", /*onSkinToneOptionsFocusOut*/ ctx[38]), + listen(div3, "click", /*onSkinToneOptionsClick*/ ctx[34]), + listen(div3, "keydown", /*onSkinToneOptionsKeydown*/ ctx[36]), + listen(div3, "keyup", /*onSkinToneOptionsKeyup*/ ctx[37]), + listen(div5, "keydown", /*onNavKeydown*/ ctx[32]), + action_destroyer(/*calculateEmojiGridStyle*/ ctx[29].call(null, div9)), + listen(div10, "click", /*onEmojiClick*/ ctx[33]), + listen(div11, "click", /*onEmojiClick*/ ctx[33]) + ]; + + mounted = true; + } + }, + p(ctx, dirty) { + if (dirty[0] & /*i18n*/ 1 && input_placeholder_value !== (input_placeholder_value = /*i18n*/ ctx[0].searchLabel)) { + attr(input, "placeholder", input_placeholder_value); + } + + if (dirty[0] & /*searchMode, currentEmojis*/ 18 && input_aria_expanded_value !== (input_aria_expanded_value = !!(/*searchMode*/ ctx[4] && /*currentEmojis*/ ctx[1].length))) { + attr(input, "aria-expanded", input_aria_expanded_value); + } + + if (dirty[0] & /*activeSearchItemId*/ 67108864 && input_aria_activedescendant_value !== (input_aria_activedescendant_value = /*activeSearchItemId*/ ctx[26] + ? `emo-${/*activeSearchItemId*/ ctx[26]}` + : '')) { + attr(input, "aria-activedescendant", input_aria_activedescendant_value); + } + + if (dirty[0] & /*rawSearchText*/ 4) { + set_input_value(input, /*rawSearchText*/ ctx[2]); + } + + if (dirty[0] & /*i18n*/ 1 && t0_value !== (t0_value = /*i18n*/ ctx[0].searchLabel + "")) set_data(t0, t0_value); + if (dirty[0] & /*i18n*/ 1 && t1_value !== (t1_value = /*i18n*/ ctx[0].searchDescription + "")) set_data(t1, t1_value); + if (dirty[0] & /*skinToneButtonText*/ 2097152) set_data(t2, /*skinToneButtonText*/ ctx[21]); + + if (dirty[0] & /*skinTonePickerExpanded*/ 64 && button0_class_value !== (button0_class_value = "emoji " + (/*skinTonePickerExpanded*/ ctx[6] ? 'hide-focus' : ''))) { + attr(button0, "class", button0_class_value); + } + + if (dirty[0] & /*skinToneButtonLabel*/ 8388608) { + attr(button0, "aria-label", /*skinToneButtonLabel*/ ctx[23]); + } + + if (dirty[0] & /*skinToneButtonLabel*/ 8388608) { + attr(button0, "title", /*skinToneButtonLabel*/ ctx[23]); + } + + if (dirty[0] & /*skinTonePickerExpanded*/ 64) { + attr(button0, "aria-expanded", /*skinTonePickerExpanded*/ ctx[6]); + } + + if (dirty[0] & /*skinTonePickerExpandedAfterAnimation*/ 524288 && div2_class_value !== (div2_class_value = "skintone-button-wrapper " + (/*skinTonePickerExpandedAfterAnimation*/ ctx[19] + ? 'expanded' + : ''))) { + attr(div2, "class", div2_class_value); + } + + if (dirty[0] & /*i18n*/ 1 && t3_value !== (t3_value = /*i18n*/ ctx[0].skinToneDescription + "")) set_data(t3, t3_value); + + if (dirty[0] & /*skinTones, activeSkinTone, i18n*/ 1049089) { + each_value_4 = /*skinTones*/ ctx[9]; + each_blocks_3 = update_keyed_each(each_blocks_3, dirty, get_key, 1, ctx, each_value_4, each0_lookup, div3, destroy_block, create_each_block_4, null, get_each_context_4); + } + + if (dirty[0] & /*skinTonePickerExpanded*/ 64 && div3_class_value !== (div3_class_value = "skintone-list " + (/*skinTonePickerExpanded*/ ctx[6] + ? '' + : 'hidden no-animate'))) { + attr(div3, "class", div3_class_value); + } + + if (dirty[0] & /*skinTonePickerExpanded*/ 64) { + set_style(div3, "transform", "translateY(" + (/*skinTonePickerExpanded*/ ctx[6] + ? 0 + : 'calc(-1 * var(--num-skintones) * var(--total-emoji-size))') + ")"); + } + + if (dirty[0] & /*i18n*/ 1 && div3_aria_label_value !== (div3_aria_label_value = /*i18n*/ ctx[0].skinTonesLabel)) { + attr(div3, "aria-label", div3_aria_label_value); + } + + if (dirty[0] & /*activeSkinTone*/ 1048576 && div3_aria_activedescendant_value !== (div3_aria_activedescendant_value = "skintone-" + /*activeSkinTone*/ ctx[20])) { + attr(div3, "aria-activedescendant", div3_aria_activedescendant_value); + } + + if (dirty[0] & /*skinTonePickerExpanded*/ 64 && div3_aria_hidden_value !== (div3_aria_hidden_value = !/*skinTonePickerExpanded*/ ctx[6])) { + attr(div3, "aria-hidden", div3_aria_hidden_value); + } + + if (dirty[0] & /*groups, i18n, searchMode, currentGroup*/ 12305 | dirty[1] & /*onNavClick*/ 1) { + each_value_3 = /*groups*/ ctx[12]; + each_blocks_2 = update_keyed_each(each_blocks_2, dirty, get_key_1, 1, ctx, each_value_3, each1_lookup, div5, destroy_block, create_each_block_3, null, get_each_context_3); + } + + if (dirty[0] & /*groups*/ 4096) { + set_style(div5, "grid-template-columns", "repeat(" + /*groups*/ ctx[12].length + ", 1fr)"); + } + + if (dirty[0] & /*i18n*/ 1 && div5_aria_label_value !== (div5_aria_label_value = /*i18n*/ ctx[0].categoriesLabel)) { + attr(div5, "aria-label", div5_aria_label_value); + } + + if (dirty[0] & /*isRtl, currentGroupIndex*/ 16779264) { + set_style(div6, "transform", "translateX(" + (/*isRtl*/ ctx[24] ? -1 : 1) * /*currentGroupIndex*/ ctx[11] * 100 + "%)"); + } + + if (dirty[0] & /*message*/ 262144) set_data(t4, /*message*/ ctx[18]); + + if (dirty[0] & /*message*/ 262144 && div8_class_value !== (div8_class_value = "message " + (/*message*/ ctx[18] ? '' : 'gone'))) { + attr(div8, "class", div8_class_value); + } + + if (dirty[0] & /*searchMode, currentEmojisWithCategories, activeSearchItem, labelWithSkin, currentSkinTone, unicodeWithSkin, i18n, currentGroup*/ 402694449) { + each_value_1 = /*currentEmojisWithCategories*/ ctx[15]; + each_blocks_1 = update_keyed_each(each_blocks_1, dirty, get_key_2, 1, ctx, each_value_1, each2_lookup, div9, destroy_block, create_each_block_1, null, get_each_context_1); + } + + if (dirty[0] & /*databaseLoaded, message*/ 278528 && div10_class_value !== (div10_class_value = "tabpanel " + (!/*databaseLoaded*/ ctx[14] || /*message*/ ctx[18] + ? 'gone' + : ''))) { + attr(div10, "class", div10_class_value); + } + + if (dirty[0] & /*searchMode*/ 16 && div10_role_value !== (div10_role_value = /*searchMode*/ ctx[4] ? 'region' : 'tabpanel')) { + attr(div10, "role", div10_role_value); + } + + if (dirty[0] & /*searchMode, i18n, currentGroup*/ 8209 && div10_aria_label_value !== (div10_aria_label_value = /*searchMode*/ ctx[4] + ? /*i18n*/ ctx[0].searchResultsLabel + : /*i18n*/ ctx[0].categories[/*currentGroup*/ ctx[13].name])) { + attr(div10, "aria-label", div10_aria_label_value); + } + + if (dirty[0] & /*searchMode, currentGroup*/ 8208 && div10_id_value !== (div10_id_value = /*searchMode*/ ctx[4] + ? '' + : `tab-${/*currentGroup*/ ctx[13].id}`)) { + attr(div10, "id", div10_id_value); + } + + if (dirty[0] & /*labelWithSkin, currentFavorites, currentSkinTone, unicodeWithSkin*/ 402654464) { + each_value = /*currentFavorites*/ ctx[10]; + each_blocks = update_keyed_each(each_blocks, dirty, get_key_3, 1, ctx, each_value, each3_lookup, div11, destroy_block, create_each_block, null, get_each_context); + } + + if (dirty[0] & /*message*/ 262144 && div11_class_value !== (div11_class_value = "favorites emoji-menu " + (/*message*/ ctx[18] ? 'gone' : ''))) { + attr(div11, "class", div11_class_value); + } + + if (dirty[0] & /*i18n*/ 1 && div11_aria_label_value !== (div11_aria_label_value = /*i18n*/ ctx[0].favoritesLabel)) { + attr(div11, "aria-label", div11_aria_label_value); + } + + if (dirty[0] & /*scrollbarWidth*/ 33554432) { + set_style(div11, "padding-inline-end", /*scrollbarWidth*/ ctx[25] + "px"); + } + + if (dirty[0] & /*i18n*/ 1 && section_aria_label_value !== (section_aria_label_value = /*i18n*/ ctx[0].regionLabel)) { + attr(section, "aria-label", section_aria_label_value); + } + + if (dirty[0] & /*pickerStyle*/ 4194304) { + attr(section, "style", /*pickerStyle*/ ctx[22]); + } + }, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(section); + + for (let i = 0; i < each_blocks_3.length; i += 1) { + each_blocks_3[i].d(); + } + + /*div3_binding*/ ctx[49](null); + + for (let i = 0; i < each_blocks_2.length; i += 1) { + each_blocks_2[i].d(); + } + + for (let i = 0; i < each_blocks_1.length; i += 1) { + each_blocks_1[i].d(); + } + + /*div10_binding*/ ctx[51](null); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].d(); + } + + /*button1_binding*/ ctx[52](null); + /*section_binding*/ ctx[53](null); + mounted = false; + run_all(dispose); + } + }; +} + +function instance($$self, $$props, $$invalidate) { + let { skinToneEmoji } = $$props; + let { i18n } = $$props; + let { database } = $$props; + let { customEmoji } = $$props; + let { customCategorySorting } = $$props; + let { emojiVersion } = $$props; + + // private + let initialLoad = true; + + let currentEmojis = []; + let currentEmojisWithCategories = []; // eslint-disable-line no-unused-vars + let rawSearchText = ''; + let searchText = ''; + let rootElement; + let baselineEmoji; + let tabpanelElement; + let searchMode = false; // eslint-disable-line no-unused-vars + let activeSearchItem = -1; + let message; // eslint-disable-line no-unused-vars + let skinTonePickerExpanded = false; + let skinTonePickerExpandedAfterAnimation = false; // eslint-disable-line no-unused-vars + let skinToneDropdown; + let currentSkinTone = 0; + let activeSkinTone = 0; + let skinToneButtonText; // eslint-disable-line no-unused-vars + let pickerStyle; // eslint-disable-line no-unused-vars + let skinToneButtonLabel = ''; // eslint-disable-line no-unused-vars + let skinTones = []; + let currentFavorites = []; // eslint-disable-line no-unused-vars + let defaultFavoriteEmojis; + let numColumns = DEFAULT_NUM_COLUMNS; + let isRtl = false; // eslint-disable-line no-unused-vars + let scrollbarWidth = 0; // eslint-disable-line no-unused-vars + let currentGroupIndex = 0; + let groups$1 = groups; + let currentGroup; + let databaseLoaded = false; // eslint-disable-line no-unused-vars + let activeSearchItemId; // eslint-disable-line no-unused-vars + + // + // Utils/helpers + // + const focus = id => { + rootElement.getRootNode().getElementById(id).focus(); + }; + + // fire a custom event that crosses the shadow boundary + const fireEvent = (name, detail) => { + rootElement.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true })); + }; + + // eslint-disable-next-line no-unused-vars + const unicodeWithSkin = (emoji, currentSkinTone) => currentSkinTone && emoji.skins && emoji.skins[currentSkinTone] || emoji.unicode; + + // eslint-disable-next-line no-unused-vars + const labelWithSkin = (emoji, currentSkinTone) => uniq([ + emoji.name || unicodeWithSkin(emoji, currentSkinTone), + ...emoji.shortcodes || [] + ]).join(', '); + + // Detect a skintone option button + const isSkinToneOption = element => (/^skintone-/).test(element.id); + + // + // Determine the emoji support level (in requestIdleCallback) + // + onMount(() => { + if (!emojiVersion) { + detectEmojiSupportLevel().then(level => { + // Can't actually test emoji support in Jest/JSDom, emoji never render in color in Cairo + /* istanbul ignore next */ + if (!level) { + $$invalidate(18, message = i18n.emojiUnsupportedMessage); + } + }); + } + }); + + // + // Calculate the width of the emoji grid. This serves two purposes: + // 1) Re-calculate the --num-columns var because it may have changed + // 2) Re-calculate the scrollbar width because it may have changed + // (i.e. because the number of items changed) + // 3) Re-calculate whether we're in RTL mode or not. + // + // The benefit of doing this in one place is to align with rAF/ResizeObserver + // and do all the calculations in one go. RTL vs LTR is not strictly width-related, + // but since we're already reading the style here, and since it's already aligned with + // the rAF loop, this is the most appropriate place to do it perf-wise. + // + // eslint-disable-next-line no-unused-vars + function calculateEmojiGridStyle(node) { + return calculateWidth(node, width => { + /* istanbul ignore next */ + if ("production" !== 'test') { + // jsdom throws errors for this kind of fancy stuff + // read all the style/layout calculations we need to make + const style = getComputedStyle(rootElement); + + const newNumColumns = parseInt(style.getPropertyValue('--num-columns'), 10); + const newIsRtl = style.getPropertyValue('direction') === 'rtl'; + const parentWidth = node.parentElement.getBoundingClientRect().width; + const newScrollbarWidth = parentWidth - width; + + // write to Svelte variables + $$invalidate(47, numColumns = newNumColumns); + + $$invalidate(25, scrollbarWidth = newScrollbarWidth); // eslint-disable-line no-unused-vars + $$invalidate(24, isRtl = newIsRtl); // eslint-disable-line no-unused-vars + } + }); + } + + function checkZwjSupportAndUpdate(zwjEmojisToCheck) { + const rootNode = rootElement.getRootNode(); + const emojiToDomNode = emoji => rootNode.getElementById(`emo-${emoji.id}`); + checkZwjSupport(zwjEmojisToCheck, baselineEmoji, emojiToDomNode); + + // force update + $$invalidate(1, currentEmojis = currentEmojis); // eslint-disable-line no-self-assign + } + + function isZwjSupported(emoji) { + return !emoji.unicode || !hasZwj(emoji) || supportedZwjEmojis.get(emoji.unicode); + } + + async function filterEmojisByVersion(emojis) { + const emojiSupportLevel = emojiVersion || await detectEmojiSupportLevel(); + + // !version corresponds to custom emoji + return emojis.filter(({ version }) => !version || version <= emojiSupportLevel); + } + + async function summarizeEmojis(emojis) { + return summarizeEmojisForUI(emojis, emojiVersion || await detectEmojiSupportLevel()); + } + + async function getEmojisByGroup(group) { + + // -1 is custom emoji + const emoji = group === -1 + ? customEmoji + : await database.getEmojiByGroup(group); + + return summarizeEmojis(await filterEmojisByVersion(emoji)); + } + + async function getEmojisBySearchQuery(query) { + return summarizeEmojis(await filterEmojisByVersion(await database.getEmojiBySearchQuery(query))); + } + + // eslint-disable-next-line no-unused-vars + function onSearchKeydown(event) { + if (!searchMode || !currentEmojis.length) { + return; + } + + const goToNextOrPrevious = previous => { + halt(event); + $$invalidate(5, activeSearchItem = incrementOrDecrement(previous, activeSearchItem, currentEmojis)); + }; + + switch (event.key) { + case 'ArrowDown': + return goToNextOrPrevious(false); + case 'ArrowUp': + return goToNextOrPrevious(true); + case 'Enter': + if (activeSearchItem !== -1) { + halt(event); + return clickEmoji(currentEmojis[activeSearchItem].id); + } else if (currentEmojis.length) { + $$invalidate(5, activeSearchItem = 0); + } + } + } + + // + // Handle user input on nav + // + // eslint-disable-next-line no-unused-vars + function onNavClick(group) { + $$invalidate(2, rawSearchText = ''); + $$invalidate(45, searchText = ''); + $$invalidate(5, activeSearchItem = -1); + $$invalidate(11, currentGroupIndex = groups$1.findIndex(_ => _.id === group.id)); + } + + // eslint-disable-next-line no-unused-vars + function onNavKeydown(event) { + const { target, key } = event; + + const doFocus = el => { + if (el) { + halt(event); + el.focus(); + } + }; + + switch (key) { + case 'ArrowLeft': + return doFocus(target.previousSibling); + case 'ArrowRight': + return doFocus(target.nextSibling); + case 'Home': + return doFocus(target.parentElement.firstChild); + case 'End': + return doFocus(target.parentElement.lastChild); + } + } + + // + // Handle user input on an emoji + // + async function clickEmoji(unicodeOrName) { + const emoji = await database.getEmojiByUnicodeOrName(unicodeOrName); + const emojiSummary = [...currentEmojis, ...currentFavorites].find(_ => _.id === unicodeOrName); + const skinTonedUnicode = emojiSummary.unicode && unicodeWithSkin(emojiSummary, currentSkinTone); + await database.incrementFavoriteEmojiCount(unicodeOrName); + + fireEvent('emoji-click', { + emoji, + skinTone: currentSkinTone, + ...skinTonedUnicode && { unicode: skinTonedUnicode }, + ...emojiSummary.name && { name: emojiSummary.name } + }); + } + + // eslint-disable-next-line no-unused-vars + async function onEmojiClick(event) { + const { target } = event; + + if (!target.classList.contains('emoji')) { + return; + } + + halt(event); + const id = target.id.substring(4); // replace 'emo-' or 'fav-' prefix + + /* no await */ + clickEmoji(id); + } + + // + // Handle user input on the skintone picker + // + // eslint-disable-next-line no-unused-vars + async function onSkinToneOptionsClick(event) { + const { target } = event; + + if (!isSkinToneOption(target)) { + return; + } + + halt(event); + const skinTone = parseInt(target.id.slice(9), 10); // remove 'skintone-' prefix + $$invalidate(8, currentSkinTone = skinTone); + $$invalidate(6, skinTonePickerExpanded = false); + focus('skintone-button'); + fireEvent('skin-tone-change', { skinTone }); + + /* no await */ + database.setPreferredSkinTone(skinTone); + } + + // eslint-disable-next-line no-unused-vars + async function onClickSkinToneButton(event) { + $$invalidate(6, skinTonePickerExpanded = !skinTonePickerExpanded); + $$invalidate(20, activeSkinTone = currentSkinTone); + + if (skinTonePickerExpanded) { + halt(event); + rAF(() => focus(`skintone-${activeSkinTone}`)); + } + } + + // eslint-disable-next-line no-unused-vars + function onSkinToneOptionsKeydown(event) { + if (!skinTonePickerExpanded) { + return; + } + + const changeActiveSkinTone = async nextSkinTone => { + halt(event); + $$invalidate(20, activeSkinTone = nextSkinTone); + await tick(); + focus(`skintone-${activeSkinTone}`); + }; + + switch (event.key) { + case 'ArrowUp': + return changeActiveSkinTone(incrementOrDecrement(true, activeSkinTone, skinTones)); + case 'ArrowDown': + return changeActiveSkinTone(incrementOrDecrement(false, activeSkinTone, skinTones)); + case 'Home': + return changeActiveSkinTone(0); + case 'End': + return changeActiveSkinTone(skinTones.length - 1); + case 'Enter': + // enter on keydown, space on keyup. this is just how browsers work for buttons + // https://lists.w3.org/Archives/Public/w3c-wai-ig/2019JanMar/0086.html + return onSkinToneOptionsClick(event); + case 'Escape': + halt(event); + $$invalidate(6, skinTonePickerExpanded = false); + return focus('skintone-button'); + } + } + + // eslint-disable-next-line no-unused-vars + function onSkinToneOptionsKeyup(event) { + if (!skinTonePickerExpanded) { + return; + } + + switch (event.key) { + case ' ': + // enter on keydown, space on keyup. this is just how browsers work for buttons + // https://lists.w3.org/Archives/Public/w3c-wai-ig/2019JanMar/0086.html + return onSkinToneOptionsClick(event); + } + } + + // eslint-disable-next-line no-unused-vars + async function onSkinToneOptionsFocusOut(event) { + // On blur outside of the skintone options, collapse the skintone picker. + // Except if focus is just moving to another skintone option, e.g. pressing up/down to change focus + const { relatedTarget } = event; + + if (!relatedTarget || !isSkinToneOption(relatedTarget)) { + $$invalidate(6, skinTonePickerExpanded = false); + } + } + + function input_input_handler() { + rawSearchText = this.value; + $$invalidate(2, rawSearchText); + } + + function div3_binding($$value) { + binding_callbacks[$$value ? 'unshift' : 'push'](() => { + skinToneDropdown = $$value; + $$invalidate(7, skinToneDropdown); + }); + } + + const click_handler = group => onNavClick(group); + + function div10_binding($$value) { + binding_callbacks[$$value ? 'unshift' : 'push'](() => { + tabpanelElement = $$value; + $$invalidate(3, tabpanelElement); + }); + } + + function button1_binding($$value) { + binding_callbacks[$$value ? 'unshift' : 'push'](() => { + baselineEmoji = $$value; + $$invalidate(17, baselineEmoji); + }); + } + + function section_binding($$value) { + binding_callbacks[$$value ? 'unshift' : 'push'](() => { + rootElement = $$value; + $$invalidate(16, rootElement); + }); + } + + $$self.$$set = $$props => { + if ('skinToneEmoji' in $$props) $$invalidate(40, skinToneEmoji = $$props.skinToneEmoji); + if ('i18n' in $$props) $$invalidate(0, i18n = $$props.i18n); + if ('database' in $$props) $$invalidate(39, database = $$props.database); + if ('customEmoji' in $$props) $$invalidate(41, customEmoji = $$props.customEmoji); + if ('customCategorySorting' in $$props) $$invalidate(42, customCategorySorting = $$props.customCategorySorting); + if ('emojiVersion' in $$props) $$invalidate(43, emojiVersion = $$props.emojiVersion); + }; + + $$self.$$.update = () => { + if ($$self.$$.dirty[1] & /*customEmoji, database*/ 1280) { + /* eslint-enable no-unused-vars */ + // + // Set or update the customEmoji + // + { + if (customEmoji && database) { + $$invalidate(39, database.customEmoji = customEmoji, database); + } + } + } + + if ($$self.$$.dirty[0] & /*i18n*/ 1 | $$self.$$.dirty[1] & /*database*/ 256) { + // + // Set or update the database object + // + { + // show a Loading message if it takes a long time, or show an error if there's a network/IDB error + async function handleDatabaseLoading() { + let showingLoadingMessage = false; + + const timeoutHandle = setTimeout( + () => { + showingLoadingMessage = true; + $$invalidate(18, message = i18n.loadingMessage); + }, + TIMEOUT_BEFORE_LOADING_MESSAGE + ); + + try { + await database.ready(); + $$invalidate(14, databaseLoaded = true); // eslint-disable-line no-unused-vars + } catch(err) { + console.error(err); + $$invalidate(18, message = i18n.networkErrorMessage); + } finally { + clearTimeout(timeoutHandle); + + if (showingLoadingMessage) { + // Seems safer than checking the i18n string, which may change + showingLoadingMessage = false; + + $$invalidate(18, message = ''); // eslint-disable-line no-unused-vars + } + } + } + + if (database) { + /* no await */ + handleDatabaseLoading(); + } + } + } + + if ($$self.$$.dirty[0] & /*groups, currentGroupIndex*/ 6144 | $$self.$$.dirty[1] & /*customEmoji*/ 1024) { + { + if (customEmoji && customEmoji.length) { + $$invalidate(12, groups$1 = [customGroup, ...groups]); + } else if (groups$1 !== groups) { + if (currentGroupIndex) { + // If the current group is anything other than "custom" (which is first), decrement. + // This fixes the odd case where you set customEmoji, then pick a category, then unset customEmoji + $$invalidate(11, currentGroupIndex--, currentGroupIndex); + } + + $$invalidate(12, groups$1 = groups); + } + } + } + + if ($$self.$$.dirty[0] & /*rawSearchText*/ 4) { + /* eslint-enable no-unused-vars */ + // + // Handle user input on the search input + // + { + rIC(() => { + $$invalidate(45, searchText = (rawSearchText || '').trim()); // defer to avoid input delays, plus we can trim here + $$invalidate(5, activeSearchItem = -1); + }); + } + } + + if ($$self.$$.dirty[0] & /*groups, currentGroupIndex*/ 6144) { + // + // Update the current group based on the currentGroupIndex + // + $$invalidate(13, currentGroup = groups$1[currentGroupIndex]); + } + + if ($$self.$$.dirty[0] & /*databaseLoaded, currentGroup*/ 24576 | $$self.$$.dirty[1] & /*searchText*/ 16384) { + // + // Set or update the currentEmojis. Check for invalid ZWJ renderings + // (i.e. double emoji). + // + { + async function updateEmojis() { + + if (!databaseLoaded) { + $$invalidate(1, currentEmojis = []); + $$invalidate(4, searchMode = false); + } else if (searchText.length >= MIN_SEARCH_TEXT_LENGTH) { + const currentSearchText = searchText; + const newEmojis = await getEmojisBySearchQuery(currentSearchText); + + if (currentSearchText === searchText) { + // if the situation changes asynchronously, do not update + $$invalidate(1, currentEmojis = newEmojis); + + $$invalidate(4, searchMode = true); + } + } else if (currentGroup) { + const currentGroupId = currentGroup.id; + const newEmojis = await getEmojisByGroup(currentGroupId); + + if (currentGroupId === currentGroup.id) { + // if the situation changes asynchronously, do not update + $$invalidate(1, currentEmojis = newEmojis); + + $$invalidate(4, searchMode = false); + } + } + } + + /* no await */ + updateEmojis(); + } + } + + if ($$self.$$.dirty[0] & /*groups, searchMode*/ 4112) { + // + // Global styles for the entire picker + // + /* eslint-disable no-unused-vars */ + $$invalidate(22, pickerStyle = ` + --num-groups: ${groups$1.length}; + --indicator-opacity: ${searchMode ? 0 : 1}; + --num-skintones: ${NUM_SKIN_TONES};`); + } + + if ($$self.$$.dirty[0] & /*databaseLoaded*/ 16384 | $$self.$$.dirty[1] & /*database*/ 256) { + // + // Set or update the preferred skin tone + // + { + async function updatePreferredSkinTone() { + if (databaseLoaded) { + $$invalidate(8, currentSkinTone = await database.getPreferredSkinTone()); + } + } + + /* no await */ + updatePreferredSkinTone(); + } + } + + if ($$self.$$.dirty[1] & /*skinToneEmoji*/ 512) { + $$invalidate(9, skinTones = Array(NUM_SKIN_TONES).fill().map((_, i) => applySkinTone(skinToneEmoji, i))); + } + + if ($$self.$$.dirty[0] & /*skinTones, currentSkinTone*/ 768) { + /* eslint-disable no-unused-vars */ + $$invalidate(21, skinToneButtonText = skinTones[currentSkinTone]); + } + + if ($$self.$$.dirty[0] & /*i18n, currentSkinTone*/ 257) { + $$invalidate(23, skinToneButtonLabel = i18n.skinToneLabel.replace('{skinTone}', i18n.skinTones[currentSkinTone])); + } + + if ($$self.$$.dirty[0] & /*databaseLoaded*/ 16384 | $$self.$$.dirty[1] & /*database*/ 256) { + /* eslint-enable no-unused-vars */ + // + // Set or update the favorites emojis + // + { + async function updateDefaultFavoriteEmojis() { + $$invalidate(46, defaultFavoriteEmojis = (await Promise.all(MOST_COMMONLY_USED_EMOJI.map(unicode => database.getEmojiByUnicodeOrName(unicode)))).filter(Boolean)); // filter because in Jest tests we don't have all the emoji in the DB + } + + if (databaseLoaded) { + /* no await */ + updateDefaultFavoriteEmojis(); + } + } + } + + if ($$self.$$.dirty[0] & /*databaseLoaded*/ 16384 | $$self.$$.dirty[1] & /*database, numColumns, defaultFavoriteEmojis*/ 98560) { + { + async function updateFavorites() { + const dbFavorites = await database.getTopFavoriteEmoji(numColumns); + const favorites = await summarizeEmojis(uniqBy([...dbFavorites, ...defaultFavoriteEmojis], _ => _.unicode || _.name).slice(0, numColumns)); + $$invalidate(10, currentFavorites = favorites); + } + + if (databaseLoaded && defaultFavoriteEmojis) { + /* no await */ + updateFavorites(); + } + } + } + + if ($$self.$$.dirty[0] & /*currentEmojis, tabpanelElement*/ 10 | $$self.$$.dirty[1] & /*emojiVersion*/ 4096) { + // Some emojis have their ligatures rendered as two or more consecutive emojis + // We want to treat these the same as unsupported emojis, so we compare their + // widths against the baseline widths and remove them as necessary + { + const zwjEmojisToCheck = currentEmojis.filter(emoji => emoji.unicode).filter(emoji => hasZwj(emoji) && !supportedZwjEmojis.has(emoji.unicode)); // filter custom emoji + + if (!emojiVersion && zwjEmojisToCheck.length) { + // render now, check their length later + rAF(() => checkZwjSupportAndUpdate(zwjEmojisToCheck)); + } else { + $$invalidate(1, currentEmojis = emojiVersion + ? currentEmojis + : currentEmojis.filter(isZwjSupported)); + + // Reset scroll top to 0 when emojis change + rAF(() => resetScrollTopIfPossible(tabpanelElement)); + } + } + } + + if ($$self.$$.dirty[0] & /*currentEmojis, currentFavorites*/ 1026 | $$self.$$.dirty[1] & /*initialLoad*/ 8192) { + { + // consider initialLoad to be complete when the first tabpanel and favorites are rendered + /* istanbul ignore next */ + if ("production" !== 'production' || false) { + if (currentEmojis.length && currentFavorites.length && initialLoad) { + $$invalidate(44, initialLoad = false); + requestPostAnimationFrame(() => (void 0)); + } + } + } + } + + if ($$self.$$.dirty[0] & /*searchMode, currentEmojis*/ 18 | $$self.$$.dirty[1] & /*customCategorySorting*/ 2048) { + // + // Derive currentEmojisWithCategories from currentEmojis. This is always done even if there + // are no categories, because it's just easier to code the HTML this way. + // + { + function calculateCurrentEmojisWithCategories() { + if (searchMode) { + return [{ category: '', emojis: currentEmojis }]; + } + + const categoriesToEmoji = new Map(); + + for (const emoji of currentEmojis) { + const category = emoji.category || ''; + let emojis = categoriesToEmoji.get(category); + + if (!emojis) { + emojis = []; + categoriesToEmoji.set(category, emojis); + } + + emojis.push(emoji); + } + + return [...categoriesToEmoji.entries()].map(([category, emojis]) => ({ category, emojis })).sort((a, b) => customCategorySorting(a.category, b.category)); + } + + // eslint-disable-next-line no-unused-vars + $$invalidate(15, currentEmojisWithCategories = calculateCurrentEmojisWithCategories()); + } + } + + if ($$self.$$.dirty[0] & /*activeSearchItem, currentEmojis*/ 34) { + // + // Handle active search item (i.e. pressing up or down while searching) + // + /* eslint-disable no-unused-vars */ + $$invalidate(26, activeSearchItemId = activeSearchItem !== -1 && currentEmojis[activeSearchItem].id); + } + + if ($$self.$$.dirty[0] & /*skinTonePickerExpanded, skinToneDropdown*/ 192) { + // To make the animation nicer, change the z-index of the skintone picker button + // *after* the animation has played. This makes it appear that the picker box + // is expanding "below" the button + { + if (skinTonePickerExpanded) { + skinToneDropdown.addEventListener( + 'transitionend', + () => { + $$invalidate(19, skinTonePickerExpandedAfterAnimation = true); // eslint-disable-line no-unused-vars + }, + { once: true } + ); + } else { + $$invalidate(19, skinTonePickerExpandedAfterAnimation = false); // eslint-disable-line no-unused-vars + } + } + } + }; + + return [ + i18n, + currentEmojis, + rawSearchText, + tabpanelElement, + searchMode, + activeSearchItem, + skinTonePickerExpanded, + skinToneDropdown, + currentSkinTone, + skinTones, + currentFavorites, + currentGroupIndex, + groups$1, + currentGroup, + databaseLoaded, + currentEmojisWithCategories, + rootElement, + baselineEmoji, + message, + skinTonePickerExpandedAfterAnimation, + activeSkinTone, + skinToneButtonText, + pickerStyle, + skinToneButtonLabel, + isRtl, + scrollbarWidth, + activeSearchItemId, + unicodeWithSkin, + labelWithSkin, + calculateEmojiGridStyle, + onSearchKeydown, + onNavClick, + onNavKeydown, + onEmojiClick, + onSkinToneOptionsClick, + onClickSkinToneButton, + onSkinToneOptionsKeydown, + onSkinToneOptionsKeyup, + onSkinToneOptionsFocusOut, + database, + skinToneEmoji, + customEmoji, + customCategorySorting, + emojiVersion, + initialLoad, + searchText, + defaultFavoriteEmojis, + numColumns, + input_input_handler, + div3_binding, + click_handler, + div10_binding, + button1_binding, + section_binding + ]; +} + +class Picker extends SvelteComponent { + constructor(options) { + super(); + + init( + this, + options, + instance, + create_fragment, + safe_not_equal, + { + skinToneEmoji: 40, + i18n: 0, + database: 39, + customEmoji: 41, + customCategorySorting: 42, + emojiVersion: 43 + }, + null, + [-1, -1, -1] + ); + } +} + +const DEFAULT_DATA_SOURCE = 'https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json'; +const DEFAULT_LOCALE = 'en'; + +var enI18n = { + categoriesLabel: 'Categories', + emojiUnsupportedMessage: 'Your browser does not support color emoji.', + favoritesLabel: 'Favorites', + loadingMessage: 'Loadingβ¦', + networkErrorMessage: 'Could not load emoji.', + regionLabel: 'Emoji picker', + searchDescription: 'When search results are available, press up or down to select and enter to choose.', + searchLabel: 'Search', + searchResultsLabel: 'Search results', + skinToneDescription: 'When expanded, press up or down to select and enter to choose.', + skinToneLabel: 'Choose a skin tone (currently {skinTone})', + skinTonesLabel: 'Skin tones', + skinTones: [ + 'Default', + 'Light', + 'Medium-Light', + 'Medium', + 'Medium-Dark', + 'Dark' + ], + categories: { + custom: 'Custom', + 'smileys-emotion': 'Smileys and emoticons', + 'people-body': 'People and body', + 'animals-nature': 'Animals and nature', + 'food-drink': 'Food and drink', + 'travel-places': 'Travel and places', + activities: 'Activities', + objects: 'Objects', + symbols: 'Symbols', + flags: 'Flags' + } +}; + +const PROPS = [ + 'customEmoji', + 'customCategorySorting', + 'database', + 'dataSource', + 'i18n', + 'locale', + 'skinToneEmoji', + 'emojiVersion' +]; + +// Styles injected ourselves, so we can declare the FONT_FAMILY variable in one place +const EXTRA_STYLES = `:host{--emoji-font-family:${FONT_FAMILY}}`; + +class PickerElement extends HTMLElement { + constructor (props) { + super(); + this.attachShadow({ mode: 'open' }); + const style = document.createElement('style'); + style.textContent = ":host{--emoji-size:1.375rem;--emoji-padding:0.5rem;--category-emoji-size:var(--emoji-size);--category-emoji-padding:var(--emoji-padding);--indicator-height:3px;--input-border-radius:0.5rem;--input-border-size:1px;--input-font-size:1rem;--input-line-height:1.5;--input-padding:0.25rem;--num-columns:8;--outline-size:2px;--border-size:1px;--skintone-border-radius:1rem;--category-font-size:1rem;display:flex;width:min-content;height:400px}:host,:host(.light){color-scheme:light;--background:#fff;--border-color:#e0e0e0;--indicator-color:#385ac1;--input-border-color:#999;--input-font-color:#111;--input-placeholder-color:#999;--outline-color:#999;--category-font-color:#111;--button-active-background:#e6e6e6;--button-hover-background:#d9d9d9}:host(.dark){color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}@media (prefers-color-scheme:dark){:host{color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}}:host([hidden]){display:none}button{margin:0;padding:0;border:0;background:0 0;box-shadow:none;-webkit-tap-highlight-color:transparent}button::-moz-focus-inner{border:0}input{padding:0;margin:0;line-height:1.15;font-family:inherit}input[type=search]{-webkit-appearance:none}:focus{outline:var(--outline-color) solid var(--outline-size);outline-offset:calc(-1*var(--outline-size))}:host([data-js-focus-visible]) :focus:not([data-focus-visible-added]){outline:0}:focus:not(:focus-visible){outline:0}.hide-focus{outline:0}*{box-sizing:border-box}.picker{contain:content;display:flex;flex-direction:column;background:var(--background);border:var(--border-size) solid var(--border-color);width:100%;height:100%;overflow:hidden;--total-emoji-size:calc(var(--emoji-size) + (2 * var(--emoji-padding)));--total-category-emoji-size:calc(var(--category-emoji-size) + (2 * var(--category-emoji-padding)))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.hidden{opacity:0;pointer-events:none}.abs-pos{position:absolute;left:0;top:0}.gone{display:none!important}.skintone-button-wrapper,.skintone-list{background:var(--background);z-index:3}.skintone-button-wrapper.expanded{z-index:1}.skintone-list{position:absolute;inset-inline-end:0;top:0;z-index:2;overflow:visible;border-bottom:var(--border-size) solid var(--border-color);border-radius:0 0 var(--skintone-border-radius) var(--skintone-border-radius);will-change:transform;transition:transform .2s ease-in-out;transform-origin:center 0}@media (prefers-reduced-motion:reduce){.skintone-list{transition-duration:.001s}}@supports not (inset-inline-end:0){.skintone-list{right:0}}.skintone-list.no-animate{transition:none}.tabpanel{overflow-y:auto;-webkit-overflow-scrolling:touch;will-change:transform;min-height:0;flex:1;contain:content}.emoji-menu{display:grid;grid-template-columns:repeat(var(--num-columns),var(--total-emoji-size));justify-content:space-around;align-items:flex-start;width:100%}.category{padding:var(--emoji-padding);font-size:var(--category-font-size);color:var(--category-font-color)}.custom-emoji,.emoji,button.emoji{height:var(--total-emoji-size);width:var(--total-emoji-size)}.emoji,button.emoji{font-size:var(--emoji-size);display:flex;align-items:center;justify-content:center;border-radius:100%;line-height:1;overflow:hidden;font-family:var(--emoji-font-family);cursor:pointer}@media (hover:hover) and (pointer:fine){.emoji:hover,button.emoji:hover{background:var(--button-hover-background)}}.emoji.active,.emoji:active,button.emoji.active,button.emoji:active{background:var(--button-active-background)}.custom-emoji{padding:var(--emoji-padding);object-fit:contain;pointer-events:none;background-repeat:no-repeat;background-position:center center;background-size:var(--emoji-size) var(--emoji-size)}.nav,.nav-button{align-items:center}.nav{display:grid;justify-content:space-between;contain:content}.nav-button{display:flex;justify-content:center}.nav-emoji{font-size:var(--category-emoji-size);width:var(--total-category-emoji-size);height:var(--total-category-emoji-size)}.indicator-wrapper{display:flex;border-bottom:1px solid var(--border-color)}.indicator{width:calc(100%/var(--num-groups));height:var(--indicator-height);opacity:var(--indicator-opacity);background-color:var(--indicator-color);will-change:transform,opacity;transition:opacity .1s linear,transform .25s ease-in-out}@media (prefers-reduced-motion:reduce){.indicator{will-change:opacity;transition:opacity .1s linear}}.pad-top,input.search{background:var(--background);width:100%}.pad-top{height:var(--emoji-padding);z-index:3}.search-row{display:flex;align-items:center;position:relative;padding-inline-start:var(--emoji-padding);padding-bottom:var(--emoji-padding)}.search-wrapper{flex:1;min-width:0}input.search{padding:var(--input-padding);border-radius:var(--input-border-radius);border:var(--input-border-size) solid var(--input-border-color);color:var(--input-font-color);font-size:var(--input-font-size);line-height:var(--input-line-height)}input.search::placeholder{color:var(--input-placeholder-color)}.favorites{display:flex;flex-direction:row;border-top:var(--border-size) solid var(--border-color);contain:content}.message{padding:var(--emoji-padding)}" + EXTRA_STYLES; + this.shadowRoot.appendChild(style); + this._ctx = { + // Set defaults + locale: DEFAULT_LOCALE, + dataSource: DEFAULT_DATA_SOURCE, + skinToneEmoji: DEFAULT_SKIN_TONE_EMOJI, + customCategorySorting: DEFAULT_CATEGORY_SORTING, + customEmoji: null, + i18n: enI18n, + emojiVersion: null, + ...props + }; + // Handle properties set before the element was upgraded + for (const prop of PROPS) { + if (prop !== 'database' && Object.prototype.hasOwnProperty.call(this, prop)) { + this._ctx[prop] = this[prop]; + delete this[prop]; + } + } + this._dbFlush(); // wait for a flush before creating the db, in case the user calls e.g. a setter or setAttribute + } + + connectedCallback () { + // The _cmp may be defined if the component was immediately disconnected and then reconnected. In that case, + // do nothing (preserve the state) + if (!this._cmp) { + this._cmp = new Picker({ + target: this.shadowRoot, + props: this._ctx + }); + } + } + + disconnectedCallback () { + // Check in a microtask if the element is still connected. If so, treat this as a "move" rather than a disconnect + // Inspired by Vue: https://vuejs.org/guide/extras/web-components.html#building-custom-elements-with-vue + Promise.resolve().then(() => { + // this._cmp may be defined if connect-disconnect-connect-disconnect occurs synchronously + if (!this.isConnected && this._cmp) { + this._cmp.$destroy(); + this._cmp = undefined; + + const { database } = this._ctx; + database.close() + // only happens if the database failed to load in the first place, so we don't care + .catch(err => console.error(err)); + } + }); + } + + static get observedAttributes () { + return ['locale', 'data-source', 'skin-tone-emoji', 'emoji-version'] // complex objects aren't supported, also use kebab-case + } + + attributeChangedCallback (attrName, oldValue, newValue) { + this._set( + // convert from kebab-case to camelcase + // see https://github.com/sveltejs/svelte/issues/3852#issuecomment-665037015 + attrName.replace(/-([a-z])/g, (_, up) => up.toUpperCase()), + // convert string attribute to float if necessary + attrName === 'emoji-version' ? parseFloat(newValue) : newValue + ); + } + + _set (prop, newValue) { + this._ctx[prop] = newValue; + if (this._cmp) { + this._cmp.$set({ [prop]: newValue }); + } + if (['locale', 'dataSource'].includes(prop)) { + this._dbFlush(); + } + } + + _dbCreate () { + const { locale, dataSource, database } = this._ctx; + // only create a new database if we really need to + if (!database || database.locale !== locale || database.dataSource !== dataSource) { + this._set('database', new Database({ locale, dataSource })); + } + } + + // Update the Database in one microtask if the locale/dataSource change. We do one microtask + // so we don't create two Databases if e.g. both the locale and the dataSource change + _dbFlush () { + Promise.resolve().then(() => ( + this._dbCreate() + )); + } +} + +const definitions = {}; + +for (const prop of PROPS) { + definitions[prop] = { + get () { + if (prop === 'database') { + // in rare cases, the microtask may not be flushed yet, so we need to instantiate the DB + // now if the user is asking for it + this._dbCreate(); + } + return this._ctx[prop] + }, + set (val) { + if (prop === 'database') { + throw new Error('database is read-only') + } + this._set(prop, val); + } + }; +} + +Object.defineProperties(PickerElement.prototype, definitions); + +/* istanbul ignore else */ +if (!customElements.get('emoji-picker')) { // if already defined, do nothing (e.g. same script imported twice) + customElements.define('emoji-picker', PickerElement); +} + +export { PickerElement as default }; |