PageSourceSearch

https://powercoach.ai/assets/controllers/mobile_tabs_controller-8e6faafc.js

js powercoach.ai collected 2026-10-05 14:00:02 UTC 3,114 bytes, 90 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// Manages mobile tabbed dashboard layout.
4// Persists selected tab in sessionStorage and triggers chart re-renders
5// when tabs become visible via Bootstrap's shown.bs.tab event.
6export default class extends Controller {
7  static values = { gameId: Number }
8
9  connect() {
10    this.boundHandleTabShown = this.handleTabShown.bind(this)
11    this.boundResizePanes = this.resizeActivePane.bind(this)
12    this.element.addEventListener("shown.bs.tab", this.boundHandleTabShown)
13    window.addEventListener("resize", this.boundResizePanes)
14    this.restoreTab()
15    // Size the active pane — multiple attempts to catch tab transitions and async content loads
16    setTimeout(() => this.resizeActivePane(), 300)
17    setTimeout(() => this.resizeActivePane(), 800)
18  }
19
20  disconnect() {
21    this.element.removeEventListener("shown.bs.tab", this.boundHandleTabShown)
22    window.removeEventListener("resize", this.boundResizePanes)
23  }
24
25  get storageKey() {
26    return `pc-mobile-tab-${this.gameIdValue}`
27  }
28
29  restoreTab() {
30    const savedTab = sessionStorage.getItem(this.storageKey)
31    if (!savedTab) return
32
33    const tab = this.element.querySelector(`[data-bs-target="#${savedTab}"]`)
34    if (tab && !tab.classList.contains("active")) {
35      tab.click()
36    }
37  }
38
39  handleTabShown(event) {
40    const paneId = event.target.getAttribute("data-bs-target")?.replace("#", "")
41    if (paneId) {
42      sessionStorage.setItem(this.storageKey, paneId)
43    }
44
45    // Tab panes are siblings of the nav wrapper, so look up from document
46    const pane = document.getElementById(paneId)
47    if (pane) {
48      // Trigger resize on any Chart.js canvases in the newly visible pane
49      pane.querySelectorAll("canvas").forEach((canvas) => {
50        if (typeof Chart !== "undefined") {
51          const chartInstance = Chart.getChart?.(canvas)
52          if (chartInstance) {
53            chartInstance.resize()
54          }
55        }
56      })
57
58      // Dispatch a custom event so controllers can handle visibility changes
59      pane.dispatchEvent(new CustomEvent("tab:shown", { bubbles: true }))
60    }
61
62    this.resizeActivePane()
63    // Retry after transition fully settles to catch edge cases
64    setTimeout(() => this.resizeActivePane(), 200)
65  }
66
67  resizeActivePane() {
68    // Only constrain height on mobile — desktop shows all panes simultaneously
69    if (window.innerWidth >= 768) {
70      // Clear any previously set height if window was resized from mobile to desktop
71      this.paneContainer.querySelectorAll(".mobile-tab-pane").forEach(pane => {
72        pane.style.height = ""
73      })
74      return
75    }
76
77    const activePane = this.paneContainer.querySelector(".mobile-tab-pane.show")
78    if (!activePane) return
79
80    const top = activePane.getBoundingClientRect().top
81    activePane.style.height = `${window.innerHeight - top}px`
82  }
83
84  get paneContainer() {
85    // Tab panes span both the main column and the sidebar column,
86    // so search from the nearest row ancestor that contains both.
87    return this.element.closest(".row") || this.element.parentElement
88  }
89
90}

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.