expo-native-sheet-emojis 2.1.0 → 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.
@@ -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
- onOpen(mapOf<String, Any>())
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
  }
@@ -127,6 +127,12 @@ class EmojiSheetModule : Module() {
127
127
 
128
128
  private fun presentSheet(options: Map<String, Any>, promise: Promise) {
129
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
+ }
130
136
  val themeString = options["theme"] as? String ?: "light"
131
137
  val isDark = when (themeString) {
132
138
  "dark" -> true
@@ -560,8 +566,19 @@ class EmojiSheetModule : Module() {
560
566
 
561
567
  private fun parseColor(hex: String?): Int? {
562
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
+ }
563
580
  return try {
564
- Color.parseColor(hex)
581
+ Color.parseColor(trimmed)
565
582
  } catch (e: IllegalArgumentException) {
566
583
  null
567
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
- throw e
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 val viewScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
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
- rv.stopScroll()
243
- return true
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
- /** Call after setting configurable properties but before loadDataAsync to apply layout changes. */
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
- // Category bar position
435
+ applyCategoryBarPosition()
436
+ }
437
+
438
+ private fun applyCategoryBarPosition() {
407
439
  if (categoryBarPosition == "bottom") {
408
- // Move category strip to floating pill overlay at bottom
409
- val stripIndex = indexOfChild(categoryStrip)
410
- if (stripIndex >= 0) {
411
- removeView(categoryStrip)
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
- // Remove contentFrame from linear layout and re-add in a wrapper
414
- removeView(contentFrame)
449
+ restoreCategoryBarToTop()
450
+ }
451
+ }
415
452
 
416
- val density = context.resources.displayMetrics.density
417
- val horizontalInset = (16 * density).toInt()
418
- val bottomInset = (8 * density).toInt()
419
- val stripHeight = (44 * density).toInt()
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
- val wrapperFrame = FrameLayout(context).apply {
423
- layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
424
- }
425
-
426
- contentFrame.layoutParams = FrameLayout.LayoutParams(
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
- // Floating pill container with rounded background
433
- val pillBackground = android.graphics.drawable.GradientDrawable().apply {
434
- setColor(currentTheme.categoryBarBackgroundColor)
435
- this.cornerRadius = cornerRadius
436
- }
464
+ val wrapperFrame = FrameLayout(context).apply {
465
+ layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
466
+ }
437
467
 
438
- val pillContainer = FrameLayout(context).apply {
439
- background = pillBackground
440
- elevation = 8 * density
441
- clipToOutline = true
442
- outlineProvider = android.view.ViewOutlineProvider.BACKGROUND
443
- }
468
+ contentFrame.layoutParams = FrameLayout.LayoutParams(
469
+ FrameLayout.LayoutParams.MATCH_PARENT,
470
+ FrameLayout.LayoutParams.MATCH_PARENT
471
+ )
472
+ wrapperFrame.addView(contentFrame)
444
473
 
445
- categoryStrip.setBackgroundColor(android.graphics.Color.TRANSPARENT)
446
- categoryStrip.layoutParams = FrameLayout.LayoutParams(
447
- FrameLayout.LayoutParams.MATCH_PARENT,
448
- stripHeight
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
- pillContainer.layoutParams = FrameLayout.LayoutParams(
453
- FrameLayout.LayoutParams.MATCH_PARENT,
454
- stripHeight
455
- ).apply {
456
- gravity = Gravity.BOTTOM
457
- setMargins(horizontalInset, 0, horizontalInset, bottomInset)
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
- // Bottom padding so grid content scrolls above the floating bar
463
- val totalBarSpace = stripHeight + bottomInset * 2
464
- baseRecyclerBottomPadding = totalBarSpace
465
- updateRecyclerBottomPadding()
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
- addView(wrapperFrame)
468
- } else {
469
- baseRecyclerBottomPadding = 0
470
- updateRecyclerBottomPadding()
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
  }
@@ -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
- if let attributes = collectionView.layoutAttributesForSupplementaryElement(
156
- ofKind: UICollectionView.elementKindSectionHeader,
157
- at: indexPath
158
- ) {
159
- let offset = CGPoint(x: 0, y: attributes.frame.origin.y - collectionView.contentInset.top)
160
- collectionView.setContentOffset(offset, animated: true)
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
- let modifierScalar = modifier.unicodeScalars.first!
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
- onOpen([:])
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.1.0'
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 = ''
@@ -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
- var excludeEmojis: Set<String> = []
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: resolvedTheme,
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","resolvedTheme","jsx","searchPlaceholder","noResultsText","categoryNames","nativeEvent","emoji","name","id","undefined"],"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,MAAMC,aAAa,GAAG,OAAOd,KAAK,KAAK,QAAQ,GAAG,QAAQ,GAAGA,KAAK;;EAElE;EACA;EACA;;EAEA,oBACE,IAAAP,WAAA,CAAAsB,GAAA,EAACrB,UAAU;IAAA,GACLmB,IAAI;IACRb,KAAK,EAAEc,aAAc;IACrBZ,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,GAAGyB,SAAU;IACrDxB,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGwB;EAAU,CAC7C,CAAC;AAEN","ignoreList":[]}
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: resolvedTheme,
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","resolvedTheme","searchPlaceholder","noResultsText","categoryNames","nativeEvent","emoji","name","id","undefined"],"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,MAAMC,aAAa,GAAG,OAAOd,KAAK,KAAK,QAAQ,GAAG,QAAQ,GAAGA,KAAK;;EAElE;EACA;EACA;;EAEA,oBACEN,IAAA,CAACC,UAAU;IAAA,GACLkB,IAAI;IACRb,KAAK,EAAEc,aAAc;IACrBZ,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,GAAGwB,SAAU;IACrDvB,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGuB;EAAU,CAC7C,CAAC;AAEN","ignoreList":[]}
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?: EmojiSheetTheme | 'dark' | 'light' | 'system';
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,2CAgCrB"}
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.1.0",
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?: EmojiSheetTheme | 'dark' | 'light' | 'system';
68
+ theme?: 'dark' | 'light' | 'system';
69
69
  translations?: EmojiSheetTranslations;
70
70
  layoutDirection?: 'ltr' | 'rtl' | 'auto';
71
71
  categoryBarPosition?: 'top' | 'bottom';
@@ -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={resolvedTheme}
33
+ theme={typeof theme === 'string' ? theme : undefined}
40
34
  layoutDirection={layoutDirection}
41
35
  categoryBarPosition={categoryBarPosition}
42
36
  columns={columns}