PageSourceSearch

https://huntwise.com/assets/controllers/interactive_accordion_controller-8266d470.js

js huntwise.com collected 2026-09-24 10:48:57 UTC 7,271 bytes, 252 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2import { enter, leave } from "lib/transition"
3
4export default class extends Controller {
5  static targets = ["item", "itemHeader", "itemContent", "contentInner", "expandIcon", "image", "imageContainer", "itemsContainer"]
6  static values = {
7    activeItem: String,
8    layoutReversed: Boolean
9  }
10
11  // Animation timing constants
12  static ANIMATION_DURATION = 500
13  static IMAGE_TRANSITION_DURATION = 700
14  static CONTENT_DELAY = 50
15  static COLLAPSE_DELAY = 100
16
17  connect() {
18    this.showActiveItem()
19  }
20
21  disconnect() {
22    // Clean up any ongoing animations
23    this.cancelAllAnimations()
24  }
25
26  showActiveItem() {
27    if (!this.activeItemValue) return
28
29    const activeItem = this.itemTargets.find(item => 
30      item.dataset.itemId === this.activeItemValue
31    )
32    
33    if (activeItem) {
34      this.initializeAllItems()
35    }
36  }
37
38  initializeAllItems() {
39    // Set up initial states for all items
40    this.itemTargets.forEach((item, index) => {
41      const isActive = item.dataset.itemId === this.activeItemValue
42      this.initializeItemState(item, index, isActive)
43    })
44    
45    // Set up initial image states
46    this.initializeImageStates()
47  }
48
49  initializeItemState(item, index, isActive) {
50    const header = this.itemHeaderTargets[index]
51    const content = this.itemContentTargets[index]
52    const contentInner = this.contentInnerTargets[index]
53    
54    // Update header styling
55    this.updateHeaderStyling(header, isActive)
56    
57    // Update content state
58    if (content && contentInner) {
59      this.updateContentState(content, contentInner, isActive)
60    }
61  }
62
63  updateHeaderStyling(header, isActive) {
64    if (isActive) {
65      header.classList.remove('bg-antler-white/30', 'dark:bg-black', 'text-black', 'dark:text-orange-400')
66      header.classList.add('bg-antler-white', 'dark:bg-black', 'inset-shadow', 'text-black', 'dark:text-orange-400')
67    } else {
68      header.classList.remove('bg-antler-white', 'dark:bg-black', 'inset-shadow', 'text-black', 'dark:text-orange-400')
69      header.classList.add('bg-antler-white/30', 'dark:bg-black', 'text-gray-500', 'dark:text-gray-200')
70    }
71  }
72
73  updateContentState(content, contentInner, isActive) {
74    if (isActive) {
75      content.style.maxHeight = 'none'
76      content.style.opacity = '1'
77      contentInner.style.transform = 'translateY(0)'
78    } else {
79      content.style.maxHeight = '0px'
80      content.style.opacity = '0'
81      contentInner.style.transform = 'translateY(-10px)'
82    }
83  }
84
85  initializeImageStates() {
86    this.imageTargets.forEach(image => {
87      const isActive = image.dataset.itemId === this.activeItemValue
88      
89      if (isActive) {
90        image.style.opacity = '1'
91        image.style.transform = 'scale(1)'
92        image.style.pointerEvents = 'auto'
93      } else {
94        image.style.opacity = '0'
95        image.style.transform = 'scale(0.95)'
96        image.style.pointerEvents = 'none'
97      }
98    })
99  }
100
101  toggleItem(event) {
102    const clickedItem = event.currentTarget.closest('[data-interactive-accordion-target="item"]')
103    const clickedItemId = clickedItem.dataset.itemId
104
105    // Prevent toggling the same item
106    if (this.activeItemValue === clickedItemId) {
107      return
108    }
109
110    this.deactivateCurrentItem()
111    this.activateItem(clickedItem)
112  }
113
114  activateItem(item, withTransitions = true) {
115    const itemId = item.dataset.itemId
116    this.activeItemValue = itemId
117
118    this.updateItemStyling(item, true)
119    this.showImage(itemId, withTransitions)
120
121    if (withTransitions) {
122      this.expandContent(item)
123    }
124  }
125
126  deactivateCurrentItem() {
127    const currentActiveItem = this.itemTargets.find(item => 
128      item.dataset.itemId === this.activeItemValue
129    )
130
131    if (currentActiveItem) {
132      this.updateItemStyling(currentActiveItem, false)
133      this.collapseContent(currentActiveItem)
134    }
135  }
136
137  updateItemStyling(item, isActive) {
138    const index = this.itemTargets.indexOf(item)
139    const header = this.itemHeaderTargets[index]
140    const expandIcon = this.expandIconTargets[index]
141
142    this.updateHeaderStyling(header, isActive)
143    this.updateIconRotation(expandIcon, isActive)
144
145    if (isActive) {
146      item.classList.add('bg-antler-white', 'inset-shadow', 'text-black')
147      item.classList.remove('bg-antler-white/30', 'text-gray-500')
148    } else {
149      item.classList.remove('bg-antler-white', 'inset-shadow', 'text-black')
150      item.classList.add('bg-antler-white/30', 'text-gray-500')
151    }
152
153  }
154
155  updateIconRotation(expandIcon, isActive) {
156    expandIcon.style.transform = isActive ? 'rotate(0deg)' : 'rotate(180deg)'
157  }
158
159  expandContent(item) {
160    const index = this.itemTargets.indexOf(item)
161    const content = this.itemContentTargets[index]
162    const contentInner = this.contentInnerTargets[index]
163    
164    if (!content) return
165
166    // Set initial state
167    content.style.maxHeight = '0px'
168    content.style.opacity = '1'
169    content.offsetHeight // Force reflow
170
171    // Animate to full height
172    const targetHeight = content.scrollHeight
173    content.style.maxHeight = `${targetHeight}px`
174    
175    // Animate inner content
176    if (contentInner) {
177      setTimeout(() => {
178        contentInner.style.transform = 'translateY(0)'
179      }, this.constructor.CONTENT_DELAY)
180    }
181    
182    // Clean up after animation
183    setTimeout(() => {
184      content.style.maxHeight = 'none'
185    }, this.constructor.ANIMATION_DURATION)
186  }
187
188  collapseContent(item) {
189    const index = this.itemTargets.indexOf(item)
190    const content = this.itemContentTargets[index]
191    const contentInner = this.contentInnerTargets[index]
192    
193    if (!content) return
194
195    const currentHeight = content.scrollHeight
196    content.style.maxHeight = `${currentHeight}px`
197    content.offsetHeight // Force reflow
198
199    // Animate inner content first
200    if (contentInner) {
201      contentInner.style.transform = 'translateY(-10px)'
202    }
203    
204    // Then collapse container
205    setTimeout(() => {
206      content.style.maxHeight = '0px'
207      content.style.opacity = '0'
208    }, this.constructor.COLLAPSE_DELAY)
209  }
210
211  showImage(itemId, withTransitions = true) {
212    const transitionStyle = withTransitions 
213      ? `opacity ${this.constructor.IMAGE_TRANSITION_DURATION}ms ease-in-out, transform ${this.constructor.IMAGE_TRANSITION_DURATION}ms ease-in-out`
214      : 'none'
215
216    // Hide all images
217    this.imageTargets.forEach(image => {
218      image.style.transition = transitionStyle
219      image.style.opacity = '0'
220      image.style.transform = 'scale(0.95)'
221      image.style.pointerEvents = 'none'
222    })
223
224    // Show target image
225    const targetImage = this.imageTargets.find(image => 
226      image.dataset.itemId === itemId
227    )
228
229    if (targetImage) {
230      targetImage.style.opacity = '1'
231      targetImage.style.transform = 'scale(1)'
232      targetImage.style.pointerEvents = 'auto'
233    }
234  }
235
236  // Public method to programmatically activate an item
237  activateItemById(itemId) {
238    const item = this.itemTargets.find(item => 
239      item.dataset.itemId === itemId
240    )
241    
242    if (item) {
243      this.activateItem(item)
244    }
245  }
246
247  // Cleanup method
248  cancelAllAnimations() {
249    // Cancel any ongoing timeouts or animations
250    // This is a placeholder for future cleanup needs
251  }
252} 

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.