├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── css
└── emoji-mart.css
├── data
├── all.json
├── apple.json
├── emojione.json
├── facebook.json
├── google.json
├── messenger.json
└── twitter.json
├── docs
├── app.vue
├── bundle.js
├── images
│ └── parrot.gif
├── index.html
├── index.js
└── webpack.config.js
├── karma.conf.js
├── package-lock.json
├── package.json
├── scripts
├── build-data.js
├── build.js
└── define.js
├── spec
├── emoji-index-spec.js
├── picker-spec.js
└── webpack.config.js
├── src
├── components
│ ├── anchors.old.js
│ ├── anchors.vue
│ ├── category.old.js
│ ├── category.vue
│ ├── emoji
│ │ ├── emoji.js
│ │ ├── emoji.vue
│ │ ├── nimble-emoji.js
│ │ └── nimbleEmoji.vue
│ ├── index.js
│ ├── picker
│ │ ├── nimble-picker.js
│ │ ├── nimblePicker.vue
│ │ ├── picker.js
│ │ └── picker.vue
│ ├── preview.old.js
│ ├── preview.vue
│ ├── search.old.js
│ ├── search.vue
│ ├── skins.old.js
│ └── skins.vue
├── index.js
├── polyfills
│ ├── createClass.js
│ ├── extends.js
│ ├── inherits.js
│ ├── objectGetPrototypeOf.js
│ ├── possibleConstructorReturn.js
│ └── stringFromCodePoint.js
├── svgs
│ └── index.js
├── utils
│ ├── data.js
│ ├── emoji-index
│ │ ├── emoji-index.js
│ │ └── nimble-emoji-index.js
│ ├── frequently.js
│ ├── index.js
│ ├── shared-props.js
│ └── store.js
├── vendor
│ └── raf-polyfill.js
└── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"],
3 | "plugins": [
4 | "check-es2015-constants",
5 | "transform-es2015-arrow-functions",
6 | "transform-es2015-block-scoped-functions",
7 | "transform-es2015-block-scoping",
8 | "transform-es2015-classes",
9 | "transform-es2015-computed-properties",
10 | "transform-es2015-destructuring",
11 | "transform-es2015-duplicate-keys",
12 | "transform-es2015-for-of",
13 | "transform-es2015-function-name",
14 | "transform-es2015-literals",
15 | "transform-es2015-object-super",
16 | "transform-es2015-parameters",
17 | "transform-es2015-shorthand-properties",
18 | "transform-es2015-spread",
19 | "transform-es2015-sticky-regex",
20 | "transform-es2015-template-literals",
21 | "transform-es2015-unicode-regex",
22 | "transform-regenerator",
23 |
24 | "transform-object-rest-spread",
25 | "transform-runtime",
26 | [
27 | "transform-define", "scripts/define.js"
28 | ],
29 | [
30 | "module-resolver",
31 | {
32 | "alias": {
33 | "babel-runtime/core-js/object/get-prototype-of": "./src/polyfills/objectGetPrototypeOf",
34 | "babel-runtime/helpers/extends": "./src/polyfills/extends",
35 | "babel-runtime/helpers/inherits": "./src/polyfills/inherits",
36 | "babel-runtime/helpers/createClass": "./src/polyfills/createClass",
37 | "babel-runtime/helpers/possibleConstructorReturn": "./src/polyfills/possibleConstructorReturn"
38 | }
39 | }
40 | ]
41 | ],
42 | "env": {
43 | "cjs": {
44 | "plugins": [
45 | "transform-es2015-modules-commonjs"
46 | ]
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | dist-es/
4 | stats.json
5 | report.html
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | dist/report.html
2 | scripts/
3 | .*
4 |
5 | src/
6 | docs/
7 | spec/
8 | example/
9 | karma.conf.js
10 | yarn.lock
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Missive
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 |
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 |
10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > This project has been forked from [emoji-mart](https://www.npmjs.com/package/emoji-mart) which was written for React
2 |
3 |
4 |
Emoji Mart (Vue) is a Slack-like customizable
emoji picker component for VueJS
5 |
Demo •
Changelog
6 |

7 |
8 |
9 | ## Installation
10 |
11 | `npm install --save emoji-mart-vue`
12 |
13 | ## Components
14 | ### Picker
15 | ```js
16 | import { Picker } from 'emoji-mart-vue'
17 | ```
18 |
19 | ```html
20 |
21 |
22 |
23 |
24 |
25 | ```
26 |
27 | | Prop | Required | Default | Description |
28 | | ---- | :------: | ------- | ----------- |
29 | | **autoFocus** | | `false` | Auto focus the search input when mounted |
30 | | **color** | | `#ae65c5` | The top bar anchors select and hover color |
31 | | **emoji** | | `department_store` | The emoji shown when no emojis are hovered, set to an empty string to show nothing |
32 | | **include** | | `[]` | Only load included categories. Accepts [I18n categories keys](#i18n). Order will be respected, except for the `recent` category which will always be the first. |
33 | | **exclude** | | `[]` | Don't load excluded categories. Accepts [I18n categories keys](#i18n). |
34 | | **custom** | | `[]` | [Custom emojis](#custom-emojis) |
35 | | **recent** | | | Pass your own frequently used emojis as array of string IDs |
36 | | **emojiSize** | | `24` | The emoji width and height |
37 | | **perLine** | | `9` | Number of emojis per line. While there’s no minimum or maximum, this will affect the picker’s width. This will set *Frequently Used* length as well (`perLine * 4`) |
38 | | **i18n** | | [`{…}`](#i18n) | [An object](#i18n) containing localized strings |
39 | | **native** | | `false` | Renders the native unicode emoji |
40 | | **set** | | `apple` | The emoji set: `'apple', 'google', 'twitter', 'emojione', 'messenger', 'facebook'` |
41 | | **sheetSize** | | `64` | The emoji [sheet size](#sheet-sizes): `16, 20, 32, 64` |
42 | | **backgroundImageFn** | | ```((set, sheetSize) => …)``` | A Fn that returns that image sheet to use for emojis. Useful for avoiding a request if you have the sheet locally. |
43 | | **emojisToShowFilter** | | ```((emoji) => true)``` | A Fn to choose whether an emoji should be displayed or not |
44 | | **showPreview** | | `true` | Display preview section |
45 | | **showSearch** | | `true` | Display search section |
46 | | **showCategories** | | `true` | Display categories |
47 | | **showSkinTones** | | `true` | Display skin tones picker |
48 | | **emojiTooltip** | | `false` | Show emojis short name when hovering (title) |
49 | | **skin** | | | Forces skin color: `1, 2, 3, 4, 5, 6` |
50 | | **defaultSkin** | | `1` | Default skin color: `1, 2, 3, 4, 5, 6` |
51 | | **pickerStyles** | | | Inline styles applied to the root element. Useful for positioning |
52 | | **title** | | `Emoji Mart™` | The title shown when no emojis are hovered |
53 | | **infiniteScroll** | | `true` | Scroll continuously through the categories |
54 |
55 |
56 | | Event | Description |
57 | | ----- | ----------- |
58 | | **select** | Params: `(emoji) => {}` |
59 | | **skin-change** | Params: `(skin) => {}` |
60 |
61 |
62 | #### I18n
63 | ```js
64 | search: 'Search',
65 | notfound: 'No Emoji Found',
66 | categories: {
67 | search: 'Search Results',
68 | recent: 'Frequently Used',
69 | people: 'Smileys & People',
70 | nature: 'Animals & Nature',
71 | foods: 'Food & Drink',
72 | activity: 'Activity',
73 | places: 'Travel & Places',
74 | objects: 'Objects',
75 | symbols: 'Symbols',
76 | flags: 'Flags',
77 | custom: 'Custom',
78 | }
79 | ```
80 |
81 | #### Sheet sizes
82 | Sheets are served from [unpkg](https://unpkg.com), a global CDN that serves files published to [npm](https://www.npmjs.com).
83 |
84 | | Set | Size (`sheetSize: 16`) | Size (`sheetSize: 20`) | Size (`sheetSize: 32`) | Size (`sheetSize: 64`) |
85 | | --------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- |
86 | | apple | 334 KB | 459 KB | 1.08 MB | 2.94 MB |
87 | | emojione | 315 KB | 435 KB | 1020 KB | 2.33 MB |
88 | | facebook | 322 KB | 439 KB | 1020 KB | 2.50 MB |
89 | | google | 301 KB | 409 KB | 907 KB | 2.17 MB |
90 | | messenger | 325 KB | 449 MB | 1.05 MB | 2.69 MB |
91 | | twitter | 288 KB | 389 KB | 839 KB | 1.82 MB |
92 |
93 | #### Datasets
94 | While all sets are available by default, you may want to include only a single set data to reduce the size of your bundle.
95 |
96 | | Set | Size (on disk) |
97 | | --------- | -------------- |
98 | | all | 570 KB |
99 | | apple | 484 KB |
100 | | emojione | 485 KB |
101 | | facebook | 421 KB |
102 | | google | 483 KB |
103 | | messenger | 197 KB |
104 | | twitter | 484 KB |
105 |
106 | To use these data files (or any other custom data), use the `NimblePicker` component:
107 |
108 | ```js
109 | import data from 'emoji-mart-vue/data/messenger.json'
110 | import { NimblePicker } from 'emoji-mart-vue'
111 | ```
112 |
113 | ```html
114 |
115 | ```
116 |
117 | #### Examples of `emoji` object:
118 | ```js
119 | {
120 | id: 'smiley',
121 | name: 'Smiling Face with Open Mouth',
122 | colons: ':smiley:',
123 | text: ':)',
124 | emoticons: [
125 | '=)',
126 | '=-)'
127 | ],
128 | skin: null,
129 | native: '😃'
130 | }
131 |
132 | {
133 | id: 'santa',
134 | name: 'Father Christmas',
135 | colons: ':santa::skin-tone-3:',
136 | text: '',
137 | emoticons: [],
138 | skin: 3,
139 | native: '🎅🏼'
140 | }
141 |
142 | {
143 | id: 'octocat',
144 | name: 'Octocat',
145 | colons: ':octocat',
146 | text: '',
147 | emoticons: [],
148 | custom: true,
149 | imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
150 | }
151 |
152 | ```
153 |
154 | ### Emoji
155 | ```js
156 | import { Emoji } from 'emoji-mart-vue'
157 | ```
158 |
159 | ```html
160 |
161 |
162 |
163 | ```
164 |
165 | | Prop | Required | Default | Description |
166 | | ---- | :------: | ------- | ----------- |
167 | | **emoji** | ✓ | | Either a string or an `emoji` object |
168 | | **size** | ✓ | | The emoji width and height. |
169 | | **native** | | `false` | Renders the native unicode emoji |
170 | | [**fallback**](#unsupported-emojis-fallback) | | | Params: `(emoji) => {}` |
171 | | **set** | | `apple` | The emoji set: `'apple', 'google', 'twitter', 'emojione'` |
172 | | **sheetSize** | | `64` | The emoji [sheet size](#sheet-sizes): `16, 20, 32, 64` |
173 | | **backgroundImageFn** | | ```((set, sheetSize) => `https://unpkg.com/emoji-datasource@3.0.0/sheet_${set}_${sheetSize}.png`)``` | A Fn that returns that image sheet to use for emojis. Useful for avoiding a request if you have the sheet locally. |
174 | | **skin** | | `1` | Skin color: `1, 2, 3, 4, 5, 6` |
175 | | **tooltip** | | `false` | Show emoji short name when hovering (title) |
176 |
177 | | Event | Description |
178 | | ----- | ----------- |
179 | | **select** | Params: `(emoji) => {}` |
180 | | **mouseenter** | Params: `(emoji) => {}` |
181 | | **mouseleave** | Params: `(emoji) => {}` |
182 |
183 | #### Unsupported emojis fallback
184 | Certain sets don’t support all emojis (i.e. Messenger & Facebook don’t support `:shrug:`). By default the Emoji component will not render anything so that the emojis’ don’t take space in the picker when not available. When using the standalone Emoji component, you can however render anything you want by providing the `fallback` props.
185 |
186 | To have the component render `:shrug:` you would need to:
187 |
188 | ```js
189 | function emojiFallback(emoji) {
190 | return `:${emoji.short_names[0]}:`
191 | }
192 | ```
193 |
194 | ```html
195 |
201 | ```
202 |
203 | ## Custom emojis
204 | You can provide custom emojis which will show up in their own category.
205 |
206 | ```js
207 | import { Picker } from 'emoji-mart-vue'
208 |
209 | const customEmojis = [
210 | {
211 | name: 'Octocat',
212 | short_names: ['octocat'],
213 | text: '',
214 | emoticons: [],
215 | keywords: ['github'],
216 | imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
217 | }
218 | ]
219 | ```
220 |
221 | ```html
222 |
223 | ```
224 |
225 | ## Headless search
226 | The `Picker` doesn’t have to be mounted for you to take advantage of the advanced search results.
227 |
228 | ```js
229 | import { emojiIndex } from 'emoji-mart-vue'
230 |
231 | emojiIndex.search('christmas').map((o) => o.native)
232 | // => [🎄, 🎅🏼, 🔔, 🎁, ⛄️, ❄️]
233 | ```
234 |
235 | ### With custom data
236 | ```js
237 | import data from 'emoji-mart-vue/data/messenger'
238 | import { NimbleEmojiIndex } from 'emoji-mart-vue'
239 |
240 | let emojiIndex = new NimbleEmojiIndex(data)
241 | emojiIndex.search('christmas')
242 | ```
243 |
244 | ## Storage
245 | By default EmojiMart will store user chosen skin and frequently used emojis in `localStorage`. That can however be overwritten should you want to store these in your own storage.
246 |
247 | ```js
248 | import { store } from 'emoji-mart-vue'
249 |
250 | store.setHandlers({
251 | getter: (key) => {
252 | // Get from your own storage (sync)
253 | },
254 |
255 | setter: (key, value) => {
256 | // Persist in your own storage (can be async)
257 | }
258 | })
259 | ```
260 |
261 | Possible keys are:
262 |
263 | | Key | Value | Description |
264 | | --- | ----- | ----------- |
265 | | skin | `1, 2, 3, 4, 5, 6` | |
266 | | frequently | `{ 'astonished': 11, '+1': 22 }` | An object where the key is the emoji name and the value is the usage count |
267 | | last | 'astonished' | (Optional) Used by `frequently` to be sure the latest clicked emoji will always appear in the “Recent” category |
268 |
269 | ## Features
270 | ### Powerful search
271 | #### Short name, name and keywords
272 | Not only does **Emoji Mart** return more results than most emoji picker, they’re more accurate and sorted by relevance.
273 |
274 |
275 |
276 | #### Emoticons
277 | The only emoji picker that returns emojis when searching for emoticons.
278 |
279 |
280 |
281 | #### Results intersection
282 | For better results, **Emoji Mart** split search into words and only returns results matching both terms.
283 |
284 |
285 |
286 | ### Fully customizable
287 | #### Anchors color, title and default emoji
288 | 
289 |
290 | #### Emojis sizes and length
291 |
292 |
293 | #### Default skin color
294 | As the developer, you have control over which skin color is used by default.
295 |
296 |
297 |
298 | It can however be overwritten as per user preference.
299 |
300 |
301 |
302 | #### Multiple sets supported
303 | Apple / Google / Twitter / EmojiOne / Messenger / Facebook
304 |
305 |
306 |
307 | ## Not opinionated
308 | **Emoji Mart** doesn’t automatically insert anything into a text input, nor does it show or hide itself. It simply returns an `emoji` object. It’s up to the developer to mount/unmount (it’s fast!) and position the picker. You can use the returned object as props for the `EmojiMart.Emoji` component. You could also use `emoji.colons` to insert text into a textarea or `emoji.native` to use the emoji.
309 |
310 | ## Development
311 | ```sh
312 | $ yarn build
313 | $ yarn start
314 | $ yarn storybook
315 | ```
316 |
317 | ## 🎩 Hat tips!
318 | Powered by [iamcal/emoji-data](https://github.com/iamcal/emoji-data) and inspired by [iamcal/js-emoji](https://github.com/iamcal/js-emoji).
319 | 🙌🏼 [Cal Henderson](https://github.com/iamcal).
320 |
--------------------------------------------------------------------------------
/css/emoji-mart.css:
--------------------------------------------------------------------------------
1 | .emoji-mart,
2 | .emoji-mart * {
3 | box-sizing: border-box;
4 | line-height: 1.15;
5 | }
6 |
7 | .emoji-mart {
8 | font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
9 | font-size: 16px;
10 | display: inline-block;
11 | color: #222427;
12 | border: 1px solid #d9d9d9;
13 | border-radius: 5px;
14 | background: #fff;
15 | }
16 |
17 | .emoji-mart .emoji-mart-emoji {
18 | padding: 6px;
19 | }
20 |
21 | .emoji-mart-bar {
22 | border: 0 solid #d9d9d9;
23 | }
24 | .emoji-mart-bar:first-child {
25 | border-bottom-width: 1px;
26 | border-top-left-radius: 5px;
27 | border-top-right-radius: 5px;
28 | }
29 | .emoji-mart-bar:last-child {
30 | border-top-width: 1px;
31 | border-bottom-left-radius: 5px;
32 | border-bottom-right-radius: 5px;
33 | }
34 |
35 | .emoji-mart-anchors {
36 | display: flex;
37 | flex-direction: row;
38 | justify-content: space-between;
39 | padding: 0 6px;
40 | color: #858585;
41 | line-height: 0;
42 | }
43 |
44 | .emoji-mart-anchor {
45 | position: relative;
46 | display: block;
47 | flex: 1 1 auto;
48 | text-align: center;
49 | padding: 12px 4px;
50 | overflow: hidden;
51 | transition: color .1s ease-out;
52 | }
53 | .emoji-mart-anchor:hover,
54 | .emoji-mart-anchor-selected {
55 | color: #464646;
56 | }
57 |
58 | .emoji-mart-anchor-selected .emoji-mart-anchor-bar {
59 | bottom: 0;
60 | }
61 |
62 | .emoji-mart-anchor-bar {
63 | position: absolute;
64 | bottom: -3px; left: 0;
65 | width: 100%; height: 3px;
66 | background-color: #464646;
67 | }
68 |
69 | .emoji-mart-anchors i {
70 | display: inline-block;
71 | width: 100%;
72 | max-width: 22px;
73 | }
74 |
75 | .emoji-mart-anchors svg {
76 | fill: currentColor;
77 | max-height: 18px;
78 | }
79 |
80 | .emoji-mart-scroll {
81 | overflow-y: scroll;
82 | height: 270px;
83 | padding: 0 6px 6px 6px;
84 | will-change: transform; /* avoids "repaints on scroll" in mobile Chrome */
85 | }
86 |
87 | .emoji-mart-search {
88 | margin-top: 6px;
89 | padding: 0 6px;
90 | }
91 | .emoji-mart-search input {
92 | font-size: 16px;
93 | display: block;
94 | width: 100%;
95 | padding: .2em .6em;
96 | border-radius: 25px;
97 | border: 1px solid #d9d9d9;
98 | outline: 0;
99 | }
100 |
101 | .emoji-mart-category .emoji-mart-emoji span {
102 | z-index: 1;
103 | position: relative;
104 | text-align: center;
105 | cursor: default;
106 | }
107 |
108 | .emoji-mart-category .emoji-mart-emoji:hover:before {
109 | z-index: 0;
110 | content: "";
111 | position: absolute;
112 | top: 0; left: 0;
113 | width: 100%; height: 100%;
114 | background-color: #f4f4f4;
115 | border-radius: 100%;
116 | }
117 |
118 | .emoji-mart-category-label {
119 | z-index: 2;
120 | position: relative;
121 | position: -webkit-sticky;
122 | position: sticky;
123 | top: 0;
124 | }
125 |
126 | .emoji-mart-category-label span {
127 | display: block;
128 | width: 100%;
129 | font-weight: 500;
130 | padding: 5px 6px;
131 | background-color: #fff;
132 | background-color: rgba(255, 255, 255, .95);
133 | }
134 |
135 | .emoji-mart-emoji {
136 | position: relative;
137 | display: inline-block;
138 | font-size: 0;
139 | }
140 |
141 | .emoji-mart-no-results {
142 | font-size: 14px;
143 | text-align: center;
144 | padding-top: 70px;
145 | color: #858585;
146 | }
147 | .emoji-mart-no-results .emoji-mart-category-label {
148 | display: none;
149 | }
150 | .emoji-mart-no-results .emoji-mart-no-results-label {
151 | margin-top: .2em;
152 | }
153 | .emoji-mart-no-results .emoji-mart-emoji:hover:before {
154 | content: none;
155 | }
156 |
157 | .emoji-mart-preview {
158 | position: relative;
159 | height: 70px;
160 | }
161 |
162 | .emoji-mart-preview-emoji,
163 | .emoji-mart-preview-data,
164 | .emoji-mart-preview-skins {
165 | position: absolute;
166 | top: 50%;
167 | transform: translateY(-50%);
168 | }
169 |
170 | .emoji-mart-preview-emoji {
171 | left: 12px;
172 | }
173 |
174 | .emoji-mart-preview-data {
175 | left: 68px; right: 12px;
176 | word-break: break-all;
177 | }
178 |
179 | .emoji-mart-preview-skins {
180 | right: 30px;
181 | text-align: right;
182 | }
183 |
184 | .emoji-mart-preview-name {
185 | font-size: 14px;
186 | }
187 |
188 | .emoji-mart-preview-shortname {
189 | font-size: 12px;
190 | color: #888;
191 | }
192 | .emoji-mart-preview-shortname + .emoji-mart-preview-shortname,
193 | .emoji-mart-preview-shortname + .emoji-mart-preview-emoticon,
194 | .emoji-mart-preview-emoticon + .emoji-mart-preview-emoticon {
195 | margin-left: .5em;
196 | }
197 |
198 | .emoji-mart-preview-emoticon {
199 | font-size: 11px;
200 | color: #bbb;
201 | }
202 |
203 | .emoji-mart-title span {
204 | display: inline-block;
205 | vertical-align: middle;
206 | }
207 |
208 | .emoji-mart-title .emoji-mart-emoji {
209 | padding: 0;
210 | }
211 |
212 | .emoji-mart-title-label {
213 | color: #999A9C;
214 | font-size: 26px;
215 | font-weight: 300;
216 | }
217 |
218 | .emoji-mart-skin-swatches {
219 | font-size: 0;
220 | padding: 2px 0;
221 | border: 1px solid #d9d9d9;
222 | border-radius: 12px;
223 | background-color: #fff;
224 | }
225 |
226 | .emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch {
227 | width: 16px;
228 | padding: 0 2px;
229 | }
230 |
231 | .emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch-selected:after {
232 | opacity: .75;
233 | }
234 |
235 | .emoji-mart-skin-swatch {
236 | display: inline-block;
237 | width: 0;
238 | vertical-align: middle;
239 | transition-property: width, padding;
240 | transition-duration: .125s;
241 | transition-timing-function: ease-out;
242 | }
243 |
244 | .emoji-mart-skin-swatch:nth-child(1) { transition-delay: 0s }
245 | .emoji-mart-skin-swatch:nth-child(2) { transition-delay: .03s }
246 | .emoji-mart-skin-swatch:nth-child(3) { transition-delay: .06s }
247 | .emoji-mart-skin-swatch:nth-child(4) { transition-delay: .09s }
248 | .emoji-mart-skin-swatch:nth-child(5) { transition-delay: .12s }
249 | .emoji-mart-skin-swatch:nth-child(6) { transition-delay: .15s }
250 |
251 | .emoji-mart-skin-swatch-selected {
252 | position: relative;
253 | width: 16px;
254 | padding: 0 2px;
255 | }
256 | .emoji-mart-skin-swatch-selected:after {
257 | content: "";
258 | position: absolute;
259 | top: 50%; left: 50%;
260 | width: 4px; height: 4px;
261 | margin: -2px 0 0 -2px;
262 | background-color: #fff;
263 | border-radius: 100%;
264 | pointer-events: none;
265 | opacity: 0;
266 | transition: opacity .2s ease-out;
267 | }
268 |
269 | .emoji-mart-skin {
270 | display: inline-block;
271 | width: 100%; padding-top: 100%;
272 | max-width: 12px;
273 | border-radius: 100%;
274 | }
275 |
276 | .emoji-mart-skin-tone-1 { background-color: #ffc93a }
277 | .emoji-mart-skin-tone-2 { background-color: #fadcbc }
278 | .emoji-mart-skin-tone-3 { background-color: #e0bb95 }
279 | .emoji-mart-skin-tone-4 { background-color: #bf8f68 }
280 | .emoji-mart-skin-tone-5 { background-color: #9b643d }
281 | .emoji-mart-skin-tone-6 { background-color: #594539 }
282 |
--------------------------------------------------------------------------------
/docs/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Emoji Mart Vue 🏬
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
23 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
37 |
83 |
84 |
--------------------------------------------------------------------------------
/docs/images/parrot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jm-david/emoji-mart-vue/e441565fe4cd842322792bf04b09a71c55a102d0/docs/images/parrot.gif
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Emoji Mart Vue 🏬 | One component to pick them all
5 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/docs/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './app'
3 |
4 | new Vue({
5 | el: '#app',
6 | render: h => h(App)
7 | })
8 |
--------------------------------------------------------------------------------
/docs/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var pack = require('../package.json')
3 | var webpack = require('webpack')
4 |
5 | var PROD = process.env.NODE_ENV === 'production'
6 | var TEST = process.env.NODE_ENV === 'test'
7 |
8 | var config = {
9 | entry: path.resolve('docs/index.js'),
10 | output: {
11 | path: path.resolve('docs'),
12 | filename: 'bundle.js',
13 | library: 'EmojiMart',
14 | libraryTarget: 'umd',
15 | },
16 |
17 | externals: [],
18 |
19 | module: {
20 | rules: [
21 | {
22 | test: /\.js$/,
23 | loader: 'babel-loader',
24 | include: [
25 | path.resolve('src'),
26 | path.resolve('docs'),
27 | ],
28 | },
29 | {
30 | test: /\.vue$/,
31 | loader: 'vue-loader',
32 | include: [
33 | path.resolve('src'),
34 | path.resolve('docs'),
35 | ]
36 | },
37 | {
38 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
39 | loader: 'url-loader',
40 | options: {
41 | limit: 10000
42 | }
43 | }
44 | ],
45 | },
46 |
47 | resolve: {
48 | extensions: ['.vue', '.js'],
49 | },
50 |
51 | plugins: [
52 | new webpack.DefinePlugin({
53 | EMOJI_DATASOURCE_VERSION: `'${pack.devDependencies['emoji-datasource']}'`,
54 | }),
55 | ],
56 |
57 | bail: true,
58 | }
59 |
60 | module.exports = config
61 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Fri Jan 27 2017 13:33:03 GMT-0700 (MST)
3 | var webpackConfig = require('./spec/webpack.config.js');
4 |
5 | module.exports = function(config) {
6 | config.set({
7 |
8 | // base path that will be used to resolve all patterns (eg. files, exclude)
9 | basePath: '',
10 |
11 |
12 | // frameworks to use
13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
14 | frameworks: ['jasmine'],
15 |
16 |
17 | // list of files / patterns to load in the browser
18 | files: [
19 | 'spec/*spec.js',
20 | ],
21 |
22 |
23 | // list of files to exclude
24 | exclude: [
25 | ],
26 |
27 |
28 | // preprocess matching files before serving them to the browser
29 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
30 | preprocessors: {
31 | 'spec/*spec.js': ['webpack'],
32 | },
33 |
34 |
35 | // test results reporter to use
36 | // possible values: 'dots', 'progress'
37 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
38 | reporters: ['progress'],
39 |
40 |
41 | // web server port
42 | port: 9876,
43 |
44 |
45 | // enable / disable colors in the output (reporters and logs)
46 | colors: true,
47 |
48 |
49 | // level of logging
50 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
51 | logLevel: config.LOG_INFO,
52 |
53 |
54 | // enable / disable watching file and executing tests whenever any file changes
55 | autoWatch: true,
56 |
57 |
58 | // start these browsers
59 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
60 | browsers: ['Chrome'],
61 |
62 |
63 | // Continuous Integration mode
64 | // if true, Karma captures browsers, runs the tests and exits
65 | singleRun: true,
66 |
67 | // Concurrency level
68 | // how many browser should be started simultaneous
69 | concurrency: Infinity,
70 |
71 | webpack: webpackConfig,
72 | })
73 | }
74 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "emoji-mart-vue",
3 | "version": "2.6.6",
4 | "description": "Customizable Slack-like emoji picker for VueJS",
5 | "main": "dist/emoji-mart.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git@github.com:jm-david/emoji-mart-vue.git"
9 | },
10 | "keywords": [
11 | "vue",
12 | "vuejs",
13 | "emoji",
14 | "picker"
15 | ],
16 | "author": "Etienne Lemay",
17 | "license": "BSD-3-Clause",
18 | "bugs": {
19 | "url": "https://github.com/jm-david/emoji-mart-vue/issues"
20 | },
21 | "homepage": "https://github.com/jm-david/emoji-mart-vue",
22 | "dependencies": {
23 | "postcss-loader": "^3.0.0"
24 | },
25 | "peerDependencies": {
26 | "vue": "^2.0.0"
27 | },
28 | "devDependencies": {
29 | "babel-cli": "^6.26.0",
30 | "babel-core": "6.7.2",
31 | "babel-loader": "^7.1.2",
32 | "babel-plugin-module-resolver": "2.7.1",
33 | "babel-plugin-transform-define": "^1.3.0",
34 | "babel-plugin-transform-es2015-destructuring": "6.9.0",
35 | "babel-plugin-transform-object-rest-spread": "6.8.0",
36 | "babel-plugin-transform-runtime": "^6.23.0",
37 | "babel-preset-es2015": "6.6.0",
38 | "babel-runtime": "^6.26.0",
39 | "css-loader": "^0.28.0",
40 | "emoji-datasource": "4.0.4",
41 | "emojilib": "^2.2.1",
42 | "inflection": "1.10.0",
43 | "jasmine-core": "^2.5.2",
44 | "karma": "^1.4.0",
45 | "karma-chrome-launcher": "^2.0.0",
46 | "karma-cli": "^1.0.1",
47 | "karma-jasmine": "^1.1.0",
48 | "karma-webpack": "^2.0.4",
49 | "mkdirp": "0.5.1",
50 | "prettier": "1.11.1",
51 | "rimraf": "2.5.2",
52 | "size-limit": "^0.11.4",
53 | "url-loader": "^0.5.8",
54 | "vue": "^2.5.2",
55 | "vue-loader": "^13.3.0",
56 | "vue-style-loader": "^4.1.2",
57 | "vue-template-compiler": "^2.5.2",
58 | "webpack": "^3.6.0",
59 | "webpack-dev-server": "^2.9.1"
60 | },
61 | "scripts": {
62 | "clean": "rm -rf dist/",
63 | "build:data": "node scripts/build-data",
64 | "build:dist": "webpack --config src/webpack.config.js",
65 | "build:docs": "webpack --config docs/webpack.config.js",
66 | "build": "npm run clean && npm run build:data && npm run build:dist",
67 | "dev:docs": "webpack -w --config docs/webpack.config.js",
68 | "start": "npm run dev:docs",
69 | "stats": "webpack --config ./spec/webpack.config.js --json > spec/stats.json",
70 | "test": "NODE_ENV=test karma start && size-limit",
71 | "prepublishOnly": "npm run build",
72 | "prettier": "prettier --write \"{src,spec}/**/*.js\""
73 | },
74 | "size-limit": [
75 | {
76 | "path": "dist/emoji-mart.js",
77 | "limit": "80 KB"
78 | }
79 | ],
80 | "postcss": {
81 | "plugins": {
82 | "autoprefixer": {}
83 | }
84 | },
85 | "browserslist": [
86 | "last 3 version",
87 | "IE >= 11",
88 | "iOS >= 9"
89 | ]
90 | }
91 |
--------------------------------------------------------------------------------
/scripts/build-data.js:
--------------------------------------------------------------------------------
1 | const build = require('./build')
2 | const sets = ['apple', 'emojione', 'facebook', 'google', 'messenger', 'twitter']
3 |
4 | build({ output: 'data/all.json' })
5 |
6 | sets.forEach((set) => {
7 | build({
8 | output: `data/${set}.json`,
9 | sets: [set],
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs'),
2 | emojiLib = require('emojilib'),
3 | inflection = require('inflection'),
4 | mkdirp = require('mkdirp')
5 |
6 | var { compress } = require('../src/utils/data')
7 |
8 | var categories = [
9 | ['Smileys & People', 'people'],
10 | ['Animals & Nature', 'nature'],
11 | ['Food & Drink', 'foods'],
12 | ['Activities', 'activity'],
13 | ['Travel & Places', 'places'],
14 | ['Objects', 'objects'],
15 | ['Symbols', 'symbols'],
16 | ['Flags', 'flags'],
17 | ]
18 |
19 | var sets = ['apple', 'emojione', 'facebook', 'google', 'messenger', 'twitter']
20 |
21 | module.exports = (options) => {
22 | delete require.cache[require.resolve('emoji-datasource')]
23 | var emojiData = require('emoji-datasource')
24 |
25 | var data = { compressed: true, categories: [], emojis: {}, aliases: {} },
26 | categoriesIndex = {}
27 |
28 | categories.forEach((category, i) => {
29 | let [name, id] = category
30 | data.categories[i] = { id: id, name: name, emojis: [] }
31 | categoriesIndex[name] = i
32 | })
33 |
34 | emojiData.sort((a, b) => {
35 | var aTest = a.sort_order || a.short_name,
36 | bTest = b.sort_order || b.short_name
37 |
38 | return aTest - bTest
39 | })
40 |
41 | emojiData.forEach((datum) => {
42 | var category = datum.category,
43 | keywords = [],
44 | categoryIndex
45 |
46 | if (!datum.category) {
47 | throw new Error('“' + datum.short_name + '” doesn’t have a category')
48 | }
49 |
50 | if (options.sets) {
51 | var keepEmoji = false
52 |
53 | options.sets.forEach((set) => {
54 | if (keepEmoji) return
55 | if (datum[`has_img_${set}`]) {
56 | keepEmoji = true
57 | }
58 | })
59 |
60 | if (!keepEmoji) {
61 | return
62 | }
63 |
64 | sets.forEach((set) => {
65 | if (options.sets.length == 1 || options.sets.indexOf(set) == -1) {
66 | var key = `has_img_${set}`
67 | delete datum[key]
68 | }
69 | })
70 | }
71 |
72 | datum.name || (datum.name = datum.short_name.replace(/\-/g, ' '))
73 | datum.name = inflection.titleize(datum.name || '')
74 |
75 | if (!datum.name) {
76 | throw new Error('“' + datum.short_name + '” doesn’t have a name')
77 | }
78 |
79 | datum.emoticons = datum.texts || []
80 | datum.text = datum.text || ''
81 | delete datum.texts
82 |
83 | if (emojiLib.lib[datum.short_name]) {
84 | datum.keywords = emojiLib.lib[datum.short_name].keywords
85 | }
86 |
87 | if (datum.category != 'Skin Tones') {
88 | categoryIndex = categoriesIndex[category]
89 | data.categories[categoryIndex].emojis.push(datum.short_name)
90 | data.emojis[datum.short_name] = datum
91 | }
92 |
93 | datum.short_names.forEach((short_name, i) => {
94 | if (i == 0) {
95 | return
96 | }
97 |
98 | data.aliases[short_name] = datum.short_name
99 | })
100 |
101 | delete datum.docomo
102 | delete datum.au
103 | delete datum.softbank
104 | delete datum.google
105 | delete datum.image
106 | delete datum.category
107 | delete datum.sort_order
108 |
109 | compress(datum)
110 | })
111 |
112 | var flags = data.categories[categoriesIndex['Flags']]
113 | flags.emojis = flags.emojis
114 | .filter((flag) => {
115 | // Until browsers support Flag UN
116 | if (flag == 'flag-un') return
117 | return true
118 | })
119 | .sort()
120 |
121 | fs.writeFile(options.output, JSON.stringify(data), (err) => {
122 | if (err) throw err
123 | })
124 | }
125 |
--------------------------------------------------------------------------------
/scripts/define.js:
--------------------------------------------------------------------------------
1 | var pack = require('../package.json')
2 |
3 | module.exports = {
4 | 'process.env.NODE_ENV': 'production',
5 | EMOJI_DATASOURCE_VERSION: pack.devDependencies['emoji-datasource'],
6 | }
7 |
--------------------------------------------------------------------------------
/spec/emoji-index-spec.js:
--------------------------------------------------------------------------------
1 | import emojiIndex from '../src/utils/emoji-index/emoji-index'
2 |
3 | describe('#emojiIndex', () => {
4 | describe('search', function() {
5 | it('should work', () => {
6 | expect(emojiIndex.search('pineapple')).toEqual([
7 | {
8 | id: 'pineapple',
9 | name: 'Pineapple',
10 | colons: ':pineapple:',
11 | emoticons: [],
12 | unified: '1f34d',
13 | skin: null,
14 | native: '🍍',
15 | },
16 | ])
17 | })
18 |
19 | it('should filter only emojis we care about, exclude pineapple', () => {
20 | let emojisToShowFilter = (data) => {
21 | data.unified !== '1F34D'
22 | }
23 | expect(
24 | emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id),
25 | ).not.toContain('pineapple')
26 | })
27 |
28 | it('can include/exclude categories', () => {
29 | expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([])
30 | })
31 |
32 | it('can search for thinking_face', () => {
33 | expect(emojiIndex.search('thinking_fac').map((x) => x.id)).toEqual([
34 | 'thinking_face',
35 | ])
36 | })
37 |
38 | it('can search for woman-facepalming', () => {
39 | expect(emojiIndex.search('woman-facep').map((x) => x.id)).toEqual([
40 | 'woman-facepalming',
41 | ])
42 | })
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/spec/picker-spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import TestUtils from 'react-dom/test-utils'
3 |
4 | import data from '../data/all.json'
5 | import { NimblePicker } from '../src/components'
6 |
7 | const { click } = TestUtils.Simulate
8 |
9 | const {
10 | renderIntoDocument,
11 | scryRenderedComponentsWithType,
12 | findRenderedComponentWithType,
13 | } = TestUtils
14 |
15 | const render = (props = {}) => {
16 | const defaultProps = { data }
17 | return renderIntoDocument()
18 | }
19 |
20 | describe('NimblePicker', () => {
21 | let subject
22 |
23 | it('works', () => {
24 | subject = render()
25 | expect(subject).toBeDefined()
26 | })
27 |
28 | describe('categories', () => {
29 | it('shows 10 by default', () => {
30 | subject = render()
31 | expect(subject.categories.length).toEqual(10)
32 | })
33 |
34 | it('will not show some based upon our filter', () => {
35 | subject = render({ emojisToShowFilter: (unified) => false })
36 | expect(subject.categories.length).toEqual(2)
37 | })
38 |
39 | it('maintains category ids after it is filtered', () => {
40 | subject = render({ emojisToShowFilter: (emoji) => true })
41 | const categoriesWithIds = subject.categories.filter(
42 | (category) => category.id,
43 | )
44 | expect(categoriesWithIds.length).toEqual(10)
45 | })
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/spec/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var pack = require('../package.json')
3 | var webpack = require('webpack')
4 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
5 | .BundleAnalyzerPlugin
6 |
7 | var PROD = process.env.NODE_ENV === 'production'
8 | var TEST = process.env.NODE_ENV === 'test'
9 |
10 | var config = {
11 | entry: path.resolve('src/index.js'),
12 | output: {
13 | path: path.resolve('spec'),
14 | filename: 'bundle.js',
15 | library: 'EmojiMart',
16 | libraryTarget: 'umd',
17 | },
18 |
19 | externals: [],
20 |
21 | module: {
22 | rules: [
23 | {
24 | test: /\.js$/,
25 | use: 'babel-loader',
26 | include: [path.resolve('src'), path.resolve('spec')],
27 | },
28 | ],
29 | },
30 |
31 | resolve: {
32 | extensions: ['.js'],
33 | },
34 |
35 | plugins: [
36 | new webpack.DefinePlugin({
37 | EMOJI_DATASOURCE_VERSION: `'${pack.devDependencies['emoji-datasource']}'`,
38 | }),
39 | ],
40 |
41 | bail: true,
42 | }
43 |
44 | if (!TEST) {
45 | config.externals = config.externals.concat([
46 | {
47 | react: {
48 | root: 'React',
49 | commonjs2: 'react',
50 | commonjs: 'react',
51 | amd: 'react',
52 | },
53 | },
54 | ])
55 |
56 | config.plugins = config.plugins.concat([
57 | new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false }),
58 | ])
59 | }
60 |
61 | module.exports = config
62 |
--------------------------------------------------------------------------------
/src/components/anchors.old.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import SVGs from '../svgs'
5 |
6 | export default class Anchors extends React.PureComponent {
7 | constructor(props) {
8 | super(props)
9 |
10 | let defaultCategory = props.categories.filter(
11 | (category) => category.first,
12 | )[0]
13 |
14 | this.state = {
15 | selected: defaultCategory.name,
16 | }
17 |
18 | this.handleClick = this.handleClick.bind(this)
19 | }
20 |
21 | getSVG(id) {
22 | this.SVGs || (this.SVGs = {})
23 |
24 | if (this.SVGs[id]) {
25 | return this.SVGs[id]
26 | } else {
27 | let svg = ``
30 |
31 | this.SVGs[id] = svg
32 | return svg
33 | }
34 | }
35 |
36 | handleClick(e) {
37 | var index = e.currentTarget.getAttribute('data-index')
38 | var { categories, onAnchorClick } = this.props
39 |
40 | onAnchorClick(categories[index], index)
41 | }
42 |
43 | render() {
44 | var { categories, onAnchorClick, color, i18n } = this.props,
45 | { selected } = this.state
46 |
47 | return (
48 |
49 | {categories.map((category, i) => {
50 | var { id, name, anchor } = category,
51 | isSelected = name == selected
52 |
53 | if (anchor === false) {
54 | return null
55 | }
56 |
57 | return (
58 |
68 |
69 |
73 |
74 | )
75 | })}
76 |
77 | )
78 | }
79 | }
80 |
81 | Anchors.propTypes = {
82 | categories: PropTypes.array,
83 | onAnchorClick: PropTypes.func,
84 | }
85 |
86 | Anchors.defaultProps = {
87 | categories: [],
88 | onAnchorClick: () => {},
89 | }
90 |
--------------------------------------------------------------------------------
/src/components/anchors.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
48 |
49 |
92 |
93 |
101 |
--------------------------------------------------------------------------------
/src/components/category.old.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import frequently from '../utils/frequently'
5 | import { getData } from '../utils'
6 | import { NimbleEmoji } from '.'
7 |
8 | export default class Category extends React.Component {
9 | constructor(props) {
10 | super(props)
11 |
12 | this.data = props.data
13 | this.setContainerRef = this.setContainerRef.bind(this)
14 | this.setLabelRef = this.setLabelRef.bind(this)
15 | }
16 |
17 | componentDidMount() {
18 | this.parent = this.container.parentNode
19 |
20 | this.margin = 0
21 | this.minMargin = 0
22 |
23 | this.memoizeSize()
24 | }
25 |
26 | shouldComponentUpdate(nextProps, nextState) {
27 | var {
28 | name,
29 | perLine,
30 | native,
31 | hasStickyPosition,
32 | emojis,
33 | emojiProps,
34 | } = this.props,
35 | { skin, size, set } = emojiProps,
36 | {
37 | perLine: nextPerLine,
38 | native: nextNative,
39 | hasStickyPosition: nextHasStickyPosition,
40 | emojis: nextEmojis,
41 | emojiProps: nextEmojiProps,
42 | } = nextProps,
43 | { skin: nextSkin, size: nextSize, set: nextSet } = nextEmojiProps,
44 | shouldUpdate = false
45 |
46 | if (name == 'Recent' && perLine != nextPerLine) {
47 | shouldUpdate = true
48 | }
49 |
50 | if (name == 'Search') {
51 | shouldUpdate = !(emojis == nextEmojis)
52 | }
53 |
54 | if (
55 | skin != nextSkin ||
56 | size != nextSize ||
57 | native != nextNative ||
58 | set != nextSet ||
59 | hasStickyPosition != nextHasStickyPosition
60 | ) {
61 | shouldUpdate = true
62 | }
63 |
64 | return shouldUpdate
65 | }
66 |
67 | memoizeSize() {
68 | var { top, height } = this.container.getBoundingClientRect()
69 | var { top: parentTop } = this.parent.getBoundingClientRect()
70 | var { height: labelHeight } = this.label.getBoundingClientRect()
71 |
72 | this.top = top - parentTop + this.parent.scrollTop
73 |
74 | if (height == 0) {
75 | this.maxMargin = 0
76 | } else {
77 | this.maxMargin = height - labelHeight
78 | }
79 | }
80 |
81 | handleScroll(scrollTop) {
82 | var margin = scrollTop - this.top
83 | margin = margin < this.minMargin ? this.minMargin : margin
84 | margin = margin > this.maxMargin ? this.maxMargin : margin
85 |
86 | if (margin == this.margin) return
87 |
88 | if (!this.props.hasStickyPosition) {
89 | this.label.style.top = `${margin}px`
90 | }
91 |
92 | this.margin = margin
93 | return true
94 | }
95 |
96 | getEmojis() {
97 | var { name, emojis, recent, perLine } = this.props
98 |
99 | if (name == 'Recent') {
100 | let { custom } = this.props
101 | let frequentlyUsed = recent || frequently.get(perLine)
102 |
103 | if (frequentlyUsed.length) {
104 | emojis = frequentlyUsed
105 | .map((id) => {
106 | const emoji = custom.filter((e) => e.id === id)[0]
107 | if (emoji) {
108 | return emoji
109 | }
110 |
111 | return id
112 | })
113 | .filter((id) => !!getData(id, null, null, this.data))
114 | }
115 |
116 | if (emojis.length === 0 && frequentlyUsed.length > 0) {
117 | return null
118 | }
119 | }
120 |
121 | if (emojis) {
122 | emojis = emojis.slice(0)
123 | }
124 |
125 | return emojis
126 | }
127 |
128 | updateDisplay(display) {
129 | var emojis = this.getEmojis()
130 |
131 | if (!emojis) {
132 | return
133 | }
134 |
135 | this.container.style.display = display
136 | }
137 |
138 | setContainerRef(c) {
139 | this.container = c
140 | }
141 |
142 | setLabelRef(c) {
143 | this.label = c
144 | }
145 |
146 | render() {
147 | var { id, name, hasStickyPosition, emojiProps, i18n } = this.props,
148 | emojis = this.getEmojis(),
149 | labelStyles = {},
150 | labelSpanStyles = {},
151 | containerStyles = {}
152 |
153 | if (!emojis) {
154 | containerStyles = {
155 | display: 'none',
156 | }
157 | }
158 |
159 | if (!hasStickyPosition) {
160 | labelStyles = {
161 | height: 28,
162 | }
163 |
164 | labelSpanStyles = {
165 | position: 'absolute',
166 | }
167 | }
168 |
169 | return (
170 |
177 |
182 |
183 | {i18n.categories[id]}
184 |
185 |
186 |
187 | {emojis &&
188 | emojis.map((emoji) =>
189 | NimbleEmoji({ emoji: emoji, data: this.data, ...emojiProps }),
190 | )}
191 |
192 | {emojis &&
193 | !emojis.length && (
194 |
195 |
196 | {NimbleEmoji({
197 | data: this.data,
198 | ...emojiProps,
199 | size: 38,
200 | emoji: 'sleuth_or_spy',
201 | onOver: null,
202 | onLeave: null,
203 | onClick: null,
204 | })}
205 |
206 |
207 |
{i18n.notfound}
208 |
209 | )}
210 |
211 | )
212 | }
213 | }
214 |
215 | Category.propTypes = {
216 | emojis: PropTypes.array,
217 | hasStickyPosition: PropTypes.bool,
218 | name: PropTypes.string.isRequired,
219 | native: PropTypes.bool.isRequired,
220 | perLine: PropTypes.number.isRequired,
221 | emojiProps: PropTypes.object.isRequired,
222 | recent: PropTypes.arrayOf(PropTypes.string),
223 | }
224 |
225 | Category.defaultProps = {
226 | emojis: [],
227 | hasStickyPosition: true,
228 | }
229 |
--------------------------------------------------------------------------------
/src/components/category.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ i18n.categories[id] }}
6 |
7 |
8 |
25 |
26 |
27 |
37 |
{{ i18n.notfound }}
38 |
39 |
40 |
41 |
42 |
43 |
90 |
91 |
154 |
155 |
165 |
--------------------------------------------------------------------------------
/src/components/emoji/emoji.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import data from '../../../data/all.json'
4 | import NimbleEmoji from './nimble-emoji'
5 |
6 | import { EmojiPropTypes, EmojiDefaultProps } from '../../utils/shared-props'
7 |
8 | const Emoji = (props) => {
9 | for (let k in Emoji.defaultProps) {
10 | if (props[k] == undefined && Emoji.defaultProps[k] != undefined) {
11 | props[k] = Emoji.defaultProps[k]
12 | }
13 | }
14 |
15 | return NimbleEmoji({ ...props })
16 | }
17 |
18 | Emoji.propTypes = EmojiPropTypes
19 | Emoji.defaultProps = { ...EmojiDefaultProps, data }
20 |
21 | export default Emoji
22 |
--------------------------------------------------------------------------------
/src/components/emoji/emoji.vue:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/components/emoji/nimble-emoji.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import { getData, getSanitizedData, unifiedToNative } from '../../utils'
5 | import { uncompress } from '../../utils/data'
6 | import { EmojiPropTypes, EmojiDefaultProps } from '../../utils/shared-props'
7 |
8 | const SHEET_COLUMNS = 52
9 |
10 | const _getData = (props) => {
11 | var { emoji, skin, set, data } = props
12 | return getData(emoji, skin, set, data)
13 | }
14 |
15 | const _getPosition = (props) => {
16 | var { sheet_x, sheet_y } = _getData(props),
17 | multiply = 100 / (SHEET_COLUMNS - 1)
18 |
19 | return `${multiply * sheet_x}% ${multiply * sheet_y}%`
20 | }
21 |
22 | const _getSanitizedData = (props) => {
23 | var { emoji, skin, set, data } = props
24 | return getSanitizedData(emoji, skin, set, data)
25 | }
26 |
27 | const _handleClick = (e, props) => {
28 | if (!props.onClick) {
29 | return
30 | }
31 | var { onClick } = props,
32 | emoji = _getSanitizedData(props)
33 |
34 | onClick(emoji, e)
35 | }
36 |
37 | const _handleOver = (e, props) => {
38 | if (!props.onOver) {
39 | return
40 | }
41 | var { onOver } = props,
42 | emoji = _getSanitizedData(props)
43 |
44 | onOver(emoji, e)
45 | }
46 |
47 | const _handleLeave = (e, props) => {
48 | if (!props.onLeave) {
49 | return
50 | }
51 | var { onLeave } = props,
52 | emoji = _getSanitizedData(props)
53 |
54 | onLeave(emoji, e)
55 | }
56 |
57 | const _isNumeric = (value) => {
58 | return !isNaN(value - parseFloat(value))
59 | }
60 |
61 | const _convertStyleToCSS = (style) => {
62 | let div = document.createElement('div')
63 |
64 | for (let key in style) {
65 | let value = style[key]
66 |
67 | if (_isNumeric(value)) {
68 | value += 'px'
69 | }
70 |
71 | div.style[key] = value
72 | }
73 |
74 | return div.getAttribute('style')
75 | }
76 |
77 | const NimbleEmoji = (props) => {
78 | if (props.data.compressed) {
79 | uncompress(props.data)
80 | }
81 |
82 | for (let k in NimbleEmoji.defaultProps) {
83 | if (props[k] == undefined && NimbleEmoji.defaultProps[k] != undefined) {
84 | props[k] = NimbleEmoji.defaultProps[k]
85 | }
86 | }
87 |
88 | let data = _getData(props)
89 | if (!data) {
90 | return null
91 | }
92 |
93 | let { unified, custom, short_names, imageUrl } = data,
94 | style = {},
95 | children = props.children,
96 | className = 'emoji-mart-emoji',
97 | title = null
98 |
99 | if (!unified && !custom) {
100 | return null
101 | }
102 |
103 | if (props.tooltip) {
104 | title = short_names[0]
105 | }
106 |
107 | if (props.native && unified) {
108 | className += ' emoji-mart-emoji-native'
109 | style = { fontSize: props.size }
110 | children = unifiedToNative(unified)
111 |
112 | if (props.forceSize) {
113 | style.display = 'inline-block'
114 | style.width = props.size
115 | style.height = props.size
116 | }
117 | } else if (custom) {
118 | className += ' emoji-mart-emoji-custom'
119 | style = {
120 | width: props.size,
121 | height: props.size,
122 | display: 'inline-block',
123 | backgroundImage: `url(${imageUrl})`,
124 | backgroundSize: 'contain',
125 | }
126 | } else {
127 | let setHasEmoji =
128 | data[`has_img_${props.set}`] == undefined || data[`has_img_${props.set}`]
129 |
130 | if (!setHasEmoji) {
131 | if (props.fallback) {
132 | return props.fallback(data)
133 | } else {
134 | return null
135 | }
136 | } else {
137 | style = {
138 | width: props.size,
139 | height: props.size,
140 | display: 'inline-block',
141 | backgroundImage: `url(${props.backgroundImageFn(
142 | props.set,
143 | props.sheetSize,
144 | )})`,
145 | backgroundSize: `${100 * SHEET_COLUMNS}%`,
146 | backgroundPosition: _getPosition(props),
147 | }
148 | }
149 | }
150 |
151 | if (props.html) {
152 | style = _convertStyleToCSS(style)
153 | return `${children || ''}`
156 | } else {
157 | return (
158 | _handleClick(e, props)}
161 | onMouseEnter={(e) => _handleOver(e, props)}
162 | onMouseLeave={(e) => _handleLeave(e, props)}
163 | title={title}
164 | className={className}
165 | >
166 | {children}
167 |
168 | )
169 | }
170 | }
171 |
172 | NimbleEmoji.propTypes = { ...EmojiPropTypes, data: PropTypes.object.isRequired }
173 | NimbleEmoji.defaultProps = EmojiDefaultProps
174 |
175 | export default NimbleEmoji
176 |
--------------------------------------------------------------------------------
/src/components/emoji/nimbleEmoji.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ nativeEmoji }}
6 |
7 | {{ fallbackEmoji }}
8 |
9 |
10 |
11 |
12 |
123 |
124 |
133 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Anchors } from './anchors'
2 | export { default as Category } from './category'
3 | export { default as Preview } from './preview'
4 | export { default as Search } from './search'
5 | export { default as Skins } from './skins'
6 |
7 | export { default as Emoji } from './emoji/emoji'
8 | export { default as NimbleEmoji } from './emoji/nimbleEmoji'
9 |
10 | export { default as Picker } from './picker/picker'
11 | export { default as NimblePicker } from './picker/nimblePicker'
12 |
--------------------------------------------------------------------------------
/src/components/picker/nimble-picker.js:
--------------------------------------------------------------------------------
1 | import '../../vendor/raf-polyfill'
2 |
3 | import React from 'react'
4 | import PropTypes from 'prop-types'
5 |
6 | import store from '../../utils/store'
7 | import frequently from '../../utils/frequently'
8 | import { deepMerge, measureScrollbar } from '../../utils'
9 | import { uncompress } from '../../utils/data'
10 | import { PickerPropTypes, PickerDefaultProps } from '../../utils/shared-props'
11 |
12 | import { Anchors, Category, Preview, Search } from '..'
13 |
14 | const I18N = {
15 | search: 'Search',
16 | notfound: 'No Emoji Found',
17 | categories: {
18 | search: 'Search Results',
19 | recent: 'Frequently Used',
20 | people: 'Smileys & People',
21 | nature: 'Animals & Nature',
22 | foods: 'Food & Drink',
23 | activity: 'Activity',
24 | places: 'Travel & Places',
25 | objects: 'Objects',
26 | symbols: 'Symbols',
27 | flags: 'Flags',
28 | custom: 'Custom',
29 | },
30 | }
31 |
32 | export default class NimblePicker extends React.PureComponent {
33 | constructor(props) {
34 | super(props)
35 |
36 | this.RECENT_CATEGORY = { id: 'recent', name: 'Recent', emojis: null }
37 | this.CUSTOM_CATEGORY = { id: 'custom', name: 'Custom', emojis: [] }
38 | this.SEARCH_CATEGORY = {
39 | id: 'search',
40 | name: 'Search',
41 | emojis: null,
42 | anchor: false,
43 | }
44 |
45 | if (props.data.compressed) {
46 | uncompress(props.data)
47 | }
48 |
49 | this.data = props.data
50 | this.i18n = deepMerge(I18N, props.i18n)
51 | this.state = {
52 | skin: props.skin || store.get('skin') || props.defaultSkin,
53 | firstRender: true,
54 | }
55 |
56 | this.categories = []
57 | let allCategories = [].concat(this.data.categories)
58 |
59 | if (props.custom.length > 0) {
60 | this.CUSTOM_CATEGORY.emojis = props.custom.map((emoji) => {
61 | return {
62 | ...emoji,
63 | // `` expects emoji to have an `id`.
64 | id: emoji.short_names[0],
65 | custom: true,
66 | }
67 | })
68 |
69 | allCategories.push(this.CUSTOM_CATEGORY)
70 | }
71 |
72 | this.hideRecent = true
73 |
74 | if (props.include != undefined) {
75 | allCategories.sort((a, b) => {
76 | if (props.include.indexOf(a.id) > props.include.indexOf(b.id)) {
77 | return 1
78 | }
79 |
80 | return -1
81 | })
82 | }
83 |
84 | for (
85 | let categoryIndex = 0;
86 | categoryIndex < allCategories.length;
87 | categoryIndex++
88 | ) {
89 | const category = allCategories[categoryIndex]
90 | let isIncluded =
91 | props.include && props.include.length
92 | ? props.include.indexOf(category.id) > -1
93 | : true
94 | let isExcluded =
95 | props.exclude && props.exclude.length
96 | ? props.exclude.indexOf(category.id) > -1
97 | : false
98 | if (!isIncluded || isExcluded) {
99 | continue
100 | }
101 |
102 | if (props.emojisToShowFilter) {
103 | let newEmojis = []
104 |
105 | const { emojis } = category
106 | for (let emojiIndex = 0; emojiIndex < emojis.length; emojiIndex++) {
107 | const emoji = emojis[emojiIndex]
108 | if (props.emojisToShowFilter(this.data.emojis[emoji] || emoji)) {
109 | newEmojis.push(emoji)
110 | }
111 | }
112 |
113 | if (newEmojis.length) {
114 | let newCategory = {
115 | emojis: newEmojis,
116 | name: category.name,
117 | id: category.id,
118 | }
119 |
120 | this.categories.push(newCategory)
121 | }
122 | } else {
123 | this.categories.push(category)
124 | }
125 | }
126 |
127 | let includeRecent =
128 | props.include && props.include.length
129 | ? props.include.indexOf(this.RECENT_CATEGORY.id) > -1
130 | : true
131 | let excludeRecent =
132 | props.exclude && props.exclude.length
133 | ? props.exclude.indexOf(this.RECENT_CATEGORY.id) > -1
134 | : false
135 | if (includeRecent && !excludeRecent) {
136 | this.hideRecent = false
137 | this.categories.unshift(this.RECENT_CATEGORY)
138 | }
139 |
140 | if (this.categories[0]) {
141 | this.categories[0].first = true
142 | }
143 |
144 | this.categories.unshift(this.SEARCH_CATEGORY)
145 |
146 | this.setAnchorsRef = this.setAnchorsRef.bind(this)
147 | this.handleAnchorClick = this.handleAnchorClick.bind(this)
148 | this.setSearchRef = this.setSearchRef.bind(this)
149 | this.handleSearch = this.handleSearch.bind(this)
150 | this.setScrollRef = this.setScrollRef.bind(this)
151 | this.handleScroll = this.handleScroll.bind(this)
152 | this.handleScrollPaint = this.handleScrollPaint.bind(this)
153 | this.handleEmojiOver = this.handleEmojiOver.bind(this)
154 | this.handleEmojiLeave = this.handleEmojiLeave.bind(this)
155 | this.handleEmojiClick = this.handleEmojiClick.bind(this)
156 | this.handleEmojiSelect = this.handleEmojiSelect.bind(this)
157 | this.setPreviewRef = this.setPreviewRef.bind(this)
158 | this.handleSkinChange = this.handleSkinChange.bind(this)
159 | this.handleKeyDown = this.handleKeyDown.bind(this)
160 | }
161 |
162 | componentWillReceiveProps(props) {
163 | if (props.skin) {
164 | this.setState({ skin: props.skin })
165 | } else if (props.defaultSkin && !store.get('skin')) {
166 | this.setState({ skin: props.defaultSkin })
167 | }
168 | }
169 |
170 | componentDidMount() {
171 | if (this.state.firstRender) {
172 | this.testStickyPosition()
173 | this.firstRenderTimeout = setTimeout(() => {
174 | this.setState({ firstRender: false })
175 | }, 60)
176 | }
177 | }
178 |
179 | componentDidUpdate() {
180 | this.updateCategoriesSize()
181 | this.handleScroll()
182 | }
183 |
184 | componentWillUnmount() {
185 | this.SEARCH_CATEGORY.emojis = null
186 |
187 | clearTimeout(this.leaveTimeout)
188 | clearTimeout(this.firstRenderTimeout)
189 | }
190 |
191 | testStickyPosition() {
192 | const stickyTestElement = document.createElement('div')
193 |
194 | const prefixes = ['', '-webkit-', '-ms-', '-moz-', '-o-']
195 |
196 | prefixes.forEach(
197 | (prefix) => (stickyTestElement.style.position = `${prefix}sticky`),
198 | )
199 |
200 | this.hasStickyPosition = !!stickyTestElement.style.position.length
201 | }
202 |
203 | handleEmojiOver(emoji) {
204 | var { preview } = this
205 | if (!preview) {
206 | return
207 | }
208 |
209 | // Use Array.prototype.find() when it is more widely supported.
210 | const emojiData = this.CUSTOM_CATEGORY.emojis.filter(
211 | (customEmoji) => customEmoji.id === emoji.id,
212 | )[0]
213 | for (let key in emojiData) {
214 | if (emojiData.hasOwnProperty(key)) {
215 | emoji[key] = emojiData[key]
216 | }
217 | }
218 |
219 | preview.setState({ emoji })
220 | clearTimeout(this.leaveTimeout)
221 | }
222 |
223 | handleEmojiLeave(emoji) {
224 | var { preview } = this
225 | if (!preview) {
226 | return
227 | }
228 |
229 | this.leaveTimeout = setTimeout(() => {
230 | preview.setState({ emoji: null })
231 | }, 16)
232 | }
233 |
234 | handleEmojiClick(emoji, e) {
235 | this.props.onClick(emoji, e)
236 | this.handleEmojiSelect(emoji)
237 | }
238 |
239 | handleEmojiSelect(emoji) {
240 | this.props.onSelect(emoji)
241 | if (!this.hideRecent && !this.props.recent) frequently.add(emoji)
242 |
243 | var component = this.categoryRefs['category-1']
244 | if (component) {
245 | let maxMargin = component.maxMargin
246 | component.forceUpdate()
247 |
248 | window.requestAnimationFrame(() => {
249 | if (!this.scroll) return
250 | component.memoizeSize()
251 | if (maxMargin == component.maxMargin) return
252 |
253 | this.updateCategoriesSize()
254 | this.handleScrollPaint()
255 |
256 | if (this.SEARCH_CATEGORY.emojis) {
257 | component.updateDisplay('none')
258 | }
259 | })
260 | }
261 | }
262 |
263 | handleScroll() {
264 | if (!this.waitingForPaint) {
265 | this.waitingForPaint = true
266 | window.requestAnimationFrame(this.handleScrollPaint)
267 | }
268 | }
269 |
270 | handleScrollPaint() {
271 | this.waitingForPaint = false
272 |
273 | if (!this.scroll) {
274 | return
275 | }
276 |
277 | let activeCategory = null
278 |
279 | if (this.SEARCH_CATEGORY.emojis) {
280 | activeCategory = this.SEARCH_CATEGORY
281 | } else {
282 | var target = this.scroll,
283 | scrollTop = target.scrollTop,
284 | scrollingDown = scrollTop > (this.scrollTop || 0),
285 | minTop = 0
286 |
287 | for (let i = 0, l = this.categories.length; i < l; i++) {
288 | let ii = scrollingDown ? this.categories.length - 1 - i : i,
289 | category = this.categories[ii],
290 | component = this.categoryRefs[`category-${ii}`]
291 |
292 | if (component) {
293 | let active = component.handleScroll(scrollTop)
294 |
295 | if (!minTop || component.top < minTop) {
296 | if (component.top > 0) {
297 | minTop = component.top
298 | }
299 | }
300 |
301 | if (active && !activeCategory) {
302 | activeCategory = category
303 | }
304 | }
305 | }
306 |
307 | if (scrollTop < minTop) {
308 | activeCategory = this.categories.filter(
309 | (category) => !(category.anchor === false),
310 | )[0]
311 | } else if (scrollTop + this.clientHeight >= this.scrollHeight) {
312 | activeCategory = this.categories[this.categories.length - 1]
313 | }
314 | }
315 |
316 | if (activeCategory) {
317 | let { anchors } = this,
318 | { name: categoryName } = activeCategory
319 |
320 | if (anchors.state.selected != categoryName) {
321 | anchors.setState({ selected: categoryName })
322 | }
323 | }
324 |
325 | this.scrollTop = scrollTop
326 | }
327 |
328 | handleSearch(emojis) {
329 | this.SEARCH_CATEGORY.emojis = emojis
330 |
331 | for (let i = 0, l = this.categories.length; i < l; i++) {
332 | let component = this.categoryRefs[`category-${i}`]
333 |
334 | if (component && component.props.name != 'Search') {
335 | let display = emojis ? 'none' : 'inherit'
336 | component.updateDisplay(display)
337 | }
338 | }
339 |
340 | this.forceUpdate()
341 | this.scroll.scrollTop = 0
342 | this.handleScroll()
343 | }
344 |
345 | handleAnchorClick(category, i) {
346 | var component = this.categoryRefs[`category-${i}`],
347 | { scroll, anchors } = this,
348 | scrollToComponent = null
349 |
350 | scrollToComponent = () => {
351 | if (component) {
352 | let { top } = component
353 |
354 | if (category.first) {
355 | top = 0
356 | } else {
357 | top += 1
358 | }
359 |
360 | scroll.scrollTop = top
361 | }
362 | }
363 |
364 | if (this.SEARCH_CATEGORY.emojis) {
365 | this.handleSearch(null)
366 | this.search.clear()
367 |
368 | window.requestAnimationFrame(scrollToComponent)
369 | } else {
370 | scrollToComponent()
371 | }
372 | }
373 |
374 | handleSkinChange(skin) {
375 | var newState = { skin: skin },
376 | { onSkinChange } = this.props
377 |
378 | this.setState(newState)
379 | store.update(newState)
380 |
381 | onSkinChange(skin)
382 | }
383 |
384 | handleKeyDown(e) {
385 | let handled = false
386 |
387 | switch (e.keyCode) {
388 | case 13:
389 | let emoji
390 |
391 | if (
392 | this.SEARCH_CATEGORY.emojis &&
393 | (emoji = this.SEARCH_CATEGORY.emojis[0])
394 | ) {
395 | this.handleEmojiSelect(emoji)
396 | }
397 |
398 | handled = true
399 | break
400 | }
401 |
402 | if (handled) {
403 | e.preventDefault()
404 | }
405 | }
406 |
407 | updateCategoriesSize() {
408 | for (let i = 0, l = this.categories.length; i < l; i++) {
409 | let component = this.categoryRefs[`category-${i}`]
410 | if (component) component.memoizeSize()
411 | }
412 |
413 | if (this.scroll) {
414 | let target = this.scroll
415 | this.scrollHeight = target.scrollHeight
416 | this.clientHeight = target.clientHeight
417 | }
418 | }
419 |
420 | getCategories() {
421 | return this.state.firstRender
422 | ? this.categories.slice(0, 3)
423 | : this.categories
424 | }
425 |
426 | setAnchorsRef(c) {
427 | this.anchors = c
428 | }
429 |
430 | setSearchRef(c) {
431 | this.search = c
432 | }
433 |
434 | setPreviewRef(c) {
435 | this.preview = c
436 | }
437 |
438 | setScrollRef(c) {
439 | this.scroll = c
440 | }
441 |
442 | setCategoryRef(name, c) {
443 | if (!this.categoryRefs) {
444 | this.categoryRefs = {}
445 | }
446 |
447 | this.categoryRefs[name] = c
448 | }
449 |
450 | render() {
451 | var {
452 | perLine,
453 | emojiSize,
454 | set,
455 | sheetSize,
456 | style,
457 | title,
458 | emoji,
459 | color,
460 | native,
461 | backgroundImageFn,
462 | emojisToShowFilter,
463 | showPreview,
464 | showSkinTones,
465 | emojiTooltip,
466 | include,
467 | exclude,
468 | recent,
469 | autoFocus,
470 | } = this.props,
471 | { skin } = this.state,
472 | width = perLine * (emojiSize + 12) + 12 + 2 + measureScrollbar()
473 |
474 | return (
475 |
480 |
490 |
491 |
502 |
503 |
508 | {this.getCategories().map((category, i) => {
509 | return (
510 |
543 | )
544 | })}
545 |
546 |
547 | {showPreview && (
548 |
569 | )}
570 |
571 | )
572 | }
573 | }
574 |
575 | NimblePicker.propTypes = {
576 | ...PickerPropTypes,
577 | data: PropTypes.object.isRequired,
578 | }
579 | NimblePicker.defaultProps = { ...PickerDefaultProps }
580 |
--------------------------------------------------------------------------------
/src/components/picker/nimblePicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
28 |
29 |
30 |
39 |
51 |
52 |
53 |
65 |
66 |
67 |
68 |
69 |
305 |
306 |
319 |
320 |
361 |
--------------------------------------------------------------------------------
/src/components/picker/picker.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import data from '../../../data/all.json'
4 | import NimblePicker from './nimble-picker'
5 |
6 | import { PickerPropTypes, PickerDefaultProps } from '../../utils/shared-props'
7 |
8 | export default class Picker extends React.PureComponent {
9 | render() {
10 | return
11 | }
12 | }
13 |
14 | Picker.propTypes = PickerPropTypes
15 | Picker.defaultProps = { ...PickerDefaultProps, data }
16 |
--------------------------------------------------------------------------------
/src/components/picker/picker.vue:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/components/preview.old.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import { getData } from '../utils'
5 | import { NimbleEmoji, Skins } from '.'
6 |
7 | export default class Preview extends React.PureComponent {
8 | constructor(props) {
9 | super(props)
10 |
11 | this.data = props.data
12 | this.state = { emoji: null }
13 | }
14 |
15 | render() {
16 | var { emoji } = this.state,
17 | {
18 | emojiProps,
19 | skinsProps,
20 | showSkinTones,
21 | title,
22 | emoji: idleEmoji,
23 | } = this.props
24 |
25 | if (emoji) {
26 | var emojiData = getData(emoji, null, null, this.data),
27 | { emoticons = [] } = emojiData,
28 | knownEmoticons = [],
29 | listedEmoticons = []
30 |
31 | emoticons.forEach((emoticon) => {
32 | if (knownEmoticons.indexOf(emoticon.toLowerCase()) >= 0) {
33 | return
34 | }
35 |
36 | knownEmoticons.push(emoticon.toLowerCase())
37 | listedEmoticons.push(emoticon)
38 | })
39 |
40 | return (
41 |
42 |
43 | {NimbleEmoji({
44 | key: emoji.id,
45 | emoji: emoji,
46 | data: this.data,
47 | ...emojiProps,
48 | })}
49 |
50 |
51 |
52 |
{emoji.name}
53 |
54 | {emojiData.short_names.map((short_name) => (
55 |
56 | :{short_name}:
57 |
58 | ))}
59 |
60 |
61 | {listedEmoticons.map((emoticon) => (
62 |
63 | {emoticon}
64 |
65 | ))}
66 |
67 |
68 |
69 | )
70 | } else {
71 | return (
72 |
73 |
74 | {idleEmoji &&
75 | idleEmoji.length &&
76 | NimbleEmoji({ emoji: idleEmoji, data: this.data, ...emojiProps })}
77 |
78 |
79 |
80 | {title}
81 |
82 |
83 | {showSkinTones && (
84 |
85 |
86 |
87 | )}
88 |
89 | )
90 | }
91 | }
92 | }
93 |
94 | Preview.propTypes = {
95 | showSkinTones: PropTypes.bool,
96 | title: PropTypes.string.isRequired,
97 | emoji: PropTypes.string.isRequired,
98 | emojiProps: PropTypes.object.isRequired,
99 | skinsProps: PropTypes.object.isRequired,
100 | }
101 |
102 | Preview.defaultProps = {
103 | showSkinTones: true,
104 | onChange: () => {},
105 | }
106 |
--------------------------------------------------------------------------------
/src/components/preview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
19 |
20 |
{{ emoji.name }}
21 |
22 | :{{ shortName }}:
23 |
24 |
25 | {{ emoticon }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
43 |
44 |
45 |
46 | {{ title }}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
117 |
118 |
182 |
--------------------------------------------------------------------------------
/src/components/search.old.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import NimbleEmojiIndex from '../utils/emoji-index/nimble-emoji-index'
5 |
6 | export default class Search extends React.PureComponent {
7 | constructor(props) {
8 | super(props)
9 |
10 | this.data = props.data
11 | this.emojiIndex = new NimbleEmojiIndex(this.data)
12 | this.setRef = this.setRef.bind(this)
13 | this.handleChange = this.handleChange.bind(this)
14 | }
15 |
16 | handleChange() {
17 | var value = this.input.value
18 |
19 | this.props.onSearch(
20 | this.emojiIndex.search(value, {
21 | emojisToShowFilter: this.props.emojisToShowFilter,
22 | maxResults: this.props.maxResults,
23 | include: this.props.include,
24 | exclude: this.props.exclude,
25 | custom: this.props.custom,
26 | }),
27 | )
28 | }
29 |
30 | setRef(c) {
31 | this.input = c
32 | }
33 |
34 | clear() {
35 | this.input.value = ''
36 | }
37 |
38 | render() {
39 | var { i18n, autoFocus } = this.props
40 |
41 | return (
42 |
43 |
50 |
51 | )
52 | }
53 | }
54 |
55 | Search.propTypes = {
56 | onSearch: PropTypes.func,
57 | maxResults: PropTypes.number,
58 | emojisToShowFilter: PropTypes.func,
59 | autoFocus: PropTypes.bool,
60 | }
61 |
62 | Search.defaultProps = {
63 | onSearch: () => {},
64 | maxResults: 75,
65 | emojisToShowFilter: null,
66 | autoFocus: false,
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/search.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
82 |
83 |
101 |
--------------------------------------------------------------------------------
/src/components/skins.old.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | export default class Skins extends React.PureComponent {
5 | constructor(props) {
6 | super(props)
7 |
8 | this.state = {
9 | opened: false,
10 | }
11 |
12 | this.handleClick = this.handleClick.bind(this)
13 | }
14 |
15 | handleClick(e) {
16 | var skin = parseInt(e.currentTarget.getAttribute('data-skin'))
17 | var { onChange } = this.props
18 |
19 | if (!this.state.opened) {
20 | this.setState({ opened: true })
21 | } else {
22 | this.setState({ opened: false })
23 | if (skin != this.props.skin) {
24 | onChange(skin)
25 | }
26 | }
27 | }
28 |
29 | render() {
30 | const { skin } = this.props
31 | const { opened } = this.state
32 |
33 | const skinToneNodes = []
34 |
35 | for (let i = 0; i < 6; i++) {
36 | const skinTone = i + 1
37 | const selected = skinTone == skin
38 |
39 | skinToneNodes.push(
40 |
46 |
51 | ,
52 | )
53 | }
54 |
55 | return (
56 |
57 |
62 | {skinToneNodes}
63 |
64 |
65 | )
66 | }
67 | }
68 |
69 | Skins.propTypes = {
70 | onChange: PropTypes.func,
71 | skin: PropTypes.number.isRequired,
72 | }
73 |
74 | Skins.defaultProps = {
75 | onChange: () => {},
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/skins.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
39 |
40 |
108 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import emojiIndex from './utils/emoji-index/emoji-index'
2 | import store from './utils/store'
3 | import frequently from './utils/frequently'
4 |
5 | export {
6 | Picker,
7 | NimblePicker,
8 | Emoji,
9 | NimbleEmoji,
10 | Category,
11 | } from './components'
12 |
13 | export { default as NimbleEmojiIndex } from './utils/emoji-index/nimble-emoji-index'
14 | export { emojiIndex, store, frequently }
15 |
--------------------------------------------------------------------------------
/src/polyfills/createClass.js:
--------------------------------------------------------------------------------
1 | const _Object = Object
2 |
3 | export default (function createClass() {
4 | function defineProperties(target, props) {
5 | for (var i = 0; i < props.length; i++) {
6 | var descriptor = props[i]
7 | descriptor.enumerable = descriptor.enumerable || false
8 | descriptor.configurable = true
9 | if ('value' in descriptor) descriptor.writable = true
10 | _Object.defineProperty(target, descriptor.key, descriptor)
11 | }
12 | }
13 |
14 | return function(Constructor, protoProps, staticProps) {
15 | if (protoProps) defineProperties(Constructor.prototype, protoProps)
16 | if (staticProps) defineProperties(Constructor, staticProps)
17 | return Constructor
18 | }
19 | })()
20 |
--------------------------------------------------------------------------------
/src/polyfills/extends.js:
--------------------------------------------------------------------------------
1 | const _Object = Object
2 |
3 | export default _Object.assign ||
4 | function(target) {
5 | for (var i = 1; i < arguments.length; i++) {
6 | var source = arguments[i]
7 |
8 | for (var key in source) {
9 | if (Object.prototype.hasOwnProperty.call(source, key)) {
10 | target[key] = source[key]
11 | }
12 | }
13 | }
14 |
15 | return target
16 | }
17 |
--------------------------------------------------------------------------------
/src/polyfills/inherits.js:
--------------------------------------------------------------------------------
1 | const _Object = Object
2 |
3 | export default function inherits(subClass, superClass) {
4 | if (typeof superClass !== 'function' && superClass !== null) {
5 | throw new TypeError(
6 | 'Super expression must either be null or a function, not ' +
7 | typeof superClass,
8 | )
9 | }
10 |
11 | subClass.prototype = _Object.create(superClass && superClass.prototype, {
12 | constructor: {
13 | value: subClass,
14 | enumerable: false,
15 | writable: true,
16 | configurable: true,
17 | },
18 | })
19 | if (superClass) {
20 | _Object.setPrototypeOf
21 | ? _Object.setPrototypeOf(subClass, superClass)
22 | : (subClass.__proto__ = superClass)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/polyfills/objectGetPrototypeOf.js:
--------------------------------------------------------------------------------
1 | const _Object = Object
2 |
3 | export default _Object.getPrototypeOf ||
4 | function(O) {
5 | O = Object(O)
6 |
7 | if (typeof O.constructor === 'function' && O instanceof O.constructor) {
8 | return O.constructor.prototype
9 | }
10 |
11 | return O instanceof Object ? Object.prototype : null
12 | }
13 |
--------------------------------------------------------------------------------
/src/polyfills/possibleConstructorReturn.js:
--------------------------------------------------------------------------------
1 | export default function possibleConstructorReturn(self, call) {
2 | if (!self) {
3 | throw new ReferenceError(
4 | "this hasn't been initialised - super() hasn't been called",
5 | )
6 | }
7 |
8 | return call && (typeof call === 'object' || typeof call === 'function')
9 | ? call
10 | : self
11 | }
12 |
--------------------------------------------------------------------------------
/src/polyfills/stringFromCodePoint.js:
--------------------------------------------------------------------------------
1 | const _String = String
2 |
3 | export default _String.fromCodePoint ||
4 | function stringFromCodePoint() {
5 | var MAX_SIZE = 0x4000
6 | var codeUnits = []
7 | var highSurrogate
8 | var lowSurrogate
9 | var index = -1
10 | var length = arguments.length
11 | if (!length) {
12 | return ''
13 | }
14 | var result = ''
15 | while (++index < length) {
16 | var codePoint = Number(arguments[index])
17 | if (
18 | !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
19 | codePoint < 0 || // not a valid Unicode code point
20 | codePoint > 0x10ffff || // not a valid Unicode code point
21 | Math.floor(codePoint) != codePoint // not an integer
22 | ) {
23 | throw RangeError('Invalid code point: ' + codePoint)
24 | }
25 | if (codePoint <= 0xffff) {
26 | // BMP code point
27 | codeUnits.push(codePoint)
28 | } else {
29 | // Astral code point; split in surrogate halves
30 | // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
31 | codePoint -= 0x10000
32 | highSurrogate = (codePoint >> 10) + 0xd800
33 | lowSurrogate = codePoint % 0x400 + 0xdc00
34 | codeUnits.push(highSurrogate, lowSurrogate)
35 | }
36 | if (index + 1 === length || codeUnits.length > MAX_SIZE) {
37 | result += String.fromCharCode.apply(null, codeUnits)
38 | codeUnits.length = 0
39 | }
40 | }
41 | return result
42 | }
43 |
--------------------------------------------------------------------------------
/src/svgs/index.js:
--------------------------------------------------------------------------------
1 | const SVGs = {
2 | activity: ``,
3 |
4 | custom: ``,
5 |
6 | flags: ``,
7 |
8 | foods: ``,
9 |
10 | nature: ``,
11 |
12 | objects: ``,
13 |
14 | people: ``,
15 |
16 | places: ``,
17 |
18 | recent: ``,
19 |
20 | symbols: ``,
21 | }
22 |
23 | export default SVGs
24 |
--------------------------------------------------------------------------------
/src/utils/data.js:
--------------------------------------------------------------------------------
1 | const mapping = {
2 | name: 'a',
3 | unified: 'b',
4 | non_qualified: 'c',
5 | has_img_apple: 'd',
6 | has_img_google: 'e',
7 | has_img_twitter: 'f',
8 | has_img_emojione: 'g',
9 | has_img_facebook: 'h',
10 | has_img_messenger: 'i',
11 | keywords: 'j',
12 | sheet: 'k',
13 | emoticons: 'l',
14 | text: 'm',
15 | short_names: 'n',
16 | added_in: 'o',
17 | }
18 |
19 | const buildSearch = (emoji) => {
20 | const search = []
21 |
22 | var addToSearch = (strings, split) => {
23 | if (!strings) {
24 | return
25 | }
26 |
27 | ;(Array.isArray(strings) ? strings : [strings]).forEach((string) => {
28 | ;(split ? string.split(/[-|_|\s]+/) : [string]).forEach((s) => {
29 | s = s.toLowerCase()
30 |
31 | if (search.indexOf(s) == -1) {
32 | search.push(s)
33 | }
34 | })
35 | })
36 | }
37 |
38 | addToSearch(emoji.short_names, true)
39 | addToSearch(emoji.name, true)
40 | addToSearch(emoji.keywords, false)
41 | addToSearch(emoji.emoticons, false)
42 |
43 | return search.join(',')
44 | }
45 |
46 | const compress = (emoji) => {
47 | emoji.short_names = emoji.short_names.filter((short_name) => {
48 | return short_name !== emoji.short_name
49 | })
50 | delete emoji.short_name
51 |
52 | emoji.sheet = [emoji.sheet_x, emoji.sheet_y]
53 | delete emoji.sheet_x
54 | delete emoji.sheet_y
55 |
56 | emoji.added_in = parseInt(emoji.added_in)
57 | if (emoji.added_in === 6) {
58 | delete emoji.added_in
59 | }
60 |
61 | for (let key in mapping) {
62 | emoji[mapping[key]] = emoji[key]
63 | delete emoji[key]
64 | }
65 |
66 | for (let key in emoji) {
67 | let value = emoji[key]
68 |
69 | if (Array.isArray(value) && !value.length) {
70 | delete emoji[key]
71 | } else if (typeof value === 'string' && !value.length) {
72 | delete emoji[key]
73 | } else if (value === null) {
74 | delete emoji[key]
75 | }
76 | }
77 | }
78 |
79 | const uncompress = (data) => {
80 | data.compressed = false
81 |
82 | for (let id in data.emojis) {
83 | let emoji = data.emojis[id]
84 |
85 | for (let key in mapping) {
86 | emoji[key] = emoji[mapping[key]]
87 | delete emoji[mapping[key]]
88 | }
89 |
90 | if (!emoji.short_names) emoji.short_names = []
91 | emoji.short_names.unshift(id)
92 |
93 | emoji.sheet_x = emoji.sheet[0]
94 | emoji.sheet_y = emoji.sheet[1]
95 | delete emoji.sheet
96 |
97 | if (!emoji.text) emoji.text = ''
98 |
99 | if (!emoji.added_in) emoji.added_in = 6
100 | emoji.added_in = emoji.added_in.toFixed(1)
101 |
102 | emoji.search = buildSearch(emoji)
103 | }
104 | }
105 |
106 | module.exports = { buildSearch, compress, uncompress }
107 |
--------------------------------------------------------------------------------
/src/utils/emoji-index/emoji-index.js:
--------------------------------------------------------------------------------
1 | import data from '../../../data/all.json'
2 | import NimbleEmojiIndex from './nimble-emoji-index'
3 |
4 | const emojiIndex = new NimbleEmojiIndex(data)
5 | const { emojis, emoticons } = emojiIndex
6 |
7 | function search() {
8 | return emojiIndex.search(...arguments)
9 | }
10 |
11 | export default { search, emojis, emoticons }
12 |
--------------------------------------------------------------------------------
/src/utils/emoji-index/nimble-emoji-index.js:
--------------------------------------------------------------------------------
1 | import { getData, getSanitizedData, intersect } from '..'
2 | import { uncompress } from '../data'
3 |
4 | export default class NimbleEmojiIndex {
5 | constructor(data) {
6 | if (data.compressed) {
7 | data = uncompress(data)
8 | }
9 |
10 | this.data = data || {}
11 | this.originalPool = {}
12 | this.index = {}
13 | this.emojis = {}
14 | this.emoticons = {}
15 | this.customEmojisList = []
16 |
17 | this.buildIndex()
18 | }
19 |
20 | buildIndex() {
21 | for (let emoji in this.data.emojis) {
22 | let emojiData = this.data.emojis[emoji],
23 | { short_names, emoticons } = emojiData,
24 | id = short_names[0]
25 |
26 | if (emoticons) {
27 | emoticons.forEach((emoticon) => {
28 | if (this.emoticons[emoticon]) {
29 | return
30 | }
31 |
32 | this.emoticons[emoticon] = id
33 | })
34 | }
35 |
36 | this.emojis[id] = getSanitizedData(id, null, null, this.data)
37 | this.originalPool[id] = emojiData
38 | }
39 | }
40 |
41 | clearCustomEmojis(pool) {
42 | this.customEmojisList.forEach((emoji) => {
43 | let emojiId = emoji.id || emoji.short_names[0]
44 |
45 | delete pool[emojiId]
46 | delete this.emojis[emojiId]
47 | })
48 | }
49 |
50 | addCustomToPool(custom, pool) {
51 | if (this.customEmojisList.length) this.clearCustomEmojis(pool)
52 |
53 | custom.forEach((emoji) => {
54 | let emojiId = emoji.id || emoji.short_names[0]
55 |
56 | if (emojiId && !pool[emojiId]) {
57 | pool[emojiId] = getData(emoji, null, null, this.data)
58 | this.emojis[emojiId] = getSanitizedData(emoji, null, null, this.data)
59 | }
60 | })
61 |
62 | this.customEmojisList = custom
63 | this.index = {}
64 | }
65 |
66 | search(
67 | value,
68 | { emojisToShowFilter, maxResults, include, exclude, custom = [] } = {},
69 | ) {
70 | if (this.customEmojisList != custom)
71 | this.addCustomToPool(custom, this.originalPool)
72 |
73 | maxResults || (maxResults = 75)
74 | include || (include = [])
75 | exclude || (exclude = [])
76 |
77 | var results = null,
78 | pool = this.originalPool
79 |
80 | if (value.length) {
81 | if (value == '-' || value == '-1') {
82 | return [this.emojis['-1']]
83 | }
84 |
85 | var values = value.toLowerCase().split(/[\s|,|\-|_]+/),
86 | allResults = []
87 |
88 | if (values.length > 2) {
89 | values = [values[0], values[1]]
90 | }
91 |
92 | if (include.length || exclude.length) {
93 | pool = {}
94 |
95 | this.data.categories.forEach((category) => {
96 | let isIncluded =
97 | include && include.length ? include.indexOf(category.id) > -1 : true
98 | let isExcluded =
99 | exclude && exclude.length
100 | ? exclude.indexOf(category.id) > -1
101 | : false
102 | if (!isIncluded || isExcluded) {
103 | return
104 | }
105 |
106 | category.emojis.forEach(
107 | (emojiId) => (pool[emojiId] = this.data.emojis[emojiId]),
108 | )
109 | })
110 |
111 | if (custom.length) {
112 | let customIsIncluded =
113 | include && include.length ? include.indexOf('custom') > -1 : true
114 | let customIsExcluded =
115 | exclude && exclude.length ? exclude.indexOf('custom') > -1 : false
116 | if (customIsIncluded && !customIsExcluded) {
117 | this.addCustomToPool(custom, pool)
118 | }
119 | }
120 | }
121 |
122 | allResults = values
123 | .map((value) => {
124 | var aPool = pool,
125 | aIndex = this.index,
126 | length = 0
127 |
128 | for (let charIndex = 0; charIndex < value.length; charIndex++) {
129 | const char = value[charIndex]
130 | length++
131 |
132 | aIndex[char] || (aIndex[char] = {})
133 | aIndex = aIndex[char]
134 |
135 | if (!aIndex.results) {
136 | let scores = {}
137 |
138 | aIndex.results = []
139 | aIndex.pool = {}
140 |
141 | for (let id in aPool) {
142 | let emoji = aPool[id],
143 | { search } = emoji,
144 | sub = value.substr(0, length),
145 | subIndex = search.indexOf(sub)
146 |
147 | if (subIndex != -1) {
148 | let score = subIndex + 1
149 | if (sub == id) score = 0
150 |
151 | aIndex.results.push(this.emojis[id])
152 | aIndex.pool[id] = emoji
153 |
154 | scores[id] = score
155 | }
156 | }
157 |
158 | aIndex.results.sort((a, b) => {
159 | var aScore = scores[a.id],
160 | bScore = scores[b.id]
161 |
162 | return aScore - bScore
163 | })
164 | }
165 |
166 | aPool = aIndex.pool
167 | }
168 |
169 | return aIndex.results
170 | })
171 | .filter((a) => a)
172 |
173 | if (allResults.length > 1) {
174 | results = intersect.apply(null, allResults)
175 | } else if (allResults.length) {
176 | results = allResults[0]
177 | } else {
178 | results = []
179 | }
180 | }
181 |
182 | if (results) {
183 | if (emojisToShowFilter) {
184 | results = results.filter((result) =>
185 | emojisToShowFilter(pool[result.id]),
186 | )
187 | }
188 |
189 | if (results && results.length > maxResults) {
190 | results = results.slice(0, maxResults)
191 | }
192 | }
193 |
194 | return results
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/utils/frequently.js:
--------------------------------------------------------------------------------
1 | import store from './store'
2 |
3 | const DEFAULTS = [
4 | '+1',
5 | 'grinning',
6 | 'kissing_heart',
7 | 'heart_eyes',
8 | 'laughing',
9 | 'stuck_out_tongue_winking_eye',
10 | 'sweat_smile',
11 | 'joy',
12 | 'scream',
13 | 'disappointed',
14 | 'unamused',
15 | 'weary',
16 | 'sob',
17 | 'sunglasses',
18 | 'heart',
19 | 'poop',
20 | ]
21 |
22 | let frequently, initialized
23 | let defaults = {}
24 |
25 | function init() {
26 | initialized = true
27 | frequently = store.get('frequently')
28 | }
29 |
30 | function add(emoji) {
31 | if (!initialized) init()
32 | var { id } = emoji
33 |
34 | frequently || (frequently = defaults)
35 | frequently[id] || (frequently[id] = 0)
36 | frequently[id] += 1
37 |
38 | store.set('last', id)
39 | store.set('frequently', frequently)
40 | }
41 |
42 | function get(perLine) {
43 | if (!initialized) init()
44 | if (!frequently) {
45 | defaults = {}
46 |
47 | const result = []
48 |
49 | for (let i = 0; i < perLine; i++) {
50 | defaults[DEFAULTS[i]] = perLine - i
51 | result.push(DEFAULTS[i])
52 | }
53 |
54 | return result
55 | }
56 |
57 | const quantity = perLine * 4
58 | const frequentlyKeys = []
59 |
60 | for (let key in frequently) {
61 | if (frequently.hasOwnProperty(key)) {
62 | frequentlyKeys.push(key)
63 | }
64 | }
65 |
66 | const sorted = frequentlyKeys
67 | .sort((a, b) => frequently[a] - frequently[b])
68 | .reverse()
69 | const sliced = sorted.slice(0, quantity)
70 |
71 | const last = store.get('last')
72 |
73 | if (last && sliced.indexOf(last) == -1) {
74 | sliced.pop()
75 | sliced.push(last)
76 | }
77 |
78 | return sliced
79 | }
80 |
81 | export default { add, get }
82 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import { buildSearch } from './data'
2 | import stringFromCodePoint from '../polyfills/stringFromCodePoint'
3 |
4 | const _JSON = JSON
5 |
6 | const COLONS_REGEX = /^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/
7 | const SKINS = ['1F3FA', '1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF']
8 |
9 | function unifiedToNative(unified) {
10 | var unicodes = unified.split('-'),
11 | codePoints = unicodes.map((u) => `0x${u}`)
12 |
13 | return stringFromCodePoint.apply(null, codePoints)
14 | }
15 |
16 | function sanitize(emoji) {
17 | var {
18 | name,
19 | short_names,
20 | skin_tone,
21 | skin_variations,
22 | emoticons,
23 | unified,
24 | custom,
25 | imageUrl,
26 | } = emoji,
27 | id = emoji.id || short_names[0],
28 | colons = `:${id}:`
29 |
30 | if (custom) {
31 | return {
32 | id,
33 | name,
34 | colons,
35 | emoticons,
36 | custom,
37 | imageUrl,
38 | }
39 | }
40 |
41 | if (skin_tone) {
42 | colons += `:skin-tone-${skin_tone}:`
43 | }
44 |
45 | return {
46 | id,
47 | name,
48 | colons,
49 | emoticons,
50 | unified: unified.toLowerCase(),
51 | skin: skin_tone || (skin_variations ? 1 : null),
52 | native: unifiedToNative(unified),
53 | }
54 | }
55 |
56 | function getSanitizedData() {
57 | return sanitize(getData(...arguments))
58 | }
59 |
60 | function cloneEmoji(emoji) {
61 | if (typeof emoji === 'string') {
62 | return emoji;
63 | }
64 |
65 | return Object.assign({}, emoji);
66 | }
67 |
68 | function getData(_emoji, skin, set, data) {
69 | var emoji = cloneEmoji(_emoji)
70 | var emojiData = {}
71 |
72 | if (typeof emoji == 'string') {
73 | let matches = emoji.match(COLONS_REGEX)
74 |
75 | if (matches) {
76 | emoji = matches[1]
77 |
78 | if (matches[2]) {
79 | skin = parseInt(matches[2], 10)
80 | }
81 | }
82 |
83 | if (data.aliases.hasOwnProperty(emoji)) {
84 | emoji = data.aliases[emoji]
85 | }
86 |
87 | if (data.emojis.hasOwnProperty(emoji)) {
88 | emojiData = data.emojis[emoji]
89 | } else {
90 | return null
91 | }
92 | } else if (emoji.id) {
93 | if (data.aliases.hasOwnProperty(emoji.id)) {
94 | emoji.id = data.aliases[emoji.id]
95 | }
96 |
97 | if (data.emojis.hasOwnProperty(emoji.id)) {
98 | emojiData = data.emojis[emoji.id]
99 | skin || (skin = emoji.skin)
100 | }
101 | }
102 |
103 | if (!Object.keys(emojiData).length) {
104 | emojiData = emoji
105 | emojiData.custom = true
106 |
107 | if (!emojiData.search) {
108 | emojiData.search = buildSearch(emoji)
109 | }
110 | }
111 |
112 | emojiData.emoticons || (emojiData.emoticons = [])
113 | emojiData.variations || (emojiData.variations = [])
114 |
115 | if (emojiData.skin_variations && skin > 1) {
116 | emojiData = JSON.parse(_JSON.stringify(emojiData))
117 |
118 | var skinKey = SKINS[skin - 1],
119 | variationData = emojiData.skin_variations[skinKey]
120 |
121 | if (!variationData.variations && emojiData.variations) {
122 | delete emojiData.variations
123 | }
124 |
125 | if (
126 | set == 'native' ||
127 | variationData[`has_img_${set}`] == undefined ||
128 | variationData[`has_img_${set}`]
129 | ) {
130 | emojiData.skin_tone = skin
131 |
132 | for (let k in variationData) {
133 | let v = variationData[k]
134 | emojiData[k] = v
135 | }
136 | }
137 | }
138 |
139 | if (emojiData.variations && emojiData.variations.length) {
140 | emojiData = JSON.parse(_JSON.stringify(emojiData))
141 | emojiData.unified = emojiData.variations.shift()
142 | }
143 |
144 | return emojiData
145 | }
146 |
147 | function uniq(arr) {
148 | return arr.reduce((acc, item) => {
149 | if (acc.indexOf(item) === -1) {
150 | acc.push(item)
151 | }
152 | return acc
153 | }, [])
154 | }
155 |
156 | function intersect(a, b) {
157 | const uniqA = uniq(a)
158 | const uniqB = uniq(b)
159 |
160 | return uniqA.filter((item) => uniqB.indexOf(item) >= 0)
161 | }
162 |
163 | function deepMerge(a, b) {
164 | var o = {}
165 |
166 | for (let key in a) {
167 | let originalValue = a[key],
168 | value = originalValue
169 |
170 | if (b.hasOwnProperty(key)) {
171 | value = b[key]
172 | }
173 |
174 | if (typeof value === 'object') {
175 | value = deepMerge(originalValue, value)
176 | }
177 |
178 | o[key] = value
179 | }
180 |
181 | return o
182 | }
183 |
184 | // https://github.com/sonicdoe/measure-scrollbar
185 | function measureScrollbar() {
186 | if (typeof document == 'undefined') return 0
187 | const div = document.createElement('div')
188 |
189 | div.style.width = '100px'
190 | div.style.height = '100px'
191 | div.style.overflow = 'scroll'
192 | div.style.position = 'absolute'
193 | div.style.top = '-9999px'
194 |
195 | document.body.appendChild(div)
196 | const scrollbarWidth = div.offsetWidth - div.clientWidth
197 | document.body.removeChild(div)
198 |
199 | return scrollbarWidth
200 | }
201 |
202 | export {
203 | getData,
204 | getSanitizedData,
205 | uniq,
206 | intersect,
207 | deepMerge,
208 | unifiedToNative,
209 | measureScrollbar,
210 | }
211 |
--------------------------------------------------------------------------------
/src/utils/shared-props.js:
--------------------------------------------------------------------------------
1 | const EmojiProps = {
2 | backgroundImageFn: {
3 | type: Function,
4 | default: function(set, sheetSize) {
5 | return `https://unpkg.com/emoji-datasource-${set}@${EMOJI_DATASOURCE_VERSION}/img/${set}/sheets-256/${sheetSize}.png`
6 | }
7 | },
8 | native: {
9 | type: Boolean,
10 | default: false
11 | },
12 | forceSize: {
13 | type: Boolean,
14 | default: false
15 | },
16 | tooltip: {
17 | type: Boolean,
18 | default: false
19 | },
20 | fallback: {
21 | type: Function
22 | },
23 | skin: {
24 | type: Number,
25 | default: 1
26 | },
27 | sheetSize: {
28 | type: Number,
29 | default: 64
30 | },
31 | set: {
32 | type: String,
33 | default: 'apple'
34 | },
35 | size: {
36 | type: Number,
37 | default: 24
38 | },
39 | emoji: {
40 | type: [String, Object],
41 | required: true
42 | }
43 | }
44 |
45 | const PickerProps = {
46 | perLine: {
47 | type: Number,
48 | default: 9
49 | },
50 | emojiSize: {
51 | type: Number,
52 | default: 24
53 | },
54 | title: {
55 | type: String,
56 | default: 'Emoji Mart™'
57 | },
58 | emoji: {
59 | type: String,
60 | default: 'department_store'
61 | },
62 | color: {
63 | type: String,
64 | default: '#ae65c5'
65 | },
66 | set: {
67 | type: String,
68 | default: 'apple'
69 | },
70 | skin: {
71 | type: Number,
72 | default: null
73 | },
74 | defaultSkin: {
75 | type: Number,
76 | default: 1
77 | },
78 | native: {
79 | type: Boolean,
80 | default: false
81 | },
82 | backgroundImageFn: {
83 | type: Function
84 | },
85 | sheetSize: {
86 | type: Number,
87 | default: 64
88 | },
89 | emojisToShowFilter: {
90 | type: Function
91 | },
92 | emojiTooltip: {
93 | type: Boolean,
94 | default: false
95 | },
96 | include: {
97 | type: Array
98 | },
99 | exclude: {
100 | type: Array
101 | },
102 | recent: {
103 | type: Array
104 | },
105 | autoFocus: {
106 | type: Boolean,
107 | default: false
108 | },
109 | custom: {
110 | type: Array,
111 | default() {
112 | return []
113 | }
114 | },
115 | i18n: {
116 | type: Object,
117 | default() {
118 | return {}
119 | }
120 | },
121 | showPreview: {
122 | type: Boolean,
123 | default: true
124 | },
125 | showSearch: {
126 | type: Boolean,
127 | default: true
128 | },
129 | showCategories: {
130 | type: Boolean,
131 | default: true
132 | },
133 | showSkinTones: {
134 | type: Boolean,
135 | default: true
136 | },
137 | infiniteScroll: {
138 | type: Boolean,
139 | default: true
140 | },
141 | pickerStyles: {
142 | type: Object,
143 | default() {
144 | return {}
145 | }
146 | }
147 | }
148 |
149 | export {
150 | EmojiProps,
151 | PickerProps
152 | }
153 |
--------------------------------------------------------------------------------
/src/utils/store.js:
--------------------------------------------------------------------------------
1 | var NAMESPACE = 'emoji-mart'
2 |
3 | const _JSON = JSON
4 |
5 | var isLocalStorageSupported =
6 | typeof window !== 'undefined' && 'localStorage' in window
7 |
8 | let getter
9 | let setter
10 |
11 | function setHandlers(handlers) {
12 | handlers || (handlers = {})
13 |
14 | getter = handlers.getter
15 | setter = handlers.setter
16 | }
17 |
18 | function setNamespace(namespace) {
19 | NAMESPACE = namespace
20 | }
21 |
22 | function update(state) {
23 | for (let key in state) {
24 | let value = state[key]
25 | set(key, value)
26 | }
27 | }
28 |
29 | function set(key, value) {
30 | if (setter) {
31 | setter(key, value)
32 | } else {
33 | if (!isLocalStorageSupported) return
34 | try {
35 | window.localStorage[`${NAMESPACE}.${key}`] = _JSON.stringify(value)
36 | } catch (e) {}
37 | }
38 | }
39 |
40 | function get(key) {
41 | if (getter) {
42 | return getter(key)
43 | } else {
44 | if (!isLocalStorageSupported) return
45 | try {
46 | var value = window.localStorage[`${NAMESPACE}.${key}`]
47 | } catch (e) {
48 | return
49 | }
50 |
51 | if (value) {
52 | return JSON.parse(value)
53 | }
54 | }
55 | }
56 |
57 | export default { update, set, get, setNamespace, setHandlers }
58 |
--------------------------------------------------------------------------------
/src/vendor/raf-polyfill.js:
--------------------------------------------------------------------------------
1 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
2 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
3 |
4 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
5 |
6 | // MIT license
7 |
8 | var isWindowAvailable = typeof window !== 'undefined'
9 |
10 | isWindowAvailable &&
11 | (function() {
12 | var lastTime = 0
13 | var vendors = ['ms', 'moz', 'webkit', 'o']
14 |
15 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
16 | window.requestAnimationFrame =
17 | window[vendors[x] + 'RequestAnimationFrame']
18 | window.cancelAnimationFrame =
19 | window[vendors[x] + 'CancelAnimationFrame'] ||
20 | window[vendors[x] + 'CancelRequestAnimationFrame']
21 | }
22 |
23 | if (!window.requestAnimationFrame)
24 | window.requestAnimationFrame = function(callback, element) {
25 | var currTime = new Date().getTime()
26 | var timeToCall = Math.max(0, 16 - (currTime - lastTime))
27 | var id = window.setTimeout(function() {
28 | callback(currTime + timeToCall)
29 | }, timeToCall)
30 |
31 | lastTime = currTime + timeToCall
32 | return id
33 | }
34 |
35 | if (!window.cancelAnimationFrame)
36 | window.cancelAnimationFrame = function(id) {
37 | clearTimeout(id)
38 | }
39 | })()
40 |
--------------------------------------------------------------------------------
/src/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var pack = require('../package.json')
3 | var webpack = require('webpack')
4 |
5 | var PROD = process.env.NODE_ENV === 'production';
6 | var TEST = process.env.NODE_ENV === 'test';
7 |
8 | module.exports = {
9 | entry: path.resolve('src/index.js'),
10 | output: {
11 | path: path.resolve('dist'),
12 | filename: 'emoji-mart.js',
13 | library: 'EmojiMart',
14 | libraryTarget: 'umd',
15 | },
16 |
17 | externals: !TEST && [{
18 | 'vue': {
19 | root: 'Vue',
20 | commonjs2: 'vue',
21 | commonjs: 'vue',
22 | amd: 'vue',
23 | },
24 | }],
25 |
26 | module: {
27 | loaders: [
28 | {
29 | test: /\.js$/,
30 | loader: 'babel-loader',
31 | include: [
32 | path.resolve('src'),
33 | path.resolve('data'),
34 | ],
35 | },
36 | {
37 | test: /\.vue$/,
38 | loader: 'vue-loader',
39 | include: [
40 | path.resolve('src'),
41 | ]
42 | },
43 | {
44 | test: /\.css$/,
45 | use: ["vue-style-loader", "css-loader", "postcss-loader"]
46 | },
47 | {
48 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
49 | loader: 'url-loader',
50 | options: {
51 | limit: 10000
52 | }
53 | }
54 | ],
55 | },
56 |
57 | resolve: {
58 | extensions: ['.vue', '.js'],
59 | },
60 |
61 | plugins: [
62 | new webpack.DefinePlugin({
63 | EMOJI_DATASOURCE_VERSION: `'${pack.devDependencies['emoji-datasource']}'`,
64 | }),
65 | ],
66 |
67 | bail: true,
68 | }
69 |
--------------------------------------------------------------------------------