11/*
22Copyright 2019 Tulir Asokan <[email protected] > 3+ Copyright 2020 The Matrix.org Foundation C.I.C.
34
45Licensed under the Apache License, Version 2.0 (the "License");
56you may not use this file except in compliance with the License.
@@ -15,25 +16,43 @@ limitations under the License.
1516*/
1617
1718import React from 'react' ;
18- import PropTypes from 'prop-types' ;
1919
20- import * as sdk from '../../../index' ;
2120import { _t } from '../../../languageHandler' ;
22-
2321import * as recent from '../../../emojipicker/recent' ;
24- import { DATA_BY_CATEGORY , getEmojiFromUnicode } from "../../../emoji" ;
22+ import { DATA_BY_CATEGORY , getEmojiFromUnicode , IEmoji } from "../../../emoji" ;
2523import AutoHideScrollbar from "../../structures/AutoHideScrollbar" ;
24+ import Header from "./Header" ;
25+ import Search from "./Search" ;
26+ import Preview from "./Preview" ;
27+ import QuickReactions from "./QuickReactions" ;
28+ import Category , { ICategory , CategoryKey } from "./Category" ;
2629
2730export const CATEGORY_HEADER_HEIGHT = 22 ;
2831export const EMOJI_HEIGHT = 37 ;
2932export const EMOJIS_PER_ROW = 8 ;
3033
31- class EmojiPicker extends React . Component {
32- static propTypes = {
33- onChoose : PropTypes . func . isRequired ,
34- selectedEmojis : PropTypes . instanceOf ( Set ) ,
35- showQuickReactions : PropTypes . bool ,
36- } ;
34+ interface IProps {
35+ selectedEmojis : Set < string > ;
36+ showQuickReactions ?: boolean ;
37+ onChoose ( unicode : string ) : boolean ;
38+ }
39+
40+ interface IState {
41+ filter : string ;
42+ previewEmoji ?: IEmoji ;
43+ scrollTop : number ;
44+ // initial estimation of height, dialog is hardcoded to 450px height.
45+ // should be enough to never have blank rows of emojis as
46+ // 3 rows of overflow are also rendered. The actual value is updated on scroll.
47+ viewportHeight : number ;
48+ }
49+
50+ class EmojiPicker extends React . Component < IProps , IState > {
51+ private readonly recentlyUsed : IEmoji [ ] ;
52+ private readonly memoizedDataByCategory : Record < CategoryKey , IEmoji [ ] > ;
53+ private readonly categories : ICategory [ ] ;
54+
55+ private bodyRef = React . createRef < HTMLDivElement > ( ) ;
3756
3857 constructor ( props ) {
3958 super ( props ) ;
@@ -42,9 +61,6 @@ class EmojiPicker extends React.Component {
4261 filter : "" ,
4362 previewEmoji : null ,
4463 scrollTop : 0 ,
45- // initial estimation of height, dialog is hardcoded to 450px height.
46- // should be enough to never have blank rows of emojis as
47- // 3 rows of overflow are also rendered. The actual value is updated on scroll.
4864 viewportHeight : 280 ,
4965 } ;
5066
@@ -110,18 +126,9 @@ class EmojiPicker extends React.Component {
110126 visible : false ,
111127 ref : React . createRef ( ) ,
112128 } ] ;
113-
114- this . bodyRef = React . createRef ( ) ;
115-
116- this . onChangeFilter = this . onChangeFilter . bind ( this ) ;
117- this . onHoverEmoji = this . onHoverEmoji . bind ( this ) ;
118- this . onHoverEmojiEnd = this . onHoverEmojiEnd . bind ( this ) ;
119- this . onClickEmoji = this . onClickEmoji . bind ( this ) ;
120- this . scrollToCategory = this . scrollToCategory . bind ( this ) ;
121- this . updateVisibility = this . updateVisibility . bind ( this ) ;
122129 }
123130
124- onScroll = ( ) => {
131+ private onScroll = ( ) => {
125132 const body = this . bodyRef . current ;
126133 this . setState ( {
127134 scrollTop : body . scrollTop ,
@@ -130,7 +137,7 @@ class EmojiPicker extends React.Component {
130137 this . updateVisibility ( ) ;
131138 } ;
132139
133- updateVisibility ( ) {
140+ private updateVisibility = ( ) => {
134141 const body = this . bodyRef . current ;
135142 const rect = body . getBoundingClientRect ( ) ;
136143 for ( const cat of this . categories ) {
@@ -147,21 +154,21 @@ class EmojiPicker extends React.Component {
147154 // We update this here instead of through React to avoid re-render on scroll.
148155 if ( cat . visible ) {
149156 cat . ref . current . classList . add ( "mx_EmojiPicker_anchor_visible" ) ;
150- cat . ref . current . setAttribute ( "aria-selected" , true ) ;
151- cat . ref . current . setAttribute ( "tabindex" , 0 ) ;
157+ cat . ref . current . setAttribute ( "aria-selected" , " true" ) ;
158+ cat . ref . current . setAttribute ( "tabindex" , "0" ) ;
152159 } else {
153160 cat . ref . current . classList . remove ( "mx_EmojiPicker_anchor_visible" ) ;
154- cat . ref . current . setAttribute ( "aria-selected" , false ) ;
155- cat . ref . current . setAttribute ( "tabindex" , - 1 ) ;
161+ cat . ref . current . setAttribute ( "aria-selected" , " false" ) ;
162+ cat . ref . current . setAttribute ( "tabindex" , "-1" ) ;
156163 }
157164 }
158- }
165+ } ;
159166
160- scrollToCategory ( category ) {
167+ private scrollToCategory = ( category : string ) => {
161168 this . bodyRef . current . querySelector ( `[data-category-id="${ category } "]` ) . scrollIntoView ( ) ;
162- }
169+ } ;
163170
164- onChangeFilter ( filter ) {
171+ private onChangeFilter = ( filter : string ) => {
165172 filter = filter . toLowerCase ( ) ; // filter is case insensitive stored lower-case
166173 for ( const cat of this . categories ) {
167174 let emojis ;
@@ -181,53 +188,72 @@ class EmojiPicker extends React.Component {
181188 // Header underlines need to be updated, but updating requires knowing
182189 // where the categories are, so we wait for a tick.
183190 setTimeout ( this . updateVisibility , 0 ) ;
184- }
191+ } ;
192+
193+ private onEnterFilter = ( ) => {
194+ const btn = this . bodyRef . current . querySelector < HTMLButtonElement > ( ".mx_EmojiPicker_item" ) ;
195+ if ( btn ) {
196+ btn . click ( ) ;
197+ }
198+ } ;
185199
186- onHoverEmoji ( emoji ) {
200+ private onHoverEmoji = ( emoji : IEmoji ) => {
187201 this . setState ( {
188202 previewEmoji : emoji ,
189203 } ) ;
190- }
204+ } ;
191205
192- onHoverEmojiEnd ( emoji ) {
206+ private onHoverEmojiEnd = ( emoji : IEmoji ) => {
193207 this . setState ( {
194208 previewEmoji : null ,
195209 } ) ;
196- }
210+ } ;
197211
198- onClickEmoji ( emoji ) {
212+ private onClickEmoji = ( emoji : IEmoji ) => {
199213 if ( this . props . onChoose ( emoji . unicode ) !== false ) {
200214 recent . add ( emoji . unicode ) ;
201215 }
202- }
216+ } ;
203217
204- _categoryHeightForEmojiCount ( count ) {
218+ private static categoryHeightForEmojiCount ( count : number ) {
205219 if ( count === 0 ) {
206220 return 0 ;
207221 }
208222 return CATEGORY_HEADER_HEIGHT + ( Math . ceil ( count / EMOJIS_PER_ROW ) * EMOJI_HEIGHT ) ;
209223 }
210224
211225 render ( ) {
212- const Header = sdk . getComponent ( "emojipicker.Header" ) ;
213- const Search = sdk . getComponent ( "emojipicker.Search" ) ;
214- const Category = sdk . getComponent ( "emojipicker.Category" ) ;
215- const Preview = sdk . getComponent ( "emojipicker.Preview" ) ;
216- const QuickReactions = sdk . getComponent ( "emojipicker.QuickReactions" ) ;
217226 let heightBefore = 0 ;
218227 return (
219228 < div className = "mx_EmojiPicker" >
220- < Header categories = { this . categories } defaultCategory = "recent" onAnchorClick = { this . scrollToCategory } />
221- < Search query = { this . state . filter } onChange = { this . onChangeFilter } />
222- < AutoHideScrollbar className = "mx_EmojiPicker_body" wrappedRef = { e => this . bodyRef . current = e } onScroll = { this . onScroll } >
229+ < Header categories = { this . categories } onAnchorClick = { this . scrollToCategory } />
230+ < Search query = { this . state . filter } onChange = { this . onChangeFilter } onEnter = { this . onEnterFilter } />
231+ < AutoHideScrollbar
232+ className = "mx_EmojiPicker_body"
233+ wrappedRef = { ref => {
234+ // @ts -ignore - AutoHideScrollbar should accept a RefObject or fall back to its own instead
235+ this . bodyRef . current = ref
236+ } }
237+ onScroll = { this . onScroll }
238+ >
223239 { this . categories . map ( category => {
224240 const emojis = this . memoizedDataByCategory [ category . id ] ;
225- const categoryElement = ( < Category key = { category . id } id = { category . id } name = { category . name }
226- heightBefore = { heightBefore } viewportHeight = { this . state . viewportHeight }
227- scrollTop = { this . state . scrollTop } emojis = { emojis } onClick = { this . onClickEmoji }
228- onMouseEnter = { this . onHoverEmoji } onMouseLeave = { this . onHoverEmojiEnd }
229- selectedEmojis = { this . props . selectedEmojis } /> ) ;
230- const height = this . _categoryHeightForEmojiCount ( emojis . length ) ;
241+ const categoryElement = ( (
242+ < Category
243+ key = { category . id }
244+ id = { category . id }
245+ name = { category . name }
246+ heightBefore = { heightBefore }
247+ viewportHeight = { this . state . viewportHeight }
248+ scrollTop = { this . state . scrollTop }
249+ emojis = { emojis }
250+ onClick = { this . onClickEmoji }
251+ onMouseEnter = { this . onHoverEmoji }
252+ onMouseLeave = { this . onHoverEmojiEnd }
253+ selectedEmojis = { this . props . selectedEmojis }
254+ />
255+ ) ) ;
256+ const height = EmojiPicker . categoryHeightForEmojiCount ( emojis . length ) ;
231257 heightBefore += height ;
232258 return categoryElement ;
233259 } ) }
0 commit comments