1import { Controller } from "@hotwired/stimulus" 2 3export default class extends Controller { 4 static targets = ["indicator"] 5 static values = { threshold: { type: Number, default: 80 } } 6 7 connect() { 8 this.startY = 0 9 this.pulling = false 10 // touchmove must be manually bound (not via data-action) because we need { passive: false } 11 // to allow preventDefault() which stops native scroll during the pull gesture 12 this.element.addEventListener("touchmove", this.boundTouchmove = this.handleTouchmove.bind(this), { passive: false }) 13 } 14 15 disconnect() { 16 this.element.removeEventListener("touchmove", this.boundTouchmove) 17 } 18 19 touchstart(event) { 20 if (this.element.scrollTop <= 0) { 21 this.startY = event.touches[0].clientY 22 this.pulling = true 23 } 24 } 25 26 handleTouchmove(event) { 27 if (!this.pulling) return 28 const dy = event.touches[0].clientY - this.startY 29 if (dy < 0) { this.pulling = false; this.resetIndicator(); return } 30 31 const progress = Math.min(dy / this.thresholdValue, 1) 32 const dampedDy = dy * 0.4 33 this.indicatorTarget.style.transform = `translateY(${dampedDy}px)` 34 this.indicatorTarget.style.opacity = progress 35 this.indicatorTarget.classList.toggle("text-indigo-500", progress >= 1) 36 this.indicatorTarget.classList.toggle("text-gray-400", progress < 1) 37 38 if (dy > 10) event.preventDefault() 39 } 40 41 touchend() { 42 if (!this.pulling) return 43 const wasReady = parseFloat(this.indicatorTarget.style.opacity) >= 1 44 this.pulling = false 45 46 if (wasReady) { 47 this.reload() 48 } 49 this.resetIndicator() 50 } 51 52 reload() { 53 if (navigator.vibrate) navigator.vibrate(10) 54 this.element.querySelectorAll("turbo-frame[src]").forEach((frame) => { 55 const src = frame.getAttribute("src") 56 frame.removeAttribute("src") 57 requestAnimationFrame(() => frame.setAttribute("src", src)) 58 }) 59 } 60 61 resetIndicator() { 62 this.indicatorTarget.style.transition = "transform 200ms ease, opacity 200ms ease" 63 this.indicatorTarget.style.transform = "" 64 this.indicatorTarget.style.opacity = "0" 65 setTimeout(() => { this.indicatorTarget.style.transition = "" }, 200) 66 } 67}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.