Fork of the emoji-button package to remove FontAwesome.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

223 lines
5.9 KiB

import '../css/emoji-button.css';
import createFocusTrap, { FocusTrap } from 'focus-trap';
import { TinyEmitter as Emitter } from 'tiny-emitter';
import { createPopper, Instance as Popper } from '@popperjs/core';
import emojiData from './data/emoji';
import {
EMOJI,
SHOW_SEARCH_RESULTS,
SHOW_TABS,
HIDE_TABS,
HIDE_VARIANT_POPUP
} from './events';
import { EmojiPreview } from './preview';
import { Search } from './search';
import { Tabs } from './tabs';
import { createElement, empty } from './util';
import { VariantPopup } from './variantPopup';
import { i18n } from './i18n';
import { EmojiButtonOptions, I18NStrings } from './types';
const CLASS_PICKER = 'emoji-picker';
const CLASS_PICKER_CONTENT = 'emoji-picker__content';
const DEFAULT_OPTIONS: EmojiButtonOptions = {
position: 'right-start',
autoHide: true,
autoFocusSearch: true,
showPreview: true,
showSearch: true,
showRecents: true,
showVariants: true,
recentsCount: 50,
emojiVersion: '12.1'
};
export default class EmojiButton {
pickerVisible: boolean;
private events = new Emitter();
private publicEvents = new Emitter();
private options: EmojiButtonOptions;
private i18n: I18NStrings;
private pickerEl: HTMLElement;
private focusTrap: FocusTrap;
private hideInProgress: boolean;
private destroyTimeout: NodeJS.Timeout;
private popper: Popper;
constructor(options: EmojiButtonOptions = {}) {
this.pickerVisible = false;
this.options = { ...DEFAULT_OPTIONS, ...options };
if (!this.options.rootElement) {
this.options.rootElement = document.body;
}
this.i18n = {
...i18n,
...options.i18n
};
this.onDocumentClick = this.onDocumentClick.bind(this);
this.onDocumentKeydown = this.onDocumentKeydown.bind(this);
}
on(event: string, callback: Function): void {
this.publicEvents.on(event, callback);
}
off(event: string, callback: Function): void {
this.publicEvents.off(event, callback);
}
buildPicker(): void {
this.pickerEl = createElement('div', CLASS_PICKER);
this.focusTrap = createFocusTrap(<HTMLElement> this.pickerEl, {
clickOutsideDeactivates: true
});
if (this.options.zIndex) {
this.pickerEl.style.zIndex = this.options.zIndex + '';
}
const pickerContent = createElement('div', CLASS_PICKER_CONTENT);
if (this.options.showSearch) {
const searchContainer = new Search(
this.events,
this.i18n,
this.options,
emojiData.emojiData,
this.options.autoFocusSearch || true
).render();
this.pickerEl.appendChild(searchContainer);
}
this.pickerEl.appendChild(pickerContent);
const tabs = new Tabs(this.events, this.i18n, this.options).render();
pickerContent.appendChild(tabs);
this.events.on(HIDE_TABS, () => {
if (pickerContent.contains(tabs)) {
pickerContent.removeChild(tabs);
}
});
this.events.on(SHOW_TABS, () => {
if (!pickerContent.contains(tabs)) {
empty(pickerContent);
pickerContent.appendChild(tabs);
}
});
this.events.on(SHOW_SEARCH_RESULTS, (searchResults: HTMLElement) => {
empty(pickerContent);
searchResults.classList.add('search-results');
pickerContent.appendChild(searchResults);
});
if (this.options.showPreview) {
this.pickerEl.appendChild(new EmojiPreview(this.events).render());
}
let variantPopup: HTMLElement | null;
this.events.on(EMOJI, ({ emoji, showVariants }: { emoji: any, showVariants: boolean }) => {
if (emoji.v && showVariants && this.options.showVariants) {
variantPopup = new VariantPopup(
this.events,
emoji,
this.options
).render();
if (variantPopup) {
this.pickerEl.appendChild(variantPopup);
}
} else {
if (variantPopup && variantPopup.parentNode === this.pickerEl) {
this.pickerEl.removeChild(variantPopup);
}
this.publicEvents.emit('emoji', emoji.e);
if (this.options.autoHide) {
this.hidePicker();
}
}
});
this.events.on(HIDE_VARIANT_POPUP, () => {
if (variantPopup) {
this.pickerEl.removeChild(variantPopup);
}
variantPopup = null;
});
if (this.options.rootElement) {
this.options.rootElement.appendChild(this.pickerEl);
}
setTimeout(() => {
document.addEventListener('click', this.onDocumentClick);
document.addEventListener('keydown', this.onDocumentKeydown);
});
}
onDocumentClick(event: MouseEvent): void {
if (!this.pickerEl.contains(<Node> event.target)) {
this.hidePicker();
}
}
destroyPicker(): void {
if (this.options.rootElement) {
this.options.rootElement.removeChild(this.pickerEl);
this.popper.destroy();
this.pickerEl.style.transition = '';
this.hideInProgress = false;
}
}
hidePicker(): void {
this.focusTrap.deactivate();
this.pickerEl.classList.remove('visible');
this.pickerVisible = false;
this.events.off(EMOJI);
this.events.off(HIDE_VARIANT_POPUP);
this.hideInProgress = true;
this.destroyTimeout = setTimeout(this.destroyPicker.bind(this), 500);
document.removeEventListener('click', this.onDocumentClick);
document.removeEventListener('keydown', this.onDocumentKeydown);
}
showPicker(referenceEl: HTMLElement, options: EmojiButtonOptions = {}): void {
if (this.hideInProgress) {
clearTimeout(this.destroyTimeout);
this.destroyPicker();
}
this.pickerVisible = true;
this.buildPicker();
this.popper = createPopper(referenceEl, this.pickerEl, {
placement: options.position || this.options.position
});
this.focusTrap.activate();
requestAnimationFrame(() => this.pickerEl.classList.add('visible'));
}
onDocumentKeydown(event: KeyboardEvent): void {
if (event.key === 'Escape') {
this.hidePicker();
}
}
}