@graupl/core 1.0.0-beta.22 → 1.0.0-beta.24

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 (273) hide show
  1. package/dist/css/base/button.css +2 -2
  2. package/dist/css/base/button.css.map +1 -1
  3. package/dist/css/base/form.css +2 -2
  4. package/dist/css/base/form.css.map +1 -1
  5. package/dist/css/base/link.css +2 -2
  6. package/dist/css/base/link.css.map +1 -1
  7. package/dist/css/base/table.css +2 -2
  8. package/dist/css/base/table.css.map +1 -1
  9. package/dist/css/base.css +2 -2
  10. package/dist/css/base.css.map +1 -1
  11. package/dist/css/component/accordion.css +2 -5
  12. package/dist/css/component/accordion.css.map +1 -1
  13. package/dist/css/component/alert.css +2 -2
  14. package/dist/css/component/alert.css.map +1 -1
  15. package/dist/css/component/badge.css +2 -2
  16. package/dist/css/component/badge.css.map +1 -1
  17. package/dist/css/component/card.css +2 -2
  18. package/dist/css/component/card.css.map +1 -1
  19. package/dist/css/component/carousel.css +2 -2
  20. package/dist/css/component/carousel.css.map +1 -1
  21. package/dist/css/component/disclosure.css +2 -2
  22. package/dist/css/component/disclosure.css.map +1 -1
  23. package/dist/css/component/input-group.css +2 -2
  24. package/dist/css/component/input-group.css.map +1 -1
  25. package/dist/css/component/list.css +2 -2
  26. package/dist/css/component/list.css.map +1 -1
  27. package/dist/css/component/menu.css +2 -2
  28. package/dist/css/component/menu.css.map +1 -1
  29. package/dist/css/component/navigation.css +2 -2
  30. package/dist/css/component/navigation.css.map +1 -1
  31. package/dist/css/component.css +2 -5
  32. package/dist/css/component.css.map +1 -1
  33. package/dist/css/graupl.css +2 -5
  34. package/dist/css/graupl.css.map +1 -1
  35. package/dist/css/init.css +2 -2
  36. package/dist/css/init.css.map +1 -1
  37. package/dist/css/layout/columns.css +2 -2
  38. package/dist/css/layout/columns.css.map +1 -1
  39. package/dist/css/layout/container.css +2 -2
  40. package/dist/css/layout/container.css.map +1 -1
  41. package/dist/css/layout/flex-columns.css +2 -2
  42. package/dist/css/layout/flex-columns.css.map +1 -1
  43. package/dist/css/layout.css +2 -5
  44. package/dist/css/layout.css.map +1 -1
  45. package/dist/css/normalize.css +2 -2
  46. package/dist/css/normalize.css.map +1 -1
  47. package/dist/css/state/focus.css +2 -2
  48. package/dist/css/state/focus.css.map +1 -1
  49. package/dist/css/state.css +2 -2
  50. package/dist/css/state.css.map +1 -1
  51. package/dist/css/theme/color.css +2 -2
  52. package/dist/css/theme/color.css.map +1 -1
  53. package/dist/css/theme/typography.css +2 -2
  54. package/dist/css/theme/typography.css.map +1 -1
  55. package/dist/css/theme.css +2 -2
  56. package/dist/css/theme.css.map +1 -1
  57. package/dist/css/utilities/alignment.css +1 -1
  58. package/dist/css/utilities/alignment.css.map +1 -1
  59. package/dist/css/utilities/background.css +2 -2
  60. package/dist/css/utilities/background.css.map +1 -1
  61. package/dist/css/utilities/border.css +2 -2
  62. package/dist/css/utilities/border.css.map +1 -1
  63. package/dist/css/utilities/color.css +2 -2
  64. package/dist/css/utilities/color.css.map +1 -1
  65. package/dist/css/utilities/container.css +1 -1
  66. package/dist/css/utilities/container.css.map +1 -1
  67. package/dist/css/utilities/display.css +2 -2
  68. package/dist/css/utilities/display.css.map +1 -1
  69. package/dist/css/utilities/flex.css +2 -2
  70. package/dist/css/utilities/flex.css.map +1 -1
  71. package/dist/css/utilities/gradient.css +2 -2
  72. package/dist/css/utilities/gradient.css.map +1 -1
  73. package/dist/css/utilities/height.css +2 -2
  74. package/dist/css/utilities/height.css.map +1 -1
  75. package/dist/css/utilities/inset.css +2 -2
  76. package/dist/css/utilities/inset.css.map +1 -1
  77. package/dist/css/utilities/justification.css +1 -1
  78. package/dist/css/utilities/justification.css.map +1 -1
  79. package/dist/css/utilities/list.css +1 -1
  80. package/dist/css/utilities/list.css.map +1 -1
  81. package/dist/css/utilities/order.css +2 -2
  82. package/dist/css/utilities/order.css.map +1 -1
  83. package/dist/css/utilities/position.css +2 -2
  84. package/dist/css/utilities/position.css.map +1 -1
  85. package/dist/css/utilities/ratio.css +2 -2
  86. package/dist/css/utilities/ratio.css.map +1 -1
  87. package/dist/css/utilities/spacing.css +2 -2
  88. package/dist/css/utilities/spacing.css.map +1 -1
  89. package/dist/css/utilities/typography.css +2 -2
  90. package/dist/css/utilities/typography.css.map +1 -1
  91. package/dist/css/utilities/visibility.css +2 -2
  92. package/dist/css/utilities/visibility.css.map +1 -1
  93. package/dist/css/utilities/visually-hidden.css +2 -2
  94. package/dist/css/utilities/visually-hidden.css.map +1 -1
  95. package/dist/css/utilities/width.css +2 -2
  96. package/dist/css/utilities/width.css.map +1 -1
  97. package/dist/css/utilities/z-index.css +1 -1
  98. package/dist/css/utilities/z-index.css.map +1 -1
  99. package/dist/css/utilities.css +2 -2
  100. package/dist/css/utilities.css.map +1 -1
  101. package/dist/js/accordion.js.map +1 -1
  102. package/dist/js/alert.js.map +1 -1
  103. package/dist/js/carousel.js.map +1 -1
  104. package/dist/js/component/accordion.cjs.js.map +1 -1
  105. package/dist/js/component/accordion.es.js.map +1 -1
  106. package/dist/js/component/accordion.iife.js.map +1 -1
  107. package/dist/js/component/alert.cjs.js.map +1 -1
  108. package/dist/js/component/alert.es.js.map +1 -1
  109. package/dist/js/component/alert.iife.js.map +1 -1
  110. package/dist/js/component/carousel.cjs.js.map +1 -1
  111. package/dist/js/component/carousel.es.js.map +1 -1
  112. package/dist/js/component/carousel.iife.js.map +1 -1
  113. package/dist/js/component/disclosure.cjs.js +2 -2
  114. package/dist/js/component/disclosure.cjs.js.map +1 -1
  115. package/dist/js/component/disclosure.es.js +2 -2
  116. package/dist/js/component/disclosure.es.js.map +1 -1
  117. package/dist/js/component/disclosure.iife.js +2 -2
  118. package/dist/js/component/disclosure.iife.js.map +1 -1
  119. package/dist/js/component/tabs.cjs.js +5 -0
  120. package/dist/js/component/tabs.cjs.js.map +1 -0
  121. package/dist/js/component/tabs.es.js +5 -0
  122. package/dist/js/component/tabs.es.js.map +1 -0
  123. package/dist/js/component/tabs.iife.js +5 -0
  124. package/dist/js/component/tabs.iife.js.map +1 -0
  125. package/dist/js/disclosure.js +2 -2
  126. package/dist/js/disclosure.js.map +1 -1
  127. package/dist/js/generator/accordion.cjs.js.map +1 -1
  128. package/dist/js/generator/accordion.es.js.map +1 -1
  129. package/dist/js/generator/accordion.iife.js.map +1 -1
  130. package/dist/js/generator/alert.cjs.js.map +1 -1
  131. package/dist/js/generator/alert.es.js.map +1 -1
  132. package/dist/js/generator/alert.iife.js.map +1 -1
  133. package/dist/js/generator/carousel.cjs.js.map +1 -1
  134. package/dist/js/generator/carousel.es.js.map +1 -1
  135. package/dist/js/generator/carousel.iife.js.map +1 -1
  136. package/dist/js/generator/disclosure.cjs.js +2 -2
  137. package/dist/js/generator/disclosure.cjs.js.map +1 -1
  138. package/dist/js/generator/disclosure.es.js +2 -2
  139. package/dist/js/generator/disclosure.es.js.map +1 -1
  140. package/dist/js/generator/disclosure.iife.js +2 -2
  141. package/dist/js/generator/disclosure.iife.js.map +1 -1
  142. package/dist/js/generator/navigation.cjs.js.map +1 -1
  143. package/dist/js/generator/navigation.es.js.map +1 -1
  144. package/dist/js/generator/navigation.iife.js.map +1 -1
  145. package/dist/js/generator/tabs.cjs.js +5 -0
  146. package/dist/js/generator/tabs.cjs.js.map +1 -0
  147. package/dist/js/generator/tabs.es.js +5 -0
  148. package/dist/js/generator/tabs.es.js.map +1 -0
  149. package/dist/js/generator/tabs.iife.js +5 -0
  150. package/dist/js/generator/tabs.iife.js.map +1 -0
  151. package/dist/js/graupl.js +5 -5
  152. package/dist/js/graupl.js.map +1 -1
  153. package/dist/js/navigation.js.map +1 -1
  154. package/dist/js/tabs.js +5 -0
  155. package/dist/js/tabs.js.map +1 -0
  156. package/package.json +4 -3
  157. package/src/js/disclosure/Disclosure.js +25 -45
  158. package/src/js/domHelpers.js +39 -22
  159. package/src/js/tabs/TabToggle.js +378 -0
  160. package/src/js/tabs/Tabs.js +1091 -0
  161. package/src/js/tabs/generator.js +32 -0
  162. package/src/js/tabs/index.js +5 -0
  163. package/src/scss/_defaults.scss +21 -88
  164. package/src/scss/_variables.scss +70 -0
  165. package/src/scss/base/button/_defaults.scss +2 -24
  166. package/src/scss/base/button/_index.scss +53 -52
  167. package/src/scss/base/button/_mixins.scss +24 -58
  168. package/src/scss/base/button/_variables.scss +139 -0
  169. package/src/scss/base/form/_defaults.scss +72 -2
  170. package/src/scss/base/form/_index.scss +196 -70
  171. package/src/scss/base/form/_variables.scss +166 -0
  172. package/src/scss/base/link/_defaults.scss +31 -0
  173. package/src/scss/base/link/_index.scss +177 -172
  174. package/src/scss/base/link/_variables.scss +215 -0
  175. package/src/scss/base/table/_defaults.scss +1 -11
  176. package/src/scss/base/table/_index.scss +126 -117
  177. package/src/scss/base/table/_variables.scss +214 -3
  178. package/src/scss/component/_index.scss +1 -0
  179. package/src/scss/component/accordion/_defaults.scss +73 -22
  180. package/src/scss/component/accordion/_index.scss +437 -62
  181. package/src/scss/component/accordion/_variables.scss +527 -101
  182. package/src/scss/component/alert/_defaults.scss +23 -32
  183. package/src/scss/component/alert/_index.scss +236 -30
  184. package/src/scss/component/alert/_variables.scss +155 -6
  185. package/src/scss/component/badge/_defaults.scss +1 -0
  186. package/src/scss/component/badge/_index.scss +25 -28
  187. package/src/scss/component/badge/_variables.scss +66 -2
  188. package/src/scss/component/card/_defaults.scss +64 -13
  189. package/src/scss/component/card/_index.scss +276 -30
  190. package/src/scss/component/card/_variables.scss +132 -0
  191. package/src/scss/component/carousel/_defaults.scss +73 -15
  192. package/src/scss/component/carousel/_index.scss +357 -41
  193. package/src/scss/component/carousel/_variables.scss +391 -0
  194. package/src/scss/component/disclosure/_index.scss +4 -4
  195. package/src/scss/component/disclosure/_variables.scss +127 -2
  196. package/src/scss/component/input-group/_defaults.scss +10 -3
  197. package/src/scss/component/input-group/_index.scss +72 -4
  198. package/src/scss/component/input-group/_variables.scss +37 -0
  199. package/src/scss/component/list/_defaults.scss +35 -2
  200. package/src/scss/component/list/_index.scss +159 -5
  201. package/src/scss/component/list/_variables.scss +152 -0
  202. package/src/scss/component/menu/_defaults.scss +49 -7
  203. package/src/scss/component/menu/_index.scss +325 -99
  204. package/src/scss/component/menu/_variables.scss +484 -1
  205. package/src/scss/component/navigation/_defaults.scss +45 -3
  206. package/src/scss/component/navigation/_index.scss +249 -98
  207. package/src/scss/component/navigation/_variables.scss +334 -5
  208. package/src/scss/component/tabs/_defaults.scss +82 -0
  209. package/src/scss/component/tabs/_index.scss +497 -0
  210. package/src/scss/component/tabs/_variables.scss +974 -0
  211. package/src/scss/layout/columns/_defaults.scss +5 -3
  212. package/src/scss/layout/columns/_index.scss +27 -21
  213. package/src/scss/layout/columns/_variables.scss +13 -0
  214. package/src/scss/layout/container/_defaults.scss +28 -11
  215. package/src/scss/layout/container/_index.scss +305 -161
  216. package/src/scss/layout/container/_variables.scss +148 -15
  217. package/src/scss/layout/flex-columns/_defaults.scss +5 -3
  218. package/src/scss/layout/flex-columns/_index.scss +28 -20
  219. package/src/scss/layout/flex-columns/_variables.scss +9 -0
  220. package/src/scss/mixins/_state.scss +37 -0
  221. package/src/scss/mixins/_theme.scss +36 -1
  222. package/src/scss/state/focus/_index.scss +17 -12
  223. package/src/scss/state/focus/_variables.scss +30 -4
  224. package/src/scss/theme/color/_defaults.scss +6 -35
  225. package/src/scss/theme/color/_index.scss +167 -1
  226. package/src/scss/theme/color/_variables.scss +155 -0
  227. package/src/scss/theme/typography/_defaults.scss +26 -19
  228. package/src/scss/theme/typography/_index.scss +176 -20
  229. package/src/scss/theme/typography/_variables.scss +186 -0
  230. package/src/scss/utilities/_template/_index.scss +0 -1
  231. package/src/scss/utilities/alignment/_defaults.scss +0 -33
  232. package/src/scss/utilities/alignment/_index.scss +24 -25
  233. package/src/scss/utilities/background/_defaults.scss +0 -77
  234. package/src/scss/utilities/background/_index.scss +32 -37
  235. package/src/scss/utilities/border/_defaults.scss +0 -33
  236. package/src/scss/utilities/border/_index.scss +25 -26
  237. package/src/scss/utilities/color/_defaults.scss +0 -19
  238. package/src/scss/utilities/color/_index.scss +22 -22
  239. package/src/scss/utilities/container/_defaults.scss +0 -11
  240. package/src/scss/utilities/container/_index.scss +20 -19
  241. package/src/scss/utilities/display/_defaults.scss +0 -11
  242. package/src/scss/utilities/display/_index.scss +20 -19
  243. package/src/scss/utilities/flex/_defaults.scss +0 -55
  244. package/src/scss/utilities/flex/_index.scss +28 -31
  245. package/src/scss/utilities/gradient/_defaults.scss +0 -28
  246. package/src/scss/utilities/gradient/_index.scss +27 -28
  247. package/src/scss/utilities/gradient/_variables.scss +15 -0
  248. package/src/scss/utilities/height/_defaults.scss +0 -8
  249. package/src/scss/utilities/height/_index.scss +22 -21
  250. package/src/scss/utilities/inset/_defaults.scss +0 -16
  251. package/src/scss/utilities/inset/_index.scss +20 -20
  252. package/src/scss/utilities/justification/_defaults.scss +0 -33
  253. package/src/scss/utilities/justification/_index.scss +24 -25
  254. package/src/scss/utilities/list/_defaults.scss +0 -22
  255. package/src/scss/utilities/list/_index.scss +22 -22
  256. package/src/scss/utilities/order/_defaults.scss +0 -8
  257. package/src/scss/utilities/order/_index.scss +20 -19
  258. package/src/scss/utilities/position/_defaults.scss +0 -11
  259. package/src/scss/utilities/position/_index.scss +20 -19
  260. package/src/scss/utilities/ratio/_defaults.scss +0 -8
  261. package/src/scss/utilities/ratio/_index.scss +22 -21
  262. package/src/scss/utilities/ratio/_variables.scss +6 -0
  263. package/src/scss/utilities/spacing/_defaults.scss +0 -11
  264. package/src/scss/utilities/spacing/_index.scss +18 -17
  265. package/src/scss/utilities/typography/_defaults.scss +0 -22
  266. package/src/scss/utilities/typography/_index.scss +27 -26
  267. package/src/scss/utilities/visibility/_defaults.scss +0 -11
  268. package/src/scss/utilities/visibility/_index.scss +19 -18
  269. package/src/scss/utilities/visually-hidden/_index.scss +19 -17
  270. package/src/scss/utilities/width/_defaults.scss +0 -8
  271. package/src/scss/utilities/width/_index.scss +21 -20
  272. package/src/scss/utilities/z-index/_defaults.scss +0 -11
  273. package/src/scss/utilities/z-index/_index.scss +19 -18
@@ -139,22 +139,22 @@ class Disclosure {
139
139
  _closeOnBlur = false;
140
140
 
141
141
  /**
142
- * The width of the screen (in pixels) that the disclosure will automatically open/close itself.
142
+ * The width of the screen that the disclosure will automatically open/close itself.
143
143
  *
144
144
  * @protected
145
145
  *
146
- * @type {number}
146
+ * @type {string}
147
147
  */
148
- _breakpointWidth = -1;
148
+ _breakpointWidth = "";
149
149
 
150
150
  /**
151
- * This ResizeObserver for the disclosure.
151
+ * This MediaQueryList for the disclosure.
152
152
  *
153
153
  * @protected
154
154
  *
155
- * @type {ResizeObserver|null}
155
+ * @type {MediaQueryList|null}
156
156
  */
157
- _observer = null;
157
+ _mediaQueryList = null;
158
158
 
159
159
  /**
160
160
  * The event that is triggered when the disclosure expands.
@@ -231,7 +231,7 @@ class Disclosure {
231
231
  * @param {boolean} [options.openDuration = -1] - The duration of the transition from "closed" to "open" states (in milliseconds).
232
232
  * @param {boolean} [options.closeDuration = -1] - The duration of the transition from "open" to "closed" states (in milliseconds).
233
233
  * @param {boolean} [options.closeOnBlur = false] - Whether to close the disclosure when it loses focus in the dom.
234
- * @param {boolean} [options.minWidth = -1] - The width of the screen (in pixels) that the disclosure will automatically open/close itself.
234
+ * @param {?string} [options.minWidth = ""] - The width of the screen that the disclosure will automatically open/close itself.
235
235
  * @param {boolean} [options.autoOpen = false] - Whether to automatically open when above the minWidth.
236
236
  * @param {?string} [options.prefix = graupl-] - The prefix to use for CSS custom properties.
237
237
  * @param {?(string|string[])} [options.initializeClass = initializing] - The class to apply when a disclosure is initialzing.
@@ -248,7 +248,7 @@ class Disclosure {
248
248
  openDuration = -1,
249
249
  closeDuration = -1,
250
250
  closeOnBlur = false,
251
- minWidth = -1,
251
+ minWidth = "",
252
252
  autoOpen = false,
253
253
  prefix = "graupl-",
254
254
  initializeClass = "initializing",
@@ -276,7 +276,7 @@ class Disclosure {
276
276
  this._closeOnBlur = closeOnBlur;
277
277
 
278
278
  // Set collapse width and auto open functionality.
279
- this._breakpointWidth = minWidth;
279
+ this._breakpointWidth = minWidth || "";
280
280
  this._shouldOpen = autoOpen;
281
281
 
282
282
  // Set the prefix.
@@ -516,7 +516,7 @@ class Disclosure {
516
516
  }
517
517
 
518
518
  /**
519
- * The width of the screen (in pixels) that the disclosure will automatically open/close itself.
519
+ * The width of the screen that the disclosure will automatically open/close itself.
520
520
  *
521
521
  * @type {number}
522
522
  *
@@ -527,7 +527,7 @@ class Disclosure {
527
527
  }
528
528
 
529
529
  set minWidth(value) {
530
- isValidType("number", { value });
530
+ isValidType("string", { value });
531
531
 
532
532
  if (this._breakpointWidth !== value) {
533
533
  this._breakpointWidth = value;
@@ -1105,45 +1105,25 @@ class Disclosure {
1105
1105
  }
1106
1106
 
1107
1107
  _handleResize() {
1108
- if (this._breakpointWidth <= 0) {
1108
+ if (this._breakpointWidth === "") {
1109
1109
  return;
1110
1110
  }
1111
1111
 
1112
- let width = 0;
1112
+ this._mediaQueryList = window.matchMedia(
1113
+ `(width <= ${this._breakpointWidth})`
1114
+ );
1113
1115
 
1114
- this._observer = new ResizeObserver((entries) => {
1115
- requestAnimationFrame(() => {
1116
- for (const entry of entries) {
1117
- const boxSize = Array.isArray(entry.contentBoxSize)
1118
- ? entry.contentBoxSize[0]
1119
- : entry.contentBoxSize;
1120
- const inlineSize =
1121
- boxSize && typeof boxSize.inlineSize === "number"
1122
- ? boxSize.inlineSize
1123
- : entry.contentRect.width;
1124
-
1125
- if (typeof inlineSize !== "number") continue;
1126
-
1127
- if (width === inlineSize) continue;
1128
-
1129
- const belowBreakpoint = inlineSize <= this.minWidth;
1130
- const aboveBreakpoint = inlineSize > this.minWidth;
1131
-
1132
- if (belowBreakpoint && this.isOpen) {
1133
- this.close({ preserveState: true });
1134
- } else if (
1135
- aboveBreakpoint &&
1136
- !this.isOpen &&
1137
- (this.hasOpened || this.shouldOpen)
1138
- ) {
1139
- this.open();
1140
- }
1141
-
1142
- width = inlineSize;
1143
- }
1144
- });
1116
+ this._mediaQueryList.addEventListener("change", (event) => {
1117
+ if (event.matches && this.isOpen) {
1118
+ this.close({ preserveState: true });
1119
+ } else if (
1120
+ !event.matches &&
1121
+ !this.isOpen &&
1122
+ (this.hasOpened || this.shouldOpen)
1123
+ ) {
1124
+ this.open();
1125
+ }
1145
1126
  });
1146
- this._observer.observe(document.body);
1147
1127
  }
1148
1128
 
1149
1129
  /**
@@ -39,10 +39,11 @@ export function removeClass(className, element) {
39
39
  /**
40
40
  * Select all focusable elements within a given context.
41
41
  *
42
- * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
43
- * @return {HTMLElement[]} - An array of focusable elements.
42
+ * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
43
+ * @param {?function(HTMLElement): boolean} [fn = null] - An optional addition filter function to process out focusable elements.
44
+ * @return {HTMLElement[]} - An array of focusable elements.
44
45
  */
45
- export function selectAllFocusableElements(context = document) {
46
+ export function selectAllFocusableElements(context = document, fn = null) {
46
47
  const querySelector =
47
48
  "a[href],area[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),[tabindex]";
48
49
  const elements = Array.from(context.querySelectorAll(querySelector));
@@ -55,17 +56,22 @@ export function selectAllFocusableElements(context = document) {
55
56
  return check;
56
57
  });
57
58
 
58
- return tabbableElements;
59
+ if (fn !== null) {
60
+ return tabbableElements.filter(fn);
61
+ } else {
62
+ return tabbableElements;
63
+ }
59
64
  }
60
65
 
61
66
  /**
62
67
  * Select the first focusable element within a given context.
63
68
  *
64
- * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
65
- * @return {HTMLElement|boolean} - The first focusable element or false if none found.
69
+ * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
70
+ * @param {?function(HTMLElement): boolean} [fn = null] - An optional addition filter function to process out focusable elements.
71
+ * @return {HTMLElement|boolean} - The first focusable element or false if none found.
66
72
  */
67
- export function selectFirstFocusableElement(context = document) {
68
- const tabbableElements = selectAllFocusableElements(context);
73
+ export function selectFirstFocusableElement(context = document, fn = null) {
74
+ const tabbableElements = selectAllFocusableElements(context, fn);
69
75
 
70
76
  return tabbableElements[0] || false;
71
77
  }
@@ -73,11 +79,12 @@ export function selectFirstFocusableElement(context = document) {
73
79
  /**
74
80
  * Select the last focusable element within a given context.
75
81
  *
76
- * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
77
- * @return {HTMLElement|boolean} - The last focusable element or false if none found.
82
+ * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
83
+ * @param {?function(HTMLElement): boolean} [fn = null] - An optional addition filter function to process out focusable elements.
84
+ * @return {HTMLElement|boolean} - The last focusable element or false if none found.
78
85
  */
79
- export function selectLastFocusableElement(context = document) {
80
- const tabbableElements = selectAllFocusableElements(context);
86
+ export function selectLastFocusableElement(context = document, fn = null) {
87
+ const tabbableElements = selectAllFocusableElements(context, fn);
81
88
 
82
89
  return tabbableElements[tabbableElements.length - 1] || false;
83
90
  }
@@ -85,12 +92,17 @@ export function selectLastFocusableElement(context = document) {
85
92
  /**
86
93
  * Select the next focusable element relative to the given element within a context.
87
94
  *
88
- * @param {HTMLElement} element - The reference element.
89
- * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
90
- * @return {HTMLElement|boolean} - The next focusable element or false if none found.
95
+ * @param {HTMLElement} element - The reference element.
96
+ * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
97
+ * @param {?function(HTMLElement): boolean} [fn = null] - An optional addition filter function to process out focusable elements.
98
+ * @return {HTMLElement|boolean} - The next focusable element or false if none found.
91
99
  */
92
- export function selectNextFocusableElement(element, context = document) {
93
- const tabbableElements = selectAllFocusableElements(context);
100
+ export function selectNextFocusableElement(
101
+ element,
102
+ context = document,
103
+ fn = null
104
+ ) {
105
+ const tabbableElements = selectAllFocusableElements(context, fn);
94
106
  const index = tabbableElements.indexOf(element);
95
107
 
96
108
  return index === tabbableElements.length - 1
@@ -101,12 +113,17 @@ export function selectNextFocusableElement(element, context = document) {
101
113
  /**
102
114
  * Select the previous focusable element relative to the given element within a context.
103
115
  *
104
- * @param {HTMLElement} element - The reference element.
105
- * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
106
- * @return {HTMLElement|boolean} - The previous focusable element or false if none found.
116
+ * @param {HTMLElement} element - The reference element.
117
+ * @param {HTMLElement} [context = document] - The context in which to search for focusable elements.
118
+ * @param {?function(HTMLElement): boolean} [fn = null] - An optional addition filter function to process out focusable elements.
119
+ * @return {HTMLElement|boolean} - The previous focusable element or false if none found.
107
120
  */
108
- export function selectPreviousFocusableElement(element, context = document) {
109
- const tabbableElements = selectAllFocusableElements(context);
121
+ export function selectPreviousFocusableElement(
122
+ element,
123
+ context = document,
124
+ fn = null
125
+ ) {
126
+ const tabbableElements = selectAllFocusableElements(context, fn);
110
127
  const index = tabbableElements.indexOf(element);
111
128
 
112
129
  return index === 0 ? false : tabbableElements[index - 1];
@@ -0,0 +1,378 @@
1
+ /* global Tabs */
2
+
3
+ import { addClass, removeClass } from "@graupl/core/src/domHelpers.js";
4
+ import { TransactionalValue } from "../TransactionalValue.js";
5
+
6
+ class TabToggle {
7
+ /**
8
+ * The DOM elements within the tab toggle.
9
+ *
10
+ * @protected
11
+ *
12
+ * @type {Object<HTMLElement>}
13
+ *
14
+ * @property {HTMLElement} toggle - The toggle element.
15
+ * @property {HTMLElement} content - The content element.
16
+ */
17
+ _dom = {
18
+ toggle: null,
19
+ content: null,
20
+ };
21
+
22
+ /**
23
+ * The declared elements within the tab toggle.
24
+ *
25
+ * @protected
26
+ *
27
+ * @type {Object<Tabs>}
28
+ *
29
+ * @property {Tabs} parent - The parent tabs element.
30
+ */
31
+ _elements = {
32
+ parent: null,
33
+ };
34
+
35
+ /**
36
+ * The active state of the tab toggle.
37
+ *
38
+ * @protected
39
+ *
40
+ * @type {TransactionalValue}
41
+ */
42
+ _active = new TransactionalValue(false);
43
+
44
+ /**
45
+ * The event that is triggered when the tab toggle is shown.
46
+ *
47
+ * @protected
48
+ *
49
+ * @event grauplTabToggleActivate
50
+ *
51
+ * @type {CustomEvent}
52
+ *
53
+ * @property {boolean} bubbles - A flag to bubble the event.
54
+ * @property {Object<TabToggle>} detail - The details object containing the Accordion item itself.
55
+ */
56
+ _activateEvent = new CustomEvent("grauplTabToggleActivate", {
57
+ bubbles: true,
58
+ detail: { item: this },
59
+ });
60
+
61
+ /**
62
+ * The event that is triggered when the tab toggle is hidden.
63
+ *
64
+ * @protected
65
+ *
66
+ * @event grauplTabToggleDeactivate
67
+ *
68
+ * @type {CustomEvent}
69
+ *
70
+ * @property {boolean} bubbles - A flag to bubble the event.
71
+ * @property {Object<TabToggle>} detail - The details object containing the Accordion item itself.
72
+ */
73
+ _deactivateEvent = new CustomEvent("grauplTabToggleDeactivate", {
74
+ bubbles: true,
75
+ detail: { item: this },
76
+ });
77
+
78
+ constructor({ toggleElement, contentElement, parentTab }) {
79
+ // Set DOM elements.
80
+ this._dom.toggle = toggleElement;
81
+ this._dom.content = contentElement;
82
+
83
+ // Set the parent tab instance.
84
+ this._elements.parent = parentTab;
85
+ }
86
+
87
+ /**
88
+ * Initializes the tab toggle.
89
+ */
90
+ initialize() {
91
+ // Set up the DOM.
92
+ this._setIds();
93
+ this._setAriaAttributes();
94
+
95
+ if (this.dom.toggle.getAttribute("aria-selected") === "true") {
96
+ this.show();
97
+ } else {
98
+ this._deactivate({ emit: false, transition: false });
99
+ }
100
+ }
101
+
102
+ /**
103
+ * The DOM elements of the tab toggle.
104
+ *
105
+ * @readonly
106
+ *
107
+ * @type {Object<HTMLElement>}
108
+ *
109
+ * @see _dom
110
+ */
111
+ get dom() {
112
+ return this._dom;
113
+ }
114
+
115
+ /**
116
+ * The declared elements in the tab toggle.
117
+ *
118
+ * @protected
119
+ *
120
+ * @type {Object<Tabs>}
121
+ *
122
+ * @see _elements
123
+ */
124
+ get elements() {
125
+ return this._elements;
126
+ }
127
+
128
+ /**
129
+ * The active state of the tab toggle.
130
+ *
131
+ * @readonly
132
+ *
133
+ * @type {TransactionalValue}
134
+ *
135
+ * @see _active
136
+ */
137
+ get isActive() {
138
+ return this._active.value;
139
+ }
140
+
141
+ /**
142
+ * Sets the IDs of the tab toggle and it's content if they do not already exist.
143
+ *
144
+ * The generated IDs use the following format:
145
+ * - toggle: `tab-toggle-${key}-${index}`
146
+ * - content: `tab-content-${key}-${index}`
147
+ *
148
+ * Where `${key}` is the parent's key and `${index}` is the toggles index in the list of the parent's toggles.
149
+ *
150
+ * @protected
151
+ */
152
+ _setIds() {
153
+ // Get the required information for IDs.
154
+ const { key } = this.elements.parent;
155
+ const index = this.elements.parent.dom.tabToggle.indexOf(this.dom.toggle);
156
+
157
+ this.dom.toggle.id = this.dom.toggle.id || `tab-toggle-${key}-${index}`;
158
+ this.dom.content.id = this.dom.content.id || `tab-content-${key}-${index}`;
159
+ }
160
+
161
+ /**
162
+ * Sets the ARIA attributes on the disclosure and it's content.
163
+ */
164
+ _setAriaAttributes() {
165
+ // Set the ARIA attributes for the tab item toggle.
166
+ this.dom.toggle.setAttribute("role", "tab");
167
+
168
+ // If aria-selected is not explicitly set to "true", then set it to "false".
169
+ if (this.dom.toggle.getAttribute("aria-selected") !== "true") {
170
+ this.dom.toggle.setAttribute("aria-selected", "false");
171
+ }
172
+
173
+ // Set the aria-controls attribute for the toggle.
174
+ this.dom.toggle.setAttribute("aria-controls", this.dom.content.id);
175
+
176
+ // Set the role for the content.
177
+ this.dom.content.setAttribute("role", "tabpanel");
178
+ }
179
+
180
+ /**
181
+ * Activate the toggle.
182
+ *
183
+ * Sets the toggles's `aria-selected` to "true", adds the
184
+ * open class to the content, and removes the closed class from the content.
185
+ *
186
+ * @protected
187
+ *
188
+ * @fires grauplTabToggleActivate
189
+ *
190
+ * @param {Object<boolean>} [options = {}] - Options for activating the toggle.
191
+ * @param {boolean} [options.emit = true] - Emit the activat event once activated.
192
+ * @param {boolean} [options.transition = true] - Respect the transition class.
193
+ */
194
+ _activate({ emit = true, transition = true } = {}) {
195
+ const { closeClass, openClass, transitionClass, openDuration } =
196
+ this.elements.parent;
197
+
198
+ // Set aria-selected to true when hiding accordion item.
199
+ this.dom.toggle.setAttribute("aria-selected", "true");
200
+
201
+ // If we're dealing with transition classes, then we need to utilize
202
+ // requestAnimationFrame to add the transition class, remove the hide class,
203
+ // add the show class, and finally remove the transition class.
204
+ //
205
+ // If `transition` is false, then it doesn't matter if the transition class
206
+ // is set. Do not use the transition.
207
+ if (transition && transitionClass !== "") {
208
+ addClass(transitionClass, this.dom.content);
209
+
210
+ requestAnimationFrame(() => {
211
+ removeClass(closeClass, this.dom.content);
212
+
213
+ requestAnimationFrame(() => {
214
+ addClass(openClass, this.dom.content);
215
+
216
+ requestAnimationFrame(() => {
217
+ setTimeout(() => {
218
+ removeClass(transitionClass, this.dom.content);
219
+ }, openDuration);
220
+ });
221
+ });
222
+ });
223
+ } else {
224
+ // Add the show class
225
+ addClass(openClass, this.dom.content);
226
+
227
+ // Remove the hide class.
228
+ removeClass(closeClass, this.dom.content);
229
+ }
230
+
231
+ if (emit) {
232
+ this.dom.toggle.dispatchEvent(this._activateEvent);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Deactivates the disclosure.
238
+ *
239
+ * Sets the toggles's `aria-expanded` to "false", adds the
240
+ * close class to the content, and removes the open class from the content.
241
+ *
242
+ * @protected
243
+ *
244
+ * @fires grauplTabToggleDeactivate
245
+ *
246
+ * @param {Object<boolean>} [options = {}] - Options for collapsing the toggle.
247
+ * @param {boolean} [options.emit = true] - Emit the deactivate event once deactivated.
248
+ * @param {boolean} [options.transition = true] - Respect the transition class.
249
+ */
250
+ _deactivate({ emit = true, transition = true } = {}) {
251
+ const { closeClass, openClass, transitionClass, closeDuration } =
252
+ this.elements.parent;
253
+
254
+ // Set aria-selected to false when hiding accordion item.
255
+ this.dom.toggle.setAttribute("aria-selected", "false");
256
+
257
+ // If we're dealing with transition classes, then we need to utilize
258
+ // requestAnimationFrame to add the transition class, remove the show class,
259
+ // add the hide class, and finally remove the transition class.
260
+ //
261
+ // If `transition` is false, then it doesn't matter if the transition class
262
+ // is set. Do not use the transition.
263
+ if (transition && transitionClass !== "") {
264
+ addClass(transitionClass, this.dom.content);
265
+
266
+ requestAnimationFrame(() => {
267
+ removeClass(openClass, this.dom.content);
268
+
269
+ requestAnimationFrame(() => {
270
+ addClass(closeClass, this.dom.content);
271
+
272
+ requestAnimationFrame(() => {
273
+ setTimeout(() => {
274
+ removeClass(transitionClass, this.dom.content);
275
+ }, closeDuration);
276
+ });
277
+ });
278
+ });
279
+ } else {
280
+ // Add the hide class
281
+ addClass(closeClass, this.dom.content);
282
+
283
+ // Remove the show class.
284
+ removeClass(openClass, this.dom.content);
285
+ }
286
+
287
+ if (emit) {
288
+ this.dom.toggle.dispatchEvent(this._deactivateEvent);
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Shows the tab toggle's content.
294
+ *
295
+ * @param {Object<boolean>} [options = {}] - Options for showing the toggle.
296
+ * @param {boolean} [options.force = false] - Whether to force the show action.
297
+ * @param {boolean} [options.preserveState = false] - Whether to preserve the active state.
298
+ */
299
+ show({ force = false, preserveState = false } = {}) {
300
+ if (this.isActive && !force) return;
301
+
302
+ // Set the focus state of the parent tabs element.
303
+ this.elements.parent.focusState = "self";
304
+
305
+ // Activate the toggle.
306
+ this._activate();
307
+
308
+ // Set the active state
309
+ this._active.value = true;
310
+
311
+ if (!preserveState) {
312
+ this._active.commit();
313
+ }
314
+
315
+ // Set the tabindex to 0 so it can be focused.
316
+ this.dom.toggle.setAttribute("tabindex", "0");
317
+
318
+ // Deactivate all sibling tab toggles.
319
+ this.deactivateSiblings();
320
+ }
321
+
322
+ /**
323
+ * Hides the tab toggle's content.
324
+ *
325
+ * @param {Object<boolean>} [options = {}] - Options for hiding the toggle.
326
+ * @param {boolean} [options.force = false] - Whether to force the show action.
327
+ * @param {boolean} [options.preserveState = false] - Whether to preserve the active state.
328
+ */
329
+ hide({ force = false, preserveState = false } = {}) {
330
+ if (!this.isActive && !force) return;
331
+
332
+ // Set the focus state of the parent tabs element.
333
+ this.elements.parent.focusState = "none";
334
+
335
+ // Deactivate the toggle.
336
+ this._deactivate();
337
+
338
+ // Set the active state
339
+ this._active.value = false;
340
+
341
+ if (!preserveState) {
342
+ this._active.commit();
343
+ }
344
+
345
+ // Set the tabindex to -1.
346
+ this.dom.toggle.setAttribute("tabindex", "-1");
347
+ }
348
+
349
+ /**
350
+ * Focuses the accordion item.
351
+ *
352
+ * @public
353
+ */
354
+ focus() {
355
+ this.dom.toggle.focus();
356
+ }
357
+
358
+ /**
359
+ * Blurs the accordion item.
360
+ *
361
+ * @public
362
+ */
363
+ blur() {
364
+ this.dom.toggle.blur();
365
+ }
366
+
367
+ deactivateSiblings() {
368
+ if (this.elements.parent) {
369
+ this.elements.parent.elements.tabToggle.forEach((toggle) => {
370
+ if (toggle !== this) {
371
+ toggle.hide();
372
+ }
373
+ });
374
+ }
375
+ }
376
+ }
377
+
378
+ export default TabToggle;