emoji-picker-element/src/picker/components/Picker/Picker.js

147 lines
4.4 KiB
JavaScript
Raw Normal View History

2020-05-18 04:08:00 +02:00
/* eslint-disable prefer-const,no-labels */
import Database from '../../../database/Database.js'
2020-05-18 04:08:00 +02:00
import i18n from '../../i18n/en.json'
import { categories } from '../../categories'
import { DEFAULT_LOCALE, DEFAULT_DATA_SOURCE } from '../../../database/constants'
2020-06-03 04:23:07 +02:00
import { MIN_SEARCH_TEXT_LENGTH } from '../../constants'
2020-05-18 04:08:00 +02:00
import { requestIdleCallback } from '../../utils/requestIdleCallback'
2020-06-01 03:05:51 +02:00
import { calculateTextWidth } from '../../utils/calculateTextWidth'
2020-05-18 04:08:00 +02:00
import { hasZwj } from '../../utils/hasZwj'
2020-06-01 03:05:51 +02:00
import { thunk } from '../../utils/thunk'
import { emojiSupportLevel, supportedZwjEmojis } from '../../utils/emojiSupport'
2020-05-18 04:08:00 +02:00
let database
let currentEmojis = []
let locale = DEFAULT_LOCALE
let dataSource = DEFAULT_DATA_SOURCE
let currentCategory = categories[0]
let rawSearchText = ''
let searchText = ''
let rootElement
let baselineEmoji
2020-06-02 17:42:33 +02:00
let searchMode = false // eslint-disable-line no-unused-vars
let activeSearchItem = -1
2020-05-31 22:57:07 +02:00
2020-06-01 03:05:51 +02:00
const getBaselineEmojiWidth = thunk(() => calculateTextWidth(baselineEmoji))
2020-05-18 04:08:00 +02:00
$: database = new Database({ dataSource, locale })
$: {
2020-06-02 17:42:33 +02:00
// eslint-disable-next-line no-inner-declarations
async function updateEmojis () {
2020-05-18 04:08:00 +02:00
if (searchText.length >= MIN_SEARCH_TEXT_LENGTH) {
2020-06-02 17:42:33 +02:00
searchMode = true
2020-05-18 04:08:00 +02:00
currentEmojis = await getEmojisBySearchPrefix(searchText)
} else {
2020-06-02 17:42:33 +02:00
searchMode = false
2020-05-18 04:08:00 +02:00
currentEmojis = await getEmojisByGroup(currentCategory.group)
}
2020-06-02 17:42:33 +02:00
}
updateEmojis()
2020-05-18 04:08:00 +02:00
}
$: {
requestIdleCallback(() => {
searchText = rawSearchText // defer to avoid input delays
2020-06-02 17:42:33 +02:00
activeSearchItem = -1
2020-05-18 04:08:00 +02:00
})
}
// 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 => hasZwj(emoji) && !supportedZwjEmojis.has(emoji.unicode))
if (zwjEmojisToCheck.length) {
// render now, check their length later
requestAnimationFrame(() => checkZwjSupport(zwjEmojisToCheck))
} else {
currentEmojis = currentEmojis.filter(isZwjSupported)
}
}
function checkZwjSupport (zwjEmojisToCheck) {
2020-06-01 03:05:51 +02:00
const rootNode = rootElement.getRootNode()
for (const emoji of zwjEmojisToCheck) {
const domNode = rootNode.getElementById(`emoji-${emoji.unicode}`)
2020-06-01 03:05:51 +02:00
const emojiWidth = calculateTextWidth(domNode)
const baselineEmojiWidth = getBaselineEmojiWidth()
// compare sizes rounded to 1/10 of a pixel to avoid issues with slightly different measurements (e.g. GNOME Web)
const supported = emojiWidth.toFixed(1) === baselineEmojiWidth.toFixed(1)
2020-05-18 04:08:00 +02:00
supportedZwjEmojis.set(emoji.unicode, supported)
2020-06-01 03:05:51 +02:00
if (!supported) {
console.log('Filtered unsupported emoji', emoji.unicode)
}
2020-05-18 04:08:00 +02:00
}
// force update
currentEmojis = currentEmojis // eslint-disable-line no-self-assign
}
function isZwjSupported (emoji) {
return !hasZwj(emoji) || supportedZwjEmojis.get(emoji.unicode)
}
function filterEmojisByVersion (emojis) {
return emojis.filter(({ version }) => version <= emojiSupportLevel)
}
async function getEmojisByGroup (group) {
return filterEmojisByVersion(await database.getEmojiByGroup(group))
}
async function getEmojisBySearchPrefix (prefix) {
return filterEmojisByVersion(await database.getEmojiBySearchPrefix(prefix))
}
// eslint-disable-next-line no-unused-vars
function handleCategoryClick (category) {
// throttle to avoid input delays
requestIdleCallback(() => {
2020-06-02 17:42:33 +02:00
rawSearchText = ''
searchText = ''
activeSearchItem = -1
2020-05-18 04:08:00 +02:00
currentCategory = category
})
}
2020-06-02 17:42:33 +02:00
// eslint-disable-next-line no-unused-vars
function onSearchKeydown (event) {
if (!searchMode || !currentEmojis.length) {
return
}
2020-06-04 04:12:33 +02:00
const goToNextOrPrevious = (previous) => {
event.preventDefault()
event.stopPropagation()
activeSearchItem += (previous ? -1 : 1)
if (activeSearchItem < 0) {
activeSearchItem = currentEmojis.length - 1
} else if (activeSearchItem >= currentEmojis.length) {
activeSearchItem = 0
}
}
2020-06-02 17:42:33 +02:00
switch (event.key) {
case 'ArrowDown':
2020-06-04 04:12:33 +02:00
return goToNextOrPrevious(false)
2020-06-02 17:42:33 +02:00
case 'ArrowUp':
2020-06-04 04:12:33 +02:00
return goToNextOrPrevious(true)
2020-06-02 17:42:33 +02:00
}
}
2020-06-04 03:42:27 +02:00
// eslint-disable-next-line no-unused-vars
function onNavKeydown (event) {
const { target, key } = event
switch (key) {
case 'ArrowLeft':
return target.previousSibling && target.previousSibling.focus()
case 'ArrowRight':
return target.nextSibling && target.nextSibling.focus()
}
}
2020-05-18 04:08:00 +02:00
export {
locale,
dataSource,
2020-06-03 04:23:07 +02:00
i18n
2020-05-18 04:08:00 +02:00
}