@dr33m/react-native-readium 5.0.0-rc.23 → 5.0.0-rc.25

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.
@@ -42,6 +42,11 @@ abstract class BaseReaderFragment : Fragment() {
42
42
  // TTS
43
43
  private var ttsManager: TTSManager? = null
44
44
 
45
+ // True while the user has an active text selection.
46
+ // applyDecorations injects JS into the EPUB WebView; doing so while the user
47
+ // is dragging selection handles causes the selection to jump or reset.
48
+ protected var isUserSelecting = false
49
+
45
50
  // Track active decoration listeners to avoid duplicates
46
51
  private val activeDecorationGroups = mutableSetOf<String>()
47
52
 
@@ -107,8 +112,8 @@ abstract class BaseReaderFragment : Fragment() {
107
112
  // Apply any pending decorations now that navigator is ready
108
113
  pendingDecorations?.let { applyDecorations(it) }
109
114
 
110
- // Start monitoring text selection
111
- startSelectionMonitoring()
115
+ // Selection detection is handled via ActionMode callbacks in EpubReaderFragment,
116
+ // not polling — see onSelectionActionModeCreated/Destroyed.
112
117
  }
113
118
 
114
119
  override fun onHiddenChanged(hidden: Boolean) {
@@ -275,6 +280,7 @@ abstract class BaseReaderFragment : Fragment() {
275
280
 
276
281
  private fun applyTTSDecoration(locator: Locator) {
277
282
  if (!isNavigatorReady) return
283
+ if (isUserSelecting) return // skip DOM update while user drags selection handles
278
284
  val decorableNavigator = navigator as? DecorableNavigator ?: return
279
285
  // applyDecorations is suspend in Readium 3.x — must be called from a coroutine.
280
286
  // viewLifecycleOwner.lifecycleScope dispatches to Dispatchers.Main, matching
@@ -311,60 +317,45 @@ abstract class BaseReaderFragment : Fragment() {
311
317
  }
312
318
 
313
319
  /**
314
- * Start monitoring text selection and emit selection change events.
320
+ * Called by [EpubReaderFragment] when the WebView's ActionMode is created
321
+ * (i.e. the user selects text). Queries the selection once and emits a
322
+ * [SelectionChanged] event so the React Native side can show the highlight menu.
315
323
  *
316
- * Uses 500ms polling because Readium's [SelectableNavigator] does not
317
- * provide an observable API (Flow, callback, or listener) for selection
318
- * changes. The only available method is the suspending
319
- * [SelectableNavigator.currentSelection], which must be called on-demand.
320
- * Polling is the least-invasive way to detect changes without forking the
321
- * Readium toolkit.
324
+ * This replaces the old 500ms polling loop which executed JavaScript in the
325
+ * WebView every tick and disrupted the browser's selection state, causing
326
+ * the selection to glitch/jump while dragging handles.
322
327
  */
323
- private fun startSelectionMonitoring() {
324
- val viewScope = viewLifecycleOwner.lifecycleScope
325
-
326
- viewScope.launch {
327
- var previousSelection: Locator? = null
328
-
329
- while (true) {
330
- delay(500) // Check every 500ms
331
-
332
- if (!isNavigatorReady) continue
333
-
334
- val selectableNavigator = navigator as? SelectableNavigator
335
- if (selectableNavigator == null) continue
336
-
337
- val currentSelection = try {
338
- selectableNavigator.currentSelection()
339
- } catch (e: Exception) {
340
- android.util.Log.w("BaseReaderFragment", "Error getting selection: ${e.message}")
341
- null
342
- }
343
-
344
- val currentLocator = currentSelection?.locator
345
- val currentText = currentLocator?.text?.highlight
346
-
347
- // Check if selection has changed
348
- val hasChanged = when {
349
- previousSelection == null && currentLocator == null -> false
350
- previousSelection == null || currentLocator == null -> true
351
- previousSelection.href != currentLocator.href -> true
352
- previousSelection.text.highlight != currentText -> true
353
- else -> false
354
- }
355
-
356
- if (hasChanged) {
357
- channel.send(
358
- ReaderViewModel.Event.SelectionChanged(
359
- locator = currentLocator,
360
- selectedText = currentText
361
- )
328
+ fun onSelectionActionModeCreated() {
329
+ isUserSelecting = true
330
+ viewLifecycleOwner.lifecycleScope.launch {
331
+ delay(100) // brief delay so the WebView selection stabilises
332
+ if (!isNavigatorReady) return@launch
333
+ val sel = (navigator as? SelectableNavigator)?.currentSelection()
334
+ if (sel != null) {
335
+ channel.send(
336
+ ReaderViewModel.Event.SelectionChanged(
337
+ locator = sel.locator,
338
+ selectedText = sel.locator.text.highlight
362
339
  )
363
-
364
- previousSelection = currentLocator
365
- }
340
+ )
366
341
  }
367
342
  }
368
343
  }
369
344
 
345
+ /**
346
+ * Called by [EpubReaderFragment] when the WebView's ActionMode is destroyed
347
+ * (i.e. the user taps away or the selection is dismissed).
348
+ */
349
+ fun onSelectionActionModeDestroyed() {
350
+ isUserSelecting = false
351
+ viewLifecycleOwner.lifecycleScope.launch {
352
+ channel.send(
353
+ ReaderViewModel.Event.SelectionChanged(
354
+ locator = null,
355
+ selectedText = null
356
+ )
357
+ )
358
+ }
359
+ }
360
+
370
361
  }
@@ -133,11 +133,14 @@ class EpubReaderFragment : VisualReaderFragment() {
133
133
  suppressNativeSelectionMenu -> selectionActionModeCallback = object : ActionMode.Callback {
134
134
  override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
135
135
  menu.clear()
136
+ onSelectionActionModeCreated()
136
137
  return true
137
138
  }
138
139
  override fun onPrepareActionMode(mode: ActionMode, menu: Menu) = false
139
140
  override fun onActionItemClicked(mode: ActionMode, item: MenuItem) = false
140
- override fun onDestroyActionMode(mode: ActionMode) {}
141
+ override fun onDestroyActionMode(mode: ActionMode) {
142
+ onSelectionActionModeDestroyed()
143
+ }
141
144
  }
142
145
  selectionActions.isNotEmpty() -> selectionActionModeCallback = customSelectionActionModeCallback
143
146
  }
@@ -225,6 +228,8 @@ class EpubReaderFragment : VisualReaderFragment() {
225
228
  // Clear previous action mappings
226
229
  actionIdMap.clear()
227
230
 
231
+ onSelectionActionModeCreated()
232
+
228
233
  // Only add menu items if navigator supports decorations
229
234
  if (navigator !is DecorableNavigator) {
230
235
  return true
@@ -283,6 +288,7 @@ class EpubReaderFragment : VisualReaderFragment() {
283
288
  override fun onDestroyActionMode(mode: ActionMode) {
284
289
  // Clean up action mappings
285
290
  actionIdMap.clear()
291
+ onSelectionActionModeDestroyed()
286
292
  }
287
293
  }
288
294
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dr33m/react-native-readium",
3
- "version": "5.0.0-rc.23",
3
+ "version": "5.0.0-rc.25",
4
4
  "description": "A react-native wrapper for https://readium.org/",
5
5
  "main": "lib/src/index",
6
6
  "types": "lib/src/index.d.ts",