@graupl/graupl 1.0.0-alpha.9 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/.github/workflows/codeql-analysis.yml +3 -3
  2. package/.husky/commit-msg +0 -1
  3. package/.husky/pre-commit +0 -1
  4. package/CHANGELOG.md +160 -0
  5. package/build.js +7 -0
  6. package/dist/css/base/button.css +2 -0
  7. package/dist/css/base/button.css.map +1 -0
  8. package/dist/css/base/form.css +2 -0
  9. package/dist/css/base/form.css.map +1 -0
  10. package/dist/css/base/link.css +2 -0
  11. package/dist/css/base/link.css.map +1 -0
  12. package/dist/css/base/table.css +2 -0
  13. package/dist/css/base/table.css.map +1 -0
  14. package/dist/css/base.css +2 -0
  15. package/dist/css/base.css.map +1 -0
  16. package/dist/css/component/accordion.css +2 -0
  17. package/dist/css/component/accordion.css.map +1 -0
  18. package/dist/css/component/alert.css +2 -0
  19. package/dist/css/component/alert.css.map +1 -0
  20. package/dist/css/component/card.css +2 -0
  21. package/dist/css/component/card.css.map +1 -0
  22. package/dist/css/component/carousel.css +2 -0
  23. package/dist/css/component/carousel.css.map +1 -0
  24. package/dist/css/component/input-group.css +2 -0
  25. package/dist/css/component/input-group.css.map +1 -0
  26. package/dist/css/component/menu.css +2 -0
  27. package/dist/css/component/menu.css.map +1 -0
  28. package/dist/css/component/navigation.css +2 -0
  29. package/dist/css/component/navigation.css.map +1 -0
  30. package/dist/css/component.css +2 -0
  31. package/dist/css/component.css.map +1 -0
  32. package/dist/css/graupl.css +2 -0
  33. package/dist/css/graupl.css.map +1 -0
  34. package/dist/css/init.css +2 -0
  35. package/dist/css/init.css.map +1 -0
  36. package/dist/css/layout/columns.css +2 -0
  37. package/dist/css/layout/columns.css.map +1 -0
  38. package/dist/css/layout/container.css +2 -0
  39. package/dist/css/layout/container.css.map +1 -0
  40. package/dist/css/layout/flex-columns.css +2 -0
  41. package/dist/css/layout/flex-columns.css.map +1 -0
  42. package/dist/css/layout.css +2 -0
  43. package/dist/css/layout.css.map +1 -0
  44. package/dist/css/normalize.css +2 -0
  45. package/dist/css/normalize.css.map +1 -0
  46. package/dist/css/state/focus.css +2 -0
  47. package/dist/css/state/focus.css.map +1 -0
  48. package/dist/css/state.css +2 -0
  49. package/dist/css/state.css.map +1 -0
  50. package/dist/css/theme/color.css +2 -0
  51. package/dist/css/theme/color.css.map +1 -0
  52. package/dist/css/theme/typography.css +2 -0
  53. package/dist/css/theme/typography.css.map +1 -0
  54. package/dist/css/theme.css +2 -0
  55. package/dist/css/theme.css.map +1 -0
  56. package/dist/css/utilities/alignment.css +2 -0
  57. package/dist/css/utilities/alignment.css.map +1 -0
  58. package/dist/css/utilities/color.css +2 -0
  59. package/dist/css/utilities/color.css.map +1 -0
  60. package/dist/css/utilities/display.css +2 -0
  61. package/dist/css/utilities/display.css.map +1 -0
  62. package/dist/css/utilities/flex.css +2 -0
  63. package/dist/css/utilities/flex.css.map +1 -0
  64. package/dist/css/utilities/height.css +2 -0
  65. package/dist/css/utilities/height.css.map +1 -0
  66. package/dist/css/utilities/inset.css +2 -0
  67. package/dist/css/utilities/inset.css.map +1 -0
  68. package/dist/css/utilities/justification.css +2 -0
  69. package/dist/css/utilities/justification.css.map +1 -0
  70. package/dist/css/utilities/list.css +2 -0
  71. package/dist/css/utilities/list.css.map +1 -0
  72. package/dist/css/utilities/order.css +2 -0
  73. package/dist/css/utilities/order.css.map +1 -0
  74. package/dist/css/utilities/postion.css +2 -0
  75. package/dist/css/utilities/postion.css.map +1 -0
  76. package/dist/css/utilities/ratio.css +2 -0
  77. package/dist/css/utilities/ratio.css.map +1 -0
  78. package/dist/css/utilities/spacing.css +2 -0
  79. package/dist/css/utilities/spacing.css.map +1 -0
  80. package/dist/css/utilities/typography.css +2 -0
  81. package/dist/css/utilities/typography.css.map +1 -0
  82. package/dist/css/utilities/visibility.css +2 -0
  83. package/dist/css/utilities/visibility.css.map +1 -0
  84. package/dist/css/utilities/width.css +2 -0
  85. package/dist/css/utilities/width.css.map +1 -0
  86. package/dist/css/utilities.css +2 -0
  87. package/dist/css/utilities.css.map +1 -0
  88. package/dist/js/component/accordion.cjs.js +3 -0
  89. package/dist/js/component/accordion.esm.js +1289 -0
  90. package/dist/js/component/accordion.iife.js +3 -0
  91. package/dist/js/component/alert.cjs.js +3 -0
  92. package/dist/js/component/alert.esm.js +529 -0
  93. package/dist/js/component/alert.iife.js +3 -0
  94. package/dist/js/component/carousel.cjs.js +3 -0
  95. package/dist/js/component/carousel.esm.js +1110 -0
  96. package/dist/js/component/carousel.iife.js +3 -0
  97. package/dist/js/graupl.cjs.js +5 -0
  98. package/dist/js/graupl.esm.js +1462 -0
  99. package/dist/js/graupl.iife.js +5 -0
  100. package/docs/.vitepress/config.js +39 -12
  101. package/docs/components/alert.md +130 -0
  102. package/docs/components/button.md +84 -0
  103. package/docs/components/card.md +369 -0
  104. package/docs/components/index.md +1 -0
  105. package/docs/components/inputgroup.md +159 -0
  106. package/docs/components/menu.md +326 -0
  107. package/docs/components/navigation.md +158 -0
  108. package/docs/content.md +237 -0
  109. package/docs/defaults.md +121 -0
  110. package/docs/forms.md +79 -0
  111. package/docs/functions.md +9 -0
  112. package/docs/getting-started.md +1 -0
  113. package/docs/index.md +1 -7
  114. package/docs/introduction.md +22 -2
  115. package/docs/layout.md +200 -0
  116. package/docs/mixins.md +47 -0
  117. package/docs/state.md +67 -0
  118. package/docs/theme.md +258 -0
  119. package/docs/utilities.md +357 -0
  120. package/eslint.config.js +1 -0
  121. package/index.html +816 -327
  122. package/index.js +12 -0
  123. package/package.json +31 -10
  124. package/scss/base/button.scss +3 -0
  125. package/scss/base/form.scss +1 -1
  126. package/scss/base/link.scss +1 -1
  127. package/scss/base/table.scss +1 -1
  128. package/scss/base.scss +1 -1
  129. package/scss/component/accordion.scss +3 -0
  130. package/scss/component/alert.scss +3 -0
  131. package/scss/component/card.scss +3 -0
  132. package/scss/component/carousel.scss +3 -0
  133. package/scss/component/input-group.scss +1 -1
  134. package/scss/component/menu.scss +3 -0
  135. package/scss/component/navigation.scss +3 -0
  136. package/scss/component.scss +1 -1
  137. package/scss/graupl.scss +1 -3
  138. package/scss/init.scss +3 -0
  139. package/scss/layout/columns.scss +1 -1
  140. package/scss/layout/container.scss +1 -1
  141. package/scss/layout/flex-columns.scss +3 -0
  142. package/scss/layout.scss +1 -1
  143. package/scss/normalize.scss +3 -0
  144. package/scss/state/focus.scss +1 -1
  145. package/scss/state.scss +1 -1
  146. package/scss/theme/color.scss +1 -1
  147. package/scss/theme/typography.scss +1 -1
  148. package/scss/theme.scss +1 -1
  149. package/scss/utilities/alignment.scss +3 -0
  150. package/scss/utilities/color.scss +3 -0
  151. package/scss/utilities/display.scss +3 -0
  152. package/scss/utilities/flex.scss +3 -0
  153. package/scss/utilities/height.scss +3 -0
  154. package/scss/utilities/inset.scss +3 -0
  155. package/scss/utilities/justification.scss +3 -0
  156. package/scss/utilities/list.scss +3 -0
  157. package/scss/utilities/order.scss +3 -0
  158. package/scss/utilities/postion.scss +3 -0
  159. package/scss/utilities/ratio.scss +3 -0
  160. package/scss/utilities/spacing.scss +1 -1
  161. package/scss/utilities/typography.scss +1 -1
  162. package/scss/utilities/visibility.scss +3 -0
  163. package/scss/utilities/width.scss +3 -0
  164. package/scss/utilities.scss +1 -1
  165. package/src/js/accordion/Accordion.js +1163 -0
  166. package/src/js/accordion/AccordionItem.js +496 -0
  167. package/src/js/accordion/index.js +10 -0
  168. package/src/js/alert/Alert.js +581 -0
  169. package/src/js/alert/index.js +11 -0
  170. package/src/js/carousel/Carousel.js +1427 -0
  171. package/src/js/carousel/index.js +10 -0
  172. package/src/js/domHelpers.js +37 -0
  173. package/src/js/eventHandlers.js +39 -0
  174. package/src/js/navigation/index.js +36 -0
  175. package/src/js/storage.js +106 -0
  176. package/src/js/validate.js +225 -0
  177. package/src/scss/_defaults.scss +75 -6
  178. package/src/scss/_index.scss +4 -3
  179. package/src/scss/_init.scss +2 -2
  180. package/src/scss/_normalize.scss +197 -0
  181. package/src/scss/_variables.scss +17 -17
  182. package/src/scss/base/_index.scss +2 -1
  183. package/src/scss/base/button/_defaults.scss +60 -0
  184. package/src/scss/base/button/_index.scss +107 -0
  185. package/src/scss/base/button/_mixins.scss +166 -0
  186. package/src/scss/base/button/_variables.scss +176 -0
  187. package/src/scss/base/form/_defaults.scss +14 -4
  188. package/src/scss/base/form/_index.scss +23 -20
  189. package/src/scss/base/form/_variables.scss +46 -37
  190. package/src/scss/base/link/_defaults.scss +48 -5
  191. package/src/scss/base/link/_index.scss +111 -10
  192. package/src/scss/base/link/_variables.scss +239 -9
  193. package/src/scss/base/table/_defaults.scss +49 -4
  194. package/src/scss/base/table/_index.scss +102 -8
  195. package/src/scss/base/table/_variables.scss +87 -17
  196. package/src/scss/component/_index.scss +7 -3
  197. package/src/scss/component/accordion/_defaults.scss +40 -0
  198. package/src/scss/component/accordion/_index.scss +180 -0
  199. package/src/scss/component/accordion/_variables.scss +316 -0
  200. package/src/scss/component/alert/_defaults.scss +49 -0
  201. package/src/scss/component/alert/_index.scss +118 -0
  202. package/src/scss/component/alert/_variables.scss +170 -0
  203. package/src/scss/component/card/_defaults.scss +32 -0
  204. package/src/scss/component/card/_index.scss +178 -0
  205. package/src/scss/component/card/_variables.scss +186 -0
  206. package/src/scss/component/carousel/_defaults.scss +43 -0
  207. package/src/scss/component/carousel/_index.scss +188 -0
  208. package/src/scss/component/carousel/_variables.scss +104 -0
  209. package/src/scss/component/input-group/_defaults.scss +11 -4
  210. package/src/scss/component/input-group/_index.scss +13 -11
  211. package/src/scss/component/input-group/_variables.scss +16 -13
  212. package/src/scss/component/menu/_defaults.scss +66 -0
  213. package/src/scss/component/menu/_index.scss +305 -0
  214. package/src/scss/component/menu/_variables.scss +500 -0
  215. package/src/scss/component/navigation/_defaults.scss +29 -0
  216. package/src/scss/component/navigation/_index.scss +189 -0
  217. package/src/scss/component/navigation/_variables.scss +237 -0
  218. package/src/scss/functions/_important.scss +2 -0
  219. package/src/scss/functions/_screen.scss +30 -0
  220. package/src/scss/functions/_theme.scss +28 -7
  221. package/src/scss/layout/_index.scss +2 -1
  222. package/src/scss/layout/columns/_defaults.scss +12 -4
  223. package/src/scss/layout/columns/_index.scss +32 -10
  224. package/src/scss/layout/columns/_variables.scss +13 -9
  225. package/src/scss/layout/container/_defaults.scss +13 -4
  226. package/src/scss/layout/container/_index.scss +12 -7
  227. package/src/scss/layout/container/_variables.scss +14 -11
  228. package/src/scss/layout/flex-columns/_defaults.scss +18 -0
  229. package/src/scss/layout/flex-columns/_index.scss +80 -0
  230. package/src/scss/layout/flex-columns/_variables.scss +26 -0
  231. package/src/scss/mixins/_animation.scss +15 -0
  232. package/src/scss/mixins/_layer.scss +3 -5
  233. package/src/scss/mixins/_screen.scss +56 -0
  234. package/src/scss/mixins/_utility.scss +30 -0
  235. package/src/scss/mixins/_visually-hidden.scss +20 -0
  236. package/src/scss/state/_index.scss +1 -1
  237. package/src/scss/state/focus/_defaults.scss +6 -4
  238. package/src/scss/state/focus/_index.scss +7 -7
  239. package/src/scss/state/focus/_mixins.scss +15 -0
  240. package/src/scss/state/focus/_variables.scss +14 -10
  241. package/src/scss/theme/_index.scss +1 -1
  242. package/src/scss/theme/color/_defaults.scss +101 -19
  243. package/src/scss/theme/color/_index.scss +18 -18
  244. package/src/scss/theme/color/_variables.scss +73 -65
  245. package/src/scss/theme/typography/_defaults.scss +7 -5
  246. package/src/scss/theme/typography/_index.scss +1 -1
  247. package/src/scss/theme/typography/_variables.scss +62 -58
  248. package/src/scss/utilities/_index.scss +14 -2
  249. package/src/scss/utilities/alignment/_defaults.scss +62 -0
  250. package/src/scss/utilities/alignment/_index.scss +75 -0
  251. package/src/scss/utilities/alignment/_variables.scss +6 -0
  252. package/src/scss/utilities/color/_defaults.scss +35 -0
  253. package/src/scss/utilities/color/_index.scss +91 -0
  254. package/src/scss/utilities/color/_variables.scss +6 -0
  255. package/src/scss/utilities/display/_defaults.scss +32 -0
  256. package/src/scss/utilities/display/_index.scss +61 -0
  257. package/src/scss/utilities/display/_variables.scss +6 -0
  258. package/src/scss/utilities/flex/_defaults.scss +63 -0
  259. package/src/scss/utilities/flex/_index.scss +71 -0
  260. package/src/scss/utilities/flex/_variables.scss +6 -0
  261. package/src/scss/utilities/height/_defaults.scss +41 -0
  262. package/src/scss/utilities/height/_index.scss +98 -0
  263. package/src/scss/utilities/height/_variables.scss +6 -0
  264. package/src/scss/utilities/inset/_defaults.scss +41 -0
  265. package/src/scss/utilities/inset/_index.scss +37 -0
  266. package/src/scss/utilities/inset/_variables.scss +6 -0
  267. package/src/scss/utilities/justification/_defaults.scss +59 -0
  268. package/src/scss/utilities/justification/_index.scss +75 -0
  269. package/src/scss/utilities/justification/_variables.scss +6 -0
  270. package/src/scss/utilities/list/_defaults.scss +39 -0
  271. package/src/scss/utilities/list/_index.scss +56 -0
  272. package/src/scss/utilities/list/_variables.scss +6 -0
  273. package/src/scss/utilities/order/_defaults.scss +22 -0
  274. package/src/scss/utilities/order/_index.scss +63 -0
  275. package/src/scss/utilities/order/_variables.scss +6 -0
  276. package/src/scss/utilities/position/_defaults.scss +26 -0
  277. package/src/scss/utilities/position/_index.scss +37 -0
  278. package/src/scss/utilities/position/_variables.scss +6 -0
  279. package/src/scss/utilities/ratio/_defaults.scss +28 -0
  280. package/src/scss/utilities/ratio/_index.scss +52 -0
  281. package/src/scss/utilities/ratio/_variables.scss +9 -0
  282. package/src/scss/utilities/spacing/_defaults.scss +9 -4
  283. package/src/scss/utilities/spacing/_index.scss +138 -33
  284. package/src/scss/utilities/spacing/_variables.scss +5 -2
  285. package/src/scss/utilities/typography/_defaults.scss +29 -4
  286. package/src/scss/utilities/typography/_index.scss +155 -23
  287. package/src/scss/utilities/typography/_variables.scss +5 -2
  288. package/src/scss/utilities/visibility/_defaults.scss +25 -0
  289. package/src/scss/utilities/visibility/_index.scss +36 -0
  290. package/src/scss/utilities/visibility/_variables.scss +6 -0
  291. package/src/scss/utilities/width/_defaults.scss +41 -0
  292. package/src/scss/utilities/width/_index.scss +98 -0
  293. package/src/scss/utilities/width/_variables.scss +6 -0
  294. package/stylelint.config.js +5 -0
  295. package/vite.config.js +57 -0
  296. package/dist/base/form.css +0 -2
  297. package/dist/base/form.css.map +0 -1
  298. package/dist/base/link.css +0 -2
  299. package/dist/base/link.css.map +0 -1
  300. package/dist/base/table.css +0 -2
  301. package/dist/base/table.css.map +0 -1
  302. package/dist/base.css +0 -2
  303. package/dist/base.css.map +0 -1
  304. package/dist/component/button.css +0 -2
  305. package/dist/component/button.css.map +0 -1
  306. package/dist/component/input-group.css +0 -2
  307. package/dist/component/input-group.css.map +0 -1
  308. package/dist/component/table.css +0 -2
  309. package/dist/component/table.css.map +0 -1
  310. package/dist/component.css +0 -2
  311. package/dist/component.css.map +0 -1
  312. package/dist/graupl.css +0 -2
  313. package/dist/graupl.css.map +0 -1
  314. package/dist/layout/columns.css +0 -2
  315. package/dist/layout/columns.css.map +0 -1
  316. package/dist/layout/container.css +0 -2
  317. package/dist/layout/container.css.map +0 -1
  318. package/dist/layout.css +0 -2
  319. package/dist/layout.css.map +0 -1
  320. package/dist/state/focus.css +0 -2
  321. package/dist/state/focus.css.map +0 -1
  322. package/dist/state.css +0 -2
  323. package/dist/state.css.map +0 -1
  324. package/dist/theme/color.css +0 -2
  325. package/dist/theme/color.css.map +0 -1
  326. package/dist/theme/typography.css +0 -2
  327. package/dist/theme/typography.css.map +0 -1
  328. package/dist/theme.css +0 -2
  329. package/dist/theme.css.map +0 -1
  330. package/dist/utilities/colors.css +0 -2
  331. package/dist/utilities/colors.css.map +0 -1
  332. package/dist/utilities/spacing.css +0 -2
  333. package/dist/utilities/spacing.css.map +0 -1
  334. package/dist/utilities/typography.css +0 -2
  335. package/dist/utilities/typography.css.map +0 -1
  336. package/dist/utilities.css +0 -2
  337. package/dist/utilities.css.map +0 -1
  338. package/scss/component/button.scss +0 -3
  339. package/scss/component/table.scss +0 -3
  340. package/scss/utilities/colors.scss +0 -3
  341. package/src/scss/component/button/_defaults.scss +0 -39
  342. package/src/scss/component/button/_index.scss +0 -98
  343. package/src/scss/component/button/_variables.scss +0 -131
  344. package/src/scss/component/table/_defaults.scss +0 -30
  345. package/src/scss/component/table/_index.scss +0 -77
  346. package/src/scss/component/table/_variables.scss +0 -64
  347. package/src/scss/mixins/_media-queries.scss +0 -26
  348. package/src/scss/utilities/colors/_defaults.scss +0 -5
  349. package/src/scss/utilities/colors/_index.scss +0 -22
  350. package/src/scss/utilities/colors/_variables.scss +0 -3
@@ -0,0 +1,1163 @@
1
+ /**
2
+ * @file
3
+ * The Accordion class.
4
+ */
5
+
6
+ import AccordionItem from "./AccordionItem.js";
7
+ import { keyPress, preventEvent } from "../eventHandlers.js";
8
+ import {
9
+ isValidInstance,
10
+ isValidType,
11
+ isValidClassList,
12
+ isQuerySelector,
13
+ } from "../validate.js";
14
+ import storage from "../storage.js";
15
+
16
+ class Accordion {
17
+ /**
18
+ * The DOM elements within the accordion.
19
+ *
20
+ * @protected
21
+ *
22
+ * @type {Object<HTMLElement, HTMLElement[]>}
23
+ *
24
+ * @property {HTMLElement} accordion - The accordion element.
25
+ * @property {HTMLElement[]} accordionItems - An array of accordion items.
26
+ * @property {HTMLElement[]} accordionItemToggles - An array of accordion item toggles.
27
+ * @property {HTMLElement[]} accordionItemHeaders - An array of accordion headers.
28
+ * @property {HTMLElement[]} accordionItemContents - An array of accordion item contents.
29
+ */
30
+ _dom = {
31
+ accordion: null,
32
+ accordionItems: [],
33
+ accordionItemToggles: [],
34
+ accordionItemHeaders: [],
35
+ accordionItemContents: [],
36
+ };
37
+
38
+ /**
39
+ * The DOM elements within the accordion that cannot be reset or generated by the accordion.
40
+ *
41
+ * @protected
42
+ *
43
+ * @type {string[]}
44
+ */
45
+ _domLock = ["accordion"];
46
+
47
+ /**
48
+ * The query selectors used by the accordion.
49
+ *
50
+ * @protected
51
+ *
52
+ * @type {Object<string>}
53
+ *
54
+ * @property {string} accordionItems - The query selector for accordion items.
55
+ * @property {string} accordionItemToggles - The query selector for accordion toggles.
56
+ * @property {string} accordionItemHeaders - The query selector for accordion headers.
57
+ * @property {string} accordionItemContents - The query selector for accordion contents.
58
+ */
59
+ _selectors = {
60
+ accordionItems: "",
61
+ accordionItemToggles: "",
62
+ accordionItemHeaders: "",
63
+ accordionItemContents: "",
64
+ };
65
+
66
+ /**
67
+ * The list of accordion items.
68
+ *
69
+ * @protected
70
+ *
71
+ * @type {Object<AccordionItem[]>}
72
+ *
73
+ * @property {AccordionItem[]} accordionItems - The list of accordion items.
74
+ */
75
+ _elements = {
76
+ accordionItems: [],
77
+ };
78
+
79
+ /**
80
+ * The class(es) to apply when the accordion is open.
81
+ *
82
+ * @protected
83
+ *
84
+ * @type {string|string[]}
85
+ */
86
+ _openClass = "show";
87
+
88
+ /**
89
+ * The class(es) to apply when the accordion is closed.
90
+ *
91
+ * @protected
92
+ *
93
+ * @type {string|string[]}
94
+ */
95
+ _closeClass = "hide";
96
+
97
+ /**
98
+ * The class(es) to apply when the accordion is transitioning between states.
99
+ *
100
+ * @protected
101
+ *
102
+ * @type {string|string[]}
103
+ */
104
+ _transitionClass = "transitioning";
105
+
106
+ /**
107
+ * The duration time (in milliseconds) for the transition between open and closed states.
108
+ *
109
+ * @protected
110
+ *
111
+ * @type {number}
112
+ */
113
+ _transitionDuration = 300;
114
+
115
+ /**
116
+ * The duration time (in milliseconds) for the transition from closed to open states.
117
+ *
118
+ * @protected
119
+ *
120
+ * @type {number}
121
+ */
122
+ _openDuration = -1;
123
+
124
+ /**
125
+ * The duration time (in milliseconds) for the transition from open to closed states.
126
+ *
127
+ * @protected
128
+ *
129
+ * @type {number}
130
+ */
131
+ _closeDuration = -1;
132
+
133
+ /**
134
+ * A flag to decide if the accordion items can be navigated by arrows.
135
+ *
136
+ * @protected
137
+ *
138
+ * @type {boolean}
139
+ */
140
+ _optionalKeySupport = true;
141
+
142
+ /**
143
+ * A flag to decide if multiple accordions can be open at the same time.
144
+ *
145
+ * If set to false, only one accordion can be open at a time.
146
+ *
147
+ * @protected
148
+ *
149
+ * @type {boolean}
150
+ */
151
+ _allowMultipleExpand = true;
152
+
153
+ /**
154
+ * A flag to decide if no accordions can be opened at the same time.
155
+ *
156
+ * If set to false, at least one accordion must be open at all times.
157
+ *
158
+ * @protected
159
+ *
160
+ * @type {boolean}
161
+ */
162
+ _allowNoExpand = true;
163
+
164
+ /**
165
+ * The index of the current child node.
166
+ *
167
+ * @protected
168
+ *
169
+ * @type {number}
170
+ */
171
+ _currentChild = 0;
172
+
173
+ /**
174
+ * The prefix to use for CSS custom properties.
175
+ *
176
+ * @protected
177
+ *
178
+ * @type {string}
179
+ */
180
+ _prefix = "graupl-";
181
+
182
+ /**
183
+ * The key used to generate IDs throughout the accordion.
184
+ *
185
+ * @protected
186
+ *
187
+ * @type {string}
188
+ */
189
+ _key = "";
190
+
191
+ /**
192
+ * errors - The list of errors found during validation.
193
+ *
194
+ * @protected
195
+ *
196
+ * @type {string[]}
197
+ */
198
+ _errors = [];
199
+
200
+ /**
201
+ * Constructs a new `Accordion`.
202
+ *
203
+ * @param {object} options - The options for generating the accordion.
204
+ * @param {HTMLElement} [options.accordionElement] - The accordion element in the DOM.
205
+ * @param {string} [options.accordionItemSelector = .accordion-item] - The query selector string for accordion items.
206
+ * @param {string} [options.accordionItemToggleSelector = .accordion-item-toggle] - The query selector string for accordion toggle.
207
+ * @param {string} [options.accordionItemHeaderSelector = .accordion-item-header] - The query selector string for accordion header.
208
+ * @param {string} [options.accordionItemContentSelector = .accordion-item-content] - The query selector string for accordion content.
209
+ * @param {?(string|string[])} [options.openClass = show] - The class to apply when a accordion is "open".
210
+ * @param {?(string|string[])} [options.closeClass = hide] - The class to apply when a accordion is "closed".
211
+ * @param {?(string|string[])} [options.transitionClass = transitioning] - The class to apply when a accordion is transitioning between "open" and "closed" states.
212
+ * @param {number} [options.transitionDuration = 300] - The duration of the transition between "open" and "closed" states (in milliseconds).
213
+ * @param {number} [options.openDuration = -1] - The duration of the transition from "closed" to "open" states (in milliseconds).
214
+ * @param {number} [options.closeDuration = -1] - The duration of the transition from "open" to "closed" states (in milliseconds).
215
+ * @param {boolean} [options.optionalKeySupport = false] - A flag to determine if accordions can be navigated with arrows.
216
+ * @param {boolean} [options.allowMultipleExpand = true] - A flag to determine if multiple accordions can be open at the same time.
217
+ * @param {boolean} [options.allowNoExpand = true] - A flag to determine if no accordions can be open at the same time.
218
+ * @param {?string} [options.prefix = graupl-] - The prefix to use for CSS custom properties.
219
+ * @param {?string} [options.key = null] - The key used to generate IDs throughout the accordion.
220
+ * @param {boolean} [options.initialize = false] - A flag to initialize the accordion immediately upon creation.
221
+ */
222
+ constructor({
223
+ accordionElement,
224
+ accordionItemSelector = ".accordion-item",
225
+ accordionItemToggleSelector = ".accordion-item-toggle",
226
+ accordionItemHeaderSelector = ".accordion-item-header",
227
+ accordionItemContentSelector = ".accordion-item-content",
228
+ openClass = "show",
229
+ closeClass = "hide",
230
+ transitionClass = "transitioning",
231
+ transitionDuration = 300,
232
+ openDuration = -1,
233
+ closeDuration = -1,
234
+ optionalKeySupport = false,
235
+ allowMultipleExpand = true,
236
+ allowNoExpand = true,
237
+ prefix = "am-",
238
+ key = null,
239
+ initialize = false,
240
+ }) {
241
+ // Set DOM elements.
242
+ this._dom.accordion = accordionElement;
243
+
244
+ // Set DOM selectors.
245
+ this._selectors.accordionItems = accordionItemSelector;
246
+ this._selectors.accordionItemToggles = accordionItemToggleSelector;
247
+ this._selectors.accordionItemHeaders = accordionItemHeaderSelector;
248
+ this._selectors.accordionItemContents = accordionItemContentSelector;
249
+
250
+ // Set open/close classes.
251
+ this._openClass = openClass || "";
252
+ this._closeClass = closeClass || "";
253
+ this._transitionClass = transitionClass || "";
254
+
255
+ // Set transition duration.
256
+ this._transitionDuration = transitionDuration;
257
+ this._openDuration = openDuration;
258
+ this._closeDuration = closeDuration;
259
+
260
+ // Set optional key support.
261
+ this._optionalKeySupport = optionalKeySupport;
262
+
263
+ // Set expand rules.
264
+ this._allowMultipleExpand = allowMultipleExpand;
265
+ this._allowNoExpand = allowNoExpand;
266
+
267
+ // Set prefix.
268
+ this._prefix = prefix || "";
269
+
270
+ // Set the key.
271
+ this._key = key || "";
272
+
273
+ if (initialize) {
274
+ this.initialize();
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Initializes the accordion.
280
+ */
281
+ initialize() {
282
+ try {
283
+ if (!this._validate()) {
284
+ throw new Error(
285
+ `Graupl Accordion: cannot initialize accordion. The following errors have been found:\n - ${this.errors.join(
286
+ "\n - "
287
+ )}`
288
+ );
289
+ }
290
+
291
+ // Set up the DOM.
292
+ this._generateKey();
293
+ this._setDOMElements();
294
+ this._setIds();
295
+
296
+ // Create the child elements.
297
+ this._createChildElements();
298
+
299
+ // Handle events.
300
+ this._handleFocus();
301
+ this._handleClick();
302
+ this._handleKeydown();
303
+ this._handleKeyup();
304
+
305
+ // Set the custom props.
306
+ this._setTransitionDurations();
307
+
308
+ // Set up the storage.
309
+ storage.initializeStorage("accordions");
310
+ storage.pushToStorage("accordions", this.dom.accordion.id, this);
311
+ } catch (error) {
312
+ console.error(error);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * The class(es) to apply when the accordion is open.
318
+ *
319
+ * @type {string|string[]}
320
+ *
321
+ * @see _openClass
322
+ */
323
+ get openClass() {
324
+ return this._openClass;
325
+ }
326
+
327
+ /**
328
+ * The class(es) to apply when the accordion is closed.
329
+ *
330
+ * @type {string|string[]}
331
+ *
332
+ * @see _closeClass
333
+ */
334
+ get closeClass() {
335
+ return this._closeClass;
336
+ }
337
+
338
+ /**
339
+ * The class(es) to apply when the accordion is transitioning between open and closed.
340
+ *
341
+ * @type {string|string[]}
342
+ *
343
+ * @see _transitionClass
344
+ */
345
+ get transitionClass() {
346
+ return this._transitionClass;
347
+ }
348
+
349
+ /**
350
+ * The duration time (in milliseconds) for the transition between open and closed states.
351
+ *
352
+ * @type {number}
353
+ *
354
+ * @see _transitionDuration
355
+ */
356
+ get transitionDuration() {
357
+ return this._transitionDuration;
358
+ }
359
+
360
+ /**
361
+ * The duration time (in milliseconds) for the transition from closed to open states.
362
+ *
363
+ * If openDuration is set to -1, the transitionDuration will be used instead.
364
+ *
365
+ * @type {number}
366
+ *
367
+ * @see _openDuration
368
+ */
369
+ get openDuration() {
370
+ return this._openDuration === -1
371
+ ? this.transitionDuration
372
+ : this._openDuration;
373
+ }
374
+
375
+ /**
376
+ * The duration time (in milliseconds) for the transition from open to closed states.
377
+ *
378
+ * If closeDuration is set to -1, the transitionDuration will be used instead.
379
+ *
380
+ * @type {number}
381
+ *
382
+ * @see _closeDuration
383
+ */
384
+ get closeDuration() {
385
+ return this._closeDuration === -1
386
+ ? this.transitionDuration
387
+ : this._closeDuration;
388
+ }
389
+
390
+ /**
391
+ * The current index of the accordion item.
392
+ *
393
+ * @readonly
394
+ *
395
+ * @type {number}
396
+ *
397
+ * @see _currentChild
398
+ */
399
+ get currentChild() {
400
+ return this._currentChild;
401
+ }
402
+
403
+ /**
404
+ * The dom elements of the accordion.
405
+ *
406
+ * @readonly
407
+ *
408
+ * @type {object}
409
+ *
410
+ * @see _dom
411
+ */
412
+ get dom() {
413
+ return this._dom;
414
+ }
415
+
416
+ /**
417
+ * The elements of the accordion.
418
+ *
419
+ * @readonly
420
+ *
421
+ * @type {object}
422
+ *
423
+ * @see _elements
424
+ */
425
+ get elements() {
426
+ return this._elements;
427
+ }
428
+
429
+ /**
430
+ * The selectors used for the accordion and accordion items.
431
+ *
432
+ * @readonly
433
+ *
434
+ * @type {boolean}
435
+ *
436
+ * @see _selectors
437
+ */
438
+ get selectors() {
439
+ return this._selectors;
440
+ }
441
+
442
+ /**
443
+ * A flag to decide if the accordion items can be navigated by arrows.
444
+ *
445
+ * @readonly
446
+ *
447
+ * @type {boolean}
448
+ *
449
+ * @see _optionalKeySupport
450
+ */
451
+ get optionalKeySupport() {
452
+ return this._optionalKeySupport;
453
+ }
454
+
455
+ /**
456
+ * The currently selected accordion item.
457
+ *
458
+ * @readonly
459
+ *
460
+ * @type {AccordionItem}
461
+ */
462
+ get currentAccordionItem() {
463
+ return this.elements.accordionItems[this.currentChild];
464
+ }
465
+
466
+ /**
467
+ * The currently open accordion items.
468
+ *
469
+ * @readonly
470
+ *
471
+ * @type {AccordionItem[]}
472
+ */
473
+ get openAccordionItems() {
474
+ return this.elements.accordionItems.filter((item) => item.isOpen);
475
+ }
476
+
477
+ /**
478
+ * A flag to decide if multiple accordions can be open at the same time.
479
+ *
480
+ * @type {boolean}
481
+ *
482
+ * @see _allowMultipleExpand
483
+ */
484
+ get allowMultipleExpand() {
485
+ return this._allowMultipleExpand;
486
+ }
487
+
488
+ /**
489
+ * A flag to decide if no accordions can be opened at the same time.
490
+ *
491
+ * @type {boolean}
492
+ *
493
+ * @see _allowNoExpand
494
+ */
495
+ get allowNoExpand() {
496
+ return this._allowNoExpand;
497
+ }
498
+
499
+ /**
500
+ * The prefix to use for CSS custom properties.
501
+ *
502
+ * @type {string}
503
+ *
504
+ * @see _prefix
505
+ */
506
+ get prefix() {
507
+ return this._prefix;
508
+ }
509
+
510
+ /**
511
+ * The key used to generate IDs throughout the accordion.
512
+ *
513
+ * @type {string}
514
+ *
515
+ * @see _key
516
+ */
517
+ get key() {
518
+ return this._key;
519
+ }
520
+
521
+ /**
522
+ * An array to hold error messages.
523
+ *
524
+ * @readonly
525
+ *
526
+ * @type {string[]}
527
+ *
528
+ * @see _errors
529
+ */
530
+ get errors() {
531
+ return this._errors;
532
+ }
533
+
534
+ set openClass(value) {
535
+ isValidClassList({ openClass: value });
536
+
537
+ if (this._openClass !== value) {
538
+ this._openClass = value;
539
+ }
540
+ }
541
+
542
+ set closeClass(value) {
543
+ isValidClassList({ closeClass: value });
544
+
545
+ if (this._closeClass !== value) {
546
+ this._closeClass = value;
547
+ }
548
+ }
549
+
550
+ set transitionClass(value) {
551
+ isValidClassList({ transitionClass: value });
552
+
553
+ if (this._transitionClass !== value) {
554
+ this._transitionClass = value;
555
+ }
556
+ }
557
+
558
+ set transitionDuration(value) {
559
+ isValidType("number", { value });
560
+
561
+ if (this._transitionDuration !== value) {
562
+ this._transitionDuration = value;
563
+ this._setTransitionDurations();
564
+ }
565
+ }
566
+
567
+ set openDuration(value) {
568
+ isValidType("number", { value });
569
+
570
+ if (this._openDuration !== value) {
571
+ this._openDuration = value;
572
+ this._setTransitionDurations();
573
+ }
574
+ }
575
+
576
+ set closeDuration(value) {
577
+ isValidType("number", { value });
578
+
579
+ if (this._closeDuration !== value) {
580
+ this._closeDuration = value;
581
+ this._setTransitionDurations();
582
+ }
583
+ }
584
+
585
+ set currentChild(value) {
586
+ isValidType("number", { value });
587
+
588
+ if (
589
+ this._currentChild !== value &&
590
+ value >= 0 &&
591
+ value < this.elements.accordionItems.length
592
+ ) {
593
+ this._currentChild = value;
594
+ }
595
+ }
596
+
597
+ set accordionItems(value) {
598
+ isValidType("object", { value });
599
+
600
+ if (
601
+ value?.isArray() &&
602
+ value.every((item) => item instanceof AccordionItem)
603
+ ) {
604
+ this._accordionItems = value;
605
+ }
606
+ }
607
+
608
+ set allowMultipleExpand(value) {
609
+ isValidType("boolean", { value });
610
+
611
+ if (this._allowMultipleExpand !== value) {
612
+ this._allowMultipleExpand = value;
613
+ }
614
+ }
615
+
616
+ set allowNoExpand(value) {
617
+ isValidType("boolean", { value });
618
+
619
+ if (this._allowNoExpand !== value) {
620
+ this._allowNoExpand = value;
621
+ }
622
+ }
623
+
624
+ set prefix(value) {
625
+ isValidType("string", { value });
626
+
627
+ if (this._prefix !== value) {
628
+ this._prefix = value;
629
+ }
630
+ }
631
+
632
+ set key(value) {
633
+ isValidType("string", { value });
634
+
635
+ if (this._key !== value) {
636
+ this._key = value;
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Sets DOM elements.
642
+ *
643
+ * Elements listed in _domLock cannot be set using this method.
644
+ *
645
+ * @protected
646
+ *
647
+ * @param {string} elementType - The type of element to populate.
648
+ * @param {HTMLElement} [base = this.dom.accordion] - The element used as the base for the querySelect.
649
+ * @param {boolean} [overwrite = true] - A flag to set if the existing elements will be overwritten.
650
+ * @param {boolean} [strict = false] - A flag to set if the elements must be direct children of the base.
651
+ */
652
+ _setDOMElementType(
653
+ elementType,
654
+ base = this.dom.accordion,
655
+ overwrite = true,
656
+ strict = false
657
+ ) {
658
+ if (typeof this.selectors[elementType] === "string") {
659
+ if (this._domLock.includes(elementType)) {
660
+ throw new Error(
661
+ `Graupl ${this.contructor.name}: "${elementType}" element cannot be set through _setDOMElementType.`
662
+ );
663
+ }
664
+
665
+ if (base !== this.dom.accordion) isValidInstance(HTMLElement, { base });
666
+
667
+ // Get the all elements matching the selector in the base.
668
+ const domElements = Array.from(
669
+ base.querySelectorAll(this.selectors[elementType])
670
+ );
671
+
672
+ // Filter the elements so only direct children of the base are kept.
673
+ const filteredElements = domElements.filter((item) =>
674
+ strict ? item.parentElement === base : true
675
+ );
676
+
677
+ if (overwrite) {
678
+ this._dom[elementType] = filteredElements;
679
+ } else {
680
+ this._dom[elementType] = [
681
+ ...this._dom[elementType],
682
+ ...filteredElements,
683
+ ];
684
+ }
685
+ } else {
686
+ throw new Error(
687
+ `Graupl ${this.contructor.name}: "${elementType}" is not a valid element type.`
688
+ );
689
+ }
690
+ }
691
+
692
+ /**
693
+ * Resets DOM elements.
694
+ *
695
+ * Elements listed in _domLock cannot be reset using this method.
696
+ *
697
+ * @protected
698
+ *
699
+ * @param {string} elementType - The type of element to clear.
700
+ */
701
+ _resetDOMElementType(elementType) {
702
+ if (typeof this.selectors[elementType] === "string") {
703
+ if (this._domLock.includes(elementType)) {
704
+ throw new Error(
705
+ `Graupl ${this.contructor.name}: "${elementType}" element cannot be reset through _resetDOMElementType.`
706
+ );
707
+ }
708
+
709
+ if (Array.isArray(this._dom[elementType])) {
710
+ this._dom[elementType] = [];
711
+ } else {
712
+ this._dom[elementType] = null;
713
+ }
714
+ } else {
715
+ throw new Error(
716
+ `Graupl ${this.contructor.name}: "${elementType}" is not a valid element type.`
717
+ );
718
+ }
719
+ }
720
+
721
+ /**
722
+ * Sets all DOM elements within the accordion.
723
+ *
724
+ * Utilizes _setDOMElementType and
725
+ * _resetDOMElementType.
726
+ *
727
+ * @protected
728
+ */
729
+ _setDOMElements() {
730
+ this._setDOMElementType("accordionItems");
731
+ this._resetDOMElementType("accordionItemToggles");
732
+
733
+ this.dom.accordionItems.forEach((accordionItem) => {
734
+ this._setDOMElementType("accordionItemToggles", accordionItem, false);
735
+ this._setDOMElementType("accordionItemHeaders", accordionItem, false);
736
+ this._setDOMElementType("accordionItemContents", accordionItem, false);
737
+ });
738
+ }
739
+
740
+ /**
741
+ * Generates a key for the accordion.
742
+ *
743
+ * @param {boolean} [regenerate = false] - A flag to determine if the key should be regenerated.
744
+ */
745
+ _generateKey(regenerate = false) {
746
+ if (this.key === "" || regenerate) {
747
+ this.key = Math.random()
748
+ .toString(36)
749
+ .replace(/[^a-z]+/g, "")
750
+ .substring(0, 10);
751
+ }
752
+ }
753
+
754
+ /**
755
+ * Sets the IDs of the accordion and it's children if they do not already exist.
756
+ *
757
+ * The generated IDs use the key and follow the format:
758
+ * - accordion: `accordion-${key}`
759
+ *
760
+ * @protected
761
+ */
762
+ _setIds() {
763
+ this.dom.accordion.id = this.dom.accordion.id || `accordion-${this.key}`;
764
+ }
765
+
766
+ /**
767
+ * Creates and initializes all accordion items.
768
+ *
769
+ * @protected
770
+ */
771
+ _createChildElements() {
772
+ this.dom.accordionItems.forEach((accordionItem, index) => {
773
+ const item = new AccordionItem({
774
+ accordionItemElement: accordionItem,
775
+ accordionItemToggleElement: this.dom.accordionItemToggles[index],
776
+ accordionItemHeaderElement: this.dom.accordionItemHeaders[index],
777
+ accordionItemContentElement: this.dom.accordionItemContents[index],
778
+ parentAccordion: this,
779
+ });
780
+
781
+ item.initialize();
782
+
783
+ this.elements.accordionItems.push(item);
784
+ });
785
+ }
786
+
787
+ /**
788
+ * Validates all aspects of the accordion item to ensure proper functionality.
789
+ *
790
+ * @protected
791
+ *
792
+ * @return {boolean} - The result of the validation.
793
+ */
794
+ _validate() {
795
+ let check = true;
796
+
797
+ // HTML element checks.
798
+ const htmlElementChecks = isValidInstance(HTMLElement, {
799
+ accordionElement: this.dom.accordion,
800
+ });
801
+
802
+ if (!htmlElementChecks) {
803
+ this._errors.push(htmlElementChecks.message);
804
+ check = false;
805
+ }
806
+
807
+ // Query selector checks.
808
+ const querySelectorChecks = isQuerySelector({
809
+ accordionItemSelector: this._selectors.accordionItems,
810
+ accordionItemToggleSelector: this._selectors.accordionItemToggles,
811
+ accordionItemHeaderSelector: this._selectors.accordionItemHeaders,
812
+ accordionItemContentSelector: this._selectors.accordionItemContents,
813
+ });
814
+
815
+ if (!querySelectorChecks) {
816
+ this._errors.push(querySelectorChecks.message);
817
+ check = false;
818
+ }
819
+
820
+ // Class list checks.
821
+ if (this._openClass !== "") {
822
+ const openClassCheck = isValidClassList({ openClass: this._openClass });
823
+
824
+ if (!openClassCheck.status) {
825
+ this._errors.push(openClassCheck.error.message);
826
+ check = false;
827
+ }
828
+ }
829
+
830
+ if (this._closeClass !== "") {
831
+ const closeClassCheck = isValidClassList({
832
+ closeClass: this._closeClass,
833
+ });
834
+
835
+ if (!closeClassCheck.status) {
836
+ this._errors.push(closeClassCheck.error.message);
837
+ check = false;
838
+ }
839
+ }
840
+
841
+ if (this._transitionClass !== "") {
842
+ const transitionClassCheck = isValidClassList({
843
+ transitionClass: this._transitionClass,
844
+ });
845
+
846
+ if (!transitionClassCheck.status) {
847
+ this._errors.push(transitionClassCheck.error.message);
848
+ check = false;
849
+ }
850
+ }
851
+
852
+ // Transition duration check.
853
+ const transitionDurationCheck = isValidType("number", {
854
+ transitionDuration: this._transitionDuration,
855
+ });
856
+
857
+ if (!transitionDurationCheck.status) {
858
+ this._errors.push(transitionDurationCheck.error.message);
859
+ check = false;
860
+ }
861
+
862
+ // Open duration check.
863
+ const openDurationCheck = isValidType("number", {
864
+ openDuration: this._openDuration,
865
+ });
866
+
867
+ if (!openDurationCheck.status) {
868
+ this._errors.push(openDurationCheck.error.message);
869
+ check = false;
870
+ }
871
+
872
+ // Close duration check.
873
+ const closeDurationCheck = isValidType("number", {
874
+ closeDuration: this._closeDuration,
875
+ });
876
+
877
+ if (!closeDurationCheck.status) {
878
+ this._errors.push(closeDurationCheck.error.message);
879
+ check = false;
880
+ }
881
+
882
+ // Boolean checks.
883
+ const booleanCheck = isValidType("boolean", {
884
+ optionalKeySupport: this._optionalKeySupport,
885
+ allowMultipleExpand: this._allowMultipleExpand,
886
+ allowNoExpand: this._allowNoExpand,
887
+ });
888
+
889
+ if (!booleanCheck.status) {
890
+ this._errors.push(booleanCheck.error.message);
891
+ check = false;
892
+ }
893
+
894
+ // Key check.
895
+ if (this._key !== "") {
896
+ const keyCheck = isValidType("string", { key: this._key });
897
+
898
+ if (!keyCheck.status) {
899
+ this._errors.push(keyCheck.error.message);
900
+ check = false;
901
+ }
902
+ }
903
+
904
+ // Prefix check.
905
+ const prefixCheck = isValidType("string", { prefix: this._prefix });
906
+
907
+ if (!prefixCheck.status) {
908
+ this._errors.push(prefixCheck.error.message);
909
+ check = false;
910
+ }
911
+
912
+ return check;
913
+ }
914
+
915
+ /**
916
+ * Handles focus events throughout the accordion for proper use.
917
+ *
918
+ * - Adds a `focus` listener to every accordion item so when it gains focus,
919
+ * it will set the accordion's current child to the index of the item.
920
+ *
921
+ * @protected
922
+ */
923
+ _handleFocus() {
924
+ this.elements.accordionItems.forEach((accordionItem, index) => {
925
+ accordionItem.dom.toggle.addEventListener("focus", () => {
926
+ this.currentChild = index;
927
+ });
928
+ });
929
+ }
930
+
931
+ /**
932
+ * Handles click events throughout the accordion item for proper use.
933
+ *
934
+ * - Adds a `pointerup` listener to the accordion item toggles that will toggle each accordion item.
935
+ *
936
+ * @protected
937
+ */
938
+ _handleClick() {
939
+ this.elements.accordionItems.forEach((accordionItem, index) => {
940
+ accordionItem.dom.toggle.addEventListener("pointerup", () => {
941
+ this.currentChild = index;
942
+ accordionItem.toggle();
943
+ });
944
+ });
945
+ }
946
+
947
+ /**
948
+ * Handles keydown events throughout the accordion item for proper use.
949
+ *
950
+ * This method exists to assist the _handleKeyup method.
951
+ *
952
+ * - Adds a `keydown` listener to all accordion item toggles.
953
+ * - Blocks propagation on "Space" and "Enter" keys.
954
+ * - _If_ optionalKeySupport is enabled, blocks propagation on the following keys:
955
+ * - "ArrowDown", "ArrowUp", "Home", and "End".
956
+ */
957
+ _handleKeydown() {
958
+ this.dom.accordionItemToggles.forEach((accordionToggle) => {
959
+ accordionToggle.addEventListener("keydown", (event) => {
960
+ const key = keyPress(event);
961
+ const ToggleKeys = ["Space", "Enter"];
962
+
963
+ // Prevent default behavior for space and enter keys.
964
+ if (ToggleKeys.includes(key)) {
965
+ preventEvent(event);
966
+ } else if (this.optionalKeySupport) {
967
+ const optionalKeys = ["ArrowDown", "ArrowUp", "Home", "End"];
968
+
969
+ if (optionalKeys.includes(key)) {
970
+ preventEvent(event);
971
+ }
972
+ }
973
+ });
974
+ });
975
+ }
976
+
977
+ /**
978
+ * Handles keyup events throughout the accordion item for proper use.
979
+ *
980
+ * Adds the follow keybindings (explanations are taken from the WAI ARIA Practices Guide Accordion Pattern):
981
+ *
982
+ * - `Enter` or `Space`:
983
+ * - When focus is on the accordion header for a collapsed panel, expands the associated panel. If the implementation allows only one panel to be expanded, and if another panel is expanded, collapses that panel.
984
+ * - When focus is on the accordion header for an expanded panel, collapses the panel if the implementation supports collapsing. Some implementations require one panel to be expanded at all times and allow only one panel to be expanded; so, they do not support a collapse function.
985
+ * - `Tab`: Moves focus to the next focusable element; all focusable elements in the accordion are included in the page `Tab` sequence.
986
+ * - `Shift + Tab`: Moves focus to the previous focusable element; all focusable elements in the accordion are included in the page `Tab` sequence.
987
+ * - `Down Arrow` (Optional): If focus is on an accordion header, moves focus to the next accordion header. If focus is on the last accordion header, either does nothing or moves focus to the first accordion header.
988
+ * - `Up Arrow` (Optional): If focus is on an accordion header, moves focus to the previous accordion header. If focus is on the first accordion header, either does nothing or moves focus to the last accordion header.
989
+ * - `Home` (Optional): When focus is on an accordion header, moves focus to the first accordion header.
990
+ * - `End` (Optional): When focus is on an accordion header, moves focus to the last accordion header.
991
+ *
992
+ * Note: When the above explanations mention "accordion header", they are referring to the accordion item toggle.
993
+ */
994
+ _handleKeyup() {
995
+ this.dom.accordionItemToggles.forEach((accordionToggle) => {
996
+ accordionToggle.addEventListener("keyup", (event) => {
997
+ const key = keyPress(event);
998
+
999
+ switch (key) {
1000
+ case "Space":
1001
+ case "Enter":
1002
+ preventEvent(event);
1003
+ this.currentAccordionItem.toggle();
1004
+
1005
+ break;
1006
+ }
1007
+
1008
+ if (this.optionalKeySupport) {
1009
+ switch (key) {
1010
+ case "Home":
1011
+ preventEvent(event);
1012
+ this.focusFirstChild();
1013
+
1014
+ break;
1015
+ case "End":
1016
+ preventEvent(event);
1017
+ this.focusLastChild();
1018
+
1019
+ break;
1020
+ case "ArrowDown":
1021
+ preventEvent(event);
1022
+ this.focusNextChild();
1023
+
1024
+ break;
1025
+ case "ArrowUp":
1026
+ preventEvent(event);
1027
+ this.focusPreviousChild();
1028
+
1029
+ break;
1030
+ }
1031
+ }
1032
+ });
1033
+ });
1034
+ }
1035
+
1036
+ /**
1037
+ * Sets the transition durations of the accordion as a CSS custom properties.
1038
+ *
1039
+ * The custom properties are:
1040
+ * - `--graupl-accordion-transition-duration`,
1041
+ * - `--graupl-accordion-open-transition-duration`, and
1042
+ * - `--graupl-accordion-close-transition-duration`.
1043
+ *
1044
+ * The prefix of `graupl-` can be changed by setting the accordion's prefix value.
1045
+ *
1046
+ * @protected
1047
+ */
1048
+ _setTransitionDurations() {
1049
+ this.dom.accordion.style.setProperty(
1050
+ `--${this.prefix}accordion-transition-duration`,
1051
+ `${this.transitionDuration}ms`
1052
+ );
1053
+
1054
+ this.dom.accordion.style.setProperty(
1055
+ `--${this.prefix}accordion-open-transition-duration`,
1056
+ `${this.openDuration}ms`
1057
+ );
1058
+
1059
+ this.dom.accordion.style.setProperty(
1060
+ `--${this.prefix}accordion-close-transition-duration`,
1061
+ `${this.closeDuration}ms`
1062
+ );
1063
+ }
1064
+
1065
+ /**
1066
+ * Focus the accordion's current child.
1067
+ *
1068
+ * @public
1069
+ */
1070
+ focusCurrentChild() {
1071
+ if (this.currentChild !== -1) {
1072
+ this.currentAccordionItem.focus();
1073
+ }
1074
+ }
1075
+
1076
+ /**
1077
+ * Focuses the accordion's child at a given index.
1078
+ *
1079
+ * @public
1080
+ *
1081
+ * @param {number} index - The index of the child to focus.
1082
+ */
1083
+ focusChild(index) {
1084
+ this.blurCurrentChild();
1085
+ this.currentChild = index;
1086
+ this.focusCurrentChild();
1087
+ }
1088
+
1089
+ /**
1090
+ * Focuses the accordion's first child.
1091
+ *
1092
+ * @public
1093
+ */
1094
+ focusFirstChild() {
1095
+ this.focusChild(0);
1096
+ }
1097
+
1098
+ /**
1099
+ * Focus the accordion's last child.
1100
+ *
1101
+ * @public
1102
+ */
1103
+ focusLastChild() {
1104
+ this.focusChild(this.elements.accordionItems.length - 1);
1105
+ }
1106
+
1107
+ /**
1108
+ * Focus the accordion's next child.
1109
+ *
1110
+ * @public
1111
+ */
1112
+ focusNextChild() {
1113
+ if (this.currentChild < this.elements.accordionItems.length - 1) {
1114
+ this.focusChild(this.currentChild + 1);
1115
+ } else {
1116
+ this.focusCurrentChild();
1117
+ }
1118
+ }
1119
+
1120
+ /**
1121
+ * Focus the accordion's previous child.
1122
+ *
1123
+ * @public
1124
+ */
1125
+ focusPreviousChild() {
1126
+ if (this.currentChild > 0) {
1127
+ this.focusChild(this.currentChild - 1);
1128
+ } else {
1129
+ this.focusCurrentChild();
1130
+ }
1131
+ }
1132
+
1133
+ /**
1134
+ * Blurs the accordion's current child.
1135
+ *
1136
+ * @public
1137
+ */
1138
+ blurCurrentChild() {
1139
+ if (this.currentChild !== -1) {
1140
+ this.currentAccordionItem.blur();
1141
+ }
1142
+ }
1143
+
1144
+ /**
1145
+ * Open all accordion items.
1146
+ *
1147
+ * @public
1148
+ */
1149
+ openChildren() {
1150
+ this.elements.accordionItems.forEach((item) => item.show());
1151
+ }
1152
+
1153
+ /**
1154
+ * Close all accordion items.
1155
+ *
1156
+ * @public
1157
+ */
1158
+ closeChildren() {
1159
+ this.elements.accordionItems.forEach((item) => item.hide());
1160
+ }
1161
+ }
1162
+
1163
+ export default Accordion;