modal.js 17.1 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
  getTransitionDurationFromElement,
  isVisible,
  reflow,
  typeCheckConfig
17
18
19
20
21
} 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'
22

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

XhmikosR's avatar
XhmikosR committed
29
30
31
32
33
const NAME = 'modal'
const VERSION = '4.3.1'
const DATA_KEY = 'bs.modal'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
34
const ESCAPE_KEY = 'Escape'
Johann-S's avatar
Johann-S committed
35
36

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

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

XhmikosR's avatar
XhmikosR committed
50
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
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_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
76

Johann-S's avatar
Johann-S committed
77
78
79
80
81
/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */
82

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

Johann-S's avatar
Johann-S committed
97
98
99
100
  // Getters

  static get VERSION() {
    return VERSION
101
102
  }

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

Johann-S's avatar
Johann-S committed
107
  // Public
108

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

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

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

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

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

Johann-S's avatar
Johann-S committed
130
    this._isShown = true
131

Johann-S's avatar
Johann-S committed
132
133
    this._checkScrollbar()
    this._setScrollbar()
134

Johann-S's avatar
Johann-S committed
135
    this._adjustDialog()
136

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

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

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

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
179
180
    this._setEscapeEvent()
    this._setResizeEvent()
181

XhmikosR's avatar
XhmikosR committed
182
    EventHandler.off(document, EVENT_FOCUSIN)
183

XhmikosR's avatar
XhmikosR committed
184
    this._element.classList.remove(CLASS_NAME_SHOW)
185

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

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

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

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

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

210
    Data.removeData(this._element, DATA_KEY)
211

XhmikosR's avatar
XhmikosR committed
212
213
214
215
216
217
    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
218
    this._ignoreBackdropClick = null
XhmikosR's avatar
XhmikosR committed
219
220
    this._isTransitioning = null
    this._scrollbarWidth = null
Johann-S's avatar
Johann-S committed
221
  }
fat's avatar
fat committed
222

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

Johann-S's avatar
Johann-S committed
227
  // Private
fat's avatar
fat committed
228

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

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

Johann-S's avatar
Johann-S committed
242
243
244
245
    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
246
247
    }

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

ysds's avatar
ysds committed
253
    if (modalBody) {
254
      modalBody.scrollTop = 0
Shohei Yoshida's avatar
Shohei Yoshida committed
255
    }
256

Johann-S's avatar
Johann-S committed
257
    if (transition) {
258
      reflow(this._element)
Johann-S's avatar
Johann-S committed
259
    }
260

XhmikosR's avatar
XhmikosR committed
261
    this._element.classList.add(CLASS_NAME_SHOW)
262

Johann-S's avatar
Johann-S committed
263
264
265
    if (this._config.focus) {
      this._enforceFocus()
    }
266

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

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

Johann-S's avatar
Johann-S committed
278
    if (transition) {
XhmikosR's avatar
XhmikosR committed
279
      const transitionDuration = getTransitionDurationFromElement(this._dialog)
280

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

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

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

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

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

  _removeBackdrop() {
Johann-S's avatar
Johann-S committed
336
337
    this._backdrop.parentNode.removeChild(this._backdrop)
    this._backdrop = null
Johann-S's avatar
Johann-S committed
338
  }
339

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

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

      if (animate) {
        this._backdrop.classList.add(animate)
351
352
      }

353
      document.body.appendChild(this._backdrop)
Johann-S's avatar
Johann-S committed
354

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

Johann-S's avatar
Johann-S committed
361
362
363
        if (event.target !== event.currentTarget) {
          return
        }
XhmikosR's avatar
XhmikosR committed
364

365
        this._triggerBackdropTransition()
366
367
      })

Johann-S's avatar
Johann-S committed
368
      if (animate) {
369
        reflow(this._backdrop)
370
371
      }

XhmikosR's avatar
XhmikosR committed
372
      this._backdrop.classList.add(CLASS_NAME_SHOW)
373

Johann-S's avatar
Johann-S committed
374
375
376
377
      if (!animate) {
        callback()
        return
      }
378

379
      const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
380

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

Johann-S's avatar
Johann-S committed
386
387
      const callbackRemove = () => {
        this._removeBackdrop()
Johann-S's avatar
Johann-S committed
388
        callback()
Johann-S's avatar
Johann-S committed
389
      }
390

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

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

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

Johann-S's avatar
Johann-S committed
422
423
424
  // ----------------------------------------------------------------------
  // the following methods are used to handle overflowing modals
  // ----------------------------------------------------------------------
425

Johann-S's avatar
Johann-S committed
426
427
428
  _adjustDialog() {
    const isModalOverflowing =
      this._element.scrollHeight > document.documentElement.clientHeight
429

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

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

Johann-S's avatar
Johann-S committed
439
440
441
442
  _resetAdjustments() {
    this._element.style.paddingLeft = ''
    this._element.style.paddingRight = ''
  }
443

Johann-S's avatar
Johann-S committed
444
  _checkScrollbar() {
445
446
447
    const { left, right } = document.body.getBoundingClientRect()

    this._isBodyOverflowing = Math.floor(left + right) < window.innerWidth
Johann-S's avatar
Johann-S committed
448
449
    this._scrollbarWidth = this._getScrollbarWidth()
  }
450

Johann-S's avatar
Johann-S committed
451
452
453
454
455
456
  _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
457
      SelectorEngine.find(SELECTOR_FIXED_CONTENT)
XhmikosR's avatar
XhmikosR committed
458
        .forEach(element => {
459
460
461
462
463
          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`
        })
464

Johann-S's avatar
Johann-S committed
465
      // Adjust sticky content margin
466
      SelectorEngine.find(SELECTOR_STICKY_CONTENT)
XhmikosR's avatar
XhmikosR committed
467
        .forEach(element => {
468
469
470
471
472
          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`
        })
473

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
525
526
  // Static

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

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

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

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

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

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */
562

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

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

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

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

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

590
591
592
593
    data = new Modal(target, config)
  }

  data.show(this)
Johann-S's avatar
Johann-S committed
594
595
})

596
597
const $ = getjQuery()

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

export default Modal