PageSourceSearch

https://www.energymonitor.ai/wp-content/themes/goodlife-wp-B2B/assets/js/modules/GDMContentToggle.js

js energymonitor.ai collected 2026-09-24 08:45:11 UTC 4,574 bytes, 136 lines download raw bytes

1export default function GDMContentToggle({
2  rootSelector,
3  buttonSelector,
4  contentSelector,
5  activeClass = "is-active",
6  bodyClassPrefix = "gd-toggle-view-",
7  stateAttr = "data-active-key",
8  buttonKeyAttr = "data-toggle-key",
9  contentKeyAttr = "data-toggle-content-key",
10}) {
11  const roots = document.querySelectorAll(rootSelector);
12  if (!roots.length) return;
13
14  roots.forEach((root) => {
15    const buttons = Array.from(root.querySelectorAll(buttonSelector));
16    const contents = Array.from(document.querySelectorAll(contentSelector)).filter(
17      (item) => item.closest(rootSelector) === root || item.dataset.toggleGroup === root.id,
18    );
19
20    if (!buttons.length || !contents.length) return;
21
22    const contentKeys = contents
23      .map((item) => item.getAttribute(contentKeyAttr))
24      .filter(Boolean);
25
26    const buttonKeys = buttons
27      .map((item) => item.getAttribute(buttonKeyAttr))
28      .filter(Boolean);
29
30    const allowedKeys = Array.from(new Set([...contentKeys, ...buttonKeys]));
31    if (!allowedKeys.length) return;
32
33    const getConfiguredKey = () => {
34      const configured = root.getAttribute(stateAttr);
35      if (configured && allowedKeys.includes(configured)) return configured;
36
37      const activeButton = buttons.find((item) => item.classList.contains(activeClass));
38      const fromButton = activeButton && activeButton.getAttribute(buttonKeyAttr);
39      if (fromButton && allowedKeys.includes(fromButton)) return fromButton;
40
41      const activeContent = contents.find((item) => item.classList.contains(activeClass));
42      const fromContent = activeContent && activeContent.getAttribute(contentKeyAttr);
43      if (fromContent && allowedKeys.includes(fromContent)) return fromContent;
44
45      return allowedKeys[0];
46    };
47
48    const getContentByKey = (key) =>
49      contents.find((item) => item.getAttribute(contentKeyAttr) === key);
50
51    const getKeyFromHash = () => {
52      const hashId = window.location.hash.slice(1);
53      if (!hashId) return null;
54
55      const decodedHashId = decodeURIComponent(hashId);
56      const matchingContent = contents.find((item) => item.id === decodedHashId);
57      if (!matchingContent) return null;
58
59      const contentKey = matchingContent.getAttribute(contentKeyAttr);
60      return contentKey && allowedKeys.includes(contentKey) ? contentKey : null;
61    };
62
63    const defaultKey = getConfiguredKey();
64
65    const state = {
66      activeKey: getKeyFromHash() || defaultKey,
67    };
68
69    const setBodyClass = (key) => {
70      const classesToRemove = Array.from(document.body.classList).filter((className) =>
71        className.startsWith(bodyClassPrefix),
72      );
73
74      classesToRemove.forEach((className) => document.body.classList.remove(className));
75      document.body.classList.add(bodyClassPrefix + key);
76    };
77
78    const render = () => {
79      root.setAttribute(stateAttr, state.activeKey);
80
81      buttons.forEach((button) => {
82        const key = button.getAttribute(buttonKeyAttr);
83        const isActive = key === state.activeKey;
84
85        button.classList.toggle(activeClass, isActive);
86        button.setAttribute("aria-pressed", isActive ? "true" : "false");
87      });
88
89      contents.forEach((content) => {
90        const key = content.getAttribute(contentKeyAttr);
91        const isActive = key === state.activeKey;
92
93        content.classList.toggle(activeClass, isActive);
94        content.setAttribute("aria-hidden", isActive ? "false" : "true");
95      });
96
97      setBodyClass(state.activeKey);
98    };
99
100    const syncFromLocation = () => {
101      const locationKey = getKeyFromHash() || defaultKey;
102      if (!allowedKeys.includes(locationKey) || locationKey === state.activeKey) return;
103
104      state.activeKey = locationKey;
105      render();
106    };
107
108    const updateUrlHash = () => {
109      const activeContent = getContentByKey(state.activeKey);
110      if (!activeContent || !activeContent.id) return;
111
112      const nextHash = `#${activeContent.id}`;
113      if (window.location.hash === nextHash) return;
114
115      const url = new URL(window.location.href);
116      url.hash = activeContent.id;
117      window.history.pushState({}, "", url);
118    };
119
120    buttons.forEach((button) => {
121      button.addEventListener("click", () => {
122        const nextKey = button.getAttribute(buttonKeyAttr);
123        if (!nextKey || nextKey === state.activeKey) return;
124
125        state.activeKey = nextKey;
126        render();
127        updateUrlHash();
128      });
129    });
130
131    window.addEventListener("hashchange", syncFromLocation);
132    window.addEventListener("popstate", syncFromLocation);
133
134    render();
135  });
136}

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.