modal.js 17.6 KB
Newer Older
1
2
/**
 * --------------------------------------------------------------------------
3
 * Bootstrap (v5.0.0-alpha1): modal.js
4
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
6
7
 * --------------------------------------------------------------------------
 */

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
const NAME = 'modal'
30
const VERSION = '5.0.0-alpha1'
XhmikosR's avatar
XhmikosR committed
31
32
33
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)
251
    this._element.setAttribute('role', 'dialog')
ysds's avatar
ysds committed
252
    this._element.scrollTop = 0
Shohei Yoshida's avatar
Shohei Yoshida committed
253

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

367
        this._triggerBackdropTransition()
368
369
      })

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

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

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

381
      const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
382

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

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

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

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

412
413
414
415
416
417
      const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight

      if (!isModalOverflowing) {
        this._element.style.overflowY = 'hidden'
      }

XhmikosR's avatar
XhmikosR committed
418
      this._element.classList.add(CLASS_NAME_STATIC)
419
420
      const modalTransitionDuration = getTransitionDurationFromElement(this._dialog)
      EventHandler.off(this._element, TRANSITION_END)
421
      EventHandler.one(this._element, TRANSITION_END, () => {
XhmikosR's avatar
XhmikosR committed
422
        this._element.classList.remove(CLASS_NAME_STATIC)
423
424
425
426
427
428
        if (!isModalOverflowing) {
          EventHandler.one(this._element, TRANSITION_END, () => {
            this._element.style.overflowY = ''
          })
          emulateTransitionEnd(this._element, modalTransitionDuration)
        }
429
430
431
432
433
434
435
436
      })
      emulateTransitionEnd(this._element, modalTransitionDuration)
      this._element.focus()
    } else {
      this.hide()
    }
  }

Johann-S's avatar
Johann-S committed
437
438
439
  // ----------------------------------------------------------------------
  // the following methods are used to handle overflowing modals
  // ----------------------------------------------------------------------
440

Johann-S's avatar
Johann-S committed
441
442
443
  _adjustDialog() {
    const isModalOverflowing =
      this._element.scrollHeight > document.documentElement.clientHeight
444

Johann-S's avatar
Johann-S committed
445
446
    if (!this._isBodyOverflowing && isModalOverflowing) {
      this._element.style.paddingLeft = `${this._scrollbarWidth}px`
447
448
    }

Johann-S's avatar
Johann-S committed
449
450
    if (this._isBodyOverflowing && !isModalOverflowing) {
      this._element.style.paddingRight = `${this._scrollbarWidth}px`
451
    }
Johann-S's avatar
Johann-S committed
452
  }
453

Johann-S's avatar
Johann-S committed
454
455
456
457
  _resetAdjustments() {
    this._element.style.paddingLeft = ''
    this._element.style.paddingRight = ''
  }
458

Johann-S's avatar
Johann-S committed
459
  _checkScrollbar() {
460
    const rect = document.body.getBoundingClientRect()
461
    this._isBodyOverflowing = Math.round(rect.left + rect.right) < window.innerWidth
Johann-S's avatar
Johann-S committed
462
463
    this._scrollbarWidth = this._getScrollbarWidth()
  }
464

Johann-S's avatar
Johann-S committed
465
466
467
468
469
470
  _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
471
      SelectorEngine.find(SELECTOR_FIXED_CONTENT)
XhmikosR's avatar
XhmikosR committed
472
        .forEach(element => {
473
474
475
476
477
          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`
        })
478

Johann-S's avatar
Johann-S committed
479
      // Adjust sticky content margin
480
      SelectorEngine.find(SELECTOR_STICKY_CONTENT)
XhmikosR's avatar
XhmikosR committed
481
        .forEach(element => {
482
483
484
485
486
          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`
        })
487

Johann-S's avatar
Johann-S committed
488
489
      // Adjust body padding
      const actualPadding = document.body.style.paddingRight
490
491
492
493
      const calculatedPadding = window.getComputedStyle(document.body)['padding-right']

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

XhmikosR's avatar
XhmikosR committed
496
    document.body.classList.add(CLASS_NAME_OPEN)
Johann-S's avatar
Johann-S committed
497
  }
498

Johann-S's avatar
Johann-S committed
499
500
  _resetScrollbar() {
    // Restore fixed content padding
501
    SelectorEngine.find(SELECTOR_FIXED_CONTENT)
XhmikosR's avatar
XhmikosR committed
502
      .forEach(element => {
503
        const padding = Manipulator.getDataAttribute(element, 'padding-right')
504
505
506
507
508
        if (typeof padding !== 'undefined') {
          Manipulator.removeDataAttribute(element, 'padding-right')
          element.style.paddingRight = padding
        }
      })
Johann-S's avatar
Johann-S committed
509

510
    // Restore sticky content and navbar-toggler margin
511
    SelectorEngine.find(`${SELECTOR_STICKY_CONTENT}`)
XhmikosR's avatar
XhmikosR committed
512
      .forEach(element => {
513
        const margin = Manipulator.getDataAttribute(element, 'margin-right')
514
515
516
517
518
        if (typeof margin !== 'undefined') {
          Manipulator.removeDataAttribute(element, 'margin-right')
          element.style.marginRight = margin
        }
      })
519

Johann-S's avatar
Johann-S committed
520
    // Restore body padding
521
    const padding = Manipulator.getDataAttribute(document.body, 'padding-right')
XhmikosR's avatar
XhmikosR committed
522
523
524
    if (typeof padding === 'undefined') {
      document.body.style.paddingRight = ''
    } else {
525
526
527
      Manipulator.removeDataAttribute(document.body, 'padding-right')
      document.body.style.paddingRight = padding
    }
Johann-S's avatar
Johann-S committed
528
  }
529

Johann-S's avatar
Johann-S committed
530
531
  _getScrollbarWidth() { // thx d.walsh
    const scrollDiv = document.createElement('div')
XhmikosR's avatar
XhmikosR committed
532
    scrollDiv.className = CLASS_NAME_SCROLLBAR_MEASURER
Johann-S's avatar
Johann-S committed
533
534
535
536
537
    document.body.appendChild(scrollDiv)
    const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth
    document.body.removeChild(scrollDiv)
    return scrollbarWidth
  }
538

Johann-S's avatar
Johann-S committed
539
540
  // Static

541
  static jQueryInterface(config, relatedTarget) {
Johann-S's avatar
Johann-S committed
542
    return this.each(function () {
543
      let data = Data.getData(this, DATA_KEY)
Johann-S's avatar
Johann-S committed
544
545
      const _config = {
        ...Default,
546
        ...Manipulator.getDataAttributes(this),
547
        ...(typeof config === 'object' && config ? config : {})
Johann-S's avatar
Johann-S committed
548
549
550
551
552
      }

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

Johann-S's avatar
Johann-S committed
554
555
556
      if (typeof config === 'string') {
        if (typeof data[config] === 'undefined') {
          throw new TypeError(`No method named "${config}"`)
557
        }
XhmikosR's avatar
XhmikosR committed
558

Johann-S's avatar
Johann-S committed
559
560
561
562
563
        data[config](relatedTarget)
      } else if (_config.show) {
        data.show(relatedTarget)
      }
    })
564
  }
565

566
  static getInstance(element) {
567
568
    return Data.getData(element, DATA_KEY)
  }
Johann-S's avatar
Johann-S committed
569
570
571
572
573
574
575
}

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */
576

XhmikosR's avatar
XhmikosR committed
577
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
578
  const target = getElementFromSelector(this)
579

Johann-S's avatar
Johann-S committed
580
581
582
  if (this.tagName === 'A' || this.tagName === 'AREA') {
    event.preventDefault()
  }
583

XhmikosR's avatar
XhmikosR committed
584
  EventHandler.one(target, EVENT_SHOW, showEvent => {
585
586
    if (showEvent.defaultPrevented) {
      // only register focus restorer if modal will actually get shown
Johann-S's avatar
Johann-S committed
587
      return
588
589
    }

XhmikosR's avatar
XhmikosR committed
590
    EventHandler.one(target, EVENT_HIDDEN, () => {
591
      if (isVisible(this)) {
Johann-S's avatar
Johann-S committed
592
        this.focus()
593
594
595
596
      }
    })
  })

597
598
  let data = Data.getData(target, DATA_KEY)
  if (!data) {
Johann-S's avatar
Johann-S committed
599
600
601
602
603
    const config = {
      ...Manipulator.getDataAttributes(target),
      ...Manipulator.getDataAttributes(this)
    }

604
605
606
607
    data = new Modal(target, config)
  }

  data.show(this)
Johann-S's avatar
Johann-S committed
608
609
})

610
611
const $ = getjQuery()

Johann-S's avatar
Johann-S committed
612
613
614
615
/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
Johann-S's avatar
Johann-S committed
616
 * add .modal to jQuery only if jQuery is present
Johann-S's avatar
Johann-S committed
617
 */
Johann-S's avatar
Johann-S committed
618
/* istanbul ignore if */
619
if ($) {
620
  const JQUERY_NO_CONFLICT = $.fn[NAME]
621
  $.fn[NAME] = Modal.jQueryInterface
XhmikosR's avatar
XhmikosR committed
622
623
  $.fn[NAME].Constructor = Modal
  $.fn[NAME].noConflict = () => {
624
    $.fn[NAME] = JQUERY_NO_CONFLICT
625
    return Modal.jQueryInterface
626
  }
Johann-S's avatar
Johann-S committed
627
}
628
629

export default Modal