2295 lines
79 KiB
JavaScript
2295 lines
79 KiB
JavaScript
|
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;poi
|
|||
|
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 };
|