@schwitzerskills/emojipicker 1.0.0 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +677 -0
  2. package/package.json +4 -4
package/README.md ADDED
@@ -0,0 +1,677 @@
1
+ # EmojiPicker.js
2
+
3
+ > A flexible, event-driven emoji picker library for the web.
4
+ > Zero dependencies. Full developer control. Framework agnostic.
5
+
6
+ ```js
7
+ const picker = new EmojiPicker({ container: '#btn' })
8
+ picker.on('emojiClick', (emoji) => console.log(emoji.char)) // 😂
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ - [Installation](#installation)
16
+ - [Module Formats](#module-formats)
17
+ - [Quick Start](#quick-start)
18
+ - [Configuration Options](#configuration-options)
19
+ - [Events](#events)
20
+ - [Methods](#methods)
21
+ - [Emoji Object](#emoji-object)
22
+ - [Modes](#modes)
23
+ - [Skin Tone Support](#skin-tone-support)
24
+ - [Theming & CSS Variables](#theming--css-variables)
25
+ - [Custom Emojis](#custom-emojis)
26
+ - [Helper: attachToInput()](#helper-attachtoinput)
27
+ - [Framework Integration](#framework-integration)
28
+ - [Recipes / Examples](#recipes--examples)
29
+ - [Accessibility](#accessibility)
30
+ - [Browser Support](#browser-support)
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ npm install @schwitzerskills/emojipicker
38
+ # or
39
+ yarn add @schwitzerskills/emojipicker
40
+ # or
41
+ pnpm add @schwitzerskills/emojipicker
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Module Formats
47
+
48
+ The library ships as a **UMD build** and supports all environments out of the box.
49
+
50
+ ### CommonJS (Node.js / bundlers)
51
+
52
+ ```js
53
+ const EmojiPicker = require('emojipicker-js')
54
+ ```
55
+
56
+ ### ES Module
57
+
58
+ ```js
59
+ import EmojiPicker from 'emojipicker-js'
60
+ ```
61
+
62
+ ### Browser global (CDN / script tag)
63
+
64
+ ```html
65
+ <script src="emoji-picker.js"></script>
66
+ <script>
67
+ const picker = new EmojiPicker({ container: '#btn' })
68
+ </script>
69
+ ```
70
+
71
+ ### AMD (RequireJS)
72
+
73
+ ```js
74
+ define(['emojipicker-js'], function(EmojiPicker) {
75
+ const picker = new EmojiPicker({ container: '#btn' })
76
+ })
77
+ ```
78
+
79
+ The `package.json` fields are set up accordingly:
80
+
81
+ ```json
82
+ {
83
+ "main": "emoji-picker.js",
84
+ "module": "emoji-picker.esm.js",
85
+ "browser": "emoji-picker.js",
86
+ "exports": {
87
+ ".": {
88
+ "import": "./emoji-picker.esm.js",
89
+ "require": "./emoji-picker.js"
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Quick Start
98
+
99
+ **Step 1 — Add a trigger button to your HTML:**
100
+
101
+ ```html
102
+ <button id="emoji-btn">😊</button>
103
+ <input type="text" id="message" placeholder="Type a message...">
104
+
105
+ <script src="emoji-picker.js"></script>
106
+ ```
107
+
108
+ **Step 2 — Initialize and listen for events:**
109
+
110
+ ```html
111
+ <script>
112
+ const picker = new EmojiPicker({
113
+ container: '#emoji-btn',
114
+ theme: 'auto'
115
+ })
116
+
117
+ picker.on('emojiClick', (emoji) => {
118
+ document.querySelector('#message').value += emoji.char
119
+ })
120
+ </script>
121
+ ```
122
+
123
+ That's it. Clicking `#emoji-btn` opens the picker. Clicking an emoji fires `emojiClick`.
124
+
125
+ ---
126
+
127
+ ## Configuration Options
128
+
129
+ All properties are optional.
130
+
131
+ ```js
132
+ const picker = new EmojiPicker({
133
+ container: '#my-button', // Trigger element (CSS selector or DOM node)
134
+ theme: 'auto', // 'light' | 'dark' | 'auto'
135
+ mode: 'dropdown', // 'dropdown' | 'inline' | 'popup'
136
+ search: true, // Show search input
137
+ recentEmojis: true, // Track & show recently used emojis
138
+ maxRecent: 24, // Max number of recent emojis to store
139
+ skinTone: 'default', // Default skin tone (see Skin Tone Support)
140
+ customEmojis: [], // Array of custom emoji objects
141
+ perRow: 8, // Emojis per row in the grid
142
+ emojiSize: 28, // Emoji size in pixels
143
+ autoClose: true, // Close picker after selecting an emoji
144
+ })
145
+ ```
146
+
147
+ | Option | Type | Default | Description |
148
+ |--------|------|---------|-------------|
149
+ | `container` | `string \| HTMLElement` | `null` | Element that triggers open/close on click |
150
+ | `theme` | `string` | `'auto'` | Color theme: `'light'`, `'dark'`, or `'auto'` |
151
+ | `mode` | `string` | `'dropdown'` | Display mode (see [Modes](#modes)) |
152
+ | `search` | `boolean` | `true` | Show/hide the search input |
153
+ | `recentEmojis` | `boolean` | `true` | Enable recent emojis tab (uses localStorage) |
154
+ | `maxRecent` | `number` | `24` | Max recent emojis to remember |
155
+ | `skinTone` | `string` | `'default'` | Default skin tone modifier |
156
+ | `customEmojis` | `array` | `[]` | Custom emoji definitions |
157
+ | `perRow` | `number` | `8` | Grid columns |
158
+ | `emojiSize` | `number` | `28` | Emoji size in px |
159
+ | `autoClose` | `boolean` | `true` | Auto-close picker on emoji select |
160
+
161
+ ---
162
+
163
+ ## Events
164
+
165
+ The library is fully event-driven. Attach any number of listeners to any event.
166
+
167
+ ```js
168
+ picker.on(eventName, handler)
169
+ picker.off(eventName, handler) // Remove a specific listener
170
+ ```
171
+
172
+ ### `emojiClick`
173
+
174
+ Fired when the user clicks/selects an emoji. This is the main event you'll use.
175
+
176
+ ```js
177
+ picker.on('emojiClick', (emoji, mouseEvent) => {
178
+ console.log(emoji.char) // "😂"
179
+ console.log(emoji.name) // "face_with_tears_of_joy"
180
+ console.log(emoji.category) // "Smileys & Emotion"
181
+ console.log(emoji.unicode) // "1F602"
182
+ console.log(emoji.skinTone) // null | "light" | "medium" | ...
183
+ })
184
+ ```
185
+
186
+ ### `emojiHover`
187
+
188
+ Fired when the user hovers over an emoji.
189
+
190
+ ```js
191
+ picker.on('emojiHover', (emoji, mouseEvent) => {
192
+ myPreview.textContent = `${emoji.char} ${emoji.name}`
193
+ })
194
+ ```
195
+
196
+ ### `pickerOpen`
197
+
198
+ ```js
199
+ picker.on('pickerOpen', () => {
200
+ console.log('Picker opened')
201
+ })
202
+ ```
203
+
204
+ ### `pickerClose`
205
+
206
+ ```js
207
+ picker.on('pickerClose', () => {
208
+ console.log('Picker closed')
209
+ })
210
+ ```
211
+
212
+ ### `categoryChange`
213
+
214
+ ```js
215
+ picker.on('categoryChange', ({ category }) => {
216
+ console.log('Switched to:', category) // "Food & Drink"
217
+ })
218
+ ```
219
+
220
+ ### `search`
221
+
222
+ ```js
223
+ picker.on('search', ({ query }) => {
224
+ console.log('Searching for:', query)
225
+ })
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Methods
231
+
232
+ All methods return `this` (chainable, except `destroy()`).
233
+
234
+ ```js
235
+ picker.open() // Open the picker
236
+ picker.close() // Close the picker
237
+ picker.toggle() // Toggle open/close
238
+ picker.setTheme('dark') // Switch theme at runtime
239
+ picker.destroy() // Remove from DOM and clean up all listeners
240
+ ```
241
+
242
+ **Chaining:**
243
+
244
+ ```js
245
+ new EmojiPicker({ container: '#btn', theme: 'light' })
246
+ .on('emojiClick', (e) => addEmoji(e.char))
247
+ .on('pickerOpen', () => analytics.track('emoji_picker_opened'))
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Emoji Object
253
+
254
+ Every emoji-related event provides this data structure:
255
+
256
+ ```js
257
+ {
258
+ char: "👍🏽", // The emoji character (with skin tone applied)
259
+ name: "thumbs_up", // Snake_case identifier
260
+ category: "People & Body", // Category name
261
+ unicode: "1F44D", // Base Unicode code point (hex)
262
+ skinTone: "medium", // null if default, otherwise the tone name
263
+ isCustom: false // true for custom emojis
264
+ }
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Modes
270
+
271
+ ### `dropdown` *(default)*
272
+
273
+ Opens as a floating panel anchored to the trigger element. Closes on outside click or `Esc`.
274
+
275
+ ```js
276
+ new EmojiPicker({ container: '#btn', mode: 'dropdown' })
277
+ ```
278
+
279
+ ### `inline`
280
+
281
+ Always visible, embedded directly inside the container element. `autoClose` is ignored.
282
+
283
+ ```js
284
+ new EmojiPicker({ container: '#my-div', mode: 'inline', autoClose: false })
285
+ ```
286
+
287
+ ```html
288
+ <div id="my-div"></div> <!-- Picker renders here -->
289
+ ```
290
+
291
+ ### `popup`
292
+
293
+ Positions itself in the center of the viewport. Useful for modal-style pickers without a fixed trigger.
294
+
295
+ ```js
296
+ const picker = new EmojiPicker({ mode: 'popup' })
297
+ document.getElementById('open-btn').addEventListener('click', () => picker.open())
298
+ ```
299
+
300
+ ---
301
+
302
+ ## Skin Tone Support
303
+
304
+ Users can select a skin tone in the picker footer. Set a default via options:
305
+
306
+ ```js
307
+ new EmojiPicker({ skinTone: 'medium-dark' })
308
+ ```
309
+
310
+ | Value | Example |
311
+ |-------|---------|
312
+ | `'default'` | 👍 |
313
+ | `'light'` | 👍🏻 |
314
+ | `'medium-light'` | 👍🏼 |
315
+ | `'medium'` | 👍🏽 |
316
+ | `'medium-dark'` | 👍🏾 |
317
+ | `'dark'` | 👍🏿 |
318
+
319
+ The selected tone is reflected in `emoji.char` and reported in `emoji.skinTone`.
320
+
321
+ ---
322
+
323
+ ## Theming & CSS Variables
324
+
325
+ Override CSS variables to match any design system:
326
+
327
+ ```css
328
+ .ep-picker {
329
+ --ep-bg: #0f1117;
330
+ --ep-surface: #1a1d2e;
331
+ --ep-surface2: #21253a;
332
+ --ep-border: rgba(255,255,255,0.08);
333
+ --ep-text: #e4e7f3;
334
+ --ep-text-dim: #6b738f;
335
+ --ep-accent: #6c63ff;
336
+ --ep-hover: rgba(108,99,255,0.12);
337
+ --ep-size: 28px; /* Emoji size */
338
+ --ep-radius: 16px; /* Picker border radius */
339
+ }
340
+ ```
341
+
342
+ ### Built-in themes
343
+
344
+ ```js
345
+ new EmojiPicker({ theme: 'light' }) // Light
346
+ new EmojiPicker({ theme: 'dark' }) // Dark
347
+ new EmojiPicker({ theme: 'auto' }) // Follows OS preference
348
+
349
+ picker.setTheme('dark') // Switch at runtime
350
+ ```
351
+
352
+ ### Custom accent color
353
+
354
+ ```css
355
+ .ep-picker {
356
+ --ep-accent: #e91e8c;
357
+ --ep-hover: rgba(233, 30, 140, 0.1);
358
+ --ep-active-tab: rgba(233, 30, 140, 0.15);
359
+ }
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Custom Emojis
365
+
366
+ Add your own images, GIFs or SVGs alongside the standard set:
367
+
368
+ ```js
369
+ new EmojiPicker({
370
+ customEmojis: [
371
+ { name: 'party_parrot', url: '/assets/parrot.gif' },
372
+ { name: 'company_logo', url: '/assets/logo.png' },
373
+ { name: 'custom_star', url: 'https://example.com/star.svg' }
374
+ ]
375
+ })
376
+ ```
377
+
378
+ Custom emojis appear in their own **Custom** tab. The click event returns:
379
+
380
+ ```js
381
+ {
382
+ char: null,
383
+ name: 'party_parrot',
384
+ category: 'custom',
385
+ isCustom: true
386
+ }
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Helper: attachToInput()
392
+
393
+ Wraps any `<input>` or `<textarea>` and inserts the selected emoji at the cursor position automatically.
394
+
395
+ ```js
396
+ // Attach by selector
397
+ EmojiPicker.attachToInput('#message-input')
398
+
399
+ // With options
400
+ EmojiPicker.attachToInput('#chat-box', {
401
+ theme: 'dark',
402
+ skinTone: 'medium'
403
+ })
404
+
405
+ // Returns the picker instance for further event binding
406
+ const picker = EmojiPicker.attachToInput('#my-input')
407
+ picker.on('emojiClick', (emoji) => {
408
+ counter.textContent = myInput.value.length
409
+ })
410
+ ```
411
+
412
+ ---
413
+
414
+ ## Framework Integration
415
+
416
+ ### React
417
+
418
+ Works out of the box with a standard `import`. Initialize inside `useEffect` so the library only runs in the browser.
419
+
420
+ ```jsx
421
+ import { useEffect, useRef } from 'react'
422
+ import EmojiPicker from 'emojipicker-js'
423
+
424
+ function EmojiButton({ onSelect }) {
425
+ const btnRef = useRef(null)
426
+
427
+ useEffect(() => {
428
+ const picker = new EmojiPicker({
429
+ container: btnRef.current,
430
+ theme: 'auto'
431
+ })
432
+ picker.on('emojiClick', onSelect)
433
+
434
+ return () => picker.destroy() // clean up on unmount
435
+ }, [onSelect])
436
+
437
+ return <button ref={btnRef}>😊</button>
438
+ }
439
+ ```
440
+
441
+ ### Next.js
442
+
443
+ EmojiPicker uses `window` and `document` internally, so it must only run on the client. There are two ways to handle this:
444
+
445
+ **Option A — dynamic import (recommended):**
446
+
447
+ ```jsx
448
+ // components/EmojiButton.jsx
449
+ import { useEffect, useRef } from 'react'
450
+ import EmojiPicker from 'emojipicker-js'
451
+
452
+ export default function EmojiButton({ onSelect }) {
453
+ const btnRef = useRef(null)
454
+ useEffect(() => {
455
+ const picker = new EmojiPicker({ container: btnRef.current })
456
+ picker.on('emojiClick', onSelect)
457
+ return () => picker.destroy()
458
+ }, [onSelect])
459
+ return <button ref={btnRef}>😊</button>
460
+ }
461
+ ```
462
+
463
+ ```jsx
464
+ // pages/index.jsx or any page
465
+ import dynamic from 'next/dynamic'
466
+
467
+ const EmojiButton = dynamic(() => import('../components/EmojiButton'), {
468
+ ssr: false // prevents server-side execution
469
+ })
470
+ ```
471
+
472
+ **Option B — useEffect guard (also safe):**
473
+
474
+ ```jsx
475
+ useEffect(() => {
476
+ // useEffect only runs in the browser, so this is always safe.
477
+ // Add typeof window check only if you import EmojiPicker outside of useEffect.
478
+ const picker = new EmojiPicker({ container: btnRef.current })
479
+ picker.on('emojiClick', onSelect)
480
+ return () => picker.destroy()
481
+ }, [onSelect])
482
+ ```
483
+
484
+ ### Vue 3
485
+
486
+ ```vue
487
+ <template>
488
+ <button ref="btnRef">😊</button>
489
+ </template>
490
+
491
+ <script setup>
492
+ import { ref, onMounted, onUnmounted } from 'vue'
493
+ import EmojiPicker from 'emojipicker-js'
494
+
495
+ const emit = defineEmits(['select'])
496
+ const btnRef = ref(null)
497
+ let picker = null
498
+
499
+ onMounted(() => {
500
+ picker = new EmojiPicker({ container: btnRef.value, theme: 'auto' })
501
+ picker.on('emojiClick', (emoji) => emit('select', emoji))
502
+ })
503
+
504
+ onUnmounted(() => picker?.destroy())
505
+ </script>
506
+ ```
507
+
508
+ ### Svelte
509
+
510
+ ```svelte
511
+ <script>
512
+ import { onMount, onDestroy, createEventDispatcher } from 'svelte'
513
+ import EmojiPicker from 'emojipicker-js'
514
+
515
+ const dispatch = createEventDispatcher()
516
+ let btnEl
517
+ let picker
518
+
519
+ onMount(() => {
520
+ picker = new EmojiPicker({ container: btnEl, theme: 'auto' })
521
+ picker.on('emojiClick', (emoji) => dispatch('select', emoji))
522
+ })
523
+
524
+ onDestroy(() => picker?.destroy())
525
+ </script>
526
+
527
+ <button bind:this={btnEl}>😊</button>
528
+ ```
529
+
530
+ ### Vanilla JS (no bundler)
531
+
532
+ ```html
533
+ <script src="https://cdn.example.com/emoji-picker.js"></script>
534
+ <script>
535
+ const picker = new EmojiPicker({ container: '#btn', theme: 'auto' })
536
+ picker.on('emojiClick', (emoji) => {
537
+ document.querySelector('#input').value += emoji.char
538
+ })
539
+ </script>
540
+ ```
541
+
542
+ ---
543
+
544
+ ## Recipes / Examples
545
+
546
+ ### Insert emoji at cursor in a textarea
547
+
548
+ ```js
549
+ const textarea = document.querySelector('#editor')
550
+ const picker = new EmojiPicker({ container: '#emoji-trigger' })
551
+
552
+ picker.on('emojiClick', (emoji) => {
553
+ const start = textarea.selectionStart
554
+ const end = textarea.selectionEnd
555
+ textarea.value =
556
+ textarea.value.substring(0, start) +
557
+ emoji.char +
558
+ textarea.value.substring(end)
559
+ textarea.setSelectionRange(
560
+ start + emoji.char.length,
561
+ start + emoji.char.length
562
+ )
563
+ textarea.focus()
564
+ })
565
+ ```
566
+
567
+ ### Copy to clipboard
568
+
569
+ ```js
570
+ picker.on('emojiClick', (emoji) => {
571
+ navigator.clipboard.writeText(emoji.char).then(() => {
572
+ showToast(`Copied ${emoji.char}`)
573
+ })
574
+ })
575
+ ```
576
+
577
+ ### Send emoji reaction to a server
578
+
579
+ ```js
580
+ picker.on('emojiClick', (emoji) => {
581
+ fetch('/api/reactions', {
582
+ method: 'POST',
583
+ headers: { 'Content-Type': 'application/json' },
584
+ body: JSON.stringify({
585
+ messageId: currentMessageId,
586
+ emoji: emoji.name,
587
+ char: emoji.char
588
+ })
589
+ })
590
+ })
591
+ ```
592
+
593
+ ### Multiple pickers on the same page
594
+
595
+ ```js
596
+ // Minimal reaction picker
597
+ const reactionPicker = new EmojiPicker({
598
+ container: '#reaction-btn',
599
+ search: false,
600
+ recentEmojis: false,
601
+ autoClose: true
602
+ })
603
+
604
+ // Full editor picker
605
+ const editorPicker = new EmojiPicker({
606
+ container: '#editor-btn',
607
+ search: true,
608
+ skinTone: 'medium'
609
+ })
610
+ ```
611
+
612
+ ### Track analytics
613
+
614
+ ```js
615
+ picker.on('pickerOpen', () => analytics.track('picker_opened'))
616
+ picker.on('emojiClick', (emoji) => analytics.track('emoji_used', { name: emoji.name }))
617
+ picker.on('search', ({ query }) => analytics.track('emoji_search', { query }))
618
+ picker.on('categoryChange', ({ category }) => analytics.track('category_view', { category }))
619
+ ```
620
+
621
+ ### Dynamically switch themes
622
+
623
+ ```js
624
+ document.querySelector('#theme-toggle').addEventListener('click', () => {
625
+ const isDark = document.body.classList.toggle('dark')
626
+ picker.setTheme(isDark ? 'dark' : 'light')
627
+ })
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Accessibility
633
+
634
+ - All interactive elements have `aria-label` and `role` attributes
635
+ - The emoji grid uses `role="grid"` and `role="gridcell"`
636
+ - The picker uses `role="dialog"` with `aria-label`
637
+ - Category tabs use `role="tablist"`
638
+ - Preview area uses `aria-live="polite"` for screen reader announcements
639
+ - `Esc` closes the picker
640
+ - Focus moves to the search input on open
641
+
642
+ ---
643
+
644
+ ## Browser Support
645
+
646
+ | Browser | Version |
647
+ |---------|---------|
648
+ | Chrome | 80+ |
649
+ | Firefox | 78+ |
650
+ | Safari | 14+ |
651
+ | Edge | 80+ |
652
+ | iOS Safari | 14+ |
653
+ | Android Chrome | 80+ |
654
+
655
+ Requires `localStorage` for recent emojis — gracefully disabled if unavailable (e.g. private browsing).
656
+
657
+ ---
658
+
659
+ ## Categories
660
+
661
+ | Tab | Category | Example |
662
+ |-----|----------|---------|
663
+ | 🕐 | Recently Used | *dynamic* |
664
+ | 😊 | Smileys & Emotion | 😀 😂 🥰 😎 |
665
+ | 👋 | People & Body | 👋 💪 🙏 🤝 |
666
+ | 🐶 | Animals & Nature | 🐶 🦊 🌸 🌈 |
667
+ | 🍕 | Food & Drink | 🍕 🍜 🍺 🧋 |
668
+ | ⚽ | Activities | ⚽ 🎮 🎸 🏆 |
669
+ | ✈️ | Travel & Places | ✈️ 🚀 🏖️ 🏰 |
670
+ | 💡 | Objects | 💡 💻 📷 🔑 |
671
+ | ❤️ | Symbols | ❤️ ✅ ♻️ 💯 |
672
+ | 🏳️ | Flags | 🏳️‍🌈 🇺🇸 🇩🇪 🇯🇵 |
673
+
674
+ ---
675
+
676
+ ## License
677
+ Apache — free
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schwitzerskills/emojipicker",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "description": "A flexible, event-driven emoji picker library for the web.",
5
5
  "main": "emoji-picker.js",
6
6
  "scripts": {
@@ -66,15 +66,15 @@
66
66
  "url": "https://github.com/pfurpass/EmojiPicker/issues"
67
67
  },
68
68
  "homepage": "https://github.com/pfurpass/EmojiPicker#readme",
69
- "module": "emoji-picker.esm.js",
70
- "browser": "emoji-picker.js",
69
+ "module": "emoji-picker.esm.js",
70
+ "browser": "emoji-picker.js",
71
71
  "exports": {
72
72
  ".": {
73
73
  "import": "./emoji-picker.esm.js",
74
74
  "require": "./emoji-picker.js"
75
75
  }
76
76
  },
77
- "files": [
77
+ "files": [
78
78
  "emoji-picker.js",
79
79
  "emoji-picker.esm.js"
80
80
  ]