PageSourceSearch

https://haiphen.io/components/sidebar/sidebar-nav.js

js haiphen.io collected 2026-10-01 12:01:46 UTC 5,704 bytes, 166 lines download raw bytes

1/* docs/components/sidebar/sidebar-nav.js
2 * Sidebar deep-link navigation:
3 * - ensure section is visible (via showSection)
4 * - scroll to a target element inside the section
5 * - flash/outline the emphasized element (the "line" the user lands on)
6 */
7(function () {
8  'use strict';
9
10  const LOG_PREFIX = '[sidebar-nav]';
11
12  function getHeaderHeightPx() {
13    const header =
14      document.querySelector('.site-header') ||
15      document.querySelector('#site-header .site-header') ||
16      document.querySelector('nav.navbar');
17
18    const cssVar = getComputedStyle(document.documentElement)
19      .getPropertyValue('--header-h')
20      .trim();
21
22    const fallback = Number.parseInt(cssVar || '70', 10) || 70;
23    const measured = header?.getBoundingClientRect().height || 0;
24
25    return Math.max(fallback, measured || 0);
26  }
27
28  function scrollToWithHeaderOffset(targetEl, extra = 12) {
29    if (!targetEl) return;
30
31    const header =
32      document.querySelector('.site-header') ||
33      document.querySelector('#site-header .site-header') ||
34      document.querySelector('nav.navbar');
35
36    const headerH =
37      header?.getBoundingClientRect().height ||
38      parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-h') || '70', 10) ||
39      70;
40
41    const bannerH =
42      parseInt(getComputedStyle(document.documentElement).getPropertyValue('--cohort-banner-offset') || '0', 10) ||
43      0;
44
45    const topChrome = headerH + bannerH;
46
47    const y = window.scrollY + targetEl.getBoundingClientRect().top - topChrome - extra;
48    window.scrollTo({ top: Math.max(0, y), behavior: 'smooth' });
49  }
50  function getTopChromeHeightPx() {
51    const header =
52      document.querySelector('.site-header') ||
53      document.querySelector('#site-header .site-header') ||
54      document.querySelector('nav.navbar');
55
56    const cssHeaderVar = getComputedStyle(document.documentElement)
57      .getPropertyValue('--header-h')
58      .trim();
59
60    const headerFallback = Number.parseInt(cssHeaderVar || '70', 10) || 70;
61    const headerMeasured = header?.getBoundingClientRect().height || 0;
62    const headerH = Math.max(headerFallback, headerMeasured || 0);
63
64    const bannerVar = getComputedStyle(document.documentElement)
65      .getPropertyValue('--cohort-banner-offset')
66      .trim();
67
68    const bannerH = Number.parseInt(bannerVar || '0', 10) || 0;
69
70    return headerH + bannerH;
71  }
72  function openAnyDetailsAncestors(el) {
73    // If target is inside <details>, open them so the user actually sees it.
74    let node = el?.parentElement;
75    while (node) {
76      if (node.tagName === 'DETAILS' && !node.open) node.open = true;
77      node = node.parentElement;
78    }
79  }
80
81  function flashEmphasis(el, ms = 1600) {
82    if (!el) return;
83
84    openAnyDetailsAncestors(el);
85
86    // Remove then re-add so repeated clicks retrigger animation reliably
87    el.classList.remove('haiphen-nav-flash');
88    // force reflow
89    void el.offsetWidth;
90    el.classList.add('haiphen-nav-flash');
91
92    window.setTimeout(() => {
93      el.classList.remove('haiphen-nav-flash');
94    }, ms);
95  }
96
97  function waitForElement(root, selector, timeoutMs = 2500) {
98    return new Promise((resolve) => {
99      if (!root) return resolve(null);
100      const existing = root.querySelector(selector);
101      if (existing) return resolve(existing);
102
103      const obs = new MutationObserver(() => {
104        const found = root.querySelector(selector);
105        if (found) {
106          obs.disconnect();
107          resolve(found);
108        }
109      });
110
111      obs.observe(root, { childList: true, subtree: true });
112
113      window.setTimeout(() => {
114        obs.disconnect();
115        resolve(root.querySelector(selector) || null);
116      }, timeoutMs);
117    });
118  }
119
120  async function navigate({ section, target, emphasize, extraOffset = 12 }) {
121    const contentRoot = document.getElementById('content-widget');
122
123    if (!section || typeof window.showSection !== 'function') {
124      console.warn(`${LOG_PREFIX} missing section or showSection()`, { section });
125      return;
126    }
127
128    // 2) Wait for target to exist (content is injected dynamically)
129    // Prefer emphasize selector; fallback to target selector; fallback to contentRoot.
130    const pickSelector = emphasize || target;
131
132    // 1) Ensure the section is rendered + URL updated
133    // Use navigateTo for path-based URLs; fall back to showSection
134    if (typeof window.navigateTo === 'function' && !pickSelector) {
135      window.navigateTo(section);
136    } else {
137      window.showSection(section, pickSelector ? { suppressScroll: true } : undefined);
138      // Update URL path even when using showSection for scroll control
139      if (typeof window.navigateTo === 'function') {
140        const SECTION_PATH = { Trades: '/signals', Services: '/services', OnePager: '/
140platform', FAQ: '/faq', Contact: '/contact', Docs: '/docs', GettingStarted: '/getting-started', Profile: '/profile' };
141        const p = SECTION_PATH[section];
142        if (p) history.replaceState({ section: section }, '', p);
143      }
144    }
145    let el = null;
146
147    if (pickSelector) {
148      el = await waitForElement(contentRoot, pickSelector);
149    }
150
151    const fallbackTarget = el || (target ? contentRoot?.querySelector(target) : null) || contentRoot;
152    if (!fallbackTarget) return;
153
154    // 3) Scroll to it with header offset
155    // Delay one frame so layout settles after showSection's own scroll
156    requestAnimationFrame(() => {
157      scrollToWithHeaderOffset(fallbackTarget, extraOffset);
158      // 4) Flash the emphasized "line"
159      flashEmphasis(el || fallbackTarget);
160    });
161  }
162
163  // Expose on your existing namespace
164  window.HAIPHEN = window.HAIPHEN || {};
165  window.HAIPHEN.sidebarNavigate = navigate;
166})();

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.