expo-native-sheet-emojis 2.0.4 → 2.1.1
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.
- package/android/src/main/java/expo/community/modules/emojisheet/EmojiSheetContentView.kt +29 -1
- package/android/src/main/java/expo/community/modules/emojisheet/EmojiSheetModule.kt +45 -7
- package/android/src/main/java/expo/community/modules/emojisheet/EmojiSheetUIView.kt +140 -64
- package/ios/EmojiGridView.swift +20 -16
- package/ios/EmojiSheetContentView.swift +16 -1
- package/ios/EmojiSheetModule.podspec +1 -1
- package/ios/EmojiSheetModule.swift +36 -9
- package/ios/EmojiSheetUIView.swift +17 -17
- package/lib/commonjs/EmojiSheetView.js +1 -7
- package/lib/commonjs/EmojiSheetView.js.map +1 -1
- package/lib/module/EmojiSheetView.js +1 -7
- package/lib/module/EmojiSheetView.js.map +1 -1
- package/lib/typescript/EmojiSheetModule.types.d.ts +1 -1
- package/lib/typescript/EmojiSheetModule.types.d.ts.map +1 -1
- package/lib/typescript/EmojiSheetView.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EmojiSheetModule.types.ts +1 -1
- package/src/EmojiSheetView.tsx +1 -7
|
@@ -2,6 +2,8 @@ package expo.community.modules.emojisheet
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
5
7
|
import expo.modules.kotlin.AppContext
|
|
6
8
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
7
9
|
import expo.modules.kotlin.views.ExpoView
|
|
@@ -27,9 +29,30 @@ class EmojiSheetContentView(
|
|
|
27
29
|
pickerView.loadDataAsync()
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
private var hasOpened = false
|
|
33
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
34
|
+
// A transient detach (layout reparenting, brief clipping) is followed by a
|
|
35
|
+
// reattach on the same or next loop. Defer the dismiss one loop and skip it if
|
|
36
|
+
// the view came back, so onDismiss fires only when the view stays detached.
|
|
37
|
+
private val emitDismissIfStillDetached = Runnable {
|
|
38
|
+
if (!isAttachedToWindow) {
|
|
39
|
+
hasOpened = false
|
|
40
|
+
onDismiss(mapOf<String, Any>())
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
override fun onAttachedToWindow() {
|
|
31
45
|
super.onAttachedToWindow()
|
|
32
|
-
|
|
46
|
+
mainHandler.removeCallbacks(emitDismissIfStillDetached)
|
|
47
|
+
if (!hasOpened) {
|
|
48
|
+
hasOpened = true
|
|
49
|
+
onOpen(mapOf<String, Any>())
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun onDetachedFromWindow() {
|
|
54
|
+
super.onDetachedFromWindow()
|
|
55
|
+
mainHandler.post(emitDismissIfStillDetached)
|
|
33
56
|
}
|
|
34
57
|
|
|
35
58
|
fun applyConfiguration() {
|
|
@@ -61,14 +84,17 @@ class EmojiSheetContentView(
|
|
|
61
84
|
|
|
62
85
|
fun updateRecentLimit(limit: Int) {
|
|
63
86
|
pickerView.recentLimit = limit
|
|
87
|
+
pickerView.rebuildAfterPropChange()
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
fun updateShowSearch(show: Boolean) {
|
|
67
91
|
pickerView.showSearch = show
|
|
92
|
+
pickerView.applyConfiguration()
|
|
68
93
|
}
|
|
69
94
|
|
|
70
95
|
fun updateShowRecents(show: Boolean) {
|
|
71
96
|
pickerView.showRecents = show
|
|
97
|
+
pickerView.rebuildAfterPropChange()
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
fun updateEnableSkinTones(enable: Boolean) {
|
|
@@ -93,9 +119,11 @@ class EmojiSheetContentView(
|
|
|
93
119
|
|
|
94
120
|
fun updateCategoryNames(names: Map<String, String>) {
|
|
95
121
|
pickerView.categoryNames = names
|
|
122
|
+
pickerView.rebuildAfterPropChange()
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
fun updateExcludeEmojis(ids: List<String>) {
|
|
99
126
|
pickerView.excludeEmojis = ids.toSet()
|
|
127
|
+
pickerView.rebuildAfterPropChange()
|
|
100
128
|
}
|
|
101
129
|
}
|
|
@@ -9,11 +9,14 @@ import android.view.ViewGroup
|
|
|
9
9
|
import android.view.animation.AccelerateDecelerateInterpolator
|
|
10
10
|
import android.view.WindowManager
|
|
11
11
|
import android.view.inputmethod.InputMethodManager
|
|
12
|
+
import android.widget.Button
|
|
13
|
+
import android.widget.FrameLayout
|
|
14
|
+
import android.widget.LinearLayout
|
|
15
|
+
import androidx.core.view.AccessibilityDelegateCompat
|
|
12
16
|
import androidx.core.view.ViewCompat
|
|
13
17
|
import androidx.core.view.WindowCompat
|
|
14
18
|
import androidx.core.view.WindowInsetsCompat
|
|
15
|
-
import
|
|
16
|
-
import android.widget.LinearLayout
|
|
19
|
+
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
|
|
17
20
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
18
21
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
19
22
|
import expo.modules.kotlin.Promise
|
|
@@ -124,6 +127,12 @@ class EmojiSheetModule : Module() {
|
|
|
124
127
|
|
|
125
128
|
private fun presentSheet(options: Map<String, Any>, promise: Promise) {
|
|
126
129
|
val activity = appContext.currentActivity ?: return
|
|
130
|
+
// Single-flight guard (mirrors iOS): ignore a second present() while a sheet
|
|
131
|
+
// is already open so the first promise is never orphaned and dialogs cannot stack.
|
|
132
|
+
if (currentPromise != null || dialog != null) {
|
|
133
|
+
promise.resolve(Bundle().apply { putBoolean("cancelled", true) })
|
|
134
|
+
return
|
|
135
|
+
}
|
|
127
136
|
val themeString = options["theme"] as? String ?: "light"
|
|
128
137
|
val isDark = when (themeString) {
|
|
129
138
|
"dark" -> true
|
|
@@ -250,19 +259,37 @@ class EmojiSheetModule : Module() {
|
|
|
250
259
|
}
|
|
251
260
|
|
|
252
261
|
// Drag handle
|
|
262
|
+
val handleTargetHeight = (24 * density).toInt()
|
|
253
263
|
val handleBar = View(activity).apply {
|
|
254
264
|
val width = (40 * density).toInt()
|
|
255
265
|
val height = (4 * density).toInt()
|
|
256
|
-
layoutParams =
|
|
257
|
-
gravity = Gravity.CENTER_HORIZONTAL
|
|
266
|
+
layoutParams = FrameLayout.LayoutParams(width, height, Gravity.TOP or Gravity.CENTER_HORIZONTAL).apply {
|
|
258
267
|
topMargin = (8 * density).toInt()
|
|
259
|
-
bottomMargin = (4 * density).toInt()
|
|
260
268
|
}
|
|
261
269
|
background = GradientDrawable().apply {
|
|
262
270
|
setColor(handleColor)
|
|
263
271
|
cornerRadius = height / 2f
|
|
264
272
|
}
|
|
265
273
|
}
|
|
274
|
+
val handleTouchTarget = FrameLayout(activity).apply {
|
|
275
|
+
layoutParams = LinearLayout.LayoutParams(
|
|
276
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
277
|
+
handleTargetHeight
|
|
278
|
+
)
|
|
279
|
+
contentDescription = "Dismiss emoji sheet"
|
|
280
|
+
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
|
281
|
+
isClickable = true
|
|
282
|
+
isFocusable = true
|
|
283
|
+
setOnClickListener { dismissSheet(cancelled = true) }
|
|
284
|
+
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
|
|
285
|
+
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
|
286
|
+
super.onInitializeAccessibilityNodeInfo(host, info)
|
|
287
|
+
info.className = Button::class.java.name
|
|
288
|
+
info.hintText = "Double tap to dismiss."
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
addView(handleBar)
|
|
292
|
+
}
|
|
266
293
|
|
|
267
294
|
// Container
|
|
268
295
|
val halfExpandedRatio = (snapPoints.firstOrNull()?.toFloat() ?: 0.5f).coerceIn(0.05f, 1f)
|
|
@@ -285,7 +312,7 @@ class EmojiSheetModule : Module() {
|
|
|
285
312
|
}
|
|
286
313
|
clipToOutline = true
|
|
287
314
|
outlineProvider = android.view.ViewOutlineProvider.BACKGROUND
|
|
288
|
-
addView(
|
|
315
|
+
addView(handleTouchTarget)
|
|
289
316
|
val pickerLp = LinearLayout.LayoutParams(
|
|
290
317
|
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f
|
|
291
318
|
)
|
|
@@ -539,8 +566,19 @@ class EmojiSheetModule : Module() {
|
|
|
539
566
|
|
|
540
567
|
private fun parseColor(hex: String?): Int? {
|
|
541
568
|
if (hex == null) return null
|
|
569
|
+
val trimmed = hex.trim()
|
|
570
|
+
// 8-digit hex is #RRGGBBAA (CSS / iOS order). Android's Color.parseColor reads
|
|
571
|
+
// 8-digit as #AARRGGBB, so parse it manually to keep both platforms in sync.
|
|
572
|
+
if (trimmed.startsWith("#") && trimmed.length == 9) {
|
|
573
|
+
val value = trimmed.substring(1).toLongOrNull(16) ?: return null
|
|
574
|
+
val r = ((value shr 24) and 0xFF).toInt()
|
|
575
|
+
val g = ((value shr 16) and 0xFF).toInt()
|
|
576
|
+
val b = ((value shr 8) and 0xFF).toInt()
|
|
577
|
+
val a = (value and 0xFF).toInt()
|
|
578
|
+
return Color.argb(a, r, g, b)
|
|
579
|
+
}
|
|
542
580
|
return try {
|
|
543
|
-
Color.parseColor(
|
|
581
|
+
Color.parseColor(trimmed)
|
|
544
582
|
} catch (e: IllegalArgumentException) {
|
|
545
583
|
null
|
|
546
584
|
}
|
|
@@ -21,6 +21,7 @@ import kotlinx.coroutines.SupervisorJob
|
|
|
21
21
|
import kotlinx.coroutines.cancel
|
|
22
22
|
import kotlinx.coroutines.currentCoroutineContext
|
|
23
23
|
import kotlinx.coroutines.ensureActive
|
|
24
|
+
import kotlinx.coroutines.isActive
|
|
24
25
|
import kotlinx.coroutines.launch
|
|
25
26
|
import kotlinx.coroutines.sync.Mutex
|
|
26
27
|
import kotlinx.coroutines.sync.withLock
|
|
@@ -39,8 +40,11 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
39
40
|
private var cachedData: Pair<List<EmojiCategory>, Map<String, List<String>>>? = null
|
|
40
41
|
|
|
41
42
|
fun warmCache(context: Context) {
|
|
42
|
-
if (cachedData != null) return
|
|
43
43
|
val appContext = context.applicationContext
|
|
44
|
+
cacheScope.launch {
|
|
45
|
+
appContext.getSharedPreferences(FREQ_PREFS, Context.MODE_PRIVATE).all
|
|
46
|
+
}
|
|
47
|
+
if (cachedData != null) return
|
|
44
48
|
cacheScope.launch {
|
|
45
49
|
loadCachedData(appContext)
|
|
46
50
|
}
|
|
@@ -57,8 +61,11 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
57
61
|
val keywords = loadAllKeywords(context)
|
|
58
62
|
Pair(categories, keywords).also { cachedData = it }
|
|
59
63
|
} catch (e: Exception) {
|
|
64
|
+
// Degrade to an empty data set instead of throwing an uncaught
|
|
65
|
+
// coroutine exception (which would crash the app). Not cached, so a
|
|
66
|
+
// later attempt can still succeed. Mirrors iOS, which returns [].
|
|
60
67
|
android.util.Log.e("EmojiSheet", "Failed to load emoji data", e)
|
|
61
|
-
|
|
68
|
+
Pair(emptyList(), emptyMap())
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
}
|
|
@@ -98,6 +105,13 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
98
105
|
var columns: Int = 7
|
|
99
106
|
var emojiSize: Float = 32f
|
|
100
107
|
var showSearch: Boolean = true
|
|
108
|
+
set(value) {
|
|
109
|
+
field = value
|
|
110
|
+
if (!value && currentSearchQuery.isNotBlank()) {
|
|
111
|
+
searchBar.clearSearch()
|
|
112
|
+
onSearch("")
|
|
113
|
+
}
|
|
114
|
+
}
|
|
101
115
|
var showRecents: Boolean = true
|
|
102
116
|
var enableSkinTones: Boolean = true
|
|
103
117
|
var enableHaptics: Boolean = true
|
|
@@ -130,6 +144,7 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
130
144
|
|
|
131
145
|
private var isSearchActive = false
|
|
132
146
|
private var bottomPillContainer: View? = null
|
|
147
|
+
private var bottomBarWrapper: FrameLayout? = null
|
|
133
148
|
private var suppressCategorySync = false
|
|
134
149
|
private var currentSearchQuery = ""
|
|
135
150
|
private var didTriggerExpandForCurrentDrag = false
|
|
@@ -146,7 +161,7 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
146
161
|
private var baseRecyclerBottomPadding = 0
|
|
147
162
|
private var keyboardRecyclerBottomPadding = 0
|
|
148
163
|
private var usesDockedSheetKeyboardInsets = false
|
|
149
|
-
private
|
|
164
|
+
private var viewScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
|
150
165
|
private var loadJob: Job? = null
|
|
151
166
|
private var searchJob: Job? = null
|
|
152
167
|
|
|
@@ -212,9 +227,12 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
212
227
|
lastTopPullDragDistance = 0f
|
|
213
228
|
isHandlingTopPullDrag = false
|
|
214
229
|
topPullStartY = null
|
|
230
|
+
// While the sheet is expanding, halt any momentum scroll but do
|
|
231
|
+
// NOT swallow the gesture: intercepting DOWN/UP would cancel an
|
|
232
|
+
// emoji tap. Only a drag (ACTION_MOVE below) is intercepted, so a
|
|
233
|
+
// plain tap still reaches the cell even mid-expansion.
|
|
215
234
|
if (isSheetExpansionInProgress) {
|
|
216
235
|
rv.stopScroll()
|
|
217
|
-
return true
|
|
218
236
|
}
|
|
219
237
|
}
|
|
220
238
|
android.view.MotionEvent.ACTION_UP,
|
|
@@ -232,15 +250,22 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
232
250
|
velocityTracker = null
|
|
233
251
|
if (isSheetExpansionInProgress) {
|
|
234
252
|
rv.stopScroll()
|
|
235
|
-
return true
|
|
236
253
|
}
|
|
237
254
|
}
|
|
238
255
|
}
|
|
239
256
|
|
|
240
257
|
if (e.actionMasked == android.view.MotionEvent.ACTION_MOVE) {
|
|
241
258
|
if (isSheetExpansionInProgress) {
|
|
242
|
-
|
|
243
|
-
|
|
259
|
+
// Only steal the gesture once it becomes a real drag (past touch
|
|
260
|
+
// slop). A finger tap jitters a pixel or two; intercepting that
|
|
261
|
+
// here cancels the emoji tap whenever this flag is set while the
|
|
262
|
+
// keyboard is up — which is exactly the "can't tap while searching"
|
|
263
|
+
// bug. Sub-slop movement falls through so the cell click fires.
|
|
264
|
+
if (kotlin.math.abs(e.y - initialTouchY) > touchSlop) {
|
|
265
|
+
rv.stopScroll()
|
|
266
|
+
return true
|
|
267
|
+
}
|
|
268
|
+
return false
|
|
244
269
|
}
|
|
245
270
|
|
|
246
271
|
val deltaY = e.y - initialTouchY
|
|
@@ -390,9 +415,13 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
390
415
|
updateRecyclerBottomPadding()
|
|
391
416
|
}
|
|
392
417
|
|
|
393
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* Call after setting configurable properties to apply grid + layout changes.
|
|
420
|
+
* Safe to call repeatedly: grid settings are always refreshed, and the
|
|
421
|
+
* category-bar hierarchy is only moved when the target position actually changes.
|
|
422
|
+
*/
|
|
394
423
|
fun applyConfiguration() {
|
|
395
|
-
// Update grid adapter settings
|
|
424
|
+
// Update grid adapter settings (idempotent — safe on every call)
|
|
396
425
|
gridAdapter.spanCount = columns
|
|
397
426
|
gridAdapter.emojiTextSize = emojiSize
|
|
398
427
|
gridAdapter.enableSkinTones = enableSkinTones
|
|
@@ -403,72 +432,105 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
403
432
|
// Show/hide search
|
|
404
433
|
searchBar.visibility = if (showSearch) View.VISIBLE else View.GONE
|
|
405
434
|
|
|
406
|
-
|
|
435
|
+
applyCategoryBarPosition()
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private fun applyCategoryBarPosition() {
|
|
407
439
|
if (categoryBarPosition == "bottom") {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
440
|
+
if (bottomBarWrapper != null) return // already wrapped; do not re-move the hierarchy
|
|
441
|
+
moveCategoryBarToBottom()
|
|
442
|
+
} else {
|
|
443
|
+
if (bottomBarWrapper == null) {
|
|
444
|
+
// Already in the default top layout.
|
|
445
|
+
baseRecyclerBottomPadding = 0
|
|
446
|
+
updateRecyclerBottomPadding()
|
|
447
|
+
return
|
|
412
448
|
}
|
|
413
|
-
|
|
414
|
-
|
|
449
|
+
restoreCategoryBarToTop()
|
|
450
|
+
}
|
|
451
|
+
}
|
|
415
452
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
val cornerRadius = 22 * density
|
|
453
|
+
private fun moveCategoryBarToBottom() {
|
|
454
|
+
// Detach the strip and content frame from their current parents (whatever they are).
|
|
455
|
+
(categoryStrip.parent as? android.view.ViewGroup)?.removeView(categoryStrip)
|
|
456
|
+
(contentFrame.parent as? android.view.ViewGroup)?.removeView(contentFrame)
|
|
421
457
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
428
|
-
FrameLayout.LayoutParams.MATCH_PARENT
|
|
429
|
-
)
|
|
430
|
-
wrapperFrame.addView(contentFrame)
|
|
458
|
+
val density = context.resources.displayMetrics.density
|
|
459
|
+
val horizontalInset = (16 * density).toInt()
|
|
460
|
+
val bottomInset = (8 * density).toInt()
|
|
461
|
+
val stripHeight = (44 * density).toInt()
|
|
462
|
+
val cornerRadius = 22 * density
|
|
431
463
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
this.cornerRadius = cornerRadius
|
|
436
|
-
}
|
|
464
|
+
val wrapperFrame = FrameLayout(context).apply {
|
|
465
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
|
|
466
|
+
}
|
|
437
467
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
468
|
+
contentFrame.layoutParams = FrameLayout.LayoutParams(
|
|
469
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
470
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
471
|
+
)
|
|
472
|
+
wrapperFrame.addView(contentFrame)
|
|
444
473
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
pillContainer.addView(categoryStrip)
|
|
474
|
+
// Floating pill container with rounded background
|
|
475
|
+
val pillBackground = android.graphics.drawable.GradientDrawable().apply {
|
|
476
|
+
setColor(currentTheme.categoryBarBackgroundColor)
|
|
477
|
+
this.cornerRadius = cornerRadius
|
|
478
|
+
}
|
|
451
479
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
bottomPillContainer = pillContainer
|
|
460
|
-
wrapperFrame.addView(pillContainer)
|
|
480
|
+
val pillContainer = FrameLayout(context).apply {
|
|
481
|
+
background = pillBackground
|
|
482
|
+
elevation = 8 * density
|
|
483
|
+
clipToOutline = true
|
|
484
|
+
outlineProvider = android.view.ViewOutlineProvider.BACKGROUND
|
|
485
|
+
}
|
|
461
486
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
487
|
+
categoryStrip.setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
488
|
+
categoryStrip.layoutParams = FrameLayout.LayoutParams(
|
|
489
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
490
|
+
stripHeight
|
|
491
|
+
)
|
|
492
|
+
pillContainer.addView(categoryStrip)
|
|
466
493
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
494
|
+
pillContainer.layoutParams = FrameLayout.LayoutParams(
|
|
495
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
496
|
+
stripHeight
|
|
497
|
+
).apply {
|
|
498
|
+
gravity = Gravity.BOTTOM
|
|
499
|
+
setMargins(horizontalInset, 0, horizontalInset, bottomInset)
|
|
471
500
|
}
|
|
501
|
+
bottomPillContainer = pillContainer
|
|
502
|
+
wrapperFrame.addView(pillContainer)
|
|
503
|
+
|
|
504
|
+
// Bottom padding so grid content scrolls above the floating bar
|
|
505
|
+
val totalBarSpace = stripHeight + bottomInset * 2
|
|
506
|
+
baseRecyclerBottomPadding = totalBarSpace
|
|
507
|
+
updateRecyclerBottomPadding()
|
|
508
|
+
|
|
509
|
+
bottomBarWrapper = wrapperFrame
|
|
510
|
+
addView(wrapperFrame)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private fun restoreCategoryBarToTop() {
|
|
514
|
+
val wrapper = bottomBarWrapper ?: return
|
|
515
|
+
// Detach views from the wrapper, then drop the wrapper itself.
|
|
516
|
+
(categoryStrip.parent as? android.view.ViewGroup)?.removeView(categoryStrip)
|
|
517
|
+
(contentFrame.parent as? android.view.ViewGroup)?.removeView(contentFrame)
|
|
518
|
+
removeView(wrapper)
|
|
519
|
+
bottomBarWrapper = null
|
|
520
|
+
bottomPillContainer = null
|
|
521
|
+
|
|
522
|
+
// Restore the default top order: searchBar, categoryStrip, contentFrame.
|
|
523
|
+
categoryStrip.setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
524
|
+
categoryStrip.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
525
|
+
addView(categoryStrip, indexOfChild(searchBar) + 1)
|
|
526
|
+
contentFrame.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
|
|
527
|
+
addView(contentFrame)
|
|
528
|
+
|
|
529
|
+
categoryStrip.applyTheme(currentTheme)
|
|
530
|
+
applyLayoutDirection()
|
|
531
|
+
|
|
532
|
+
baseRecyclerBottomPadding = 0
|
|
533
|
+
updateRecyclerBottomPadding()
|
|
472
534
|
}
|
|
473
535
|
|
|
474
536
|
private fun setGridItems(items: List<EmojiGridAdapter.ListItem>, sectionPositions: List<Int>) {
|
|
@@ -509,6 +571,13 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
509
571
|
}
|
|
510
572
|
}
|
|
511
573
|
|
|
574
|
+
fun rebuildAfterPropChange() {
|
|
575
|
+
if (allCategories.isEmpty()) return
|
|
576
|
+
allCategoryKeys = buildCategoryKeys()
|
|
577
|
+
rebuildCategoryStrip()
|
|
578
|
+
if (currentSearchQuery.isBlank()) buildAndSetItems() else onSearch(currentSearchQuery)
|
|
579
|
+
}
|
|
580
|
+
|
|
512
581
|
fun updateTheme(theme: String) {
|
|
513
582
|
currentTheme = EmojiSheetTheme.fromName(theme)
|
|
514
583
|
applyTheme(currentTheme)
|
|
@@ -906,6 +975,13 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
906
975
|
|
|
907
976
|
override fun onAttachedToWindow() {
|
|
908
977
|
super.onAttachedToWindow()
|
|
978
|
+
if (!viewScope.isActive) {
|
|
979
|
+
viewScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
|
980
|
+
// Only reload when data hasn't loaded yet; otherwise the populated grid
|
|
981
|
+
// and category strip survive the detach, so re-running loadDataAsync would
|
|
982
|
+
// rebuild the strip and reset scroll to 0 on every reattach.
|
|
983
|
+
if (allCategories.isEmpty()) loadDataAsync()
|
|
984
|
+
}
|
|
909
985
|
ViewCompat.requestApplyInsets(this)
|
|
910
986
|
}
|
|
911
987
|
}
|
package/ios/EmojiGridView.swift
CHANGED
|
@@ -149,15 +149,26 @@ class EmojiGridView: UIView, UICollectionViewDataSource, UICollectionViewDelegat
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
func scrollToSection(_ index: Int) {
|
|
152
|
-
guard index < sections.count else { return }
|
|
152
|
+
guard index < sections.count, !sections[index].data.isEmpty else { return }
|
|
153
153
|
isScrollingProgrammatically = true
|
|
154
154
|
let indexPath = IndexPath(item: 0, section: index)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
) {
|
|
159
|
-
let
|
|
160
|
-
|
|
155
|
+
// The header pins to the visible bounds, so its queried origin.y can be the
|
|
156
|
+
// sticky position rather than the section's natural top. Items never pin, so
|
|
157
|
+
// derive the section top from the first item and the (stable) header height.
|
|
158
|
+
if let itemAttributes = collectionView.layoutAttributesForItem(at: indexPath) {
|
|
159
|
+
let headerHeight = collectionView.layoutAttributesForSupplementaryElement(
|
|
160
|
+
ofKind: UICollectionView.elementKindSectionHeader,
|
|
161
|
+
at: indexPath
|
|
162
|
+
)?.frame.height ?? 32
|
|
163
|
+
let sectionTopInset: CGFloat = 4
|
|
164
|
+
let sectionTop = itemAttributes.frame.origin.y - headerHeight - sectionTopInset
|
|
165
|
+
let minOffset = -collectionView.adjustedContentInset.top
|
|
166
|
+
let maxOffset = max(
|
|
167
|
+
minOffset,
|
|
168
|
+
collectionView.contentSize.height - collectionView.bounds.height + collectionView.adjustedContentInset.bottom
|
|
169
|
+
)
|
|
170
|
+
let target = min(max(sectionTop - collectionView.adjustedContentInset.top, minOffset), maxOffset)
|
|
171
|
+
collectionView.setContentOffset(CGPoint(x: 0, y: target), animated: true)
|
|
161
172
|
}
|
|
162
173
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in
|
|
163
174
|
self?.isScrollingProgrammatically = false
|
|
@@ -178,15 +189,8 @@ class EmojiGridView: UIView, UICollectionViewDataSource, UICollectionViewDelegat
|
|
|
178
189
|
|
|
179
190
|
static func applyingSkinTone(_ modifier: String, to emoji: String) -> String {
|
|
180
191
|
var scalars = Array(emoji.unicodeScalars)
|
|
181
|
-
if scalars.count >= 1 {
|
|
182
|
-
|
|
183
|
-
// Insert skin tone modifier after the first scalar
|
|
184
|
-
if scalars.count > 1 && scalars[1] == "\u{200D}" {
|
|
185
|
-
// ZWJ sequence: insert before ZWJ
|
|
186
|
-
scalars.insert(modifierScalar, at: 1)
|
|
187
|
-
} else {
|
|
188
|
-
scalars.insert(modifierScalar, at: 1)
|
|
189
|
-
}
|
|
192
|
+
if scalars.count >= 1, let modifierScalar = modifier.unicodeScalars.first {
|
|
193
|
+
scalars.insert(modifierScalar, at: 1)
|
|
190
194
|
}
|
|
191
195
|
return String(String.UnicodeScalarView(scalars))
|
|
192
196
|
}
|
|
@@ -27,6 +27,7 @@ class EmojiSheetContentView: ExpoView, EmojiSheetUIViewDelegate {
|
|
|
27
27
|
let onDismiss = EventDispatcher()
|
|
28
28
|
let onOpen = EventDispatcher()
|
|
29
29
|
private let pickerView = EmojiSheetUIView()
|
|
30
|
+
private var hasOpened = false
|
|
30
31
|
|
|
31
32
|
required init(appContext: AppContext? = nil) {
|
|
32
33
|
super.init(appContext: appContext)
|
|
@@ -49,7 +50,21 @@ class EmojiSheetContentView: ExpoView, EmojiSheetUIViewDelegate {
|
|
|
49
50
|
override func didMoveToWindow() {
|
|
50
51
|
super.didMoveToWindow()
|
|
51
52
|
if window != nil {
|
|
52
|
-
|
|
53
|
+
if !hasOpened {
|
|
54
|
+
hasOpened = true
|
|
55
|
+
onOpen([:])
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
// A transient detach (reparenting, brief clipping) reattaches on the next
|
|
59
|
+
// runloop. Defer the dismiss and skip it if the view came back, so onDismiss
|
|
60
|
+
// fires only when the view stays detached (e.g. real unmount).
|
|
61
|
+
DispatchQueue.main.async { [weak self, dismiss = onDismiss] in
|
|
62
|
+
if let self {
|
|
63
|
+
if self.window != nil { return }
|
|
64
|
+
self.hasOpened = false
|
|
65
|
+
}
|
|
66
|
+
dismiss([:])
|
|
67
|
+
}
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = 'EmojiSheetModule'
|
|
3
|
-
s.version = '2.
|
|
3
|
+
s.version = '2.1.1'
|
|
4
4
|
s.summary = 'Native emoji picker bottom sheet for React Native'
|
|
5
5
|
s.description = 'A fully native iOS/Android emoji picker presented in a bottom sheet with search, skin tones, and theming support.'
|
|
6
6
|
s.author = ''
|
|
@@ -337,6 +337,7 @@ private final class SheetViewController: UIViewController, UIGestureRecognizerDe
|
|
|
337
337
|
|
|
338
338
|
private let backdropView = UIView()
|
|
339
339
|
private let sheetContainerView = UIView()
|
|
340
|
+
private let grabberHitAreaView = UIControl()
|
|
340
341
|
private let grabberView = UIView()
|
|
341
342
|
private let contentContainerView = UIView()
|
|
342
343
|
private let backdropColor: UIColor
|
|
@@ -411,6 +412,11 @@ private final class SheetViewController: UIViewController, UIGestureRecognizerDe
|
|
|
411
412
|
applyDetentLayout(currentDetent)
|
|
412
413
|
}
|
|
413
414
|
|
|
415
|
+
override func accessibilityPerformEscape() -> Bool {
|
|
416
|
+
requestDismiss()
|
|
417
|
+
return true
|
|
418
|
+
}
|
|
419
|
+
|
|
414
420
|
func embedPickerView(_ embeddedView: UIView) {
|
|
415
421
|
embeddedView.translatesAutoresizingMaskIntoConstraints = false
|
|
416
422
|
contentContainerView.addSubview(embeddedView)
|
|
@@ -574,9 +580,7 @@ private final class SheetViewController: UIViewController, UIGestureRecognizerDe
|
|
|
574
580
|
let dismissalThreshold = max(sheetContainerView.bounds.height * 0.5, 1)
|
|
575
581
|
let shouldDismiss = distance >= dismissalThreshold || velocity >= 1400
|
|
576
582
|
if shouldDismiss {
|
|
577
|
-
|
|
578
|
-
self?.onDismiss?()
|
|
579
|
-
}
|
|
583
|
+
requestDismiss()
|
|
580
584
|
return
|
|
581
585
|
}
|
|
582
586
|
|
|
@@ -632,17 +636,28 @@ private final class SheetViewController: UIViewController, UIGestureRecognizerDe
|
|
|
632
636
|
sheetContainerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
|
633
637
|
sheetContainerView.layer.masksToBounds = true
|
|
634
638
|
|
|
639
|
+
grabberHitAreaView.translatesAutoresizingMaskIntoConstraints = false
|
|
640
|
+
grabberHitAreaView.backgroundColor = .clear
|
|
641
|
+
grabberHitAreaView.isAccessibilityElement = true
|
|
642
|
+
grabberHitAreaView.accessibilityLabel = "Dismiss emoji sheet"
|
|
643
|
+
grabberHitAreaView.accessibilityHint = "Double-tap to dismiss."
|
|
644
|
+
grabberHitAreaView.accessibilityTraits = .button
|
|
645
|
+
grabberHitAreaView.addTarget(self, action: #selector(handleGrabberTap), for: .touchUpInside)
|
|
646
|
+
|
|
635
647
|
grabberView.translatesAutoresizingMaskIntoConstraints = false
|
|
636
648
|
grabberView.backgroundColor = theme.handleColor
|
|
637
649
|
grabberView.layer.cornerRadius = Layout.grabberHeight / 2
|
|
650
|
+
grabberView.isUserInteractionEnabled = false
|
|
638
651
|
|
|
639
652
|
contentContainerView.translatesAutoresizingMaskIntoConstraints = false
|
|
640
653
|
contentContainerView.backgroundColor = .clear
|
|
641
654
|
|
|
655
|
+
sheetContainerView.accessibilityViewIsModal = true
|
|
642
656
|
view.addSubview(backdropView)
|
|
643
657
|
view.addSubview(sheetContainerView)
|
|
644
658
|
sheetContainerView.addSubview(contentContainerView)
|
|
645
|
-
sheetContainerView.addSubview(
|
|
659
|
+
sheetContainerView.addSubview(grabberHitAreaView)
|
|
660
|
+
grabberHitAreaView.addSubview(grabberView)
|
|
646
661
|
|
|
647
662
|
if gestureEnabled {
|
|
648
663
|
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleSheetPan(_:)))
|
|
@@ -668,11 +683,16 @@ private final class SheetViewController: UIViewController, UIGestureRecognizerDe
|
|
|
668
683
|
sheetContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
669
684
|
sheetBottomConstraint,
|
|
670
685
|
|
|
686
|
+
grabberHitAreaView.topAnchor.constraint(equalTo: sheetContainerView.topAnchor),
|
|
687
|
+
grabberHitAreaView.centerXAnchor.constraint(equalTo: sheetContainerView.centerXAnchor),
|
|
688
|
+
grabberHitAreaView.widthAnchor.constraint(equalToConstant: 96),
|
|
689
|
+
grabberHitAreaView.heightAnchor.constraint(equalToConstant: 24),
|
|
690
|
+
|
|
671
691
|
grabberView.topAnchor.constraint(
|
|
672
|
-
equalTo:
|
|
692
|
+
equalTo: grabberHitAreaView.topAnchor,
|
|
673
693
|
constant: Layout.grabberTopInset
|
|
674
694
|
),
|
|
675
|
-
grabberView.centerXAnchor.constraint(equalTo:
|
|
695
|
+
grabberView.centerXAnchor.constraint(equalTo: grabberHitAreaView.centerXAnchor),
|
|
676
696
|
grabberView.widthAnchor.constraint(equalToConstant: Layout.grabberWidth),
|
|
677
697
|
grabberView.heightAnchor.constraint(equalToConstant: Layout.grabberHeight),
|
|
678
698
|
|
|
@@ -684,6 +704,15 @@ private final class SheetViewController: UIViewController, UIGestureRecognizerDe
|
|
|
684
704
|
}
|
|
685
705
|
|
|
686
706
|
@objc private func handleBackdropTap() {
|
|
707
|
+
requestDismiss()
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
@objc private func handleGrabberTap() {
|
|
711
|
+
requestDismiss()
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
private func requestDismiss() {
|
|
715
|
+
guard !isAnimatingDismissal else { return }
|
|
687
716
|
dismissSheet { [weak self] in
|
|
688
717
|
self?.onDismiss?()
|
|
689
718
|
}
|
|
@@ -713,9 +742,7 @@ private final class SheetViewController: UIViewController, UIGestureRecognizerDe
|
|
|
713
742
|
velocityY > Layout.dismissVelocityThreshold
|
|
714
743
|
|
|
715
744
|
if shouldDismiss {
|
|
716
|
-
|
|
717
|
-
self?.onDismiss?()
|
|
718
|
-
}
|
|
745
|
+
requestDismiss()
|
|
719
746
|
} else {
|
|
720
747
|
let targetDetent: Detent
|
|
721
748
|
|
|
@@ -118,7 +118,9 @@ class EmojiSheetUIView: UIView,
|
|
|
118
118
|
var showSearch: Bool = true {
|
|
119
119
|
didSet { if showSearch != oldValue { configureLayout() } }
|
|
120
120
|
}
|
|
121
|
-
var showRecents: Bool = true
|
|
121
|
+
var showRecents: Bool = true {
|
|
122
|
+
didSet { if showRecents != oldValue { rebuildLoadedSections() } }
|
|
123
|
+
}
|
|
122
124
|
var enableSkinTones: Bool = true {
|
|
123
125
|
didSet { gridView.enableSkinTones = enableSkinTones }
|
|
124
126
|
}
|
|
@@ -128,15 +130,21 @@ class EmojiSheetUIView: UIView,
|
|
|
128
130
|
var enableAnimations: Bool = false {
|
|
129
131
|
didSet { gridView.enableAnimations = enableAnimations }
|
|
130
132
|
}
|
|
131
|
-
var recentLimit: Int = 30
|
|
133
|
+
var recentLimit: Int = 30 {
|
|
134
|
+
didSet { if recentLimit != oldValue { rebuildLoadedSections() } }
|
|
135
|
+
}
|
|
132
136
|
var categoryBarPosition: String = "top" {
|
|
133
137
|
didSet { if categoryBarPosition != oldValue { configureLayout() } }
|
|
134
138
|
}
|
|
135
139
|
var layoutDirection: String = "auto" {
|
|
136
140
|
didSet { applyLayoutDirection() }
|
|
137
141
|
}
|
|
138
|
-
var categoryNames: [String: String]?
|
|
139
|
-
|
|
142
|
+
var categoryNames: [String: String]? {
|
|
143
|
+
didSet { if categoryNames != oldValue { rebuildLoadedSections() } }
|
|
144
|
+
}
|
|
145
|
+
var excludeEmojis: Set<String> = [] {
|
|
146
|
+
didSet { if excludeEmojis != oldValue { rebuildLoadedSections() } }
|
|
147
|
+
}
|
|
140
148
|
|
|
141
149
|
private var currentTheme: EmojiSheetTheme = .light
|
|
142
150
|
private var allSections: [EmojiSection] = []
|
|
@@ -544,6 +552,11 @@ class EmojiSheetUIView: UIView,
|
|
|
544
552
|
}
|
|
545
553
|
}
|
|
546
554
|
|
|
555
|
+
private func rebuildLoadedSections() {
|
|
556
|
+
guard !allSections.isEmpty else { return }
|
|
557
|
+
rebuildSections(searchText: currentSearchText)
|
|
558
|
+
}
|
|
559
|
+
|
|
547
560
|
// iOS version → max Unicode emoji version. Source: https://emojipedia.org/apple
|
|
548
561
|
// Cases MUST remain ordered most-specific first (Swift evaluates top-to-bottom).
|
|
549
562
|
nonisolated private static func maxSupportedEmojiVersion() -> Double {
|
|
@@ -755,19 +768,6 @@ class EmojiSheetUIView: UIView,
|
|
|
755
768
|
return 0
|
|
756
769
|
}
|
|
757
770
|
|
|
758
|
-
/// Fast path: checks pre-normalized text first, then falls back to transliteration-aware variants.
|
|
759
|
-
private func matchesNormalized(_ normalizedText: String, originalText: String, searchVariants: [String]) -> Bool {
|
|
760
|
-
if searchVariants.contains(where: normalizedText.contains) {
|
|
761
|
-
return true
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
return Self.normalizedSearchVariants(originalText)
|
|
765
|
-
.contains { candidateVariant in
|
|
766
|
-
candidateVariant != normalizedText &&
|
|
767
|
-
searchVariants.contains(where: { candidateVariant.contains($0) })
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
771
|
// MARK: - Frequently Used
|
|
772
772
|
|
|
773
773
|
private func loadFrequentlyUsed() -> [EmojiItem] {
|
|
@@ -26,15 +26,9 @@ function EmojiSheetView({
|
|
|
26
26
|
excludeEmojis,
|
|
27
27
|
...rest
|
|
28
28
|
}) {
|
|
29
|
-
const resolvedTheme = typeof theme === 'object' ? 'custom' : theme;
|
|
30
|
-
|
|
31
|
-
// For custom themes, we pass individual color props through the native view
|
|
32
|
-
// For now, the declarative view supports 'dark' | 'light' string themes
|
|
33
|
-
// Custom theme object support would require additional native view props
|
|
34
|
-
|
|
35
29
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(NativeView, {
|
|
36
30
|
...rest,
|
|
37
|
-
theme:
|
|
31
|
+
theme: typeof theme === 'string' ? theme : undefined,
|
|
38
32
|
layoutDirection: layoutDirection,
|
|
39
33
|
categoryBarPosition: categoryBarPosition,
|
|
40
34
|
columns: columns,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_expo","require","_jsxRuntime","NativeView","requireNativeView","EmojiSheetView","onEmojiSelected","onDismiss","onOpen","theme","translations","layoutDirection","categoryBarPosition","columns","emojiSize","recentLimit","showSearch","showRecents","enableSkinTones","enableHaptics","enableAnimations","excludeEmojis","rest","
|
|
1
|
+
{"version":3,"names":["_expo","require","_jsxRuntime","NativeView","requireNativeView","EmojiSheetView","onEmojiSelected","onDismiss","onOpen","theme","translations","layoutDirection","categoryBarPosition","columns","emojiSize","recentLimit","showSearch","showRecents","enableSkinTones","enableHaptics","enableAnimations","excludeEmojis","rest","jsx","undefined","searchPlaceholder","noResultsText","categoryNames","nativeEvent","emoji","name","id"],"sourceRoot":"../../src","sources":["EmojiSheetView.tsx"],"mappings":";;;;;;AAAA,IAAAA,KAAA,GAAAC,OAAA;AAAyC,IAAAC,WAAA,GAAAD,OAAA;AAOzC,MAAME,UAA0D,GAC9D,IAAAC,uBAAiB,EAAC,YAAY,CAAC;AAElB,SAASC,cAAcA,CAAC;EACrCC,eAAe;EACfC,SAAS;EACTC,MAAM;EACNC,KAAK;EACLC,YAAY;EACZC,eAAe;EACfC,mBAAmB;EACnBC,OAAO;EACPC,SAAS;EACTC,WAAW;EACXC,UAAU;EACVC,WAAW;EACXC,eAAe;EACfC,aAAa;EACbC,gBAAgB;EAChBC,aAAa;EACb,GAAGC;AACgB,CAAC,EAAE;EACtB,oBACE,IAAApB,WAAA,CAAAqB,GAAA,EAACpB,UAAU;IAAA,GACLmB,IAAI;IACRb,KAAK,EAAE,OAAOA,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAGe,SAAU;IACrDb,eAAe,EAAEA,eAAgB;IACjCC,mBAAmB,EAAEA,mBAAoB;IACzCC,OAAO,EAAEA,OAAQ;IACjBC,SAAS,EAAEA,SAAU;IACrBC,WAAW,EAAEA,WAAY;IACzBC,UAAU,EAAEA,UAAW;IACvBC,WAAW,EAAEA,WAAY;IACzBC,eAAe,EAAEA,eAAgB;IACjCC,aAAa,EAAEA,aAAc;IAC7BC,gBAAgB,EAAEA,gBAAiB;IACnCC,aAAa,EAAEA,aAAc;IAC7BI,iBAAiB,EAAEf,YAAY,EAAEe,iBAAkB;IACnDC,aAAa,EAAEhB,YAAY,EAAEgB,aAAc;IAC3CC,aAAa,EAAEjB,YAAY,EAAEiB,aAAc;IAC3CrB,eAAe,EAAEA,CAAC;MAAEsB;IAAY,CAAC,KAAK;MACpCtB,eAAe,CAACsB,WAAW,CAACC,KAAK,EAAED,WAAW,CAACE,IAAI,EAAEF,WAAW,CAACG,EAAE,CAAC;IACtE,CAAE;IACFxB,SAAS,EAAEA,SAAS,GAAG,MAAMA,SAAS,CAAC,CAAC,GAAGiB,SAAU;IACrDhB,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGgB;EAAU,CAC7C,CAAC;AAEN","ignoreList":[]}
|
|
@@ -22,15 +22,9 @@ export default function EmojiSheetView({
|
|
|
22
22
|
excludeEmojis,
|
|
23
23
|
...rest
|
|
24
24
|
}) {
|
|
25
|
-
const resolvedTheme = typeof theme === 'object' ? 'custom' : theme;
|
|
26
|
-
|
|
27
|
-
// For custom themes, we pass individual color props through the native view
|
|
28
|
-
// For now, the declarative view supports 'dark' | 'light' string themes
|
|
29
|
-
// Custom theme object support would require additional native view props
|
|
30
|
-
|
|
31
25
|
return /*#__PURE__*/_jsx(NativeView, {
|
|
32
26
|
...rest,
|
|
33
|
-
theme:
|
|
27
|
+
theme: typeof theme === 'string' ? theme : undefined,
|
|
34
28
|
layoutDirection: layoutDirection,
|
|
35
29
|
categoryBarPosition: categoryBarPosition,
|
|
36
30
|
columns: columns,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["requireNativeView","jsx","_jsx","NativeView","EmojiSheetView","onEmojiSelected","onDismiss","onOpen","theme","translations","layoutDirection","categoryBarPosition","columns","emojiSize","recentLimit","showSearch","showRecents","enableSkinTones","enableHaptics","enableAnimations","excludeEmojis","rest","
|
|
1
|
+
{"version":3,"names":["requireNativeView","jsx","_jsx","NativeView","EmojiSheetView","onEmojiSelected","onDismiss","onOpen","theme","translations","layoutDirection","categoryBarPosition","columns","emojiSize","recentLimit","showSearch","showRecents","enableSkinTones","enableHaptics","enableAnimations","excludeEmojis","rest","undefined","searchPlaceholder","noResultsText","categoryNames","nativeEvent","emoji","name","id"],"sourceRoot":"../../src","sources":["EmojiSheetView.tsx"],"mappings":";;AAAA,SAASA,iBAAiB,QAAQ,MAAM;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAOzC,MAAMC,UAA0D,GAC9DH,iBAAiB,CAAC,YAAY,CAAC;AAEjC,eAAe,SAASI,cAAcA,CAAC;EACrCC,eAAe;EACfC,SAAS;EACTC,MAAM;EACNC,KAAK;EACLC,YAAY;EACZC,eAAe;EACfC,mBAAmB;EACnBC,OAAO;EACPC,SAAS;EACTC,WAAW;EACXC,UAAU;EACVC,WAAW;EACXC,eAAe;EACfC,aAAa;EACbC,gBAAgB;EAChBC,aAAa;EACb,GAAGC;AACgB,CAAC,EAAE;EACtB,oBACEnB,IAAA,CAACC,UAAU;IAAA,GACLkB,IAAI;IACRb,KAAK,EAAE,OAAOA,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAGc,SAAU;IACrDZ,eAAe,EAAEA,eAAgB;IACjCC,mBAAmB,EAAEA,mBAAoB;IACzCC,OAAO,EAAEA,OAAQ;IACjBC,SAAS,EAAEA,SAAU;IACrBC,WAAW,EAAEA,WAAY;IACzBC,UAAU,EAAEA,UAAW;IACvBC,WAAW,EAAEA,WAAY;IACzBC,eAAe,EAAEA,eAAgB;IACjCC,aAAa,EAAEA,aAAc;IAC7BC,gBAAgB,EAAEA,gBAAiB;IACnCC,aAAa,EAAEA,aAAc;IAC7BG,iBAAiB,EAAEd,YAAY,EAAEc,iBAAkB;IACnDC,aAAa,EAAEf,YAAY,EAAEe,aAAc;IAC3CC,aAAa,EAAEhB,YAAY,EAAEgB,aAAc;IAC3CpB,eAAe,EAAEA,CAAC;MAAEqB;IAAY,CAAC,KAAK;MACpCrB,eAAe,CAACqB,WAAW,CAACC,KAAK,EAAED,WAAW,CAACE,IAAI,EAAEF,WAAW,CAACG,EAAE,CAAC;IACtE,CAAE;IACFvB,SAAS,EAAEA,SAAS,GAAG,MAAMA,SAAS,CAAC,CAAC,GAAGgB,SAAU;IACrDf,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGe;EAAU,CAC7C,CAAC;AAEN","ignoreList":[]}
|
|
@@ -62,7 +62,7 @@ export type EmojiSheetViewProps = ViewProps & {
|
|
|
62
62
|
onEmojiSelected: (emoji: string, name: string, id: string) => void;
|
|
63
63
|
onDismiss?: () => void;
|
|
64
64
|
onOpen?: () => void;
|
|
65
|
-
theme?:
|
|
65
|
+
theme?: 'dark' | 'light' | 'system';
|
|
66
66
|
translations?: EmojiSheetTranslations;
|
|
67
67
|
layoutDirection?: 'ltr' | 'rtl' | 'auto';
|
|
68
68
|
categoryBarPosition?: 'top' | 'bottom';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmojiSheetModule.types.d.ts","sourceRoot":"","sources":["../../src/EmojiSheetModule.types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"EmojiSheetModule.types.d.ts","sourceRoot":"","sources":["../../src/EmojiSheetModule.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmojiSheetView.d.ts","sourceRoot":"","sources":["../../src/EmojiSheetView.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAKlC,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,eAAe,EACf,SAAS,EACT,MAAM,EACN,KAAK,EACL,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,OAAO,EACP,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,GAAG,IAAI,EACR,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"EmojiSheetView.d.ts","sourceRoot":"","sources":["../../src/EmojiSheetView.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAKlC,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,eAAe,EACf,SAAS,EACT,MAAM,EACN,KAAK,EACL,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,OAAO,EACP,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,GAAG,IAAI,EACR,EAAE,mBAAmB,2CA0BrB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-native-sheet-emojis",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A fully native emoji picker bottom sheet for React Native. Built with Swift and Kotlin for maximum performance. Features search with multilingual keywords, skin tones, frequently used tracking, theming, and configurable layout.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -65,7 +65,7 @@ export type EmojiSheetViewProps = ViewProps & {
|
|
|
65
65
|
onEmojiSelected: (emoji: string, name: string, id: string) => void;
|
|
66
66
|
onDismiss?: () => void;
|
|
67
67
|
onOpen?: () => void;
|
|
68
|
-
theme?:
|
|
68
|
+
theme?: 'dark' | 'light' | 'system';
|
|
69
69
|
translations?: EmojiSheetTranslations;
|
|
70
70
|
layoutDirection?: 'ltr' | 'rtl' | 'auto';
|
|
71
71
|
categoryBarPosition?: 'top' | 'bottom';
|
package/src/EmojiSheetView.tsx
CHANGED
|
@@ -27,16 +27,10 @@ export default function EmojiSheetView({
|
|
|
27
27
|
excludeEmojis,
|
|
28
28
|
...rest
|
|
29
29
|
}: EmojiSheetViewProps) {
|
|
30
|
-
const resolvedTheme = typeof theme === 'object' ? 'custom' : theme;
|
|
31
|
-
|
|
32
|
-
// For custom themes, we pass individual color props through the native view
|
|
33
|
-
// For now, the declarative view supports 'dark' | 'light' string themes
|
|
34
|
-
// Custom theme object support would require additional native view props
|
|
35
|
-
|
|
36
30
|
return (
|
|
37
31
|
<NativeView
|
|
38
32
|
{...rest}
|
|
39
|
-
theme={
|
|
33
|
+
theme={typeof theme === 'string' ? theme : undefined}
|
|
40
34
|
layoutDirection={layoutDirection}
|
|
41
35
|
categoryBarPosition={categoryBarPosition}
|
|
42
36
|
columns={columns}
|