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.
 
 
 

334 lines
9.7 KiB

import { TinyEmitter as Emitter } from 'tiny-emitter';
import emojiData from './data/emoji';
import { EmojiContainer } from './emojiContainer';
import { EMOJI, HIDE_VARIANT_POPUP } from './events';
import { load } from './recent';
import { i18n as defaultI18n } from './i18n';
import * as icons from './icons';
import { createElement } from './util';
import { EmojiRecord, I18NStrings, EmojiButtonOptions, I18NCategory } from './types.js';
const CLASS_ACTIVE_TAB = 'active';
const CLASS_TABS_CONTAINER = 'emoji-picker__tabs-container';
const CLASS_TABS = 'emoji-picker__tabs';
const CLASS_TAB = 'emoji-picker__tab';
const CLASS_TAB_BODY = 'emoji-picker__tab-body';
const EMOJIS_PER_ROW = 8;
const categories = emojiData.categories;
const emojiCategories: { [key: string] : EmojiRecord[]} = {};
emojiData.emojiData.forEach(emoji => {
let categoryList = emojiCategories[categories[emoji.c]];
if (!categoryList) {
categoryList = emojiCategories[categories[emoji.c]] = [];
}
categoryList.push(emoji);
});
const categoryIcons: { [key in I18NCategory]: string } = {
smileys: icons.smile,
animals: icons.cat,
food: icons.coffee,
activities: icons.futbol,
travel: icons.building,
objects: icons.lightbulb,
symbols: icons.music,
flags: icons.flag,
recents: icons.history
};
export class Tabs {
private activeTab: number;
private tabBodies: TabBody[];
private tabs: Tab[];
private tabsList: HTMLElement;
private tabBodyContainer: HTMLElement;
private focusedEmojiIndex = 0;
constructor(private events: Emitter, private i18n: I18NStrings, private options: EmojiButtonOptions) {
this.setActiveTab = this.setActiveTab.bind(this);
}
setActiveTab(index: number, animate = true) {
if (index === this.activeTab) {
return;
}
const currentActiveTab = this.activeTab;
const newActiveTabBody = this.tabBodies[index].container;
if (currentActiveTab >= 0) {
this.tabs[currentActiveTab].setActive(false);
this.tabBodies[currentActiveTab].setActive(false);
const currentActiveTabBody = this.tabBodies[currentActiveTab].container;
currentActiveTabBody
.querySelectorAll('.emoji-picker__emoji')
.forEach((emoji: Element) => ((<HTMLElement>emoji).tabIndex = -1));
const activeEmojiContainer = newActiveTabBody.querySelector(
'.emoji-picker__emojis'
);
if (activeEmojiContainer) {
activeEmojiContainer.scrollTop = 0;
const firstEmoji = activeEmojiContainer.querySelector(
'.emoji-picker__emoji'
);
if (firstEmoji) {
(<HTMLElement>firstEmoji).tabIndex = 0;
}
}
this.focusedEmojiIndex = 0;
if (animate) {
if (index > currentActiveTab) {
this.transitionTabs(newActiveTabBody, currentActiveTabBody, 25, -25);
} else {
this.transitionTabs(newActiveTabBody, currentActiveTabBody, -25, 25);
}
}
}
this.activeTab = index;
this.tabBodies[this.activeTab].setActive(true);
this.tabs[this.activeTab].setActive(true);
}
transitionTabs(
newActiveTabBody: HTMLElement,
currentActiveTabBody: HTMLElement,
newTranslate: number,
currentTranslate: number
) {
requestAnimationFrame(() => {
newActiveTabBody.style.transition = 'none';
newActiveTabBody.style.transform = `translateX(${newTranslate}rem)`;
requestAnimationFrame(() => {
currentActiveTabBody.style.transform = `translateX(${currentTranslate}rem)`;
newActiveTabBody.style.transition = 'transform 0.25s';
requestAnimationFrame(() => {
newActiveTabBody.style.transform = 'translateX(0)';
});
});
});
}
render() {
const tabsContainer = createElement('div', CLASS_TABS_CONTAINER);
tabsContainer.appendChild(this.createTabs());
tabsContainer.appendChild(this.createTabBodies());
const initialActiveTab = this.options.showRecents ? 1 : 0;
this.setActiveTab(initialActiveTab, false);
const firstEmoji = this.tabBodies[initialActiveTab].content.querySelector(
'.emoji-picker__emoji'
);
if (firstEmoji) {
(<HTMLElement>firstEmoji).tabIndex = 0;
}
this.focusedEmojiIndex = 0;
return tabsContainer;
}
setFocusedEmoji(index: number) {
const emojis = this.tabBodies[this.activeTab].content.querySelectorAll(
'.emoji-picker__emoji'
);
const currentFocusedEmoji = <HTMLElement> emojis[this.focusedEmojiIndex];
currentFocusedEmoji.tabIndex = -1;
this.focusedEmojiIndex = index;
const newFocusedEmoji = <HTMLElement> emojis[this.focusedEmojiIndex];
newFocusedEmoji.tabIndex = 0;
newFocusedEmoji.focus();
}
createTabs() {
this.tabsList = createElement('ul', CLASS_TABS);
this.tabs = Object.keys(categoryIcons).map(
(category, index) =>
new Tab(
categoryIcons[category],
this.options.showRecents ? index + 1 : index,
this.setActiveTab
)
);
if (this.options.showRecents) {
const recentTab = new Tab(icons.history, 0, this.setActiveTab);
this.tabs.splice(0, 0, recentTab);
}
this.tabs.forEach(tab => this.tabsList.appendChild(tab.render()));
this.tabsList.addEventListener('keydown', event => {
if (event.key === 'ArrowLeft') {
this.setActiveTab(
this.activeTab === 0 ? this.tabs.length - 1 : this.activeTab - 1
);
} else if (event.key === 'ArrowRight') {
this.setActiveTab((this.activeTab + 1) % this.tabs.length);
}
});
return this.tabsList;
}
createTabBodies() {
this.tabBodyContainer = createElement('div');
this.tabBodies = Object.keys(categoryIcons).map(
(category: string, index: number) =>
new TabBody(
this.i18n.categories[category] || defaultI18n.categories[category],
new EmojiContainer(
emojiCategories[category] || [],
true,
this.events,
this.options
).render(),
this.options.showRecents ? index + 1 : index
)
);
this.tabBodyContainer.addEventListener('keydown', event => {
const emojis = this.tabBodies[this.activeTab].content.querySelectorAll(
'.emoji-picker__emoji'
);
if (event.key === 'ArrowRight') {
this.setFocusedEmoji(
Math.min(this.focusedEmojiIndex + 1, emojis.length - 1)
);
} else if (event.key === 'ArrowLeft') {
this.setFocusedEmoji(Math.max(0, this.focusedEmojiIndex - 1));
} else if (event.key === 'ArrowDown') {
event.preventDefault();
if (this.focusedEmojiIndex < emojis.length - EMOJIS_PER_ROW) {
this.setFocusedEmoji(this.focusedEmojiIndex + EMOJIS_PER_ROW);
}
} else if (event.key === 'ArrowUp') {
event.preventDefault();
if (this.focusedEmojiIndex >= EMOJIS_PER_ROW) {
this.setFocusedEmoji(this.focusedEmojiIndex - EMOJIS_PER_ROW);
}
}
});
this.events.on(HIDE_VARIANT_POPUP, () => {
setTimeout(() => this.setFocusedEmoji(this.focusedEmojiIndex));
});
this.events.on(EMOJI, ({ button }: { button: HTMLButtonElement }) => {
if (button.parentElement && button.parentElement.classList.contains('emoji-picker__emojis')) {
this.setFocusedEmoji(
Array.prototype.indexOf.call(button.parentElement.children, button)
);
} else {
this.setFocusedEmoji(this.focusedEmojiIndex);
}
});
if (this.options.showRecents) {
const recentTabBody = new TabBody(
this.i18n.categories.recents || defaultI18n.categories.recents,
new EmojiContainer(load(), false, this.events, this.options).render(),
0
);
this.tabBodies.splice(0, 0, recentTabBody);
this.events.on(EMOJI, () => {
const newRecents = new TabBody(
this.i18n.categories.recents || defaultI18n.categories.recents,
new EmojiContainer(load(), false, this.events, this.options).render(),
0
);
const newRecentsEl = newRecents.render();
if (this.activeTab === 0) {
newRecentsEl.style.transform = 'translateX(0)';
}
setTimeout(() => {
this.tabBodyContainer.replaceChild(
newRecentsEl,
<Node> this.tabBodyContainer.firstChild
);
this.tabBodies[0] = newRecents;
if (this.activeTab === 0) {
this.setActiveTab(0);
}
});
});
}
this.tabBodies.forEach(tabBody =>
this.tabBodyContainer.appendChild(tabBody.render())
);
return this.tabBodyContainer;
}
}
class Tab {
tab: HTMLElement;
constructor(private icon: string, private index: number, private setActiveTab: Function) {}
render() {
this.tab = createElement('li', CLASS_TAB);
this.tab.innerHTML = this.icon;
this.tab.addEventListener('click', () => this.setActiveTab(this.index));
return this.tab;
}
setActive(active: boolean) {
if (active) {
this.tab.classList.add(CLASS_ACTIVE_TAB);
this.tab.tabIndex = 0;
this.tab.focus();
} else {
this.tab.classList.remove(CLASS_ACTIVE_TAB);
this.tab.tabIndex = -1;
}
}
}
class TabBody {
constructor(private category: string, public content: HTMLElement, private index: number) {}
container: HTMLElement;
render() {
this.container = createElement('div', CLASS_TAB_BODY);
const title = createElement('h2');
title.innerHTML = this.category;
this.container.appendChild(title);
this.container.appendChild(this.content);
return this.container;
}
setActive(active: boolean) {
if (active) {
this.container.classList.add(CLASS_ACTIVE_TAB);
} else {
this.container.classList.remove(CLASS_ACTIVE_TAB);
}
}
}