@rhavenside/baseline 2.0.0

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 (80) hide show
  1. package/README.md +472 -0
  2. package/dist/base-line.css +5 -0
  3. package/dist/base-line.css.map +1 -0
  4. package/dist/fonts/GoogleSansCode-Bold.ttf +0 -0
  5. package/dist/fonts/GoogleSansCode-BoldItalic.ttf +0 -0
  6. package/dist/fonts/GoogleSansCode-ExtraBold.ttf +0 -0
  7. package/dist/fonts/GoogleSansCode-ExtraBoldItalic.ttf +0 -0
  8. package/dist/fonts/GoogleSansCode-Italic-VariableFont_wght.ttf +0 -0
  9. package/dist/fonts/GoogleSansCode-Italic.ttf +0 -0
  10. package/dist/fonts/GoogleSansCode-Light.ttf +0 -0
  11. package/dist/fonts/GoogleSansCode-LightItalic.ttf +0 -0
  12. package/dist/fonts/GoogleSansCode-Medium.ttf +0 -0
  13. package/dist/fonts/GoogleSansCode-MediumItalic.ttf +0 -0
  14. package/dist/fonts/GoogleSansCode-Regular.ttf +0 -0
  15. package/dist/fonts/GoogleSansCode-SemiBold.ttf +0 -0
  16. package/dist/fonts/GoogleSansCode-SemiBoldItalic.ttf +0 -0
  17. package/dist/fonts/GoogleSansCode-VariableFont_wght.ttf +0 -0
  18. package/dist/fonts/RobotoCondensed-Black.ttf +0 -0
  19. package/dist/fonts/RobotoCondensed-BlackItalic.ttf +0 -0
  20. package/dist/fonts/RobotoCondensed-Bold.ttf +0 -0
  21. package/dist/fonts/RobotoCondensed-BoldItalic.ttf +0 -0
  22. package/dist/fonts/RobotoCondensed-ExtraBold.ttf +0 -0
  23. package/dist/fonts/RobotoCondensed-ExtraBoldItalic.ttf +0 -0
  24. package/dist/fonts/RobotoCondensed-ExtraLight.ttf +0 -0
  25. package/dist/fonts/RobotoCondensed-ExtraLightItalic.ttf +0 -0
  26. package/dist/fonts/RobotoCondensed-Italic-VariableFont_wght.ttf +0 -0
  27. package/dist/fonts/RobotoCondensed-Italic.ttf +0 -0
  28. package/dist/fonts/RobotoCondensed-Light.ttf +0 -0
  29. package/dist/fonts/RobotoCondensed-LightItalic.ttf +0 -0
  30. package/dist/fonts/RobotoCondensed-Medium.ttf +0 -0
  31. package/dist/fonts/RobotoCondensed-MediumItalic.ttf +0 -0
  32. package/dist/fonts/RobotoCondensed-Regular.ttf +0 -0
  33. package/dist/fonts/RobotoCondensed-SemiBold.ttf +0 -0
  34. package/dist/fonts/RobotoCondensed-SemiBoldItalic.ttf +0 -0
  35. package/dist/fonts/RobotoCondensed-Thin.ttf +0 -0
  36. package/dist/fonts/RobotoCondensed-ThinItalic.ttf +0 -0
  37. package/dist/fonts/RobotoCondensed-VariableFont_wght.ttf +0 -0
  38. package/dist/fonts/ZalandoSansExpanded-Black.ttf +0 -0
  39. package/dist/fonts/ZalandoSansExpanded-BlackItalic.ttf +0 -0
  40. package/dist/fonts/ZalandoSansExpanded-Bold.ttf +0 -0
  41. package/dist/fonts/ZalandoSansExpanded-BoldItalic.ttf +0 -0
  42. package/dist/fonts/ZalandoSansExpanded-ExtraBold.ttf +0 -0
  43. package/dist/fonts/ZalandoSansExpanded-ExtraBoldItalic.ttf +0 -0
  44. package/dist/fonts/ZalandoSansExpanded-ExtraLight.ttf +0 -0
  45. package/dist/fonts/ZalandoSansExpanded-ExtraLightItalic.ttf +0 -0
  46. package/dist/fonts/ZalandoSansExpanded-Italic-VariableFont_wght.ttf +0 -0
  47. package/dist/fonts/ZalandoSansExpanded-Italic.ttf +0 -0
  48. package/dist/fonts/ZalandoSansExpanded-Light.ttf +0 -0
  49. package/dist/fonts/ZalandoSansExpanded-LightItalic.ttf +0 -0
  50. package/dist/fonts/ZalandoSansExpanded-Medium.ttf +0 -0
  51. package/dist/fonts/ZalandoSansExpanded-MediumItalic.ttf +0 -0
  52. package/dist/fonts/ZalandoSansExpanded-Regular.ttf +0 -0
  53. package/dist/fonts/ZalandoSansExpanded-SemiBold.ttf +0 -0
  54. package/dist/fonts/ZalandoSansExpanded-SemiBoldItalic.ttf +0 -0
  55. package/dist/fonts/ZalandoSansExpanded-VariableFont_wght.ttf +0 -0
  56. package/dist/fonts/baseline-icons.woff +0 -0
  57. package/dist/fonts/baseline-icons.woff2 +0 -0
  58. package/dist/js/accordion.js +103 -0
  59. package/dist/js/alert.js +91 -0
  60. package/dist/js/base.js +146 -0
  61. package/dist/js/button.js +80 -0
  62. package/dist/js/carousel.js +427 -0
  63. package/dist/js/collapse.js +233 -0
  64. package/dist/js/color-modes.js +70 -0
  65. package/dist/js/component.js +114 -0
  66. package/dist/js/dropdown.js +348 -0
  67. package/dist/js/index.js +108 -0
  68. package/dist/js/modal.js +440 -0
  69. package/dist/js/offcanvas.js +356 -0
  70. package/dist/js/popover.js +241 -0
  71. package/dist/js/swipe.js +143 -0
  72. package/dist/js/tab.js +285 -0
  73. package/dist/js/toast.js +228 -0
  74. package/dist/js/tooltip.js +716 -0
  75. package/dist/js/util/backdrop.js +133 -0
  76. package/dist/js/util/component-functions.js +111 -0
  77. package/dist/js/util/focustrap.js +101 -0
  78. package/dist/js/util/scrollbar.js +111 -0
  79. package/dist/js/util.js +564 -0
  80. package/package.json +47 -0
@@ -0,0 +1,427 @@
1
+ /**
2
+ * Base-Line Carousel Component
3
+ * Baseline 2.0 compatible carousel with Base-Line naming
4
+ */
5
+
6
+ import { BaseComponent } from './base.js'
7
+ import { getElement, getElements, on, off, one, executeAfterTransition, reflow, triggerTransitionEnd, onDOMContentLoaded, isVisible, getNextActiveElement, getElementFromSelector } from './util.js'
8
+ import { addClass, removeClass, hasClass } from './component.js'
9
+ import Swipe from './swipe.js'
10
+
11
+ const NAME = 'carousel'
12
+ const DATA_KEY = `c.${NAME}`
13
+ const EVENT_KEY = `.${DATA_KEY}`
14
+ const DATA_API_KEY = '.data-api'
15
+
16
+ const ARROW_LEFT_KEY = 'ArrowLeft'
17
+ const ARROW_RIGHT_KEY = 'ArrowRight'
18
+ const TOUCHEVENT_COMPAT_WAIT = 500
19
+
20
+ const ORDER_NEXT = 'next'
21
+ const ORDER_PREV = 'prev'
22
+ const DIRECTION_LEFT = 'left'
23
+ const DIRECTION_RIGHT = 'right'
24
+
25
+ const EVENT_SLIDE = `slide${EVENT_KEY}`
26
+ const EVENT_SLID = `slid${EVENT_KEY}`
27
+ const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
28
+ const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`
29
+ const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`
30
+ const EVENT_DRAG_START = `dragstart${EVENT_KEY}`
31
+ const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
32
+ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
33
+
34
+ const CLASS_NAME_CAROUSEL = 'c-carousel'
35
+ const CLASS_NAME_ACTIVE = 'is-active'
36
+ const CLASS_NAME_SLIDE = 'c-slide'
37
+ const CLASS_NAME_END = 'c-carousel-item-end'
38
+ const CLASS_NAME_START = 'c-carousel-item-start'
39
+ const CLASS_NAME_NEXT = 'c-carousel-item-next'
40
+ const CLASS_NAME_PREV = 'c-carousel-item-prev'
41
+
42
+ const SELECTOR_ACTIVE = '.is-active'
43
+ const SELECTOR_ITEM = '.c-carousel__item'
44
+ const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM
45
+ const SELECTOR_ITEM_IMG = '.c-carousel__item img'
46
+ const SELECTOR_INDICATORS = '.c-carousel-indicators'
47
+ const SELECTOR_DATA_SLIDE = '[data-c-slide], [data-c-slide-to]'
48
+ const SELECTOR_DATA_RIDE = '[data-c-ride="carousel"]'
49
+
50
+ const KEY_TO_DIRECTION = {
51
+ [ARROW_LEFT_KEY]: DIRECTION_RIGHT,
52
+ [ARROW_RIGHT_KEY]: DIRECTION_LEFT
53
+ }
54
+
55
+ const Default = {
56
+ interval: 5000,
57
+ keyboard: true,
58
+ pause: 'hover',
59
+ ride: false,
60
+ touch: true,
61
+ wrap: true
62
+ }
63
+
64
+ class Carousel extends BaseComponent {
65
+ constructor(element, config) {
66
+ super(element, config)
67
+
68
+ this._interval = null
69
+ this._activeElement = null
70
+ this._isSliding = false
71
+ this.touchTimeout = null
72
+ this._swipeHelper = null
73
+
74
+ // Read data attributes and merge into config
75
+ const rideAttr = this._element.getAttribute('data-c-ride')
76
+ if (rideAttr && !this._config.ride) {
77
+ this._config.ride = rideAttr === 'carousel' ? CLASS_NAME_CAROUSEL : rideAttr
78
+ }
79
+
80
+ // Read interval from data attribute
81
+ const intervalAttr = this._element.getAttribute('data-c-interval')
82
+ if (intervalAttr) {
83
+ this._config.interval = parseInt(intervalAttr, 10)
84
+ this._config.defaultInterval = this._config.interval
85
+ }
86
+
87
+ this._indicatorsElement = getElement(SELECTOR_INDICATORS, this._element)
88
+ this._addEventListeners()
89
+
90
+ if (this._config.ride === CLASS_NAME_CAROUSEL) {
91
+ this.cycle()
92
+ }
93
+ }
94
+
95
+ static get NAME() {
96
+ return NAME
97
+ }
98
+
99
+ static get Default() {
100
+ return Default
101
+ }
102
+
103
+ _configAfterMerge(config) {
104
+ config.defaultInterval = config.interval
105
+ return config
106
+ }
107
+
108
+ // Public
109
+ next() {
110
+ this._slide(ORDER_NEXT)
111
+ }
112
+
113
+ nextWhenVisible() {
114
+ if (!document.hidden && isVisible(this._element)) {
115
+ this.next()
116
+ }
117
+ }
118
+
119
+ prev() {
120
+ this._slide(ORDER_PREV)
121
+ }
122
+
123
+ pause() {
124
+ if (this._isSliding) {
125
+ triggerTransitionEnd(this._element)
126
+ }
127
+
128
+ this._clearInterval()
129
+ }
130
+
131
+ cycle() {
132
+ this._clearInterval()
133
+ this._updateInterval()
134
+
135
+ this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)
136
+ }
137
+
138
+ _maybeEnableCycle() {
139
+ if (!this._config.ride) {
140
+ return
141
+ }
142
+
143
+ if (this._isSliding) {
144
+ one(this._element, EVENT_SLID, () => this.cycle())
145
+ return
146
+ }
147
+
148
+ this.cycle()
149
+ }
150
+
151
+ to(index) {
152
+ const items = this._getItems()
153
+ if (index > items.length - 1 || index < 0) {
154
+ return
155
+ }
156
+
157
+ if (this._isSliding) {
158
+ one(this._element, EVENT_SLID, () => this.to(index))
159
+ return
160
+ }
161
+
162
+ const activeIndex = this._getItemIndex(this._getActive())
163
+ if (activeIndex === index) {
164
+ return
165
+ }
166
+
167
+ const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV
168
+
169
+ this._slide(order, items[index])
170
+ }
171
+
172
+ dispose() {
173
+ if (this._swipeHelper) {
174
+ this._swipeHelper.dispose()
175
+ }
176
+
177
+ super.dispose()
178
+ }
179
+
180
+ // Private
181
+ _addEventListeners() {
182
+ if (this._config.keyboard) {
183
+ on(this._element, EVENT_KEYDOWN, (event) => this._keydown(event))
184
+ }
185
+
186
+ if (this._config.pause === 'hover') {
187
+ on(this._element, EVENT_MOUSEENTER, () => this.pause())
188
+ on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())
189
+ }
190
+
191
+ if (this._config.touch && Swipe.isSupported()) {
192
+ this._addTouchEventListeners()
193
+ }
194
+ }
195
+
196
+ _addTouchEventListeners() {
197
+ for (const img of getElements(SELECTOR_ITEM_IMG, this._element)) {
198
+ on(img, EVENT_DRAG_START, (event) => event.preventDefault())
199
+ }
200
+
201
+ const endCallBack = () => {
202
+ if (this._config.pause !== 'hover') {
203
+ return
204
+ }
205
+
206
+ this.pause()
207
+ if (this.touchTimeout) {
208
+ clearTimeout(this.touchTimeout)
209
+ }
210
+
211
+ this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
212
+ }
213
+
214
+ const swipeConfig = {
215
+ leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),
216
+ rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),
217
+ endCallback: endCallBack
218
+ }
219
+
220
+ this._swipeHelper = new Swipe(this._element, swipeConfig)
221
+ }
222
+
223
+ _keydown(event) {
224
+ if (/input|textarea/i.test(event.target.tagName)) {
225
+ return
226
+ }
227
+
228
+ const direction = KEY_TO_DIRECTION[event.key]
229
+ if (direction) {
230
+ event.preventDefault()
231
+ this._slide(this._directionToOrder(direction))
232
+ }
233
+ }
234
+
235
+ _getItemIndex(element) {
236
+ return this._getItems().indexOf(element)
237
+ }
238
+
239
+ _setActiveIndicatorElement(index) {
240
+ if (!this._indicatorsElement) {
241
+ return
242
+ }
243
+
244
+ const activeIndicator = getElement(SELECTOR_ACTIVE, this._indicatorsElement)
245
+
246
+ if (activeIndicator) {
247
+ removeClass(activeIndicator, CLASS_NAME_ACTIVE)
248
+ activeIndicator.removeAttribute('aria-current')
249
+ }
250
+
251
+ const newActiveIndicator = getElement(`[data-c-slide-to="${index}"]`, this._indicatorsElement)
252
+
253
+ if (newActiveIndicator) {
254
+ addClass(newActiveIndicator, CLASS_NAME_ACTIVE)
255
+ newActiveIndicator.setAttribute('aria-current', 'true')
256
+ }
257
+ }
258
+
259
+ _updateInterval() {
260
+ const element = this._activeElement || this._getActive()
261
+
262
+ if (!element) {
263
+ return
264
+ }
265
+
266
+ const elementInterval = Number.parseInt(element.getAttribute('data-c-interval'), 10)
267
+
268
+ this._config.interval = elementInterval || this._config.defaultInterval
269
+ }
270
+
271
+ _slide(order, element = null) {
272
+ if (this._isSliding) {
273
+ return
274
+ }
275
+
276
+ const activeElement = this._getActive()
277
+ const isNext = order === ORDER_NEXT
278
+ const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)
279
+
280
+ if (nextElement === activeElement) {
281
+ return
282
+ }
283
+
284
+ const nextElementIndex = this._getItemIndex(nextElement)
285
+
286
+ const triggerEvent = (eventName) => {
287
+ const event = new CustomEvent(eventName, {
288
+ bubbles: true,
289
+ cancelable: true,
290
+ detail: {
291
+ relatedTarget: nextElement,
292
+ direction: this._orderToDirection(order),
293
+ from: this._getItemIndex(activeElement),
294
+ to: nextElementIndex
295
+ }
296
+ })
297
+ this._element.dispatchEvent(event)
298
+ return event
299
+ }
300
+
301
+ const slideEvent = triggerEvent(EVENT_SLIDE)
302
+
303
+ if (slideEvent.defaultPrevented) {
304
+ return
305
+ }
306
+
307
+ if (!activeElement || !nextElement) {
308
+ return
309
+ }
310
+
311
+ const isCycling = Boolean(this._interval)
312
+ this.pause()
313
+
314
+ this._isSliding = true
315
+
316
+ this._setActiveIndicatorElement(nextElementIndex)
317
+ this._activeElement = nextElement
318
+
319
+ const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END
320
+ const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV
321
+
322
+ addClass(nextElement, orderClassName)
323
+
324
+ reflow(nextElement)
325
+
326
+ addClass(activeElement, directionalClassName)
327
+ addClass(nextElement, directionalClassName)
328
+
329
+ const completeCallBack = () => {
330
+ removeClass(nextElement, directionalClassName)
331
+ removeClass(nextElement, orderClassName)
332
+ addClass(nextElement, CLASS_NAME_ACTIVE)
333
+
334
+ removeClass(activeElement, CLASS_NAME_ACTIVE)
335
+ removeClass(activeElement, orderClassName)
336
+ removeClass(activeElement, directionalClassName)
337
+
338
+ this._isSliding = false
339
+
340
+ triggerEvent(EVENT_SLID)
341
+ }
342
+
343
+ this._queueCallback(completeCallBack, activeElement, this._isAnimated())
344
+
345
+ if (isCycling) {
346
+ this.cycle()
347
+ }
348
+ }
349
+
350
+ _isAnimated() {
351
+ return hasClass(this._element, CLASS_NAME_SLIDE)
352
+ }
353
+
354
+ _getActive() {
355
+ return getElement(SELECTOR_ACTIVE_ITEM, this._element)
356
+ }
357
+
358
+ _getItems() {
359
+ return getElements(SELECTOR_ITEM, this._element)
360
+ }
361
+
362
+ _clearInterval() {
363
+ if (this._interval) {
364
+ clearInterval(this._interval)
365
+ this._interval = null
366
+ }
367
+ }
368
+
369
+ _directionToOrder(direction) {
370
+ // RTL support would go here, but for now assume LTR
371
+ return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV
372
+ }
373
+
374
+ _orderToDirection(order) {
375
+ // RTL support would go here, but for now assume LTR
376
+ return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT
377
+ }
378
+
379
+ static getOrCreateInstance(element, config = {}) {
380
+ return element._baseLineComponent || new Carousel(element, config)
381
+ }
382
+ }
383
+
384
+ // Data API implementation
385
+ on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {
386
+ const target = getElementFromSelector(this)
387
+
388
+ if (!target || !hasClass(target, CLASS_NAME_CAROUSEL)) {
389
+ return
390
+ }
391
+
392
+ event.preventDefault()
393
+
394
+ const carousel = Carousel.getOrCreateInstance(target)
395
+ const slideIndex = this.getAttribute('data-c-slide-to')
396
+
397
+ if (slideIndex) {
398
+ carousel.to(parseInt(slideIndex, 10))
399
+ carousel._maybeEnableCycle()
400
+ return
401
+ }
402
+
403
+ if (this.getAttribute('data-c-slide') === 'next') {
404
+ carousel.next()
405
+ carousel._maybeEnableCycle()
406
+ return
407
+ }
408
+
409
+ carousel.prev()
410
+ carousel._maybeEnableCycle()
411
+ })
412
+
413
+ // Initialize carousels with data-c-ride on page load
414
+ const initCarousels = () => {
415
+ const carousels = getElements(SELECTOR_DATA_RIDE)
416
+
417
+ for (const carousel of carousels) {
418
+ Carousel.getOrCreateInstance(carousel)
419
+ }
420
+ }
421
+
422
+ on(window, EVENT_LOAD_DATA_API, initCarousels)
423
+
424
+ // Also initialize on DOMContentLoaded if page is already loaded
425
+ onDOMContentLoaded(initCarousels)
426
+
427
+ export default Carousel
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Base-Line Collapse Component
3
+ * Baseline 2.0 compatible collapse with Base-Line naming
4
+ */
5
+
6
+ import { BaseComponent } from './base.js'
7
+ import { getElement, getElements, on, off, executeAfterTransition, reflow } from './util.js'
8
+ import { addClass, removeClass, hasClass } from './component.js'
9
+
10
+ const NAME = 'collapse'
11
+ const DATA_KEY = `c.${NAME}`
12
+ const EVENT_KEY = `.${DATA_KEY}`
13
+ const DATA_API_KEY = `[data-c-${NAME}]`
14
+
15
+ const EVENT_SHOW = `show${EVENT_KEY}`
16
+ const EVENT_SHOWN = `shown${EVENT_KEY}`
17
+ const EVENT_HIDE = `hide${EVENT_KEY}`
18
+ const EVENT_HIDDEN = `hidden${EVENT_KEY}`
19
+ const EVENT_CLICK = `click${EVENT_KEY}`
20
+ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
21
+
22
+ const CLASS_NAME_COLLAPSE = 'c-collapse'
23
+ const CLASS_NAME_COLLAPSING = 'c-collapsing'
24
+ const CLASS_NAME_COLLAPSED = 'c-collapsed'
25
+ const CLASS_NAME_SHOW = 'is-show'
26
+
27
+ const WIDTH = 'width'
28
+ const HEIGHT = 'height'
29
+
30
+ const SELECTOR_ACTIVES = '.c-collapse.is-show'
31
+ const SELECTOR_DATA_TOGGLE = '[data-c-toggle="collapse"]'
32
+
33
+ class Collapse extends BaseComponent {
34
+ constructor(element, config) {
35
+ super(element, config)
36
+ this._isTransitioning = false
37
+ this._triggerArray = []
38
+ this._init()
39
+ }
40
+
41
+ static get NAME() {
42
+ return NAME
43
+ }
44
+
45
+ static get Default() {
46
+ return {
47
+ toggle: true,
48
+ parent: null
49
+ }
50
+ }
51
+
52
+ _init() {
53
+ this._isShown = hasClass(this._element, CLASS_NAME_SHOW)
54
+
55
+ // Get parent from config, data attribute on element, or data attribute on trigger
56
+ if (!this._config.parent) {
57
+ const parentSelector = this._element.getAttribute('data-c-parent')
58
+ if (parentSelector) {
59
+ this._config.parent = getElement(parentSelector)
60
+ } else {
61
+ // Try to find parent from trigger buttons
62
+ const triggers = getElements(SELECTOR_DATA_TOGGLE)
63
+ for (const trigger of triggers) {
64
+ const selector = trigger.getAttribute('data-c-target') || trigger.getAttribute('href')
65
+ const target = getElement(selector)
66
+ if (target === this._element) {
67
+ const parentSelectorFromTrigger = trigger.getAttribute('data-c-parent')
68
+ if (parentSelectorFromTrigger) {
69
+ this._config.parent = getElement(parentSelectorFromTrigger)
70
+ break
71
+ }
72
+ }
73
+ }
74
+ }
75
+ } else if (typeof this._config.parent === 'string') {
76
+ this._config.parent = getElement(this._config.parent)
77
+ }
78
+
79
+ if (this._config.toggle) {
80
+ this.toggle()
81
+ }
82
+
83
+ this._bindEvents()
84
+ }
85
+
86
+ _bindEvents() {
87
+ const triggers = getElements(SELECTOR_DATA_TOGGLE)
88
+ triggers.forEach(trigger => {
89
+ const selector = trigger.getAttribute('data-c-target') || trigger.getAttribute('href')
90
+ const target = getElement(selector)
91
+
92
+ if (target === this._element) {
93
+ this._triggerArray.push(trigger)
94
+ on(trigger, 'click', (e) => {
95
+ e.preventDefault()
96
+ this.toggle()
97
+ })
98
+ }
99
+ })
100
+ }
101
+
102
+ _getParent() {
103
+ return this._config.parent || null
104
+ }
105
+
106
+ _getSiblings() {
107
+ const parent = this._getParent()
108
+ if (!parent) {
109
+ return []
110
+ }
111
+
112
+ return getElements(SELECTOR_ACTIVES, parent).filter(
113
+ element => element !== this._element && hasClass(element, CLASS_NAME_SHOW)
114
+ )
115
+ }
116
+
117
+ show() {
118
+ if (this._isTransitioning || hasClass(this._element, CLASS_NAME_SHOW)) {
119
+ return
120
+ }
121
+
122
+ const showEvent = new CustomEvent(EVENT_SHOW, {
123
+ bubbles: true,
124
+ cancelable: true
125
+ })
126
+
127
+ this._element.dispatchEvent(showEvent)
128
+
129
+ if (showEvent.defaultPrevented) {
130
+ return
131
+ }
132
+
133
+ // Close siblings if parent is set
134
+ const siblings = this._getSiblings()
135
+ siblings.forEach(sibling => {
136
+ const siblingInstance = Collapse.getOrCreateInstance(sibling, { toggle: false })
137
+ siblingInstance.hide()
138
+ })
139
+
140
+ this._isTransitioning = true
141
+ this._element.style.height = '0'
142
+ addClass(this._element, CLASS_NAME_COLLAPSING)
143
+ removeClass(this._element, CLASS_NAME_COLLAPSE)
144
+ reflow(this._element)
145
+
146
+ const height = this._element.scrollHeight
147
+ this._element.style.height = `${height}px`
148
+
149
+ executeAfterTransition(() => {
150
+ removeClass(this._element, CLASS_NAME_COLLAPSING)
151
+ addClass(this._element, CLASS_NAME_COLLAPSE)
152
+ addClass(this._element, CLASS_NAME_SHOW)
153
+ this._element.style.height = ''
154
+ this._isTransitioning = false
155
+
156
+ this._triggerArray.forEach(trigger => {
157
+ removeClass(trigger, CLASS_NAME_COLLAPSED)
158
+ })
159
+
160
+ const shownEvent = new CustomEvent(EVENT_SHOWN, { bubbles: true })
161
+ this._element.dispatchEvent(shownEvent)
162
+ }, this._element)
163
+ }
164
+
165
+ hide() {
166
+ if (this._isTransitioning || !hasClass(this._element, CLASS_NAME_SHOW)) {
167
+ return
168
+ }
169
+
170
+ const hideEvent = new CustomEvent(EVENT_HIDE, {
171
+ bubbles: true,
172
+ cancelable: true
173
+ })
174
+
175
+ this._element.dispatchEvent(hideEvent)
176
+
177
+ if (hideEvent.defaultPrevented) {
178
+ return
179
+ }
180
+
181
+ this._isTransitioning = true
182
+ const height = this._element.getBoundingClientRect().height
183
+ this._element.style.height = `${height}px`
184
+ reflow(this._element)
185
+
186
+ removeClass(this._element, CLASS_NAME_COLLAPSE)
187
+ addClass(this._element, CLASS_NAME_COLLAPSING)
188
+ this._element.style.height = ''
189
+
190
+ executeAfterTransition(() => {
191
+ removeClass(this._element, CLASS_NAME_COLLAPSING)
192
+ addClass(this._element, CLASS_NAME_COLLAPSE)
193
+ removeClass(this._element, CLASS_NAME_SHOW)
194
+ this._isTransitioning = false
195
+
196
+ this._triggerArray.forEach(trigger => {
197
+ addClass(trigger, CLASS_NAME_COLLAPSED)
198
+ })
199
+
200
+ const hiddenEvent = new CustomEvent(EVENT_HIDDEN, { bubbles: true })
201
+ this._element.dispatchEvent(hiddenEvent)
202
+ }, this._element)
203
+ }
204
+
205
+ toggle() {
206
+ if (hasClass(this._element, CLASS_NAME_SHOW)) {
207
+ this.hide()
208
+ } else {
209
+ this.show()
210
+ }
211
+ }
212
+
213
+ static getOrCreateInstance(element, config = {}) {
214
+ return element._baseLineComponent || new Collapse(element, config)
215
+ }
216
+ }
217
+
218
+ // Data API
219
+ on(document, 'click', SELECTOR_DATA_TOGGLE, function (event) {
220
+ event.preventDefault()
221
+ const selector = this.getAttribute('data-c-target') || this.getAttribute('href')
222
+ const target = getElement(selector)
223
+ if (target) {
224
+ // Get parent from trigger button if available
225
+ const parentSelector = this.getAttribute('data-c-parent')
226
+ const config = parentSelector ? { parent: parentSelector } : {}
227
+ const collapse = Collapse.getOrCreateInstance(target, config)
228
+ collapse.toggle()
229
+ }
230
+ })
231
+
232
+ export default Collapse
233
+