@dr33m/react-native-readium 5.0.0-rc.24 → 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.
@@ -45,7 +45,7 @@ abstract class BaseReaderFragment : Fragment() {
45
45
  // True while the user has an active text selection.
46
46
  // applyDecorations injects JS into the EPUB WebView; doing so while the user
47
47
  // is dragging selection handles causes the selection to jump or reset.
48
- private var isUserSelecting = false
48
+ protected var isUserSelecting = false
49
49
 
50
50
  // Track active decoration listeners to avoid duplicates
51
51
  private val activeDecorationGroups = mutableSetOf<String>()
@@ -112,8 +112,8 @@ abstract class BaseReaderFragment : Fragment() {
112
112
  // Apply any pending decorations now that navigator is ready
113
113
  pendingDecorations?.let { applyDecorations(it) }
114
114
 
115
- // Start monitoring text selection
116
- startSelectionMonitoring()
115
+ // Selection detection is handled via ActionMode callbacks in EpubReaderFragment,
116
+ // not polling — see onSelectionActionModeCreated/Destroyed.
117
117
  }
118
118
 
119
119
  override fun onHiddenChanged(hidden: Boolean) {
@@ -317,64 +317,45 @@ abstract class BaseReaderFragment : Fragment() {
317
317
  }
318
318
 
319
319
  /**
320
- * 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.
321
323
  *
322
- * Uses 500ms polling because Readium's [SelectableNavigator] does not
323
- * provide an observable API (Flow, callback, or listener) for selection
324
- * changes. The only available method is the suspending
325
- * [SelectableNavigator.currentSelection], which must be called on-demand.
326
- * Polling is the least-invasive way to detect changes without forking the
327
- * 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.
328
327
  */
329
- private fun startSelectionMonitoring() {
330
- val viewScope = viewLifecycleOwner.lifecycleScope
331
-
332
- viewScope.launch {
333
- var previousSelection: Locator? = null
334
-
335
- while (true) {
336
- delay(500) // Check every 500ms
337
-
338
- if (!isNavigatorReady) continue
339
-
340
- val selectableNavigator = navigator as? SelectableNavigator
341
- if (selectableNavigator == null) continue
342
-
343
- val currentSelection = try {
344
- selectableNavigator.currentSelection()
345
- } catch (e: Exception) {
346
- android.util.Log.w("BaseReaderFragment", "Error getting selection: ${e.message}")
347
- null
348
- }
349
-
350
- val currentLocator = currentSelection?.locator
351
- val currentText = currentLocator?.text?.highlight
352
-
353
- // Keep the guard flag in sync so TTS decoration skips DOM updates
354
- // while the user is dragging selection handles.
355
- isUserSelecting = currentLocator != null
356
-
357
- // Check if selection has changed
358
- val hasChanged = when {
359
- previousSelection == null && currentLocator == null -> false
360
- previousSelection == null || currentLocator == null -> true
361
- previousSelection.href != currentLocator.href -> true
362
- previousSelection.text.highlight != currentText -> true
363
- else -> false
364
- }
365
-
366
- if (hasChanged) {
367
- channel.send(
368
- ReaderViewModel.Event.SelectionChanged(
369
- locator = currentLocator,
370
- selectedText = currentText
371
- )
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
372
339
  )
373
-
374
- previousSelection = currentLocator
375
- }
340
+ )
376
341
  }
377
342
  }
378
343
  }
379
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
+
380
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.24",
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",