modal.js 17.3 KB
Newer Older
1
2
/**
 * --------------------------------------------------------------------------
XhmikosR's avatar
XhmikosR committed
3
 * Bootstrap (v4.3.1): modal.js
4
5
6
7
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

8
import {
9
  getjQuery,
10
11
  TRANSITION_END,
  emulateTransitionEnd,
12
  getElementFromSelector,
13
14
15
16
17
  getTransitionDurationFromElement,
  isVisible,
  makeArray,
  reflow,
  typeCheckConfig
18
19
20
21
22
} from './util/index'
import Data from './dom/data'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
23

Johann-S's avatar
Johann-S committed
24
25
26
27
28
/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */
29

XhmikosR's avatar
XhmikosR committed
30
31
32
33
34
35
const NAME = 'modal'
const VERSION = '4.3.1'
const DATA_KEY = 'bs.modal'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
Johann-S's avatar
Johann-S committed
36
37

const Default = {
XhmikosR's avatar
XhmikosR committed
38
39
40
41
  backdrop: true,
  keyboard: true,
  focus: true,
  show: true
Johann-S's avatar
Johann-S committed
42
43
44
}

const DefaultType = {
XhmikosR's avatar
XhmikosR committed
45
46
47
48
  backdrop: '(boolean|string)',
  keyboard: 'boolean',
  focus: 'boolean',
  show: 'boolean'
Johann-S's avatar
Johann-S committed
49
50
}

XhmikosR's avatar
XhmikosR committed
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
const EVENT_HIDE = `hide${EVENT_KEY}`
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
const EVENT_SHOW = `show${EVENT_KEY}`
const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_FOCUSIN = `focusin${EVENT_KEY}`
const EVENT_RESIZE = `resize${EVENT_KEY}`
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`
const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}`
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`

const CLASS_NAME_SCROLLABLE = 'modal-dialog-scrollable'
const CLASS_NAME_SCROLLBAR_MEASURER = 'modal-scrollbar-measure'
const CLASS_NAME_BACKDROP = 'modal-backdrop'
const CLASS_NAME_OPEN = 'modal-open'
const CLASS_NAME_FADE = 'fade'
const CLASS_NAME_SHOW = 'show'
const CLASS_NAME_STATIC = 'modal-static'

const SELECTOR_DIALOG = '.modal-dialog'
const SELECTOR_MODAL_BODY = '.modal-body'
const SELECTOR_DATA_TOGGLE = '[data-toggle="modal"]'
const SELECTOR_DATA_DISMISS = '[data-dismiss="modal"]'
const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
const SELECTOR_STICKY_CONTENT = '.sticky-top'
fat's avatar
fat committed
78

Johann-S's avatar
Johann-S committed
79
80
81
82
83
/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */
84

Johann-S's avatar
Johann-S committed
85
86
class Modal {
  constructor(element, config) {
XhmikosR's avatar
XhmikosR committed
87
88
    this._config = this._getConfig(config)
    this._element = element
XhmikosR's avatar
XhmikosR committed
89
    this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, element)
XhmikosR's avatar
XhmikosR committed
90
91
92
    this._backdrop = null
    this._isShown = false
    this._isBodyOverflowing = false
Johann-S's avatar
Johann-S committed
93
    this._ignoreBackdropClick = false
XhmikosR's avatar
XhmikosR committed
94
95
    this._isTransitioning = false
    this._scrollbarWidth = 0
96
    Data.setData(element, DATA_KEY, this)
97
98
  }

Johann-S's avatar
Johann-S committed
99
100
101
102
  // Getters

  static get VERSION() {
    return VERSION
103
104
  }

Johann-S's avatar
Johann-S committed
105
106
107
  static get Default() {
    return Default
  }
108

Johann-S's avatar
Johann-S committed
109
  // Public
110

Johann-S's avatar
Johann-S committed
111
112
113
  toggle(relatedTarget) {
    return this._isShown ? this.hide() : this.show(relatedTarget)
  }
114

Johann-S's avatar
Johann-S committed
115
  show(relatedTarget) {
116
    if (this._isShown || this._isTransitioning) {
Johann-S's avatar
Johann-S committed
117
      return
118
119
    }

XhmikosR's avatar
XhmikosR committed
120
    if (this._element.classList.contains(CLASS_NAME_FADE)) {
Johann-S's avatar
Johann-S committed
121
      this._isTransitioning = true
122
123
    }

XhmikosR's avatar
XhmikosR committed
124
    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
Johann-S's avatar
Johann-S committed
125
126
      relatedTarget
    })
127

Johann-S's avatar
Johann-S committed
128
    if (this._isShown || showEvent.defaultPrevented) {
Johann-S's avatar
Johann-S committed
129
130
      return
    }
131

Johann-S's avatar
Johann-S committed
132
    this._isShown = true
133

Johann-S's avatar
Johann-S committed
134
135
    this._checkScrollbar()
    this._setScrollbar()
136

Johann-S's avatar
Johann-S committed
137
    this._adjustDialog()
138

Johann-S's avatar
Johann-S committed
139
140
    this._setEscapeEvent()
    this._setResizeEvent()
David Bailey's avatar
David Bailey committed
141

142
    EventHandler.on(this._element,
XhmikosR's avatar
XhmikosR committed
143
144
      EVENT_CLICK_DISMISS,
      SELECTOR_DATA_DISMISS,
XhmikosR's avatar
XhmikosR committed
145
      event => this.hide(event)
Johann-S's avatar
Johann-S committed
146
    )
147

XhmikosR's avatar
XhmikosR committed
148
149
    EventHandler.on(this._dialog, EVENT_MOUSEDOWN_DISMISS, () => {
      EventHandler.one(this._element, EVENT_MOUSEUP_DISMISS, event => {
150
        if (event.target === this._element) {
Johann-S's avatar
Johann-S committed
151
152
153
154
          this._ignoreBackdropClick = true
        }
      })
    })
155

Johann-S's avatar
Johann-S committed
156
157
    this._showBackdrop(() => this._showElement(relatedTarget))
  }
158

Johann-S's avatar
Johann-S committed
159
160
161
162
  hide(event) {
    if (event) {
      event.preventDefault()
    }
163

164
    if (!this._isShown || this._isTransitioning) {
Johann-S's avatar
Johann-S committed
165
      return
166
167
    }

XhmikosR's avatar
XhmikosR committed
168
    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)
169

Johann-S's avatar
Johann-S committed
170
    if (hideEvent.defaultPrevented) {
Johann-S's avatar
Johann-S committed
171
172
      return
    }
173

Johann-S's avatar
Johann-S committed
174
    this._isShown = false
XhmikosR's avatar
XhmikosR committed
175
    const transition = this._element.classList.contains(CLASS_NAME_FADE)
176

Johann-S's avatar
Johann-S committed
177
178
179
    if (transition) {
      this._isTransitioning = true
    }
180

Johann-S's avatar
Johann-S committed
181
182
    this._setEscapeEvent()
    this._setResizeEvent()
183

XhmikosR's avatar
XhmikosR committed
184
    EventHandler.off(document, EVENT_FOCUSIN)
185

XhmikosR's avatar
XhmikosR committed
186
    this._element.classList.remove(CLASS_NAME_SHOW)
187

XhmikosR's avatar
XhmikosR committed
188
189
    EventHandler.off(this._element, EVENT_CLICK_DISMISS)
    EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS)
190

Johann-S's avatar
Johann-S committed
191
    if (transition) {
192
      const transitionDuration = getTransitionDurationFromElement(this._element)
193

XhmikosR's avatar
XhmikosR committed
194
      EventHandler.one(this._element, TRANSITION_END, event => this._hideModal(event))
195
      emulateTransitionEnd(this._element, transitionDuration)
Johann-S's avatar
Johann-S committed
196
197
198
199
    } else {
      this._hideModal()
    }
  }
200

Johann-S's avatar
Johann-S committed
201
  dispose() {
202
    [window, this._element, this._dialog]
XhmikosR's avatar
XhmikosR committed
203
      .forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY))
204
205

    /**
XhmikosR's avatar
XhmikosR committed
206
     * `document` has 2 events `EVENT_FOCUSIN` and `EVENT_CLICK_DATA_API`
207
     * Do not move `document` in `htmlElements` array
XhmikosR's avatar
XhmikosR committed
208
     * It will remove `EVENT_CLICK_DATA_API` event that should remain
209
     */
XhmikosR's avatar
XhmikosR committed
210
    EventHandler.off(document, EVENT_FOCUSIN)
211

212
    Data.removeData(this._element, DATA_KEY)
213

XhmikosR's avatar
XhmikosR committed
214
215
216
217
218
219
    this._config = null
    this._element = null
    this._dialog = null
    this._backdrop = null
    this._isShown = null
    this._isBodyOverflowing = null
Johann-S's avatar
Johann-S committed
220
    this._ignoreBackdropClick = null
XhmikosR's avatar
XhmikosR committed
221
222
    this._isTransitioning = null
    this._scrollbarWidth = null
Johann-S's avatar
Johann-S committed
223
  }
fat's avatar
fat committed
224

Johann-S's avatar
Johann-S committed
225
226
227
  handleUpdate() {
    this._adjustDialog()
  }
fat's avatar
fat committed
228

Johann-S's avatar
Johann-S committed
229
  // Private
fat's avatar
fat committed
230

Johann-S's avatar
Johann-S committed
231
232
233
234
  _getConfig(config) {
    config = {
      ...Default,
      ...config
235
    }
236
    typeCheckConfig(NAME, config, DefaultType)
Johann-S's avatar
Johann-S committed
237
238
    return config
  }
239

Johann-S's avatar
Johann-S committed
240
  _showElement(relatedTarget) {
XhmikosR's avatar
XhmikosR committed
241
242
    const transition = this._element.classList.contains(CLASS_NAME_FADE)
    const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)
243

Johann-S's avatar
Johann-S committed
244
245
246
247
    if (!this._element.parentNode ||
        this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {
      // Don't move modal's DOM position
      document.body.appendChild(this._element)
fat's avatar
fat committed
248
249
    }

Johann-S's avatar
Johann-S committed
250
251
    this._element.style.display = 'block'
    this._element.removeAttribute('aria-hidden')
252
    this._element.setAttribute('aria-modal', true)
Shohei Yoshida's avatar
Shohei Yoshida committed
253

XhmikosR's avatar
XhmikosR committed
254
    if (this._dialog.classList.contains(CLASS_NAME_SCROLLABLE) && modalBody) {
255
      modalBody.scrollTop = 0
Shohei Yoshida's avatar
Shohei Yoshida committed
256
257
258
    } else {
      this._element.scrollTop = 0
    }
259

Johann-S's avatar
Johann-S committed
260
    if (transition) {
261
      reflow(this._element)
Johann-S's avatar
Johann-S committed
262
    }
263

XhmikosR's avatar
XhmikosR committed
264
    this._element.classList.add(CLASS_NAME_SHOW)
265

Johann-S's avatar
Johann-S committed
266
267
268
    if (this._config.focus) {
      this._enforceFocus()
    }
269

Johann-S's avatar
Johann-S committed
270
    const transitionComplete = () => {
Jacob Thornton's avatar
Jacob Thornton committed
271
      if (this._config.focus) {
Johann-S's avatar
Johann-S committed
272
        this._element.focus()
Jacob Thornton's avatar
Jacob Thornton committed
273
      }
XhmikosR's avatar
XhmikosR committed
274

Johann-S's avatar
Johann-S committed
275
      this._isTransitioning = false
XhmikosR's avatar
XhmikosR committed
276
      EventHandler.trigger(this._element, EVENT_SHOWN, {
277
278
        relatedTarget
      })
Johann-S's avatar
Johann-S committed
279
    }
280

Johann-S's avatar
Johann-S committed
281
    if (transition) {
XhmikosR's avatar
XhmikosR committed
282
      const transitionDuration = getTransitionDurationFromElement(this._dialog)
283

284
285
      EventHandler.one(this._dialog, TRANSITION_END, transitionComplete)
      emulateTransitionEnd(this._dialog, transitionDuration)
Johann-S's avatar
Johann-S committed
286
287
288
289
290
291
    } else {
      transitionComplete()
    }
  }

  _enforceFocus() {
XhmikosR's avatar
XhmikosR committed
292
293
    EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop
    EventHandler.on(document, EVENT_FOCUSIN, event => {
Johann-S's avatar
Johann-S committed
294
295
296
297
298
299
      if (document !== event.target &&
          this._element !== event.target &&
          !this._element.contains(event.target)) {
        this._element.focus()
      }
    })
Johann-S's avatar
Johann-S committed
300
  }
301

Johann-S's avatar
Johann-S committed
302
  _setEscapeEvent() {
303
    if (this._isShown) {
XhmikosR's avatar
XhmikosR committed
304
      EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
305
306
307
308
        if (this._config.keyboard && event.which === ESCAPE_KEYCODE) {
          event.preventDefault()
          this.hide()
        } else if (!this._config.keyboard && event.which === ESCAPE_KEYCODE) {
309
          this._triggerBackdropTransition()
Johann-S's avatar
Johann-S committed
310
311
        }
      })
Johann-S's avatar
Johann-S committed
312
    } else {
XhmikosR's avatar
XhmikosR committed
313
      EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS)
314
    }
Johann-S's avatar
Johann-S committed
315
  }
316

Johann-S's avatar
Johann-S committed
317
318
  _setResizeEvent() {
    if (this._isShown) {
XhmikosR's avatar
XhmikosR committed
319
      EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog())
Johann-S's avatar
Johann-S committed
320
    } else {
XhmikosR's avatar
XhmikosR committed
321
      EventHandler.off(window, EVENT_RESIZE)
322
    }
Johann-S's avatar
Johann-S committed
323
  }
324

Johann-S's avatar
Johann-S committed
325
326
327
  _hideModal() {
    this._element.style.display = 'none'
    this._element.setAttribute('aria-hidden', true)
328
    this._element.removeAttribute('aria-modal')
Johann-S's avatar
Johann-S committed
329
330
    this._isTransitioning = false
    this._showBackdrop(() => {
XhmikosR's avatar
XhmikosR committed
331
      document.body.classList.remove(CLASS_NAME_OPEN)
Johann-S's avatar
Johann-S committed
332
333
      this._resetAdjustments()
      this._resetScrollbar()
XhmikosR's avatar
XhmikosR committed
334
      EventHandler.trigger(this._element, EVENT_HIDDEN)
Johann-S's avatar
Johann-S committed
335
336
337
338
    })
  }

  _removeBackdrop() {
Johann-S's avatar
Johann-S committed
339
340
    this._backdrop.parentNode.removeChild(this._backdrop)
    this._backdrop = null
Johann-S's avatar
Johann-S committed
341
  }
342

Johann-S's avatar
Johann-S committed
343
  _showBackdrop(callback) {
XhmikosR's avatar
XhmikosR committed
344
345
    const animate = this._element.classList.contains(CLASS_NAME_FADE) ?
      CLASS_NAME_FADE :
XhmikosR's avatar
XhmikosR committed
346
      ''
Johann-S's avatar
Johann-S committed
347
348
349

    if (this._isShown && this._config.backdrop) {
      this._backdrop = document.createElement('div')
XhmikosR's avatar
XhmikosR committed
350
      this._backdrop.className = CLASS_NAME_BACKDROP
Johann-S's avatar
Johann-S committed
351
352
353

      if (animate) {
        this._backdrop.classList.add(animate)
354
355
      }

356
      document.body.appendChild(this._backdrop)
Johann-S's avatar
Johann-S committed
357

XhmikosR's avatar
XhmikosR committed
358
      EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => {
Johann-S's avatar
Johann-S committed
359
360
361
362
        if (this._ignoreBackdropClick) {
          this._ignoreBackdropClick = false
          return
        }
XhmikosR's avatar
XhmikosR committed
363

Johann-S's avatar
Johann-S committed
364
365
366
        if (event.target !== event.currentTarget) {
          return
        }
XhmikosR's avatar
XhmikosR committed
367

368
        this._triggerBackdropTransition()
369
370
      })

Johann-S's avatar
Johann-S committed
371
      if (animate) {
372
        reflow(this._backdrop)
373
374
      }

XhmikosR's avatar
XhmikosR committed
375
      this._backdrop.classList.add(CLASS_NAME_SHOW)
376

Johann-S's avatar
Johann-S committed
377
378
379
380
      if (!animate) {
        callback()
        return
      }
381

382
      const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
383

384
385
      EventHandler.one(this._backdrop, TRANSITION_END, callback)
      emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
Johann-S's avatar
Johann-S committed
386
    } else if (!this._isShown && this._backdrop) {
XhmikosR's avatar
XhmikosR committed
387
      this._backdrop.classList.remove(CLASS_NAME_SHOW)
388

Johann-S's avatar
Johann-S committed
389
390
      const callbackRemove = () => {
        this._removeBackdrop()
Johann-S's avatar
Johann-S committed
391
        callback()
Johann-S's avatar
Johann-S committed
392
      }
393

XhmikosR's avatar
XhmikosR committed
394
      if (this._element.classList.contains(CLASS_NAME_FADE)) {
395
396
397
        const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
        EventHandler.one(this._backdrop, TRANSITION_END, callbackRemove)
        emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
Johann-S's avatar
Johann-S committed
398
399
      } else {
        callbackRemove()
400
      }
Johann-S's avatar
Johann-S committed
401
    } else {
Johann-S's avatar
Johann-S committed
402
      callback()
403
    }
Johann-S's avatar
Johann-S committed
404
  }
405

406
407
  _triggerBackdropTransition() {
    if (this._config.backdrop === 'static') {
XhmikosR's avatar
XhmikosR committed
408
      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)
409
410
411
412
      if (hideEvent.defaultPrevented) {
        return
      }

XhmikosR's avatar
XhmikosR committed
413
      this._element.classList.add(CLASS_NAME_STATIC)
414
415
      const modalTransitionDuration = getTransitionDurationFromElement(this._element)
      EventHandler.one(this._element, TRANSITION_END, () => {
XhmikosR's avatar
XhmikosR committed
416
        this._element.classList.remove(CLASS_NAME_STATIC)
417
418
419
420
421
422
423
424
      })
      emulateTransitionEnd(this._element, modalTransitionDuration)
      this._element.focus()
    } else {
      this.hide()
    }
  }

Johann-S's avatar
Johann-S committed
425
426
427
  // ----------------------------------------------------------------------
  // the following methods are used to handle overflowing modals
  // ----------------------------------------------------------------------
428

Johann-S's avatar
Johann-S committed
429
430
431
  _adjustDialog() {
    const isModalOverflowing =
      this._element.scrollHeight > document.documentElement.clientHeight
432

Johann-S's avatar
Johann-S committed
433
434
    if (!this._isBodyOverflowing && isModalOverflowing) {
      this._element.style.paddingLeft = `${this._scrollbarWidth}px`
435
436
    }

Johann-S's avatar
Johann-S committed
437
438
    if (this._isBodyOverflowing && !isModalOverflowing) {
      this._element.style.paddingRight = `${this._scrollbarWidth}px`
439
    }
Johann-S's avatar
Johann-S committed
440
  }
441

Johann-S's avatar
Johann-S committed
442
443
444
445
  _resetAdjustments() {
    this._element.style.paddingLeft = ''
    this._element.style.paddingRight = ''
  }
446

Johann-S's avatar
Johann-S committed
447
448
449
450
451
  _checkScrollbar() {
    const rect = document.body.getBoundingClientRect()
    this._isBodyOverflowing = rect.left + rect.right < window.innerWidth
    this._scrollbarWidth = this._getScrollbarWidth()
  }
452

Johann-S's avatar
Johann-S committed
453
454
455
456
457
458
  _setScrollbar() {
    if (this._isBodyOverflowing) {
      // Note: DOMNode.style.paddingRight returns the actual value or '' if not set
      //   while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set

      // Adjust fixed content padding
XhmikosR's avatar
XhmikosR committed
459
      makeArray(SelectorEngine.find(SELECTOR_FIXED_CONTENT))
XhmikosR's avatar
XhmikosR committed
460
        .forEach(element => {
461
462
463
464
465
          const actualPadding = element.style.paddingRight
          const calculatedPadding = window.getComputedStyle(element)['padding-right']
          Manipulator.setDataAttribute(element, 'padding-right', actualPadding)
          element.style.paddingRight = `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`
        })
466

Johann-S's avatar
Johann-S committed
467
      // Adjust sticky content margin
XhmikosR's avatar
XhmikosR committed
468
      makeArray(SelectorEngine.find(SELECTOR_STICKY_CONTENT))
XhmikosR's avatar
XhmikosR committed
469
        .forEach(element => {
470
471
472
473
474
          const actualMargin = element.style.marginRight
          const calculatedMargin = window.getComputedStyle(element)['margin-right']
          Manipulator.setDataAttribute(element, 'margin-right', actualMargin)
          element.style.marginRight = `${parseFloat(calculatedMargin) - this._scrollbarWidth}px`
        })
475

Johann-S's avatar
Johann-S committed
476
477
      // Adjust body padding
      const actualPadding = document.body.style.paddingRight
478
479
480
481
      const calculatedPadding = window.getComputedStyle(document.body)['padding-right']

      Manipulator.setDataAttribute(document.body, 'padding-right', actualPadding)
      document.body.style.paddingRight = `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`
482
    }
483

XhmikosR's avatar
XhmikosR committed
484
    document.body.classList.add(CLASS_NAME_OPEN)
Johann-S's avatar
Johann-S committed
485
  }
486

Johann-S's avatar
Johann-S committed
487
488
  _resetScrollbar() {
    // Restore fixed content padding
XhmikosR's avatar
XhmikosR committed
489
    makeArray(SelectorEngine.find(SELECTOR_FIXED_CONTENT))
XhmikosR's avatar
XhmikosR committed
490
      .forEach(element => {
491
        const padding = Manipulator.getDataAttribute(element, 'padding-right')
492
493
494
495
496
        if (typeof padding !== 'undefined') {
          Manipulator.removeDataAttribute(element, 'padding-right')
          element.style.paddingRight = padding
        }
      })
Johann-S's avatar
Johann-S committed
497

498
    // Restore sticky content and navbar-toggler margin
XhmikosR's avatar
XhmikosR committed
499
    makeArray(SelectorEngine.find(`${SELECTOR_STICKY_CONTENT}`))
XhmikosR's avatar
XhmikosR committed
500
      .forEach(element => {
501
        const margin = Manipulator.getDataAttribute(element, 'margin-right')
502
503
504
505
506
        if (typeof margin !== 'undefined') {
          Manipulator.removeDataAttribute(element, 'margin-right')
          element.style.marginRight = margin
        }
      })
507

Johann-S's avatar
Johann-S committed
508
    // Restore body padding
509
    const padding = Manipulator.getDataAttribute(document.body, 'padding-right')
XhmikosR's avatar
XhmikosR committed
510
511
512
    if (typeof padding === 'undefined') {
      document.body.style.paddingRight = ''
    } else {
513
514
515
      Manipulator.removeDataAttribute(document.body, 'padding-right')
      document.body.style.paddingRight = padding
    }
Johann-S's avatar
Johann-S committed
516
  }
517

Johann-S's avatar
Johann-S committed
518
519
  _getScrollbarWidth() { // thx d.walsh
    const scrollDiv = document.createElement('div')
XhmikosR's avatar
XhmikosR committed
520
    scrollDiv.className = CLASS_NAME_SCROLLBAR_MEASURER
Johann-S's avatar
Johann-S committed
521
522
523
524
525
    document.body.appendChild(scrollDiv)
    const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth
    document.body.removeChild(scrollDiv)
    return scrollbarWidth
  }
526

Johann-S's avatar
Johann-S committed
527
528
  // Static

529
  static jQueryInterface(config, relatedTarget) {
Johann-S's avatar
Johann-S committed
530
    return this.each(function () {
531
      let data = Data.getData(this, DATA_KEY)
Johann-S's avatar
Johann-S committed
532
533
      const _config = {
        ...Default,
534
        ...Manipulator.getDataAttributes(this),
Johann-S's avatar
Johann-S committed
535
536
537
538
539
540
        ...typeof config === 'object' && config ? config : {}
      }

      if (!data) {
        data = new Modal(this, _config)
      }
541

Johann-S's avatar
Johann-S committed
542
543
544
      if (typeof config === 'string') {
        if (typeof data[config] === 'undefined') {
          throw new TypeError(`No method named "${config}"`)
545
        }
XhmikosR's avatar
XhmikosR committed
546

Johann-S's avatar
Johann-S committed
547
548
549
550
551
        data[config](relatedTarget)
      } else if (_config.show) {
        data.show(relatedTarget)
      }
    })
552
  }
553

554
  static getInstance(element) {
555
556
    return Data.getData(element, DATA_KEY)
  }
Johann-S's avatar
Johann-S committed
557
558
559
560
561
562
563
}

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */
564

XhmikosR's avatar
XhmikosR committed
565
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
566
  const target = getElementFromSelector(this)
567

Johann-S's avatar
Johann-S committed
568
569
570
  if (this.tagName === 'A' || this.tagName === 'AREA') {
    event.preventDefault()
  }
571

XhmikosR's avatar
XhmikosR committed
572
  EventHandler.one(target, EVENT_SHOW, showEvent => {
573
574
    if (showEvent.defaultPrevented) {
      // only register focus restorer if modal will actually get shown
Johann-S's avatar
Johann-S committed
575
      return
576
577
    }

XhmikosR's avatar
XhmikosR committed
578
    EventHandler.one(target, EVENT_HIDDEN, () => {
579
      if (isVisible(this)) {
Johann-S's avatar
Johann-S committed
580
        this.focus()
581
582
583
584
      }
    })
  })

585
586
  let data = Data.getData(target, DATA_KEY)
  if (!data) {
Johann-S's avatar
Johann-S committed
587
588
589
590
591
    const config = {
      ...Manipulator.getDataAttributes(target),
      ...Manipulator.getDataAttributes(this)
    }

592
593
594
595
    data = new Modal(target, config)
  }

  data.show(this)
Johann-S's avatar
Johann-S committed
596
597
})

598
599
const $ = getjQuery()

Johann-S's avatar
Johann-S committed
600
601
602
603
/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
Johann-S's avatar
Johann-S committed
604
 * add .modal to jQuery only if jQuery is present
Johann-S's avatar
Johann-S committed
605
 */
Johann-S's avatar
Johann-S committed
606
/* istanbul ignore if */
607
if ($) {
608
  const JQUERY_NO_CONFLICT = $.fn[NAME]
609
  $.fn[NAME] = Modal.jQueryInterface
XhmikosR's avatar
XhmikosR committed
610
611
  $.fn[NAME].Constructor = Modal
  $.fn[NAME].noConflict = () => {
612
    $.fn[NAME] = JQUERY_NO_CONFLICT
613
    return Modal.jQueryInterface
614
  }
Johann-S's avatar
Johann-S committed
615
}
616
617

export default Modal