UNPKG

@ctrl/ngx-emoji-mart

Version:

Customizable Slack-like emoji picker for Angular

1,265 lines (1,240 loc) 86.1 kB
import * as i2 from '@angular/common'; import { CommonModule, isPlatformBrowser } from '@angular/common'; import * as i0 from '@angular/core'; import { EventEmitter, Component, ChangeDetectionStrategy, Input, Output, PLATFORM_ID, Injectable, Inject, ViewChild, ViewChildren, NgModule } from '@angular/core'; import * as i1 from '@ctrl/ngx-emoji-mart/ngx-emoji'; import { EmojiComponent, categories as categories$1 } from '@ctrl/ngx-emoji-mart/ngx-emoji'; import { Subject, fromEvent, takeUntil } from 'rxjs'; import * as i2$1 from '@angular/forms'; import { FormsModule } from '@angular/forms'; class AnchorsComponent { categories = []; color; selected; i18n; icons = {}; anchorClick = new EventEmitter(); trackByFn(idx, cat) { return cat.id; } handleClick($event, index) { this.anchorClick.emit({ category: this.categories[index], index, }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: AnchorsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: AnchorsComponent, isStandalone: true, selector: "emoji-mart-anchors", inputs: { categories: "categories", color: "color", selected: "selected", i18n: "i18n", icons: "icons" }, outputs: { anchorClick: "anchorClick" }, ngImport: i0, template: ` <div class="emoji-mart-anchors"> <ng-template ngFor let-category [ngForOf]="categories" let-idx="index" [ngForTrackBy]="trackByFn" > <span *ngIf="category.anchor !== false" [attr.title]="i18n.categories[category.id]" (click)="this.handleClick($event, idx)" class="emoji-mart-anchor" [class.emoji-mart-anchor-selected]="category.name === selected" [style.color]="category.name === selected ? color : null" > <div> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <path [attr.d]="icons[category.id]" /> </svg> </div> <span class="emoji-mart-anchor-bar" [style.background-color]="color"></span> </span> </ng-template> </div> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: AnchorsComponent, decorators: [{ type: Component, args: [{ selector: 'emoji-mart-anchors', template: ` <div class="emoji-mart-anchors"> <ng-template ngFor let-category [ngForOf]="categories" let-idx="index" [ngForTrackBy]="trackByFn" > <span *ngIf="category.anchor !== false" [attr.title]="i18n.categories[category.id]" (click)="this.handleClick($event, idx)" class="emoji-mart-anchor" [class.emoji-mart-anchor-selected]="category.name === selected" [style.color]="category.name === selected ? color : null" > <div> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <path [attr.d]="icons[category.id]" /> </svg> </div> <span class="emoji-mart-anchor-bar" [style.background-color]="color"></span> </span> </ng-template> </div> `, changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: false, standalone: true, imports: [CommonModule], }] }], propDecorators: { categories: [{ type: Input }], color: [{ type: Input }], selected: [{ type: Input }], i18n: [{ type: Input }], icons: [{ type: Input }], anchorClick: [{ type: Output }] } }); class EmojiFrequentlyService { platformId; NAMESPACE = 'emoji-mart'; frequently = null; defaults = {}; initialized = false; DEFAULTS = [ '+1', 'grinning', 'kissing_heart', 'heart_eyes', 'laughing', 'stuck_out_tongue_winking_eye', 'sweat_smile', 'joy', 'scream', 'disappointed', 'unamused', 'weary', 'sob', 'sunglasses', 'heart', 'poop', ]; constructor(platformId) { this.platformId = platformId; } init() { this.frequently = JSON.parse((isPlatformBrowser(this.platformId) && localStorage.getItem(`${this.NAMESPACE}.frequently`)) || 'null'); this.initialized = true; } add(emoji) { if (!this.initialized) { this.init(); } if (!this.frequently) { this.frequently = this.defaults; } if (!this.frequently[emoji.id]) { this.frequently[emoji.id] = 0; } this.frequently[emoji.id] += 1; if (isPlatformBrowser(this.platformId)) { localStorage.setItem(`${this.NAMESPACE}.last`, emoji.id); localStorage.setItem(`${this.NAMESPACE}.frequently`, JSON.stringify(this.frequently)); } } get(perLine, totalLines) { if (!this.initialized) { this.init(); } if (this.frequently === null) { this.defaults = {}; const result = []; for (let i = 0; i < perLine; i++) { this.defaults[this.DEFAULTS[i]] = perLine - i; result.push(this.DEFAULTS[i]); } return result; } const quantity = perLine * totalLines; const frequentlyKeys = Object.keys(this.frequently); const sorted = frequentlyKeys .sort((a, b) => this.frequently[a] - this.frequently[b]) .reverse(); const sliced = sorted.slice(0, quantity); const last = isPlatformBrowser(this.platformId) && localStorage.getItem(`${this.NAMESPACE}.last`); if (last && !sliced.includes(last)) { sliced.pop(); sliced.push(last); } return sliced; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: EmojiFrequentlyService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: EmojiFrequentlyService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: EmojiFrequentlyService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: [PLATFORM_ID] }] }]; } }); class CategoryComponent { ref; emojiService; frequently; emojis = null; hasStickyPosition = true; name = ''; perLine = 9; totalFrequentLines = 4; recent = []; custom = []; i18n; id; hideObsolete = true; notFoundEmoji; virtualize = false; virtualizeOffset = 0; emojiIsNative; emojiSkin; emojiSize; emojiSet; emojiSheetSize; emojiForceSize; emojiTooltip; emojiBackgroundImageFn; emojiImageUrlFn; emojiUseButton; /** * Note: the suffix is added explicitly so we know the event is dispatched outside of the Angular zone. */ emojiOverOutsideAngular = new EventEmitter(); emojiLeaveOutsideAngular = new EventEmitter(); emojiClickOutsideAngular = new EventEmitter(); container; label; containerStyles = {}; emojisToDisplay = []; filteredEmojisSubject = new Subject(); filteredEmojis$ = this.filteredEmojisSubject.asObservable(); labelStyles = {}; labelSpanStyles = {}; margin = 0; minMargin = 0; maxMargin = 0; top = 0; rows = 0; constructor(ref, emojiService, frequently) { this.ref = ref; this.emojiService = emojiService; this.frequently = frequently; } ngOnInit() { this.updateRecentEmojis(); this.emojisToDisplay = this.filterEmojis(); if (this.noEmojiToDisplay) { this.containerStyles = { display: 'none' }; } if (!this.hasStickyPosition) { this.labelStyles = { height: 28 }; // this.labelSpanStyles = { position: 'absolute' }; } } ngOnChanges(changes) { if (changes.emojis?.currentValue?.length !== changes.emojis?.previousValue?.length) { this.emojisToDisplay = this.filterEmojis(); this.ngAfterViewInit(); } } ngAfterViewInit() { if (!this.virtualize) { return; } const { width } = this.container.nativeElement.getBoundingClientRect(); const perRow = Math.floor(width / (this.emojiSize + 12)); this.rows = Math.ceil(this.emojisToDisplay.length / perRow); this.containerStyles = { ...this.containerStyles, minHeight: `${this.rows * (this.emojiSize + 12) + 28}px`, }; this.ref.detectChanges(); this.handleScroll(this.container.nativeElement.parentNode.parentNode.scrollTop); } get noEmojiToDisplay() { return this.emojisToDisplay.length === 0; } memoizeSize() { const parent = this.container.nativeElement.parentNode.parentNode; const { top, height } = this.container.nativeElement.getBoundingClientRect(); const parentTop = parent.getBoundingClientRect().top; const labelHeight = this.label.nativeElement.getBoundingClientRect().height; this.top = top - parentTop + parent.scrollTop; if (height === 0) { this.maxMargin = 0; } else { this.maxMargin = height - labelHeight; } } handleScroll(scrollTop) { let margin = scrollTop - this.top; margin = margin < this.minMargin ? this.minMargin : margin; margin = margin > this.maxMargin ? this.maxMargin : margin; if (this.virtualize) { const { top, height } = this.container.nativeElement.getBoundingClientRect(); const parentHeight = this.container.nativeElement.parentNode.parentNode.clientHeight; if (parentHeight + (parentHeight + this.virtualizeOffset) >= top && -height - (parentHeight + this.virtualizeOffset) <= top) { this.filteredEmojisSubject.next(this.emojisToDisplay); } else { this.filteredEmojisSubject.next([]); } } if (margin === this.margin) { this.ref.detectChanges(); return false; } if (!this.hasStickyPosition) { this.label.nativeElement.style.top = `${margin}px`; } this.margin = margin; this.ref.detectChanges(); return true; } updateRecentEmojis() { if (this.name !== 'Recent') { return; } let frequentlyUsed = this.recent || this.frequently.get(this.perLine, this.totalFrequentLines); if (!frequentlyUsed || !frequentlyUsed.length) { frequentlyUsed = this.frequently.get(this.perLine, this.totalFrequentLines); } if (!frequentlyUsed.length) { return; } this.emojis = frequentlyUsed .map(id => { const emoji = this.custom.filter((e) => e.id === id)[0]; if (emoji) { return emoji; } return id; }) .filter(id => !!this.emojiService.getData(id)); } updateDisplay(display) { this.containerStyles.display = display; this.updateRecentEmojis(); this.ref.detectChanges(); } trackById(index, item) { return item; } filterEmojis() { const newEmojis = []; for (const emoji of this.emojis || []) { if (!emoji) { continue; } const data = this.emojiService.getData(emoji); if (!data || (data.obsoletedBy && this.hideObsolete) || (!data.unified && !data.custom)) { continue; } newEmojis.push(emoji); } return newEmojis; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: CategoryComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1.EmojiService }, { token: EmojiFrequentlyService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: CategoryComponent, isStandalone: true, selector: "emoji-category", inputs: { emojis: "emojis", hasStickyPosition: "hasStickyPosition", name: "name", perLine: "perLine", totalFrequentLines: "totalFrequentLines", recent: "recent", custom: "custom", i18n: "i18n", id: "id", hideObsolete: "hideObsolete", notFoundEmoji: "notFoundEmoji", virtualize: "virtualize", virtualizeOffset: "virtualizeOffset", emojiIsNative: "emojiIsNative", emojiSkin: "emojiSkin", emojiSize: "emojiSize", emojiSet: "emojiSet", emojiSheetSize: "emojiSheetSize", emojiForceSize: "emojiForceSize", emojiTooltip: "emojiTooltip", emojiBackgroundImageFn: "emojiBackgroundImageFn", emojiImageUrlFn: "emojiImageUrlFn", emojiUseButton: "emojiUseButton" }, outputs: { emojiOverOutsideAngular: "emojiOverOutsideAngular", emojiLeaveOutsideAngular: "emojiLeaveOutsideAngular", emojiClickOutsideAngular: "emojiClickOutsideAngular" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }, { propertyName: "label", first: true, predicate: ["label"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: ` <section #container class="emoji-mart-category" [attr.aria-label]="i18n.categories[id]" [class.emoji-mart-no-results]="noEmojiToDisplay" [ngStyle]="containerStyles" > <div class="emoji-mart-category-label" [ngStyle]="labelStyles" [attr.data-name]="name"> <!-- already labeled by the section aria-label --> <span #label [ngStyle]="labelSpanStyles" aria-hidden="true"> {{ i18n.categories[id] }} </span> </div> <div *ngIf="virtualize; else normalRenderTemplate"> <div *ngIf="filteredEmojis$ | async as filteredEmojis"> <ngx-emoji *ngFor="let emoji of filteredEmojis; trackBy: trackById" [emoji]="emoji" [size]="emojiSize" [skin]="emojiSkin" [isNative]="emojiIsNative" [set]="emojiSet" [sheetSize]="emojiSheetSize" [forceSize]="emojiForceSize" [tooltip]="emojiTooltip" [backgroundImageFn]="emojiBackgroundImageFn" [imageUrlFn]="emojiImageUrlFn" [hideObsolete]="hideObsolete" [useButton]="emojiUseButton" (emojiOverOutsideAngular)="emojiOverOutsideAngular.emit($event)" (emojiLeaveOutsideAngular)="emojiLeaveOutsideAngular.emit($event)" (emojiClickOutsideAngular)="emojiClickOutsideAngular.emit($event)" ></ngx-emoji> </div> </div> <div *ngIf="noEmojiToDisplay"> <div> <ngx-emoji [emoji]="notFoundEmoji" [size]="38" [skin]="emojiSkin" [isNative]="emojiIsNative" [set]="emojiSet" [sheetSize]="emojiSheetSize" [forceSize]="emojiForceSize" [tooltip]="emojiTooltip" [backgroundImageFn]="emojiBackgroundImageFn" [useButton]="emojiUseButton" ></ngx-emoji> </div> <div class="emoji-mart-no-results-label"> {{ i18n.notfound }} </div> </div> </section> <ng-template #normalRenderTemplate> <ngx-emoji *ngFor="let emoji of emojisToDisplay; trackBy: trackById" [emoji]="emoji" [size]="emojiSize" [skin]="emojiSkin" [isNative]="emojiIsNative" [set]="emojiSet" [sheetSize]="emojiSheetSize" [forceSize]="emojiForceSize" [tooltip]="emojiTooltip" [backgroundImageFn]="emojiBackgroundImageFn" [imageUrlFn]="emojiImageUrlFn" [hideObsolete]="hideObsolete" [useButton]="emojiUseButton" (emojiOverOutsideAngular)="emojiOverOutsideAngular.emit($event)" (emojiLeaveOutsideAngular)="emojiLeaveOutsideAngular.emit($event)" (emojiClickOutsideAngular)="emojiClickOutsideAngular.emit($event)" ></ngx-emoji> </ng-template> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "component", type: EmojiComponent, selector: "ngx-emoji", inputs: ["skin", "set", "sheetSize", "isNative", "forceSize", "tooltip", "size", "emoji", "fallback", "hideObsolete", "sheetRows", "sheetColumns", "useButton", "backgroundImageFn", "imageUrlFn"], outputs: ["emojiOver", "emojiOverOutsideAngular", "emojiLeave", "emojiLeaveOutsideAngular", "emojiClick", "emojiClickOutsideAngular"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: CategoryComponent, decorators: [{ type: Component, args: [{ selector: 'emoji-category', template: ` <section #container class="emoji-mart-category" [attr.aria-label]="i18n.categories[id]" [class.emoji-mart-no-results]="noEmojiToDisplay" [ngStyle]="containerStyles" > <div class="emoji-mart-category-label" [ngStyle]="labelStyles" [attr.data-name]="name"> <!-- already labeled by the section aria-label --> <span #label [ngStyle]="labelSpanStyles" aria-hidden="true"> {{ i18n.categories[id] }} </span> </div> <div *ngIf="virtualize; else normalRenderTemplate"> <div *ngIf="filteredEmojis$ | async as filteredEmojis"> <ngx-emoji *ngFor="let emoji of filteredEmojis; trackBy: trackById" [emoji]="emoji" [size]="emojiSize" [skin]="emojiSkin" [isNative]="emojiIsNative" [set]="emojiSet" [sheetSize]="emojiSheetSize" [forceSize]="emojiForceSize" [tooltip]="emojiTooltip" [backgroundImageFn]="emojiBackgroundImageFn" [imageUrlFn]="emojiImageUrlFn" [hideObsolete]="hideObsolete" [useButton]="emojiUseButton" (emojiOverOutsideAngular)="emojiOverOutsideAngular.emit($event)" (emojiLeaveOutsideAngular)="emojiLeaveOutsideAngular.emit($event)" (emojiClickOutsideAngular)="emojiClickOutsideAngular.emit($event)" ></ngx-emoji> </div> </div> <div *ngIf="noEmojiToDisplay"> <div> <ngx-emoji [emoji]="notFoundEmoji" [size]="38" [skin]="emojiSkin" [isNative]="emojiIsNative" [set]="emojiSet" [sheetSize]="emojiSheetSize" [forceSize]="emojiForceSize" [tooltip]="emojiTooltip" [backgroundImageFn]="emojiBackgroundImageFn" [useButton]="emojiUseButton" ></ngx-emoji> </div> <div class="emoji-mart-no-results-label"> {{ i18n.notfound }} </div> </div> </section> <ng-template #normalRenderTemplate> <ngx-emoji *ngFor="let emoji of emojisToDisplay; trackBy: trackById" [emoji]="emoji" [size]="emojiSize" [skin]="emojiSkin" [isNative]="emojiIsNative" [set]="emojiSet" [sheetSize]="emojiSheetSize" [forceSize]="emojiForceSize" [tooltip]="emojiTooltip" [backgroundImageFn]="emojiBackgroundImageFn" [imageUrlFn]="emojiImageUrlFn" [hideObsolete]="hideObsolete" [useButton]="emojiUseButton" (emojiOverOutsideAngular)="emojiOverOutsideAngular.emit($event)" (emojiLeaveOutsideAngular)="emojiLeaveOutsideAngular.emit($event)" (emojiClickOutsideAngular)="emojiClickOutsideAngular.emit($event)" ></ngx-emoji> </ng-template> `, changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: false, standalone: true, imports: [CommonModule, EmojiComponent], }] }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i1.EmojiService }, { type: EmojiFrequentlyService }]; }, propDecorators: { emojis: [{ type: Input }], hasStickyPosition: [{ type: Input }], name: [{ type: Input }], perLine: [{ type: Input }], totalFrequentLines: [{ type: Input }], recent: [{ type: Input }], custom: [{ type: Input }], i18n: [{ type: Input }], id: [{ type: Input }], hideObsolete: [{ type: Input }], notFoundEmoji: [{ type: Input }], virtualize: [{ type: Input }], virtualizeOffset: [{ type: Input }], emojiIsNative: [{ type: Input }], emojiSkin: [{ type: Input }], emojiSize: [{ type: Input }], emojiSet: [{ type: Input }], emojiSheetSize: [{ type: Input }], emojiForceSize: [{ type: Input }], emojiTooltip: [{ type: Input }], emojiBackgroundImageFn: [{ type: Input }], emojiImageUrlFn: [{ type: Input }], emojiUseButton: [{ type: Input }], emojiOverOutsideAngular: [{ type: Output }], emojiLeaveOutsideAngular: [{ type: Output }], emojiClickOutsideAngular: [{ type: Output }], container: [{ type: ViewChild, args: ['container', { static: true }] }], label: [{ type: ViewChild, args: ['label', { static: true }] }] } }); function uniq(arr) { return arr.reduce((acc, item) => { if (!acc.includes(item)) { acc.push(item); } return acc; }, []); } function intersect(a, b) { const uniqA = uniq(a); const uniqB = uniq(b); return uniqA.filter((item) => uniqB.indexOf(item) >= 0); } // https://github.com/sonicdoe/measure-scrollbar function measureScrollbar() { if (typeof document === 'undefined') { return 0; } const div = document.createElement('div'); div.style.width = '100px'; div.style.height = '100px'; div.style.overflow = 'scroll'; div.style.position = 'absolute'; div.style.top = '-9999px'; document.body.appendChild(div); const scrollbarWidth = div.offsetWidth - div.clientWidth; document.body.removeChild(div); return scrollbarWidth; } class EmojiSearch { emojiService; originalPool = {}; index = {}; emojisList = {}; emoticonsList = {}; emojiSearch = {}; constructor(emojiService) { this.emojiService = emojiService; for (const emojiData of this.emojiService.emojis) { const { shortNames, emoticons } = emojiData; const id = shortNames[0]; for (const emoticon of emoticons) { if (this.emoticonsList[emoticon]) { continue; } this.emoticonsList[emoticon] = id; } this.emojisList[id] = this.emojiService.getSanitizedData(id); this.originalPool[id] = emojiData; } } addCustomToPool(custom, pool) { for (const emoji of custom) { const emojiId = emoji.id || emoji.shortNames[0]; if (emojiId && !pool[emojiId]) { pool[emojiId] = this.emojiService.getData(emoji); this.emojisList[emojiId] = this.emojiService.getSanitizedData(emoji); } } } search(value, emojisToShowFilter, maxResults = 75, include = [], exclude = [], custom = []) { this.addCustomToPool(custom, this.originalPool); let results; let pool = this.originalPool; if (value.length) { if (value === '-' || value === '-1') { return [this.emojisList['-1']]; } if (value === '+' || value === '+1') { return [this.emojisList['+1']]; } let values = value.toLowerCase().split(/[\s|,|\-|_]+/); let allResults = []; if (values.length > 2) { values = [values[0], values[1]]; } if (include.length || exclude.length) { pool = {}; for (const category of categories$1 || []) { const isIncluded = include && include.length ? include.indexOf(category.id) > -1 : true; const isExcluded = exclude && exclude.length ? exclude.indexOf(category.id) > -1 : false; if (!isIncluded || isExcluded) { continue; } for (const emojiId of category.emojis || []) { // Need to make sure that pool gets keyed // with the correct id, which is why we call emojiService.getData below const emoji = this.emojiService.getData(emojiId); pool[emoji?.id ?? ''] = emoji; } } if (custom.length) { const customIsIncluded = include && include.length ? include.indexOf('custom') > -1 : true; const customIsExcluded = exclude && exclude.length ? exclude.indexOf('custom') > -1 : false; if (customIsIncluded && !customIsExcluded) { this.addCustomToPool(custom, pool); } } } allResults = values .map(v => { let aPool = pool; let aIndex = this.index; let length = 0; for (let charIndex = 0; charIndex < v.length; charIndex++) { const char = v[charIndex]; length++; if (!aIndex[char]) { aIndex[char] = {}; } aIndex = aIndex[char]; if (!aIndex.results) { const scores = {}; aIndex.results = []; aIndex.pool = {}; for (const id of Object.keys(aPool)) { const emoji = aPool[id]; if (!this.emojiSearch[id]) { this.emojiSearch[id] = this.buildSearch(emoji.short_names, emoji.name, emoji.id, emoji.keywords, emoji.emoticons); } const query = this.emojiSearch[id]; const sub = v.substr(0, length); const subIndex = query.indexOf(sub); if (subIndex !== -1) { let score = subIndex + 1; if (sub === id) { score = 0; } aIndex.results.push(this.emojisList[id]); aIndex.pool[id] = emoji; scores[id] = score; } } aIndex.results.sort((a, b) => { const aScore = scores[a.id]; const bScore = scores[b.id]; return aScore - bScore; }); } aPool = aIndex.pool; } return aIndex.results; }) .filter(a => a); if (allResults.length > 1) { results = intersect.apply(null, allResults); } else if (allResults.length) { results = allResults[0]; } else { results = []; } } if (results) { if (emojisToShowFilter) { results = results.filter((result) => { if (result && result.id) { return emojisToShowFilter(this.emojiService.names[result.id]); } return false; }); } if (results && results.length > maxResults) { results = results.slice(0, maxResults); } } return results || null; } buildSearch(shortNames, name, id, keywords, emoticons) { const search = []; const addToSearch = (strings, split) => { if (!strings) { return; } const arr = Array.isArray(strings) ? strings : [strings]; for (const str of arr) { const substrings = split ? str.split(/[-|_|\s]+/) : [str]; for (let s of substrings) { s = s.toLowerCase(); if (!search.includes(s)) { search.push(s); } } } }; addToSearch(shortNames, true); addToSearch(name, true); addToSearch(id, true); addToSearch(keywords, true); addToSearch(emoticons, false); return search.join(','); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: EmojiSearch, deps: [{ token: i1.EmojiService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: EmojiSearch, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: EmojiSearch, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: i1.EmojiService }]; } }); class SkinComponent { /** currently selected skin */ skin; i18n; changeSkin = new EventEmitter(); opened = false; skinTones = [1, 2, 3, 4, 5, 6]; toggleOpen() { this.opened = !this.opened; } isSelected(skinTone) { return skinTone === this.skin; } isVisible(skinTone) { return this.opened || this.isSelected(skinTone); } pressed(skinTone) { return this.opened ? !!this.isSelected(skinTone) : ''; } tabIndex(skinTone) { return this.isVisible(skinTone) ? '0' : ''; } expanded(skinTone) { return this.isSelected(skinTone) ? this.opened : ''; } handleClick(skin) { if (!this.opened) { this.opened = true; return; } this.opened = false; if (skin !== this.skin) { this.changeSkin.emit(skin); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: SkinComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: SkinComponent, isStandalone: true, selector: "emoji-skins", inputs: { skin: "skin", i18n: "i18n" }, outputs: { changeSkin: "changeSkin" }, ngImport: i0, template: ` <section class="emoji-mart-skin-swatches" [class.opened]="opened"> <span *ngFor="let skinTone of skinTones" class="emoji-mart-skin-swatch" [class.selected]="skinTone === skin" > <span (click)="handleClick(skinTone)" (keyup.enter)="handleClick(skinTone)" (keyup.space)="handleClick(skinTone)" class="emoji-mart-skin emoji-mart-skin-tone-{{ skinTone }}" role="button" [tabIndex]="tabIndex(skinTone)" [attr.aria-hidden]="!isVisible(skinTone)" [attr.aria-pressed]="pressed(skinTone)" [attr.aria-haspopup]="!!isSelected(skinTone)" [attr.aria-expanded]="expanded(skinTone)" [attr.aria-label]="i18n.skintones[skinTone]" [attr.title]="i18n.skintones[skinTone]" ></span> </span> </section> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: SkinComponent, decorators: [{ type: Component, args: [{ selector: 'emoji-skins', template: ` <section class="emoji-mart-skin-swatches" [class.opened]="opened"> <span *ngFor="let skinTone of skinTones" class="emoji-mart-skin-swatch" [class.selected]="skinTone === skin" > <span (click)="handleClick(skinTone)" (keyup.enter)="handleClick(skinTone)" (keyup.space)="handleClick(skinTone)" class="emoji-mart-skin emoji-mart-skin-tone-{{ skinTone }}" role="button" [tabIndex]="tabIndex(skinTone)" [attr.aria-hidden]="!isVisible(skinTone)" [attr.aria-pressed]="pressed(skinTone)" [attr.aria-haspopup]="!!isSelected(skinTone)" [attr.aria-expanded]="expanded(skinTone)" [attr.aria-label]="i18n.skintones[skinTone]" [attr.title]="i18n.skintones[skinTone]" ></span> </span> </section> `, changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: false, standalone: true, imports: [CommonModule], }] }], propDecorators: { skin: [{ type: Input }], i18n: [{ type: Input }], changeSkin: [{ type: Output }] } }); class PreviewComponent { ref; emojiService; title; emoji; idleEmoji; i18n; emojiIsNative; emojiSkin; emojiSize; emojiSet; emojiSheetSize; emojiBackgroundImageFn; emojiImageUrlFn; skinChange = new EventEmitter(); emojiData = {}; listedEmoticons; constructor(ref, emojiService) { this.ref = ref; this.emojiService = emojiService; } ngOnChanges() { if (!this.emoji) { return; } this.emojiData = this.emojiService.getData(this.emoji, this.emojiSkin, this.emojiSet); const knownEmoticons = []; const listedEmoticons = []; const emoitcons = this.emojiData.emoticons || []; emoitcons.forEach((emoticon) => { if (knownEmoticons.indexOf(emoticon.toLowerCase()) >= 0) { return; } knownEmoticons.push(emoticon.toLowerCase()); listedEmoticons.push(emoticon); }); this.listedEmoticons = listedEmoticons; this.ref?.detectChanges(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PreviewComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1.EmojiService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: PreviewComponent, isStandalone: true, selector: "emoji-preview", inputs: { title: "title", emoji: "emoji", idleEmoji: "idleEmoji", i18n: "i18n", emojiIsNative: "emojiIsNative", emojiSkin: "emojiSkin", emojiSize: "emojiSize", emojiSet: "emojiSet", emojiSheetSize: "emojiSheetSize", emojiBackgroundImageFn: "emojiBackgroundImageFn", emojiImageUrlFn: "emojiImageUrlFn" }, outputs: { skinChange: "skinChange" }, usesOnChanges: true, ngImport: i0, template: ` <div class="emoji-mart-preview" *ngIf="emoji && emojiData"> <div class="emoji-mart-preview-emoji"> <ngx-emoji [emoji]="emoji" [size]="38" [isNative]="emojiIsNative" [skin]="emojiSkin" [size]="emojiSize" [set]="emojiSet" [sheetSize]="emojiSheetSize" [backgroundImageFn]="emojiBackgroundImageFn" [imageUrlFn]="emojiImageUrlFn" ></ngx-emoji> </div> <div class="emoji-mart-preview-data"> <div class="emoji-mart-preview-name">{{ emojiData.name }}</div> <div class="emoji-mart-preview-shortname"> <span class="emoji-mart-preview-shortname" *ngFor="let short_name of emojiData.shortNames" > :{{ short_name }}: </span> </div> <div class="emoji-mart-preview-emoticons"> <span class="emoji-mart-preview-emoticon" *ngFor="let emoticon of listedEmoticons"> {{ emoticon }} </span> </div> </div> </div> <div class="emoji-mart-preview" [hidden]="emoji"> <div class="emoji-mart-preview-emoji"> <ngx-emoji *ngIf="idleEmoji && idleEmoji.length" [isNative]="emojiIsNative" [skin]="emojiSkin" [set]="emojiSet" [emoji]="idleEmoji" [backgroundImageFn]="emojiBackgroundImageFn" [size]="38" [imageUrlFn]="emojiImageUrlFn" ></ngx-emoji> </div> <div class="emoji-mart-preview-data"> <span class="emoji-mart-title-label">{{ title }}</span> </div> <div class="emoji-mart-preview-skins"> <emoji-skins [skin]="emojiSkin" (changeSkin)="skinChange.emit($event)" [i18n]="i18n" ></emoji-skins> </div> </div> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: EmojiComponent, selector: "ngx-emoji", inputs: ["skin", "set", "sheetSize", "isNative", "forceSize", "tooltip", "size", "emoji", "fallback", "hideObsolete", "sheetRows", "sheetColumns", "useButton", "backgroundImageFn", "imageUrlFn"], outputs: ["emojiOver", "emojiOverOutsideAngular", "emojiLeave", "emojiLeaveOutsideAngular", "emojiClick", "emojiClickOutsideAngular"] }, { kind: "component", type: SkinComponent, selector: "emoji-skins", inputs: ["skin", "i18n"], outputs: ["changeSkin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PreviewComponent, decorators: [{ type: Component, args: [{ selector: 'emoji-preview', template: ` <div class="emoji-mart-preview" *ngIf="emoji && emojiData"> <div class="emoji-mart-preview-emoji"> <ngx-emoji [emoji]="emoji" [size]="38" [isNative]="emojiIsNative" [skin]="emojiSkin" [size]="emojiSize" [set]="emojiSet" [sheetSize]="emojiSheetSize" [backgroundImageFn]="emojiBackgroundImageFn" [imageUrlFn]="emojiImageUrlFn" ></ngx-emoji> </div> <div class="emoji-mart-preview-data"> <div class="emoji-mart-preview-name">{{ emojiData.name }}</div> <div class="emoji-mart-preview-shortname"> <span class="emoji-mart-preview-shortname" *ngFor="let short_name of emojiData.shortNames" > :{{ short_name }}: </span> </div> <div class="emoji-mart-preview-emoticons"> <span class="emoji-mart-preview-emoticon" *ngFor="let emoticon of listedEmoticons"> {{ emoticon }} </span> </div> </div> </div> <div class="emoji-mart-preview" [hidden]="emoji"> <div class="emoji-mart-preview-emoji"> <ngx-emoji *ngIf="idleEmoji && idleEmoji.length" [isNative]="emojiIsNative" [skin]="emojiSkin" [set]="emojiSet" [emoji]="idleEmoji" [backgroundImageFn]="emojiBackgroundImageFn" [size]="38" [imageUrlFn]="emojiImageUrlFn" ></ngx-emoji> </div> <div class="emoji-mart-preview-data"> <span class="emoji-mart-title-label">{{ title }}</span> </div> <div class="emoji-mart-preview-skins"> <emoji-skins [skin]="emojiSkin" (changeSkin)="skinChange.emit($event)" [i18n]="i18n" ></emoji-skins> </div> </div> `, changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: false, standalone: true, imports: [CommonModule, EmojiComponent, SkinComponent], }] }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i1.EmojiService }]; }, propDecorators: { title: [{ type: Input }], emoji: [{ type: Input }], idleEmoji: [{ type: Input }], i18n: [{ type: Input }], emojiIsNative: [{ type: Input }], emojiSkin: [{ type: Input }], emojiSize: [{ type: Input }], emojiSet: [{ type: Input }], emojiSheetSize: [{ type: Input }], emojiBackgroundImageFn: [{ type: Input }], emojiImageUrlFn: [{ type: Input }], skinChange: [{ type: Output }] } }); let id = 0; class SearchComponent { ngZone; emojiSearch; maxResults = 75; autoFocus = false; i18n; include = []; exclude = []; custom = []; icons; emojisToShowFilter; searchResults = new EventEmitter(); enterKeyOutsideAngular = new EventEmitter(); inputRef; isSearching = false; icon; query = ''; inputId = `emoji-mart-search-${++id}`; destroy$ = new Subject(); constructor(ngZone, emojiSearch) { this.ngZone = ngZone; this.emojiSearch = emojiSearch; } ngOnInit() { this.icon = this.icons.search; this.setupKeyupListener(); } ngAfterViewInit() { if (this.autoFocus) { this.inputRef.nativeElement.focus(); } } ngOnDestroy() { this.destroy$.next(); } clear() { this.query = ''; this.handleSearch(''); this.inputRef.nativeElement.focus(); } handleSearch(value) { if (value === '') { this.icon = this.icons.search; this.isSearching = false; } else { this.icon = this.icons.delete; this.isSearching = true; } const emojis = this.emojiSearch.search(this.query, this.emojisToShowFilter, this.maxResults, this.include, this.exclude, this.custom); this.searchResults.emit(emojis); } handleChange() { this.handleSearch(this.query); } setupKeyupListener() { this.ngZone.runOutsideAngular(() => fromEvent(this.inputRef.nativeElement, 'keyup') .pipe(takeUntil(this.destroy$)) .subscribe($event => { if (!this.query || $event.key !== 'Enter') { return; } this.enterKeyOutsideAngular.emit($event); $event.preventDefault(); })); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: SearchComponent, deps: [{ token: i0.NgZone }, { token: EmojiSearch }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: SearchComponent, isStandalone: true, selector: "emoji-search", inputs: { maxResults: "maxResults", autoFocus: "autoFocus", i18n: "i18n", include: "include", exclude: "exclude", custom: "custom", icons: "icons", emojisToShowFilter: "emojisToShowFilter" }, outputs: { searchResults: "searchResults", enterKeyOutsideAngular: "enterKeyOutsideAngular" }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputRef"], descendants: true, static: true }], ngImport: i0, template: ` <div class="emoji-mart-search"> <input [id]="inputId" #inputRef type="search" [placeholder]="i18n.search" [autofocus]="autoFocus" [(ngModel)]="query" (ngModelChange)="handleChange()" /> <!-- Use a <label> in addition to the placeholder for accessibility, but place it off-screen http://www.maxability.co.in/2016/01/placeholder-attribute-and-why-it-is-not-accessible/ --> <label class="emoji-mart-sr-only" [htmlFor]="inputId"> {{ i18n.search }} </label> <button type="button" class="emoji-mart-search-icon" (click)="clear()" (keyup.enter)="clear()" [disabled]="!isSearching" [attr.aria-label]="i18n.clear" > <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="13" height="13" opacity="0.5" > <path [attr.d]="icon" /> </svg> </button> </div> `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: SearchComponent, decorators: [{ type: Component, args: [{ selector: 'emoji-search', template: ` <div class="emoji-mart-search"> <input [id]="inputId" #inputRef type="search" [placeholder]="i18n.search" [autofocus]="autoFocus" [(ngModel)]="query" (ngModelChange)="handleChange()" /> <!-- Use a <label> in addition to the placeholder for accessibility, but place it off-screen http://www.maxability.co.in/2016/01/placeholder-attribute-and-why-it-is-not-accessible/ --> <label class="emoji-mart-sr-only" [htmlFor]="inputId"> {{ i18n.search }} </label> <button type="button" class="emoji-mart-search-icon" (click)="clear()" (keyup.enter)="clear()" [disabled]="!isSearching" [attr.aria-label]="i18n.clear" > <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="13" height="13" opacity="0.5" > <path [attr.d]="icon" /> </svg> </button> </div> `, preserveWhitespaces: false, standalone: true, imports: [FormsModule], }] }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: EmojiSearch }]; }, propDecorators: { maxResults: [{ type: Input }], autoFocus: [{ type: Input }], i18n: [{ type: Input }], include: [{ type: Input }], exclude: [{ type: Input }], custom: [{ type: Input }], icons: [{ type: Input }], emojisToShowFilter: [{ type: Input }], searchResults: [{ type: Output