forked from markteekman/accessible-astro-components
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Accordion.astro
133 lines (105 loc) · 4.56 KB
/
Accordion.astro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
---
import type { AccordionItem } from '.'
interface Props {
children: AccordionItem | AccordionItem[]
}
---
<div class="accordion">
<ul class="accordion__wrapper">
<slot />
</ul>
</div>
<script type="module">
// variables
const accordionItems = [...document.querySelectorAll('.accordion__item')]
// functions
const getPanelHeight = (accordionItem) => {
const accordionInner = accordionItem.querySelector('.panel__inner')
return accordionInner.getBoundingClientRect().height
}
const openAccordionItem = (accordionItem) => {
const accordionItemHeader = accordionItem.querySelector('.accordion__header')
const accordionToggleIndicator = accordionItem.querySelector('.header__toggle-indicator')
const accordionPanel = accordionItem.querySelector('.accordion__panel')
accordionPanel.style.height = `${getPanelHeight(accordionItem)}px`
accordionItem.classList.add('is-active')
accordionItemHeader.setAttribute('aria-expanded', true)
accordionToggleIndicator.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="header__toggle-indicator" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708l6-6z"/>
</svg>`
}
const closeAccordionItem = (accordionItem) => {
const accordionItemHeader = accordionItem.querySelector('.accordion__header')
const accordionToggleIndicator = accordionItem.querySelector('.header__toggle-indicator')
const accordionPanel = accordionItem.querySelector('.accordion__panel')
accordionItem.classList.remove('is-active')
accordionPanel.style.height = 0
accordionItemHeader.focus()
accordionItemHeader.setAttribute('aria-expanded', false)
accordionToggleIndicator.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="header__toggle-indicator" viewBox="0 0 16 16">
<path
fill-rule="evenodd"
d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
></path>
</svg>`
}
const isAccordionOpen = (accordionItem) => {
return accordionItem.classList.contains('is-active')
}
function toggleAccordionItem() {
const accordionItem = event.target.closest('.accordion__item')
if (!accordionItem || event.target.closest('.accordion__panel')) return
isAccordionOpen(accordionItem) ? closeAccordionItem(accordionItem) : openAccordionItem(accordionItem)
}
function recalculateHeight() {
const toggledAccordionItems = accordionItems.filter((element) => element.classList.contains('is-active'))
toggledAccordionItems.forEach((toggledAccordionItem) => {
const accordionPanel = toggledAccordionItem.querySelector('.accordion__panel')
accordionPanel.style.height = `${getPanelHeight(toggledAccordionItem)}px`
})
}
// execution
accordionItems.forEach((item, index) => {
const accordionItemHeader = item.querySelector('.accordion__header')
const accordionItemPanel = item.querySelector('.accordion__panel')
accordionItemHeader.setAttribute('id', `accordion-item${index + 1}`)
accordionItemPanel.setAttribute('id', `item${index + 1}`)
accordionItemHeader.setAttribute('aria-controls', `item${index + 1}`)
accordionItemPanel.setAttribute('aria-labelledby', `accordion-item${index + 1}`)
})
document.addEventListener('keydown', (event) => {
const accordionItem = event.target.closest('.accordion__item')
if (event.key !== 'Escape') return
if (!accordionItem) return
if (isAccordionOpen(accordionItem)) {
closeAccordionItem(accordionItem)
}
})
document.addEventListener('keydown', (event) => {
if (!event.target.closest('.accordion__header')) return
const accordionWrapper = event.target.closest('.accordion__wrapper')
const accordionItem = event.target.closest('.accordion__item')
const accordionItems = [...accordionWrapper.querySelectorAll('.accordion__item')]
const index = accordionItems.findIndex((element) => element === accordionItem)
const { key } = event
let targetItem
if (key === 'ArrowDown') {
targetItem = accordionItems[index + 1]
}
if (key === 'ArrowUp') {
targetItem = accordionItems[index - 1]
}
if (targetItem) {
event.preventDefault()
targetItem.querySelector('.accordion__header').focus()
}
})
window.toggleAccordionItem = toggleAccordionItem
window.onresize = recalculateHeight
</script>
<style is:global>
.accordion__wrapper {
list-style: none;
padding: 0;
}
</style>