PageSourceSearch

https://columnwise-operations-dplyr.netlify.app/index_files/panelset-0.2.1/panelset.js

js columnwise-operations-dplyr.netlify.app collected 2026-10-03 10:35:37 UTC 9,271 bytes, 270 lines download raw bytes

1/* global slideshow */
2(function () {
3  const ready = function (fn) {
4    /* MIT License Copyright (c) 2016 Nuclei */
5    /* https://github.com/nuclei/readyjs */
6    const completed = () => {
7      document.removeEventListener('DOMContentLoaded', completed)
8      window.removeEventListener('load', completed)
9      fn()
10    }
11    if (document.readyState !== 'loading') {
12      setTimeout(fn)
13    } else {
14      document.addEventListener('DOMContentLoaded', completed)
15      window.addEventListener('load', completed)
16    }
17  }
18
19  ready(function () {
20    [...document.querySelectorAll('.panel-name')]
21      .map(el => el.textContent.trim())
22
23    const panelIds = {}
24
25    const uniquePanelId = (name) => {
26      name = encodeURIComponent(name.toLowerCase().replace(/[\s]/g, '-'))
27      if (Object.keys(panelIds).includes(name)) {
28        name += ++panelIds[name]
29      } else {
30        panelIds[name] = 1
31      }
32      return name
33    }
34
35    const identifyPanelName = (item) => {
36      let name = 'Panel'
37
38      // In R Markdown when header-attrs.js is present, we may have found a
39      // section header but the class attributes won't be duplicated on the <hX> tag
40      if (
41        (item.tagName === 'SECTION' || item.classList.contains('section')) &&
42        /^H[1-6]/.test(item.children[0].tagName)
43      ) {
44        name = item.children[0].textContent
45        item.classList.remove('panel-name')
46        item.removeChild(item.children[0])
47        return name
48      }
49
50      const nameDiv = item.querySelector('.panel-name')
51      if (!nameDiv) return name
52
53      // In remarkjs the .panel-name span might be in a paragraph tag
54      // and if the <p> is empty, we'll remove it
55      if (
56        nameDiv.tagName === 'SPAN' &&
57        nameDiv.parentNode.tagName === 'P' &&
58        nameDiv.textContent === nameDiv.parentNode.textContent
59      ) {
60        name = nameDiv.textContent
61        item.removeChild(nameDiv.parentNode)
62        return name
63      }
64
65      // If none of the above, remove the nameDiv and return the name
66      name = nameDiv.textContent
67      nameDiv.parentNode.removeChild(nameDiv)
68      return name
69    }
70
71    const processPanelItem = (item) => {
72      const name = identifyPanelName(item)
73      return { name, content: item.children, id: uniquePanelId(name) }
74    }
75
76    const getCurrentPanelFromUrl = (panelset) => {
77      const params = new URLSearchParams(window.location.search)
78      return params.get(panelset)
79    }
80
81    const reflowPanelSet = (panels, idx) => {
82      const res = document.createElement('div')
83      res.className = 'panelset'
84      res.id = 'panelset' + (idx > 0 ? idx : '')
85      const panelSelected = getCurrentPanelFromUrl(res.id)
86
87      // create header row
88      const headerRow = document.createElement('ul')
89      headerRow.className = 'panel-tabs'
90      headerRow.setAttribute('role', 'tablist')
91      panels
92        .map((p, idx) => {
93          const panelHeaderItem = document.createElement('li')
94          panelHeaderItem.className = 'panel-tab'
95          panelHeaderItem.setAttribute('role', 'tab')
96          const thisPanelIsActive = panelSelected ? panelSelected === p.id : idx === 0
97          if (thisPanelIsActive) {
98            panelHeaderItem.classList.add('panel-tab-active')
99            panelHeaderItem.setAttribute('aria-selected', true)
100          }
101          panelHeaderItem.tabIndex = 0
102          panelHeaderItem.id = res.id + '_' + p.id // #panelsetid_panelid
103
104          const panelHeaderLink = document.createElement('a')
105          panelHeaderLink.href = '?' + res.id + '=' + p.id + '#' + panelHeaderItem.id
106          panelHeaderLink.setAttribute('onclick', 'return false;')
107          panelHeaderLink.tabIndex = -1 // list item is tabable, not link
108          panelHeaderLink.innerHTML = p.name
109          panelHeaderLink.setAttribute('aria-controls', p.id)
110
111          panelHeaderItem.appendChild(panelHeaderLink)
112          return panelHeaderItem
113        })
114        .forEach(el => headerRow.appendChild(el))
115
116      res.appendChild(headerRow)
117
118      panels
119        .map((p, idx) => {
120          const panelContent = document.createElement('section')
121          panelContent.className = 'panel'
122          panelContent.setAttribute('role', 'tabpanel')
123          const thisPanelIsActive = panelSelected ? panelSelected === p.id : idx === 0
124          panelContent.classList.toggle('panel-active', thisPanelIsActive)
125          panelContent.id = p.id
126          panelContent.setAttribute('aria-labelledby', p.id)
127          Array.from(p.content).forEach(el => panelContent.appendChild(el))
128          return panelContent
129        })
130        .forEach(el => res.appendChild(el))
131
132      return res
133    }
134
135    const updateUrl = (panelset, panel) => {
136      let params = new URLSearchParams(window.location.search)
137      if (panel) {
138        params.set(panelset, panel)
139      } else {
140        params.delete(panelset)
141      }
142      params = params.toString() ? ('?' + params.toString()) : ''
143      const { pathname, hash } = window.location
144      const uri = pathname + params + hash
145      window.history.replaceState(uri, '', uri)
146    }
147
148    const togglePanel = (clicked) => {
149      if (clicked.nodeName.toUpperCase() === 'A') {
150        clicked = clicked.parentElement
151      }
152      if (!clicked.classList.contains('panel-tab')) return
153      if (clicked.classList.contains('panel-tab-active')) return
154
155      const tabs = clicked.parentNode
156        .querySelectorAll('.panel-tab')
157      const panels = clicked.parentNode.parentNode
158        .querySelectorAll('.panel')
159      const panelTabClicked = clicked.children[0].getAttribute('aria-controls')
160      const panelClicked = clicked.parentNode.parentNode.id
161
162      Array.from(tabs)
163        .forEach(t => {
164          t.classList.remove('panel-tab-active')
165          t.removeAttribute('aria-selected')
166        })
167      Array.from(panels)
168        .forEach(p => {
169          const active = p.id === panelTabClicked
170          p.classList.toggle('panel-active', active)
171          // make inactive panels inaccessible by keyboard navigation
172          if (active) {
173            p.removeAttribute('tabIndex')
174            p.removeAttribute('aria-hidden')
175          } else {
176            p.setAttribute('tabIndex', -1)
177            p.setAttribute('aria-hidden', true)
178          }
179        })
180
181      clicked.classList.add('panel-tab-active')
182      clicked.setAttribute('aria-selected', true)
183
184      // update query string
185      updateUrl(panelClicked, panelTabClicked)
186    }
187
188    const initPanelSet = (panelset, idx) => {
189      const panels = Array.from(panelset.querySelectorAll('.panel'))
190      if (!panels.length) return
191
192      const contents = panels.map(processPanelItem)
193      const newPanelSet = reflowPanelSet(contents, idx)
194      panelset.parentNode.insertBefore(newPanelSet, panelset)
195      panelset.parentNode.removeChild(panelset)
196
197      // click and touch events
198      const panelTabs = newPanelSet.querySelector('.panel-tabs');
199      ['click', 'touchend'].forEach(eventType => {
200        panelTabs.addEventListener(eventType, function (ev) {
201          togglePanel(ev.target)
202          ev.stopPropagation()
203        })
204      })
205      panelTabs.addEventListener('touchmove', function (ev) {
206        ev.preventDefault()
207      })
208
209      // key events
210      newPanelSet
211        .querySelector('.panel-tabs')
212        .addEventListener('keydown', (ev) => {
213          const self = ev.currentTarget.querySelector('.panel-tab-active')
214          if (ev.code === 'Space' || ev.code === 'Enter') {
215            togglePanel(ev.target)
216            ev.stopPropagation()
217          } else if (ev.code === 'ArrowLeft' && self.previousSibling) {
218            togglePanel(self.previousSibling)
219            self.previousSibling.focus()
220            ev.stopPropagation()
221          } else if (ev.code === 'ArrowRight' && self.nextSibling) {
222            togglePanel(self.nextSibling)
223            self.nextSibling.focus()
224            ev.stopPropagation()
225          }
226        })
227
228      return panels
229    }
230
231    // initialize panels
232    Array.from(document.querySelectorAll('.panelset')).map(initPanelSet)
233
234    if (typeof slideshow !== 'undefined') {
235      const getVisibleActivePanelInfo = () => {
236        const slidePanels = document.querySelectorAll('.remark-visible .panel-tab-active')
237
238        if (!slidePanels.length) return null
239
240        return slidePanels.map(panel => {
241          return {
242            panel,
243            panelId: panel.children[0].getAttribute('aria-controls'),
244            panelSetId: panel.parentNode.parentNode.id
245          }
246        })
247      }
248
249      slideshow.on('hideSlide', slide => {
250        // clear focus if we had a panel-tab selected
251        document.activeElement.blur()
252
253        // clear search query for panelsets in current slide
254        document.querySelectorAll('.remark-visible .panelset')
255          .forEach(ps => updateUrl(ps.id, null))
256      })
257
258      slideshow.on('afterShowSlide', slide => {
259        const slidePanels = getVisibleActivePanelInfo()
260
261        if (slidePanels) {
262          // only first panel gets focus
263          slidePanels[0].panel.focus()
264          // but still update the url to reflect all active panels
265          slidePanels.forEach(({ panelId, panelSetId }) => updateUrl(panelSetId, panelId))
266        }
267      })
268    }
269  })
270})()

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.