Browse Source

Refactor

master
Joe Attardi 4 years ago
parent
commit
08fd6694d3
  1. 303
      src/index.ts
  2. 7
      src/types.ts

303
src/index.ts

@ -36,6 +36,7 @@ import {
EmojiButtonOptions,
I18NStrings,
EmojiRecord,
EmojiSelection,
EmojiTheme,
FixedPosition
} from './types';
@ -127,18 +128,30 @@ export class EmojiButton {
this.buildPicker();
}
/**
* Adds an event listener to the picker.
*
* @param event The name of the event to listen for
* @param callback The function to call when the event is fired
*/
on(event: string, callback: (arg: string) => void): void {
this.publicEvents.on(event, callback);
}
/**
* Removes an event listener from the picker.
*
* @param event The name of the event
* @param callback The callback to remove
*/
off(event: string, callback: (arg: string) => void): void {
this.publicEvents.off(event, callback);
}
private buildPicker(): void {
this.pickerEl = createElement('div', CLASS_PICKER);
this.pickerEl.classList.add(this.theme);
/**
* Sets any CSS variable values that need to be set.
*/
private setStyleProperties(): void {
if (!this.options.showAnimation) {
this.pickerEl.style.setProperty('--animation-duration', '0s');
}
@ -148,11 +161,13 @@ export class EmojiButton {
'--emoji-per-row',
this.options.emojisPerRow.toString()
);
this.options.rows &&
this.pickerEl.style.setProperty(
'--row-count',
this.options.rows.toString()
);
this.options.emojiSize &&
this.pickerEl.style.setProperty('--emoji-size', this.options.emojiSize);
@ -170,32 +185,117 @@ export class EmojiButton {
}
});
}
}
this.focusTrap = createFocusTrap(this.pickerEl as HTMLElement, {
clickOutsideDeactivates: true,
initialFocus:
this.options.showSearch && this.options.autoFocusSearch
? '.emoji-picker__search'
: '.emoji-picker__emoji[tabindex="0"]'
});
/**
* Shows the search results in the main emoji area.
*
* @param searchResults The element containing the search results.
*/
private showSearchResults(searchResults: HTMLElement): void {
empty(this.pickerContent);
searchResults.classList.add('search-results');
this.pickerContent.appendChild(searchResults);
}
this.pickerContent = createElement('div', CLASS_PICKER_CONTENT);
/**
* Hides the search results and resets the picker.
*/
private hideSearchResults(): void {
if (this.pickerContent.firstChild !== this.emojiArea.container) {
empty(this.pickerContent);
this.pickerContent.appendChild(this.emojiArea.container);
}
if (this.options.plugins) {
const pluginContainer = createElement('div', CLASS_PLUGIN_CONTAINER);
this.emojiArea.reset();
}
this.options.plugins.forEach(plugin => {
if (!plugin.render) {
throw new Error(
'Emoji Button plugins must have a "render" function.'
);
}
pluginContainer.appendChild(plugin.render(this));
});
/**
* Emits a selected emoji event.
* @param param0 The selected emoji and show variants flag
*/
private async emitEmoji({
emoji,
showVariants
}: {
emoji: EmojiRecord;
showVariants: boolean;
}): Promise<void> {
if (
(emoji as EmojiRecord).variations &&
showVariants &&
this.options.showVariants
) {
this.showVariantPopup(emoji as EmojiRecord);
} else {
setTimeout(() => this.emojiArea.updateRecents());
let eventData: EmojiSelection;
if (emoji.custom) {
eventData = this.emitCustomEmoji(emoji);
} else if (this.options.style === STYLE_TWEMOJI) {
eventData = await this.emitTwemoji(emoji);
} else {
eventData = this.emitNativeEmoji(emoji);
}
this.pickerEl.appendChild(pluginContainer);
this.publicEvents.emit(EMOJI, eventData);
if (this.options.autoHide) {
this.hidePicker();
}
}
}
/**
* Emits a native emoji record.
* @param emoji The selected emoji
*/
private emitNativeEmoji(emoji: EmojiRecord): EmojiSelection {
return {
emoji: emoji.emoji,
name: emoji.name
};
}
/**
* Emits a custom emoji record.
* @param emoji The selected emoji
*/
private emitCustomEmoji(emoji: EmojiRecord): EmojiSelection {
return {
url: emoji.emoji,
name: emoji.name,
custom: true
};
}
/**
* Emits a Twemoji emoji record.
* @param emoji The selected emoji
*/
private emitTwemoji(emoji: EmojiRecord): Promise<EmojiSelection> {
return new Promise(resolve => {
twemoji.parse(emoji.emoji, {
...this.options.twemojiOptions,
callback: (icon, { base, size, ext }: any) => {
const imageUrl = `${base}${size}/${icon}${ext}`;
resolve({
url: imageUrl,
emoji: emoji.emoji,
name: emoji.name
});
return imageUrl;
}
});
});
}
/**
* Builds the search UI.
*/
private buildSearch(): void {
if (this.options.showSearch) {
this.search = new Search(
this.events,
@ -206,91 +306,80 @@ export class EmojiButton {
emojiData.categories.indexOf(category)
)
);
this.pickerEl.appendChild(this.search.render());
}
}
this.pickerEl.appendChild(this.pickerContent);
this.emojiArea = new EmojiArea(this.events, this.i18n, this.options);
this.pickerContent.appendChild(this.emojiArea.render());
this.events.on(SHOW_SEARCH_RESULTS, (searchResults: HTMLElement) => {
empty(this.pickerContent);
searchResults.classList.add('search-results');
this.pickerContent.appendChild(searchResults);
});
this.events.on(HIDE_SEARCH_RESULTS, () => {
if (this.pickerContent.firstChild !== this.emojiArea.container) {
empty(this.pickerContent);
this.pickerContent.appendChild(this.emojiArea.container);
}
this.emojiArea.reset();
});
/**
* Builds the emoji preview area.
*/
private buildPreview(): void {
if (this.options.showPreview) {
this.pickerEl.appendChild(
new EmojiPreview(this.events, this.options).render()
);
}
}
let variantPopup: HTMLElement | null;
this.events.on(
EMOJI,
({
emoji,
showVariants
}: {
emoji: EmojiRecord;
showVariants: boolean;
}) => {
if (
(emoji as EmojiRecord).variations &&
showVariants &&
this.options.showVariants
) {
this.showVariantPopup(emoji as EmojiRecord);
} else {
if (variantPopup && variantPopup.parentNode === this.pickerEl) {
this.events.emit(HIDE_VARIANT_POPUP);
}
setTimeout(() => this.emojiArea.updateRecents());
if (emoji.custom) {
this.publicEvents.emit(EMOJI, {
url: emoji.emoji,
name: emoji.name,
custom: true
});
} else if (this.options.style === STYLE_TWEMOJI) {
twemoji.parse(emoji.emoji, {
...this.options.twemojiOptions,
callback: (icon, { base, size, ext }: any) => {
const imageUrl = `${base}${size}/${icon}${ext}`;
this.publicEvents.emit(EMOJI, {
url: imageUrl,
emoji: emoji.emoji,
name: emoji.name
});
return imageUrl;
}
});
} else {
this.publicEvents.emit(EMOJI, {
emoji: emoji.emoji,
name: emoji.name
});
}
if (this.options.autoHide) {
this.hidePicker();
}
/**
* Initializes any plugins that were specified.
*/
private initPlugins(): void {
if (this.options.plugins) {
const pluginContainer = createElement('div', CLASS_PLUGIN_CONTAINER);
this.options.plugins.forEach(plugin => {
if (!plugin.render) {
throw new Error(
'Emoji Button plugins must have a "render" function.'
);
}
}
);
pluginContainer.appendChild(plugin.render(this));
});
this.pickerEl.appendChild(pluginContainer);
}
}
/**
* Initializes the emoji picker's focus trap.
*/
private initFocusTrap(): void {
this.focusTrap = createFocusTrap(this.pickerEl as HTMLElement, {
clickOutsideDeactivates: true,
initialFocus:
this.options.showSearch && this.options.autoFocusSearch
? '.emoji-picker__search'
: '.emoji-picker__emoji[tabindex="0"]'
});
}
/**
* Builds the emoji picker.
*/
private buildPicker(): void {
this.pickerEl = createElement('div', CLASS_PICKER);
this.pickerEl.classList.add(this.theme);
this.setStyleProperties();
this.initFocusTrap();
this.pickerContent = createElement('div', CLASS_PICKER_CONTENT);
this.initPlugins();
this.buildSearch();
this.pickerEl.appendChild(this.pickerContent);
this.emojiArea = new EmojiArea(this.events, this.i18n, this.options);
this.pickerContent.appendChild(this.emojiArea.render());
this.events.on(SHOW_SEARCH_RESULTS, this.showSearchResults.bind(this));
this.events.on(HIDE_SEARCH_RESULTS, this.hideSearchResults.bind(this));
this.events.on(EMOJI, this.emitEmoji.bind(this));
this.buildPreview();
this.wrapper = createElement('div', CLASS_WRAPPER);
this.wrapper.appendChild(this.pickerEl);
@ -307,7 +396,12 @@ export class EmojiButton {
this.observeForLazyLoad();
}
private showVariantPopup(emoji: EmojiRecord) {
/**
* Shows the variant popup for an emoji.
*
* @param emoji The emoji whose variants are to be shown.
*/
private showVariantPopup(emoji: EmojiRecord): void {
const variantPopup = new VariantPopup(
this.events,
emoji,
@ -334,10 +428,13 @@ export class EmojiButton {
* Initializes the IntersectionObserver for lazy loading emoji images
* as they are scrolled into view.
*/
private observeForLazyLoad() {
this.observer = new IntersectionObserver(this.handleIntersectionChange, {
root: this.emojiArea.emojis
});
private observeForLazyLoad(): void {
this.observer = new IntersectionObserver(
this.handleIntersectionChange.bind(this),
{
root: this.emojiArea.emojis
}
);
this.emojiArea.emojis
.querySelectorAll(`.${CLASS_EMOJI}`)

7
src/types.ts

@ -17,6 +17,13 @@ export interface EmojiData {
emojiData: EmojiRecord[];
}
export interface EmojiSelection {
name: string;
custom?: boolean;
emoji?: string;
url?: string;
}
export interface RecentEmoji {
key: string;
name: string;

Loading…
Cancel
Save