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
445
446
447
448
  _checkScrollbar() {
    const rect = document.body.getBoundingClientRect()
    this._isBodyOverflowing = rect.left + rect.right < window.innerWidth
    this._scrollbarWidth = this._getScrollbarWidth()
  }
449

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */
561

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

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

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

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

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

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

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

595
596
const $ = getjQuery()

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

export default Modal