https://littlebirdelectronics.com.au/assets/controllers/ecommerce/live_search_controller-b6a4b7b2.js
1import { Controller } from "@hotwired/stimulus" 2 3// Submits a form into a Turbo Frame while keeping focus on the search input 4// and advancing the browser URL so the search state is bookmarkable. 5export default class extends Controller { 6 static targets = ["input", "aiSearch"] 7 static values = { 8 delay: { type: Number, default: 300 }, 9 url: String 10 } 11 12 connect() { 13 this.timeout = null 14 this.submittedByForm = false 15 16 const frameId = this.element.dataset.turboFrame 17 if (frameId) { 18 this.frame = document.getElementById(frameId) 19 if (this.frame) { 20 this.boundFrameLoad = this.onFrameLoad.bind(this) 21 this.frame.addEventListener("turbo:frame-load", this.boundFrameLoad) 22 } 23 } 24 25 // Capture Enter key on the search input to flag as deliberate search 26 if (this.hasInputTarget) { 27 this.boundKeydown = this.onInputKeydown.bind(this) 28 this.inputTarget.addEventListener("keydown", this.boundKeydown) 29 } 30 } 31 32 disconnect() { 33 clearTimeout(this.timeout) 34 this.frame?.removeEventListener("turbo:frame-load", this.boundFrameLoad) 35 this.inputTarget?.removeEventListener("keydown", this.boundKeydown) 36 } 37 38 onInputKeydown(event) { 39 if (event.key === "Enter") { 40 clearTimeout(this.timeout) 41 this.setAiSearch("1") 42 this.submittedByForm = true 43 this.element.requestSubmit() 44 event.preventDefault() 45 } 46 } 47 48 // Debounced search â called on text input (no AI summary) 49 search() { 50 clearTimeout(this.timeout) 51 this.timeout = setTimeout(() => { 52 this.setAiSearch("0") 53 this.submittedByForm = true 54 this.element.requestSubmit() 55 }, this.delayValue) 56 } 57 58 // Immediate submit â called on sort/filter change 59 submit() { 60 clearTimeout(this.timeout) 61 this.setAiSearch("0") 62 this.submittedByForm = true 63 this.element.requestSubmit() 64 } 65 66 // Explicit search button tap (mobile) â trigger AI summary 67 triggerAiSearch() { 68 this.setAiSearch("1") 69 } 70 71 setAiSearch(value) { 72 if (this.hasAiSearchTarget) { 73 this.aiSearchTarget.value = value 74 } 75 } 76 77 onFrameLoad() { 78 if (this.submittedByForm) { 79 // Form-driven: build URL from form data and push to address bar 80 this.submittedByForm = false 81 const formData = new FormData(this.element) 82 const params = new URLSearchParams() 83 for (const [key, value] of formData) { 84 if (value && value !== "0" && value !== "featured" && key !== "ai") { 85 params.set(key, value) 86 } 87 } 88 const qs = params.toString() 89 const url = qs ? `${this.urlValue}?${qs}` : this.urlValue 90 history.replaceState({ ...history.state }, "", url) 91 this.syncMarkdownLinks(this.urlValue, qs) 92 } else { 93 // Link-driven (Clear/View All): sync the form inputs from the new URL 94 const src = this.frame?.src 95 if (src) { 96 const frameUrl = new URL(src, window.location.origin) 97 if (this.hasInputTarget) { 98 this.inputTarget.value = frameUrl.searchParams.get("q") || "" 99 } 100 history.replaceState({ ...history.state }, "", frameUrl.pathname + frameUrl.search) 101 const params = new URLSearchParams(frameUrl.search) 102 params.delete("ai") 103 this.syncMarkdownLinks(frameUrl.pathname, params.toString()) 104 } 105 } 106 } 107 108 // Keep the "Copy for LLM" button and "View Markdown" link pointing at the 109 // current search/filter state. The frame swap doesn't re-render them (they 110 // live outside the results frame), so update them as the URL advances â 111 // otherwise they'd copy/show the whole catalogue instead of the results. 112 syncMarkdownLinks(path, qs) { 113 const mdUrl = `${path}.md${qs ? `?${qs}` : ""}` 114 const copy = document.querySelector('[data-controller~="ecommerce--copy-markdown"]') 115 if (copy) { 116 copy.setAttribute("data-ecommerce--copy-markdown-url-value", mdUrl) 117 copy.querySelector('a[target="_blank"]')?.setAttribute("href", mdUrl) 118 } 119 } 120}
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.