Add bridge page controller and helpers.

This commit is contained in:
Denis Švara
2026-01-06 15:54:10 +01:00
parent b84da94016
commit 177afe1a41
5 changed files with 164 additions and 2 deletions
-1
View File
@@ -2,7 +2,6 @@
import "@hotwired/turbo-rails"
import "@hotwired/hotwire-native-bridge"
import "initializers"
import "bridge/initializers"
import "controllers"
import "lexxy"
@@ -0,0 +1,131 @@
import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
import { BridgeElement } from "@hotwired/hotwire-native-bridge"
import { viewport } from "bridge/helpers/viewport"
import { nextFrame } from "helpers/timing_helpers"
export default class extends BridgeComponent {
static component = "page"
static targets = [ "header" ]
static values = { title: String }
async connect() {
super.connect()
this.notifyBridgeOfPageChange()
await nextFrame()
this.startObserver()
window.addEventListener("resize", this.windowResized)
window.addEventListener("turbo:submit-start", this.submitStart)
window.addEventListener("turbo:submit-end", this.submitEnd)
}
disconnect() {
super.disconnect()
this.stopObserver()
window.removeEventListener("resize", this.windowResized)
window.removeEventListener("turbo:submit-start", this.submitStart)
window.removeEventListener("turbo:submit-end", this.submitEnd)
}
receive(message) {
switch (message.event) {
case "change":
this.updateHeaderVisibility(message.data)
break
case "set-text-size":
this.setTextSize(message.data)
break
}
}
setTextSize(data) {
document.documentElement.dataset.textSize = data.textSize
}
updateHeaderVisibility(data) {
if (!this.hasHeaderTarget) return
const headerElement = new BridgeElement(this.headerTarget)
if (data.displayOnPlatform) {
headerElement?.showOnPlatform()
} else {
headerElement?.hideOnPlatform()
}
}
// Bridge
notifyBridgeOfPageChange() {
let headerElement = null
const data = {
title: this.title,
url: window.location.href
}
if (this.hasHeaderTarget) {
// Assume header visible by default until we get IntersectionObserver update
headerElement = new BridgeElement(this.headerTarget)
data.elementVisible = true
data.displayOnPlatform = headerElement.isDisplayedOnPlatform()
}
this.send("change", data, message => this.receive(message))
}
notifyBridgeOfVisibilityChange(visible) {
this.send("visibility", { title: this.title, elementVisible: visible })
}
notifyBridgeOfSubmitStart() {
this.send("submitStart")
}
notifyBridgeOfSubmitEnd() {
this.send("submitEnd")
}
// Intersection Observer
startObserver() {
if (!this.hasHeaderTarget) return
this.observer = new IntersectionObserver(([ entry ]) =>
this.notifyBridgeOfVisibilityChange(entry.isIntersecting),
{ rootMargin: `-${this.topOffset}px 0px 0px 0px` }
)
this.observer.observe(this.headerTarget)
this.previousTopOffset = this.topOffset
}
stopObserver() {
this.observer?.disconnect()
}
updateObserverIfNeeded() {
if (this.topOffset === this.previousTopOffset) return
this.stopObserver()
this.startObserver()
}
windowResized = () => {
this.updateObserverIfNeeded()
}
submitStart = () => {
this.notifyBridgeOfSubmitStart()
}
submitEnd = () => {
this.notifyBridgeOfSubmitEnd()
}
get title() {
return this.titleValue ? this.titleValue : document.title
}
get topOffset() {
return viewport.top
}
}
+19
View File
@@ -0,0 +1,19 @@
let top = 0
export const viewport = {
get top() {
return top
},
get height() {
return visualViewport.height
}
}
function update() {
requestAnimationFrame(() => {
top = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--safe-area-inset-top"))
})
}
visualViewport.addEventListener("resize", update)
update()
@@ -0,0 +1,13 @@
import { BridgeElement } from "@hotwired/hotwire-native-bridge"
BridgeElement.prototype.isDisplayedOnPlatform = function() {
return !this.hasClass("hide-on-native")
}
BridgeElement.prototype.showOnPlatform = function() {
this.element.classList.remove("hide-on-native")
}
BridgeElement.prototype.hideOnPlatform = function() {
this.element.classList.add("hide-on-native")
}
+1 -1
View File
@@ -1 +1 @@
// Bridge initializers live here.
import "bridge/initializers/bridge_element"