Skip to content

Commit 262fef1

Browse files
authored
Merge pull request #310 from hotwired/webview-element-scrolling
Prevent pull-to-refresh from triggering while scrolling in a nested scrollable element on the page
2 parents 2865c2d + 8a53cb7 commit 262fef1

File tree

4 files changed

+66
-5
lines changed

4 files changed

+66
-5
lines changed

turbo/src/main/assets/js/turbo_bridge.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,37 @@
179179
}
180180
}
181181

182+
// Touch detection, allowing vertically scrollable elements
183+
// to scroll properly without triggering pull-to-refresh.
184+
185+
const elementTouchStart = (event) => {
186+
if (!event.target) return
187+
188+
var element = event.target
189+
190+
while (element) {
191+
const canScroll = element.scrollHeight > element.clientHeight
192+
const overflowY = window.getComputedStyle(element).overflowY
193+
194+
if (canScroll && (overflowY === "scroll" || overflowY === "auto")) {
195+
TurboSession.elementTouchStarted(true)
196+
break
197+
}
198+
199+
element = element.parentElement
200+
}
201+
202+
if (!element) {
203+
TurboSession.elementTouchStarted(false)
204+
}
205+
}
206+
207+
const elementTouchEnd = () => {
208+
TurboSession.elementTouchEnded()
209+
}
210+
211+
// Setup and register adapter
212+
182213
window.turboNative = new TurboNative()
183214

184215
const setup = function() {
@@ -187,6 +218,9 @@
187218

188219
document.removeEventListener("turbo:load", setup)
189220
document.removeEventListener("turbolinks:load", setup)
221+
222+
document.addEventListener("touchstart", elementTouchStart)
223+
document.addEventListener("touchend", elementTouchEnd)
190224
}
191225

192226
const setupOnLoad = () => {

turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,28 @@ class TurboSession internal constructor(
482482
callback { it.onReceivedError(-1) }
483483
}
484484

485+
/**
486+
* Called when a touched element event has started.
487+
*
488+
* Warning: This method is public so it can be used as a Javascript Interface.
489+
* You should never call this directly as it could lead to unintended behavior.
490+
*/
491+
@JavascriptInterface
492+
fun elementTouchStarted(scrollable: Boolean) {
493+
webView.elementTouchIsScrollable = scrollable
494+
}
495+
496+
/**
497+
* Called when a touched element event has ended.
498+
*
499+
* Warning: This method is public so it can be used as a Javascript Interface.
500+
* You should never call this directly as it could lead to unintended behavior.
501+
*/
502+
@JavascriptInterface
503+
fun elementTouchEnded() {
504+
webView.elementTouchIsScrollable = false
505+
}
506+
485507
// Private
486508

487509
private fun visitLocation(visit: TurboVisit) {

turbo/src/main/kotlin/dev/hotwire/turbo/views/TurboSwipeRefreshLayout.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package dev.hotwire.turbo.views
22

33
import android.content.Context
44
import android.util.AttributeSet
5-
import android.webkit.WebView
65
import androidx.core.view.children
76
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
87

@@ -14,8 +13,13 @@ internal class TurboSwipeRefreshLayout @JvmOverloads constructor(context: Contex
1413
}
1514

1615
override fun canChildScrollUp(): Boolean {
17-
val webView = children.firstOrNull() as? WebView
18-
return webView?.scrollY ?: 0 > 0
16+
val webView = children.firstOrNull() as? TurboWebView
17+
18+
return if (webView != null) {
19+
webView.scrollY > 0 || webView.elementTouchIsScrollable
20+
} else {
21+
false
22+
}
1923
}
2024

2125
/**

turbo/src/main/kotlin/dev/hotwire/turbo/views/TurboWebView.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ import android.util.AttributeSet
66
import android.webkit.WebView
77
import android.widget.FrameLayout
88
import android.widget.FrameLayout.LayoutParams.MATCH_PARENT
9-
import android.widget.FrameLayout.LayoutParams.WRAP_CONTENT
109
import androidx.webkit.WebViewCompat
10+
import com.google.gson.GsonBuilder
1111
import dev.hotwire.turbo.util.contentFromAsset
1212
import dev.hotwire.turbo.util.runOnUiThread
1313
import dev.hotwire.turbo.util.toJson
1414
import dev.hotwire.turbo.visit.TurboVisitOptions
15-
import com.google.gson.GsonBuilder
1615

1716
/**
1817
* A Turbo-specific WebView that configures required settings and exposes some helpful info.
@@ -72,6 +71,8 @@ open class TurboWebView @JvmOverloads constructor(context: Context, attrs: Attri
7271
}
7372
}
7473

74+
internal var elementTouchIsScrollable = false
75+
7576
private fun WebView.runJavascript(javascript: String, onComplete: (String?) -> Unit = {}) {
7677
context.runOnUiThread {
7778
evaluateJavascript(javascript) {

0 commit comments

Comments
 (0)