@shortkitsdk/react-native 0.1.0 → 0.2.0

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 com.shortkit.reactnative
2
2
 
3
3
  import android.content.Context
4
4
  import android.content.ContextWrapper
5
+ import android.os.Handler
6
+ import android.os.Looper
5
7
  import android.util.Log
6
8
  import android.view.View
7
9
  import android.view.ViewGroup
@@ -36,6 +38,7 @@ class ShortKitFeedView(context: Context) : FrameLayout(context) {
36
38
 
37
39
  private var feedFragment: ShortKitFeedFragment? = null
38
40
  private var fragmentContainerId: Int = View.generateViewId()
41
+ private val handler = Handler(Looper.getMainLooper())
39
42
 
40
43
  init {
41
44
  id = fragmentContainerId
@@ -54,9 +57,12 @@ class ShortKitFeedView(context: Context) : FrameLayout(context) {
54
57
  /** Overlay for the upcoming cell (nativeID="overlay-next"). */
55
58
  private var nextOverlayView: View? = null
56
59
 
57
- /** The page index from which the current scroll gesture started. */
60
+ /** The page index used for overlay transform calculations. */
58
61
  private var currentPage: Int = 0
59
62
 
63
+ /** Deferred page update to avoid flashing stale metadata. */
64
+ private var pageUpdateRunnable: Runnable? = null
65
+
60
66
  // -----------------------------------------------------------------------
61
67
  // Lifecycle
62
68
  // -----------------------------------------------------------------------
@@ -145,6 +151,8 @@ class ShortKitFeedView(context: Context) : FrameLayout(context) {
145
151
  }
146
152
 
147
153
  private fun teardownScrollTracking() {
154
+ pageUpdateRunnable?.let { handler.removeCallbacks(it) }
155
+ pageUpdateRunnable = null
148
156
  pageChangeCallback?.let { viewPager?.unregisterOnPageChangeCallback(it) }
149
157
  pageChangeCallback = null
150
158
  viewPager = null
@@ -162,6 +170,41 @@ class ShortKitFeedView(context: Context) : FrameLayout(context) {
162
170
  val cellHeight = height.toFloat()
163
171
  if (cellHeight <= 0) return
164
172
 
173
+ // Detect page change, but DEFER updating currentPage.
174
+ //
175
+ // Why: when the scroll settles on a new page, overlay-current still
176
+ // shows the OLD page's metadata (React hasn't processed OVERLAY_ACTIVATE
177
+ // yet). If we update currentPage immediately, delta snaps to 0 and
178
+ // overlay-current becomes visible with stale data.
179
+ //
180
+ // By deferring ~80ms, overlay-next (which already shows the correct
181
+ // data via NextOverlayProvider) stays visible at y=0 while React
182
+ // processes the state update.
183
+ if (positionOffset == 0f) {
184
+ if (position != currentPage && pageUpdateRunnable == null) {
185
+ val targetPage = position
186
+ val runnable = Runnable {
187
+ currentPage = targetPage
188
+ pageUpdateRunnable = null
189
+ // Reapply overlay transforms now that currentPage is updated.
190
+ // Without this, overlay-next (static NextOverlayProvider state)
191
+ // stays visible at y=0 while overlay-current (live state) stays
192
+ // hidden — no scroll event fires to trigger handleScrollOffset.
193
+ val h = height.toFloat()
194
+ currentOverlayView?.translationY = 0f
195
+ nextOverlayView?.translationY = h
196
+ }
197
+ pageUpdateRunnable = runnable
198
+ handler.postDelayed(runnable, 80)
199
+ }
200
+ } else if (pageUpdateRunnable != null) {
201
+ // User is scrolling again — apply pending update immediately
202
+ // so transforms stay aligned for the new gesture.
203
+ handler.removeCallbacks(pageUpdateRunnable!!)
204
+ pageUpdateRunnable = null
205
+ currentPage = position
206
+ }
207
+
165
208
  // positionOffset is 0.0 when settled, approaches 1.0 during forward scroll.
166
209
  // position is the index of the page currently filling most of the screen.
167
210
  val delta: Float = if (position >= currentPage) {
@@ -172,11 +215,6 @@ class ShortKitFeedView(context: Context) : FrameLayout(context) {
172
215
  -(cellHeight - positionOffsetPixels.toFloat())
173
216
  }
174
217
 
175
- // Update currentPage when the scroll settles
176
- if (positionOffset == 0f) {
177
- currentPage = position
178
- }
179
-
180
218
  // Find the overlay views if not cached
181
219
  if (currentOverlayView == null || nextOverlayView == null) {
182
220
  findOverlayViews()
@@ -196,23 +234,40 @@ class ShortKitFeedView(context: Context) : FrameLayout(context) {
196
234
  }
197
235
 
198
236
  /**
199
- * Find the sibling RN overlay views by nativeID. In the Fabric hierarchy,
200
- * the ShortKitFeedView and the overlay containers are children of the
201
- * same parent (wrapped by ShortKitFeed.tsx with overflow: hidden).
237
+ * Find the sibling RN overlay views by nativeID.
238
+ *
239
+ * In Fabric interop, this view may be wrapped in an intermediate
240
+ * ViewGroup, so `getParent()` may not be the React `<View>` container
241
+ * from ShortKitFeed.tsx. We walk up the ancestor chain until we find
242
+ * the overlays.
202
243
  */
203
244
  private fun findOverlayViews() {
204
- val parent = parent as? ViewGroup ?: return
205
-
206
- for (i in 0 until parent.childCount) {
207
- val sibling = parent.getChildAt(i)
208
- if (sibling === this) continue
209
- if (currentOverlayView == null) {
210
- currentOverlayView = ReactFindViewUtil.findView(sibling, "overlay-current")
211
- }
212
- if (nextOverlayView == null) {
213
- nextOverlayView = ReactFindViewUtil.findView(sibling, "overlay-next")
245
+ var ancestor = parent as? ViewGroup ?: return
246
+ while (true) {
247
+ for (i in 0 until ancestor.childCount) {
248
+ val child = ancestor.getChildAt(i)
249
+ if (child === this || isOwnAncestor(child)) continue
250
+
251
+ if (currentOverlayView == null) {
252
+ currentOverlayView = ReactFindViewUtil.findView(child, "overlay-current")
253
+ }
254
+ if (nextOverlayView == null) {
255
+ nextOverlayView = ReactFindViewUtil.findView(child, "overlay-next")
256
+ }
214
257
  }
258
+ if (currentOverlayView != null && nextOverlayView != null) return
259
+ ancestor = ancestor.parent as? ViewGroup ?: return
260
+ }
261
+ }
262
+
263
+ /** Check if the given view is an ancestor of this view. */
264
+ private fun isOwnAncestor(view: View): Boolean {
265
+ var current: ViewParent? = parent
266
+ while (current != null) {
267
+ if (current === view) return true
268
+ current = current.parent
215
269
  }
270
+ return false
216
271
  }
217
272
 
218
273
  /** Recursively find the first [ViewPager2] in a view hierarchy. */
@@ -11,12 +11,8 @@ import com.shortkit.FeedConfig
11
11
  import com.shortkit.FeedHeight
12
12
  import com.shortkit.FeedTransitionPhase
13
13
  import com.shortkit.JsonValue
14
- import com.shortkit.OverlayActionDelegate
15
14
  import com.shortkit.ShortKit
16
- import com.shortkit.ShortKitDelegate
17
- import com.shortkit.ShortKitError
18
15
  import com.shortkit.ShortKitPlayer
19
- import com.shortkit.SurveyOption
20
16
  import com.shortkit.VideoOverlayMode
21
17
  import com.shortkit.CarouselOverlayMode
22
18
  import com.shortkit.SurveyOverlayMode
@@ -30,8 +26,7 @@ import org.json.JSONArray
30
26
  import org.json.JSONObject
31
27
 
32
28
  class ShortKitModule(reactContext: ReactApplicationContext) :
33
- NativeShortKitModuleSpec(reactContext),
34
- ShortKitDelegate {
29
+ NativeShortKitModuleSpec(reactContext) {
35
30
 
36
31
  companion object {
37
32
  const val NAME = "ShortKitModule"
@@ -116,9 +111,6 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
116
111
  clientAppVersion = clientAppVersion,
117
112
  customDimensions = dims
118
113
  )
119
- sdk.delegate = this
120
- sdk.overlayActionDelegate = overlayDelegate
121
-
122
114
  this.shortKit = sdk
123
115
  shared = this
124
116
 
@@ -219,102 +211,6 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
219
211
  shortKit?.player?.setMaxBitrate(bitrate.toInt())
220
212
  }
221
213
 
222
- // -----------------------------------------------------------------------
223
- // ShortKitDelegate
224
- // -----------------------------------------------------------------------
225
-
226
- override fun onError(error: ShortKitError) {
227
- val params = Arguments.createMap().apply {
228
- when (error) {
229
- is ShortKitError.NetworkError -> {
230
- putString("code", "network_error")
231
- putString("message", error.cause?.localizedMessage ?: "Network error")
232
- }
233
- is ShortKitError.ApiError -> {
234
- putString("code", "api_error")
235
- putString("message", error.message ?: "API error")
236
- }
237
- is ShortKitError.PlayerError -> {
238
- putString("code", error.code)
239
- putString("message", error.message ?: "Player error")
240
- }
241
- is ShortKitError.ConfigError -> {
242
- putString("code", "config_error")
243
- putString("message", error.message ?: "Config error")
244
- }
245
- is ShortKitError.AuthError -> {
246
- putString("code", "auth_error")
247
- putString("message", "Invalid API key")
248
- }
249
- }
250
- }
251
- sendEvent("onError", params)
252
- }
253
-
254
- override fun onShareTapped(item: ContentItem) {
255
- val params = Arguments.createMap().apply {
256
- putString("item", serializeContentItemToJSON(item))
257
- }
258
- sendEvent("onShareTapped", params)
259
- }
260
-
261
- override fun onSurveyResponse(surveyId: String, option: SurveyOption) {
262
- val params = Arguments.createMap().apply {
263
- putString("surveyId", surveyId)
264
- putString("optionId", option.id)
265
- putString("optionText", option.text)
266
- }
267
- sendEvent("onSurveyResponse", params)
268
- }
269
-
270
- // We implement the optional delegate methods as no-ops; events are handled
271
- // via StateFlow/SharedFlow subscriptions instead.
272
- override fun onFeedReady() {}
273
- override fun onContentChanged(item: ContentItem) {}
274
- override fun onFeedEmpty() {}
275
-
276
- // -----------------------------------------------------------------------
277
- // OverlayActionDelegate (separate object to avoid signature clash with
278
- // ShortKitDelegate.onShareTapped)
279
- // -----------------------------------------------------------------------
280
-
281
- private val overlayDelegate = object : OverlayActionDelegate {
282
- override fun onReadMoreTapped(item: ContentItem) {
283
- val params = Arguments.createMap().apply {
284
- putString("item", serializeContentItemToJSON(item))
285
- }
286
- sendEvent("onArticleTapped", params)
287
- }
288
-
289
- override fun onShareTapped(item: ContentItem) {
290
- val params = Arguments.createMap().apply {
291
- putString("item", serializeContentItemToJSON(item))
292
- }
293
- sendEvent("onOverlayShareTapped", params)
294
- }
295
-
296
- override fun onCommentTapped(item: ContentItem) {
297
- val params = Arguments.createMap().apply {
298
- putString("item", serializeContentItemToJSON(item))
299
- }
300
- sendEvent("onCommentTapped", params)
301
- }
302
-
303
- override fun onSaveTapped(item: ContentItem) {
304
- val params = Arguments.createMap().apply {
305
- putString("item", serializeContentItemToJSON(item))
306
- }
307
- sendEvent("onSaveTapped", params)
308
- }
309
-
310
- override fun onLikeTapped(item: ContentItem) {
311
- val params = Arguments.createMap().apply {
312
- putString("item", serializeContentItemToJSON(item))
313
- }
314
- sendEvent("onLikeTapped", params)
315
- }
316
- }
317
-
318
214
  // -----------------------------------------------------------------------
319
215
  // Overlay lifecycle events (called by Fabric view)
320
216
  // -----------------------------------------------------------------------
@@ -6,7 +6,7 @@ import ShortKit
6
6
  ///
7
7
  /// Holds the `ShortKit` instance, subscribes to all Combine publishers on
8
8
  /// `ShortKitPlayer`, and forwards events to JS via the delegate protocol.
9
- @objc public class ShortKitBridge: NSObject, ShortKitDelegate {
9
+ @objc public class ShortKitBridge: NSObject {
10
10
 
11
11
  // MARK: - Shared instance (accessed by Fabric view manager in Task 13)
12
12
 
@@ -41,7 +41,6 @@ import ShortKit
41
41
  clientAppVersion: clientAppVersion,
42
42
  customDimensions: dimensions
43
43
  )
44
- sdk.delegate = self
45
44
  self.shortKit = sdk
46
45
 
47
46
  ShortKitBridge.shared = self
@@ -141,33 +140,6 @@ import ShortKit
141
140
  shortKit?.player.setMaxBitrate(Int(bitrate))
142
141
  }
143
142
 
144
- // MARK: - ShortKitDelegate
145
-
146
- public func shortKit(_ shortKit: ShortKit, didEncounterError error: ShortKitError) {
147
- let body: [String: Any]
148
- switch error {
149
- case .networkError(let underlying):
150
- body = ["code": "network_error", "message": underlying.localizedDescription]
151
- case .playbackError(let code, let message):
152
- body = ["code": code, "message": message]
153
- case .authError:
154
- body = ["code": "auth_error", "message": "Invalid API key"]
155
- }
156
- emitOnMain("onError", body: body)
157
- }
158
-
159
- public func shortKit(_ shortKit: ShortKit, didTapShareFor item: ContentItem) {
160
- emitOnMain("onShareTapped", body: ["item": serializeContentItemToJSON(item)])
161
- }
162
-
163
- public func shortKit(_ shortKit: ShortKit, didRespondToSurvey surveyId: String, with option: SurveyOption) {
164
- emitOnMain("onSurveyResponse", body: [
165
- "surveyId": surveyId,
166
- "optionId": option.id,
167
- "optionText": option.text
168
- ])
169
- }
170
-
171
143
  // MARK: - Combine Subscriptions
172
144
 
173
145
  private func subscribeToPublishers(_ player: ShortKitPlayer) {
@@ -335,31 +307,6 @@ import ShortKit
335
307
  emitOnMain(name, body: body)
336
308
  }
337
309
 
338
- /// Emit an article tap event.
339
- public func emitArticleTapped(_ item: ContentItem) {
340
- emitOnMain("onArticleTapped", body: ["item": serializeContentItemToJSON(item)])
341
- }
342
-
343
- /// Emit a comment tap event.
344
- public func emitCommentTapped(_ item: ContentItem) {
345
- emitOnMain("onCommentTapped", body: ["item": serializeContentItemToJSON(item)])
346
- }
347
-
348
- /// Emit an overlay share tap event.
349
- public func emitOverlayShareTapped(_ item: ContentItem) {
350
- emitOnMain("onOverlayShareTapped", body: ["item": serializeContentItemToJSON(item)])
351
- }
352
-
353
- /// Emit a save tap event.
354
- public func emitSaveTapped(_ item: ContentItem) {
355
- emitOnMain("onSaveTapped", body: ["item": serializeContentItemToJSON(item)])
356
- }
357
-
358
- /// Emit a like tap event.
359
- public func emitLikeTapped(_ item: ContentItem) {
360
- emitOnMain("onLikeTapped", body: ["item": serializeContentItemToJSON(item)])
361
- }
362
-
363
310
  // MARK: - Content Item Serialization
364
311
 
365
312
  /// Serialize a ContentItem to a JSON string for bridge transport.
@@ -31,8 +31,10 @@ import ShortKit
31
31
  private weak var currentOverlayView: UIView?
32
32
  /// Overlay for the upcoming cell (nativeID="overlay-next").
33
33
  private weak var nextOverlayView: UIView?
34
- /// The page index from which the current scroll gesture started.
34
+ /// The page index used for overlay transform calculations.
35
35
  private var currentPage: Int = 0
36
+ /// Deferred page update to avoid flashing stale metadata.
37
+ private var pageUpdateWorkItem: DispatchWorkItem?
36
38
 
37
39
  // MARK: - Lifecycle
38
40
 
@@ -86,6 +88,8 @@ import ShortKit
86
88
  }
87
89
 
88
90
  private func removeFeedViewController() {
91
+ pageUpdateWorkItem?.cancel()
92
+ pageUpdateWorkItem = nil
89
93
  scrollObservation?.invalidate()
90
94
  scrollObservation = nil
91
95
  currentOverlayView?.transform = .identity
@@ -106,7 +110,6 @@ import ShortKit
106
110
 
107
111
  private func setupScrollTracking(_ feedVC: ShortKitFeedViewController) {
108
112
  guard let scrollView = findScrollView(in: feedVC.view) else {
109
- NSLog("[ShortKitFeedView] Could not find scroll view in feed VC hierarchy.")
110
113
  return
111
114
  }
112
115
 
@@ -120,14 +123,47 @@ import ShortKit
120
123
  guard cellHeight > 0 else { return }
121
124
 
122
125
  let offset = scrollView.contentOffset.y
123
- let delta = offset - CGFloat(currentPage) * cellHeight
124
126
 
125
- // Update currentPage when the scroll settles near a page boundary
127
+ // Detect page change, but DEFER updating currentPage.
128
+ //
129
+ // Why: when the scroll settles on a new page, overlay-current still
130
+ // shows the OLD page's metadata (React hasn't processed OVERLAY_ACTIVATE
131
+ // yet). If we update currentPage immediately, delta snaps to 0 and
132
+ // overlay-current becomes visible with stale data.
133
+ //
134
+ // By deferring ~80ms, overlay-next (which already shows the correct
135
+ // data via NextOverlayProvider) stays visible at y=0 while React
136
+ // processes the state update. After the delay, overlay-current has
137
+ // been re-rendered with correct data and takes over seamlessly.
126
138
  let nearestPage = Int(round(offset / cellHeight))
127
139
  if abs(offset - CGFloat(nearestPage) * cellHeight) < 1.0 {
140
+ if nearestPage != currentPage && pageUpdateWorkItem == nil {
141
+ let targetPage = nearestPage
142
+ let workItem = DispatchWorkItem { [weak self] in
143
+ guard let self else { return }
144
+ self.currentPage = targetPage
145
+ self.pageUpdateWorkItem = nil
146
+ // Reapply overlay transforms now that currentPage is updated.
147
+ // Without this, overlay-next (static NextOverlayProvider state)
148
+ // stays visible at y=0 while overlay-current (live state) stays
149
+ // hidden — no scroll event fires to trigger handleScrollOffset.
150
+ let h = self.bounds.height
151
+ self.currentOverlayView?.transform = .identity
152
+ self.nextOverlayView?.transform = CGAffineTransform(translationX: 0, y: h)
153
+ }
154
+ pageUpdateWorkItem = workItem
155
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.08, execute: workItem)
156
+ }
157
+ } else if let workItem = pageUpdateWorkItem {
158
+ // User is scrolling again — apply pending update immediately
159
+ // so transforms stay aligned for the new gesture.
160
+ workItem.cancel()
161
+ pageUpdateWorkItem = nil
128
162
  currentPage = nearestPage
129
163
  }
130
164
 
165
+ let delta = offset - CGFloat(currentPage) * cellHeight
166
+
131
167
  // Find the overlay views if not cached
132
168
  if currentOverlayView == nil || nextOverlayView == nil {
133
169
  findOverlayViews()
@@ -146,28 +182,39 @@ import ShortKit
146
182
  }
147
183
  }
148
184
 
149
- /// Find the sibling RN overlay views by nativeID (mapped to
150
- /// accessibilityIdentifier on iOS). In the Fabric hierarchy, the
151
- /// ShortKitFeedView and the overlay containers are children of the
152
- /// same parent (wrapped by ShortKitFeed.tsx with overflow: hidden).
185
+ /// Find the sibling RN overlay views by nativeID.
186
+ ///
187
+ /// In Fabric interop, `ShortKitFeedView` (a Paper-style `RCTViewManager`
188
+ /// view) is wrapped in an intermediate `RCTViewComponentView`. So
189
+ /// `self.superview` is that wrapper — not the React `<View>` container
190
+ /// rendered by ShortKitFeed.tsx. We walk up the ancestor chain until we
191
+ /// find the overlays.
153
192
  private func findOverlayViews() {
154
- guard let parent = superview else { return }
155
-
156
- // The nativeID views may be nested inside intermediate Fabric
157
- // wrapper views, so we search recursively within siblings.
158
- for sibling in parent.subviews where sibling !== self {
159
- if let found = findView(withNativeID: "overlay-current", in: sibling) {
160
- currentOverlayView = found
161
- }
162
- if let found = findView(withNativeID: "overlay-next", in: sibling) {
163
- nextOverlayView = found
193
+ var ancestor: UIView? = superview
194
+ while let container = ancestor {
195
+ for child in container.subviews {
196
+ // Skip subtrees that contain ourselves
197
+ guard !self.isDescendant(of: child) else { continue }
198
+
199
+ if currentOverlayView == nil {
200
+ currentOverlayView = findView(withNativeID: "overlay-current", in: child)
201
+ }
202
+ if nextOverlayView == nil {
203
+ nextOverlayView = findView(withNativeID: "overlay-next", in: child)
204
+ }
164
205
  }
206
+ if currentOverlayView != nil && nextOverlayView != nil { return }
207
+ ancestor = container.superview
165
208
  }
166
209
  }
167
210
 
168
- /// Recursively find a view by its accessibilityIdentifier (nativeID).
211
+ /// Recursively find a view by its React Native `nativeID` prop.
212
+ /// In Fabric, this is stored on `RCTViewComponentView.nativeId`,
213
+ /// not `accessibilityIdentifier`.
169
214
  private func findView(withNativeID nativeID: String, in view: UIView) -> UIView? {
170
- if view.accessibilityIdentifier == nativeID {
215
+ if view.responds(to: Selector(("nativeId"))),
216
+ let rnNativeId = view.value(forKey: "nativeId") as? String,
217
+ rnNativeId == nativeID {
171
218
  return view
172
219
  }
173
220
  for subview in view.subviews {
@@ -57,11 +57,6 @@ RCT_EXPORT_MODULE(ShortKitModule)
57
57
  @"onError",
58
58
  @"onShareTapped",
59
59
  @"onSurveyResponse",
60
- @"onArticleTapped",
61
- @"onCommentTapped",
62
- @"onOverlayShareTapped",
63
- @"onSaveTapped",
64
- @"onLikeTapped",
65
60
  @"onOverlayConfigure",
66
61
  @"onOverlayActivate",
67
62
  @"onOverlayReset",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shortkitsdk/react-native",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "ShortKit React Native SDK — short-form video feed",
5
5
  "react-native": "src/index",
6
6
  "source": "src/index",
@@ -29,11 +29,6 @@ export function ShortKitFeed(props: ShortKitFeedProps) {
29
29
  onLoop,
30
30
  onFeedTransition,
31
31
  onFormatChange,
32
- onArticleTapped,
33
- onCommentTapped,
34
- onOverlayShareTapped,
35
- onSaveTapped,
36
- onLikeTapped,
37
32
  } = props;
38
33
 
39
34
  const context = useContext(ShortKitContext);
@@ -114,51 +109,6 @@ export function ShortKitFeed(props: ShortKitFeedProps) {
114
109
  );
115
110
  }
116
111
 
117
- if (onArticleTapped) {
118
- subscriptions.push(
119
- NativeShortKitModule.onArticleTapped((event) => {
120
- const item = deserializeContentItem(event.item);
121
- if (item) onArticleTapped(item);
122
- }),
123
- );
124
- }
125
-
126
- if (onCommentTapped) {
127
- subscriptions.push(
128
- NativeShortKitModule.onCommentTapped((event) => {
129
- const item = deserializeContentItem(event.item);
130
- if (item) onCommentTapped(item);
131
- }),
132
- );
133
- }
134
-
135
- if (onOverlayShareTapped) {
136
- subscriptions.push(
137
- NativeShortKitModule.onOverlayShareTapped((event) => {
138
- const item = deserializeContentItem(event.item);
139
- if (item) onOverlayShareTapped(item);
140
- }),
141
- );
142
- }
143
-
144
- if (onSaveTapped) {
145
- subscriptions.push(
146
- NativeShortKitModule.onSaveTapped((event) => {
147
- const item = deserializeContentItem(event.item);
148
- if (item) onSaveTapped(item);
149
- }),
150
- );
151
- }
152
-
153
- if (onLikeTapped) {
154
- subscriptions.push(
155
- NativeShortKitModule.onLikeTapped((event) => {
156
- const item = deserializeContentItem(event.item);
157
- if (item) onLikeTapped(item);
158
- }),
159
- );
160
- }
161
-
162
112
  return () => {
163
113
  for (const sub of subscriptions) {
164
114
  sub.remove();
@@ -171,11 +121,6 @@ export function ShortKitFeed(props: ShortKitFeedProps) {
171
121
  onLoop,
172
122
  onFeedTransition,
173
123
  onFormatChange,
174
- onArticleTapped,
175
- onCommentTapped,
176
- onOverlayShareTapped,
177
- onSaveTapped,
178
- onLikeTapped,
179
124
  ]);
180
125
 
181
126
  // ---------------------------------------------------------------------------
@@ -68,7 +68,7 @@ type Action =
68
68
  | { type: 'CUE'; payload: { text: string; startTime: number; endTime: number } | null }
69
69
  | { type: 'PREFETCH_COUNT'; payload: number }
70
70
  | { type: 'OVERLAY_CONFIGURE'; payload: ContentItem }
71
- | { type: 'OVERLAY_ACTIVATE' }
71
+ | { type: 'OVERLAY_ACTIVATE'; payload: ContentItem }
72
72
  | { type: 'OVERLAY_RESET' }
73
73
  | { type: 'OVERLAY_FADE_OUT' }
74
74
  | { type: 'OVERLAY_RESTORE' }
@@ -111,7 +111,7 @@ function reducer(state: State, action: Action): State {
111
111
  case 'OVERLAY_CONFIGURE':
112
112
  return { ...state, nextItem: action.payload };
113
113
  case 'OVERLAY_ACTIVATE':
114
- return { ...state, currentItem: state.nextItem, isActive: true };
114
+ return { ...state, currentItem: action.payload, isActive: true };
115
115
  case 'OVERLAY_RESET':
116
116
  // Don't set isActive = false — the overlay stays mounted during
117
117
  // transitions. In the native SDK each cell has its own overlay
@@ -338,8 +338,11 @@ export function ShortKitProvider({
338
338
  );
339
339
 
340
340
  subscriptions.push(
341
- NativeShortKitModule.onOverlayActivate((_event) => {
342
- dispatch({ type: 'OVERLAY_ACTIVATE' });
341
+ NativeShortKitModule.onOverlayActivate((event) => {
342
+ const item = deserializeContentItem(event.item);
343
+ if (item) {
344
+ dispatch({ type: 'OVERLAY_ACTIVATE', payload: item });
345
+ }
343
346
  }),
344
347
  );
345
348
 
@@ -92,26 +92,6 @@ type SurveyResponseEvent = Readonly<{
92
92
  optionText: string;
93
93
  }>;
94
94
 
95
- type ArticleTappedEvent = Readonly<{
96
- item: string; // JSON-serialized ContentItem
97
- }>;
98
-
99
- type CommentTappedEvent = Readonly<{
100
- item: string; // JSON-serialized ContentItem
101
- }>;
102
-
103
- type OverlayShareTappedEvent = Readonly<{
104
- item: string; // JSON-serialized ContentItem
105
- }>;
106
-
107
- type SaveTappedEvent = Readonly<{
108
- item: string; // JSON-serialized ContentItem
109
- }>;
110
-
111
- type LikeTappedEvent = Readonly<{
112
- item: string; // JSON-serialized ContentItem
113
- }>;
114
-
115
95
  type OverlayConfigureEvent = Readonly<{
116
96
  item: string; // JSON-serialized ContentItem
117
97
  }>;
@@ -184,11 +164,6 @@ export interface Spec extends TurboModule {
184
164
  readonly onError: EventEmitter<ErrorEvent>;
185
165
  readonly onShareTapped: EventEmitter<ShareTappedEvent>;
186
166
  readonly onSurveyResponse: EventEmitter<SurveyResponseEvent>;
187
- readonly onArticleTapped: EventEmitter<ArticleTappedEvent>;
188
- readonly onCommentTapped: EventEmitter<CommentTappedEvent>;
189
- readonly onOverlayShareTapped: EventEmitter<OverlayShareTappedEvent>;
190
- readonly onSaveTapped: EventEmitter<SaveTappedEvent>;
191
- readonly onLikeTapped: EventEmitter<LikeTappedEvent>;
192
167
  readonly onOverlayConfigure: EventEmitter<OverlayConfigureEvent>;
193
168
  readonly onOverlayActivate: EventEmitter<OverlayActivateEvent>;
194
169
  readonly onOverlayReset: EventEmitter<OverlayResetEvent>;
package/src/types.ts CHANGED
@@ -127,11 +127,6 @@ export interface ShortKitFeedProps {
127
127
  onLoop?: (event: LoopEvent) => void;
128
128
  onFeedTransition?: (event: FeedTransitionEvent) => void;
129
129
  onFormatChange?: (event: FormatChangeEvent) => void;
130
- onArticleTapped?: (item: ContentItem) => void;
131
- onCommentTapped?: (item: ContentItem) => void;
132
- onOverlayShareTapped?: (item: ContentItem) => void;
133
- onSaveTapped?: (item: ContentItem) => void;
134
- onLikeTapped?: (item: ContentItem) => void;
135
130
  }
136
131
 
137
132
  // --- Hook Return Types ---