9
import Util from './util'
15
const NAME = 'collapse'
16
const VERSION = '4.6.1'
17
const DATA_KEY = 'bs.collapse'
18
const EVENT_KEY = `.${DATA_KEY}`
19
const DATA_API_KEY = '.data-api'
20
const JQUERY_NO_CONFLICT = $.fn[NAME]
22
const CLASS_NAME_SHOW = 'show'
23
const CLASS_NAME_COLLAPSE = 'collapse'
24
const CLASS_NAME_COLLAPSING = 'collapsing'
25
const CLASS_NAME_COLLAPSED = 'collapsed'
27
const DIMENSION_WIDTH = 'width'
28
const DIMENSION_HEIGHT = 'height'
30
const EVENT_SHOW = `show${EVENT_KEY}`
31
const EVENT_SHOWN = `shown${EVENT_KEY}`
32
const EVENT_HIDE = `hide${EVENT_KEY}`
33
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
34
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
36
const SELECTOR_ACTIVES = '.show, .collapsing'
37
const SELECTOR_DATA_TOGGLE = '[data-toggle="collapse"]'
46
parent: '(string|element)'
54
constructor(element, config) {
55
this._isTransitioning = false
56
this._element = element
57
this._config = this._getConfig(config)
58
this._triggerArray = [].slice.call(document.querySelectorAll(
59
`[data-toggle="collapse"][href="#${element.id}"],` +
60
`[data-toggle="collapse"][data-target="#${element.id}"]`
63
const toggleList = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLE))
64
for (let i = 0, len = toggleList.length; i < len; i++) {
65
const elem = toggleList[i]
66
const selector = Util.getSelectorFromElement(elem)
67
const filterElement = [].slice.call(document.querySelectorAll(selector))
68
.filter(foundElem => foundElem === element)
70
if (selector !== null && filterElement.length > 0) {
71
this._selector = selector
72
this._triggerArray.push(elem)
76
this._parent = this._config.parent ? this._getParent() : null
78
if (!this._config.parent) {
79
this._addAriaAndCollapsedClass(this._element, this._triggerArray)
82
if (this._config.toggle) {
88
static get VERSION() {
92
static get Default() {
98
if ($(this._element).hasClass(CLASS_NAME_SHOW)) {
106
if (this._isTransitioning ||
107
$(this._element).hasClass(CLASS_NAME_SHOW)) {
115
actives = [].slice.call(this._parent.querySelectorAll(SELECTOR_ACTIVES))
117
if (typeof this._config.parent === 'string') {
118
return elem.getAttribute('data-parent') === this._config.parent
121
return elem.classList.contains(CLASS_NAME_COLLAPSE)
124
if (actives.length === 0) {
130
activesData = $(actives).not(this._selector).data(DATA_KEY)
131
if (activesData && activesData._isTransitioning) {
136
const startEvent = $.Event(EVENT_SHOW)
137
$(this._element).trigger(startEvent)
138
if (startEvent.isDefaultPrevented()) {
143
Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide')
145
$(actives).data(DATA_KEY, null)
149
const dimension = this._getDimension()
152
.removeClass(CLASS_NAME_COLLAPSE)
153
.addClass(CLASS_NAME_COLLAPSING)
155
this._element.style[dimension] = 0
157
if (this._triggerArray.length) {
158
$(this._triggerArray)
159
.removeClass(CLASS_NAME_COLLAPSED)
160
.attr('aria-expanded', true)
163
this.setTransitioning(true)
165
const complete = () => {
167
.removeClass(CLASS_NAME_COLLAPSING)
168
.addClass(`${CLASS_NAME_COLLAPSE} ${CLASS_NAME_SHOW}`)
170
this._element.style[dimension] = ''
172
this.setTransitioning(false)
174
$(this._element).trigger(EVENT_SHOWN)
177
const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
178
const scrollSize = `scroll${capitalizedDimension}`
179
const transitionDuration = Util.getTransitionDurationFromElement(this._element)
182
.one(Util.TRANSITION_END, complete)
183
.emulateTransitionEnd(transitionDuration)
185
this._element.style[dimension] = `${this._element[scrollSize]}px`
189
if (this._isTransitioning ||
190
!$(this._element).hasClass(CLASS_NAME_SHOW)) {
194
const startEvent = $.Event(EVENT_HIDE)
195
$(this._element).trigger(startEvent)
196
if (startEvent.isDefaultPrevented()) {
200
const dimension = this._getDimension()
202
this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`
204
Util.reflow(this._element)
207
.addClass(CLASS_NAME_COLLAPSING)
208
.removeClass(`${CLASS_NAME_COLLAPSE} ${CLASS_NAME_SHOW}`)
210
const triggerArrayLength = this._triggerArray.length
211
if (triggerArrayLength > 0) {
212
for (let i = 0; i < triggerArrayLength; i++) {
213
const trigger = this._triggerArray[i]
214
const selector = Util.getSelectorFromElement(trigger)
216
if (selector !== null) {
217
const $elem = $([].slice.call(document.querySelectorAll(selector)))
218
if (!$elem.hasClass(CLASS_NAME_SHOW)) {
219
$(trigger).addClass(CLASS_NAME_COLLAPSED)
220
.attr('aria-expanded', false)
226
this.setTransitioning(true)
228
const complete = () => {
229
this.setTransitioning(false)
231
.removeClass(CLASS_NAME_COLLAPSING)
232
.addClass(CLASS_NAME_COLLAPSE)
233
.trigger(EVENT_HIDDEN)
236
this._element.style[dimension] = ''
237
const transitionDuration = Util.getTransitionDurationFromElement(this._element)
240
.one(Util.TRANSITION_END, complete)
241
.emulateTransitionEnd(transitionDuration)
244
setTransitioning(isTransitioning) {
245
this._isTransitioning = isTransitioning
249
$.removeData(this._element, DATA_KEY)
254
this._triggerArray = null
255
this._isTransitioning = null
264
config.toggle = Boolean(config.toggle)
265
Util.typeCheckConfig(NAME, config, DefaultType)
270
const hasWidth = $(this._element).hasClass(DIMENSION_WIDTH)
271
return hasWidth ? DIMENSION_WIDTH : DIMENSION_HEIGHT
277
if (Util.isElement(this._config.parent)) {
278
parent = this._config.parent
281
if (typeof this._config.parent.jquery !== 'undefined') {
282
parent = this._config.parent[0]
285
parent = document.querySelector(this._config.parent)
288
const selector = `[data-toggle="collapse"][data-parent="${this._config.parent}"]`
289
const children = [].slice.call(parent.querySelectorAll(selector))
291
$(children).each((i, element) => {
292
this._addAriaAndCollapsedClass(
293
Collapse._getTargetFromElement(element),
301
_addAriaAndCollapsedClass(element, triggerArray) {
302
const isOpen = $(element).hasClass(CLASS_NAME_SHOW)
304
if (triggerArray.length) {
306
.toggleClass(CLASS_NAME_COLLAPSED, !isOpen)
307
.attr('aria-expanded', isOpen)
312
static _getTargetFromElement(element) {
313
const selector = Util.getSelectorFromElement(element)
314
return selector ? document.querySelector(selector) : null
317
static _jQueryInterface(config) {
318
return this.each(function () {
319
const $element = $(this)
320
let data = $element.data(DATA_KEY)
324
...(typeof config === 'object' && config ? config : {})
327
if (!data && _config.toggle && typeof config === 'string' && /show|hide/.test(config)) {
328
_config.toggle = false
332
data = new Collapse(this, _config)
333
$element.data(DATA_KEY, data)
336
if (typeof config === 'string') {
337
if (typeof data[config] === 'undefined') {
338
throw new TypeError(`No method named "${config}"`)
351
$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
353
if (event.currentTarget.tagName === 'A') {
354
event.preventDefault()
357
const $trigger = $(this)
358
const selector = Util.getSelectorFromElement(this)
359
const selectors = [].slice.call(document.querySelectorAll(selector))
361
$(selectors).each(function () {
362
const $target = $(this)
363
const data = $target.data(DATA_KEY)
364
const config = data ? 'toggle' : $trigger.data()
365
Collapse._jQueryInterface.call($target, config)
373
$.fn[NAME] = Collapse._jQueryInterface
374
$.fn[NAME].Constructor = Collapse
375
$.fn[NAME].noConflict = () => {
376
$.fn[NAME] = JQUERY_NO_CONFLICT
377
return Collapse._jQueryInterface
380
export default Collapse