Skip to content

Commit

Permalink
Merge pull request #310 from hotwired/webview-element-scrolling
Browse files Browse the repository at this point in the history
Prevent pull-to-refresh from triggering while scrolling in a nested scrollable element on the page
  • Loading branch information
jayohms authored Feb 24, 2024
2 parents 2865c2d + 8a53cb7 commit 262fef1
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 5 deletions.
34 changes: 34 additions & 0 deletions turbo/src/main/assets/js/turbo_bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,37 @@
}
}

// Touch detection, allowing vertically scrollable elements
// to scroll properly without triggering pull-to-refresh.

const elementTouchStart = (event) => {
if (!event.target) return

var element = event.target

while (element) {
const canScroll = element.scrollHeight > element.clientHeight
const overflowY = window.getComputedStyle(element).overflowY

if (canScroll && (overflowY === "scroll" || overflowY === "auto")) {
TurboSession.elementTouchStarted(true)
break
}

element = element.parentElement
}

if (!element) {
TurboSession.elementTouchStarted(false)
}
}

const elementTouchEnd = () => {
TurboSession.elementTouchEnded()
}

// Setup and register adapter

window.turboNative = new TurboNative()

const setup = function() {
Expand All @@ -187,6 +218,9 @@

document.removeEventListener("turbo:load", setup)
document.removeEventListener("turbolinks:load", setup)

document.addEventListener("touchstart", elementTouchStart)
document.addEventListener("touchend", elementTouchEnd)
}

const setupOnLoad = () => {
Expand Down
22 changes: 22 additions & 0 deletions turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,28 @@ class TurboSession internal constructor(
callback { it.onReceivedError(-1) }
}

/**
* Called when a touched element event has started.
*
* Warning: This method is public so it can be used as a Javascript Interface.
* You should never call this directly as it could lead to unintended behavior.
*/
@JavascriptInterface
fun elementTouchStarted(scrollable: Boolean) {
webView.elementTouchIsScrollable = scrollable
}

/**
* Called when a touched element event has ended.
*
* Warning: This method is public so it can be used as a Javascript Interface.
* You should never call this directly as it could lead to unintended behavior.
*/
@JavascriptInterface
fun elementTouchEnded() {
webView.elementTouchIsScrollable = false
}

// Private

private fun visitLocation(visit: TurboVisit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package dev.hotwire.turbo.views

import android.content.Context
import android.util.AttributeSet
import android.webkit.WebView
import androidx.core.view.children
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout

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

override fun canChildScrollUp(): Boolean {
val webView = children.firstOrNull() as? WebView
return webView?.scrollY ?: 0 > 0
val webView = children.firstOrNull() as? TurboWebView

return if (webView != null) {
webView.scrollY > 0 || webView.elementTouchIsScrollable
} else {
false
}
}

/**
Expand Down
5 changes: 3 additions & 2 deletions turbo/src/main/kotlin/dev/hotwire/turbo/views/TurboWebView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import android.util.AttributeSet
import android.webkit.WebView
import android.widget.FrameLayout
import android.widget.FrameLayout.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout.LayoutParams.WRAP_CONTENT
import androidx.webkit.WebViewCompat
import com.google.gson.GsonBuilder
import dev.hotwire.turbo.util.contentFromAsset
import dev.hotwire.turbo.util.runOnUiThread
import dev.hotwire.turbo.util.toJson
import dev.hotwire.turbo.visit.TurboVisitOptions
import com.google.gson.GsonBuilder

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

internal var elementTouchIsScrollable = false

private fun WebView.runJavascript(javascript: String, onComplete: (String?) -> Unit = {}) {
context.runOnUiThread {
evaluateJavascript(javascript) {
Expand Down

0 comments on commit 262fef1

Please sign in to comment.