PageSourceSearch

https://littlebirdelectronics.com.au/assets/controllers/ecommerce/live_search_controller-b6a4b7b2.js

js littlebirdelectronics.com.au collected 2026-09-25 14:10:46 UTC 3,972 bytes, 120 lines download raw bytes

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.