source: trip-planner-front/node_modules/bootstrap/js/src/modal.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 11.3 KB
Line 
1/**
2 * --------------------------------------------------------------------------
3 * Bootstrap (v5.1.3): modal.js
4 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5 * --------------------------------------------------------------------------
6 */
7
8import {
9 defineJQueryPlugin,
10 getElementFromSelector,
11 isRTL,
12 isVisible,
13 reflow,
14 typeCheckConfig
15} from './util/index'
16import EventHandler from './dom/event-handler'
17import Manipulator from './dom/manipulator'
18import SelectorEngine from './dom/selector-engine'
19import ScrollBarHelper from './util/scrollbar'
20import BaseComponent from './base-component'
21import Backdrop from './util/backdrop'
22import FocusTrap from './util/focustrap'
23import { enableDismissTrigger } from './util/component-functions'
24
25/**
26 * ------------------------------------------------------------------------
27 * Constants
28 * ------------------------------------------------------------------------
29 */
30
31const NAME = 'modal'
32const DATA_KEY = 'bs.modal'
33const EVENT_KEY = `.${DATA_KEY}`
34const DATA_API_KEY = '.data-api'
35const ESCAPE_KEY = 'Escape'
36
37const Default = {
38 backdrop: true,
39 keyboard: true,
40 focus: true
41}
42
43const DefaultType = {
44 backdrop: '(boolean|string)',
45 keyboard: 'boolean',
46 focus: 'boolean'
47}
48
49const EVENT_HIDE = `hide${EVENT_KEY}`
50const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`
51const EVENT_HIDDEN = `hidden${EVENT_KEY}`
52const EVENT_SHOW = `show${EVENT_KEY}`
53const EVENT_SHOWN = `shown${EVENT_KEY}`
54const EVENT_RESIZE = `resize${EVENT_KEY}`
55const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`
56const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`
57const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}`
58const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`
59const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
60
61const CLASS_NAME_OPEN = 'modal-open'
62const CLASS_NAME_FADE = 'fade'
63const CLASS_NAME_SHOW = 'show'
64const CLASS_NAME_STATIC = 'modal-static'
65
66const OPEN_SELECTOR = '.modal.show'
67const SELECTOR_DIALOG = '.modal-dialog'
68const SELECTOR_MODAL_BODY = '.modal-body'
69const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]'
70
71/**
72 * ------------------------------------------------------------------------
73 * Class Definition
74 * ------------------------------------------------------------------------
75 */
76
77class Modal extends BaseComponent {
78 constructor(element, config) {
79 super(element)
80
81 this._config = this._getConfig(config)
82 this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)
83 this._backdrop = this._initializeBackDrop()
84 this._focustrap = this._initializeFocusTrap()
85 this._isShown = false
86 this._ignoreBackdropClick = false
87 this._isTransitioning = false
88 this._scrollBar = new ScrollBarHelper()
89 }
90
91 // Getters
92
93 static get Default() {
94 return Default
95 }
96
97 static get NAME() {
98 return NAME
99 }
100
101 // Public
102
103 toggle(relatedTarget) {
104 return this._isShown ? this.hide() : this.show(relatedTarget)
105 }
106
107 show(relatedTarget) {
108 if (this._isShown || this._isTransitioning) {
109 return
110 }
111
112 const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
113 relatedTarget
114 })
115
116 if (showEvent.defaultPrevented) {
117 return
118 }
119
120 this._isShown = true
121
122 if (this._isAnimated()) {
123 this._isTransitioning = true
124 }
125
126 this._scrollBar.hide()
127
128 document.body.classList.add(CLASS_NAME_OPEN)
129
130 this._adjustDialog()
131
132 this._setEscapeEvent()
133 this._setResizeEvent()
134
135 EventHandler.on(this._dialog, EVENT_MOUSEDOWN_DISMISS, () => {
136 EventHandler.one(this._element, EVENT_MOUSEUP_DISMISS, event => {
137 if (event.target === this._element) {
138 this._ignoreBackdropClick = true
139 }
140 })
141 })
142
143 this._showBackdrop(() => this._showElement(relatedTarget))
144 }
145
146 hide() {
147 if (!this._isShown || this._isTransitioning) {
148 return
149 }
150
151 const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)
152
153 if (hideEvent.defaultPrevented) {
154 return
155 }
156
157 this._isShown = false
158 const isAnimated = this._isAnimated()
159
160 if (isAnimated) {
161 this._isTransitioning = true
162 }
163
164 this._setEscapeEvent()
165 this._setResizeEvent()
166
167 this._focustrap.deactivate()
168
169 this._element.classList.remove(CLASS_NAME_SHOW)
170
171 EventHandler.off(this._element, EVENT_CLICK_DISMISS)
172 EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS)
173
174 this._queueCallback(() => this._hideModal(), this._element, isAnimated)
175 }
176
177 dispose() {
178 [window, this._dialog]
179 .forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY))
180
181 this._backdrop.dispose()
182 this._focustrap.deactivate()
183 super.dispose()
184 }
185
186 handleUpdate() {
187 this._adjustDialog()
188 }
189
190 // Private
191
192 _initializeBackDrop() {
193 return new Backdrop({
194 isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value
195 isAnimated: this._isAnimated()
196 })
197 }
198
199 _initializeFocusTrap() {
200 return new FocusTrap({
201 trapElement: this._element
202 })
203 }
204
205 _getConfig(config) {
206 config = {
207 ...Default,
208 ...Manipulator.getDataAttributes(this._element),
209 ...(typeof config === 'object' ? config : {})
210 }
211 typeCheckConfig(NAME, config, DefaultType)
212 return config
213 }
214
215 _showElement(relatedTarget) {
216 const isAnimated = this._isAnimated()
217 const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)
218
219 if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {
220 // Don't move modal's DOM position
221 document.body.append(this._element)
222 }
223
224 this._element.style.display = 'block'
225 this._element.removeAttribute('aria-hidden')
226 this._element.setAttribute('aria-modal', true)
227 this._element.setAttribute('role', 'dialog')
228 this._element.scrollTop = 0
229
230 if (modalBody) {
231 modalBody.scrollTop = 0
232 }
233
234 if (isAnimated) {
235 reflow(this._element)
236 }
237
238 this._element.classList.add(CLASS_NAME_SHOW)
239
240 const transitionComplete = () => {
241 if (this._config.focus) {
242 this._focustrap.activate()
243 }
244
245 this._isTransitioning = false
246 EventHandler.trigger(this._element, EVENT_SHOWN, {
247 relatedTarget
248 })
249 }
250
251 this._queueCallback(transitionComplete, this._dialog, isAnimated)
252 }
253
254 _setEscapeEvent() {
255 if (this._isShown) {
256 EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
257 if (this._config.keyboard && event.key === ESCAPE_KEY) {
258 event.preventDefault()
259 this.hide()
260 } else if (!this._config.keyboard && event.key === ESCAPE_KEY) {
261 this._triggerBackdropTransition()
262 }
263 })
264 } else {
265 EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS)
266 }
267 }
268
269 _setResizeEvent() {
270 if (this._isShown) {
271 EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog())
272 } else {
273 EventHandler.off(window, EVENT_RESIZE)
274 }
275 }
276
277 _hideModal() {
278 this._element.style.display = 'none'
279 this._element.setAttribute('aria-hidden', true)
280 this._element.removeAttribute('aria-modal')
281 this._element.removeAttribute('role')
282 this._isTransitioning = false
283 this._backdrop.hide(() => {
284 document.body.classList.remove(CLASS_NAME_OPEN)
285 this._resetAdjustments()
286 this._scrollBar.reset()
287 EventHandler.trigger(this._element, EVENT_HIDDEN)
288 })
289 }
290
291 _showBackdrop(callback) {
292 EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => {
293 if (this._ignoreBackdropClick) {
294 this._ignoreBackdropClick = false
295 return
296 }
297
298 if (event.target !== event.currentTarget) {
299 return
300 }
301
302 if (this._config.backdrop === true) {
303 this.hide()
304 } else if (this._config.backdrop === 'static') {
305 this._triggerBackdropTransition()
306 }
307 })
308
309 this._backdrop.show(callback)
310 }
311
312 _isAnimated() {
313 return this._element.classList.contains(CLASS_NAME_FADE)
314 }
315
316 _triggerBackdropTransition() {
317 const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)
318 if (hideEvent.defaultPrevented) {
319 return
320 }
321
322 const { classList, scrollHeight, style } = this._element
323 const isModalOverflowing = scrollHeight > document.documentElement.clientHeight
324
325 // return if the following background transition hasn't yet completed
326 if ((!isModalOverflowing && style.overflowY === 'hidden') || classList.contains(CLASS_NAME_STATIC)) {
327 return
328 }
329
330 if (!isModalOverflowing) {
331 style.overflowY = 'hidden'
332 }
333
334 classList.add(CLASS_NAME_STATIC)
335 this._queueCallback(() => {
336 classList.remove(CLASS_NAME_STATIC)
337 if (!isModalOverflowing) {
338 this._queueCallback(() => {
339 style.overflowY = ''
340 }, this._dialog)
341 }
342 }, this._dialog)
343
344 this._element.focus()
345 }
346
347 // ----------------------------------------------------------------------
348 // the following methods are used to handle overflowing modals
349 // ----------------------------------------------------------------------
350
351 _adjustDialog() {
352 const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight
353 const scrollbarWidth = this._scrollBar.getWidth()
354 const isBodyOverflowing = scrollbarWidth > 0
355
356 if ((!isBodyOverflowing && isModalOverflowing && !isRTL()) || (isBodyOverflowing && !isModalOverflowing && isRTL())) {
357 this._element.style.paddingLeft = `${scrollbarWidth}px`
358 }
359
360 if ((isBodyOverflowing && !isModalOverflowing && !isRTL()) || (!isBodyOverflowing && isModalOverflowing && isRTL())) {
361 this._element.style.paddingRight = `${scrollbarWidth}px`
362 }
363 }
364
365 _resetAdjustments() {
366 this._element.style.paddingLeft = ''
367 this._element.style.paddingRight = ''
368 }
369
370 // Static
371
372 static jQueryInterface(config, relatedTarget) {
373 return this.each(function () {
374 const data = Modal.getOrCreateInstance(this, config)
375
376 if (typeof config !== 'string') {
377 return
378 }
379
380 if (typeof data[config] === 'undefined') {
381 throw new TypeError(`No method named "${config}"`)
382 }
383
384 data[config](relatedTarget)
385 })
386 }
387}
388
389/**
390 * ------------------------------------------------------------------------
391 * Data Api implementation
392 * ------------------------------------------------------------------------
393 */
394
395EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
396 const target = getElementFromSelector(this)
397
398 if (['A', 'AREA'].includes(this.tagName)) {
399 event.preventDefault()
400 }
401
402 EventHandler.one(target, EVENT_SHOW, showEvent => {
403 if (showEvent.defaultPrevented) {
404 // only register focus restorer if modal will actually get shown
405 return
406 }
407
408 EventHandler.one(target, EVENT_HIDDEN, () => {
409 if (isVisible(this)) {
410 this.focus()
411 }
412 })
413 })
414
415 // avoid conflict when clicking moddal toggler while another one is open
416 const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)
417 if (allReadyOpen) {
418 Modal.getInstance(allReadyOpen).hide()
419 }
420
421 const data = Modal.getOrCreateInstance(target)
422
423 data.toggle(this)
424})
425
426enableDismissTrigger(Modal)
427
428/**
429 * ------------------------------------------------------------------------
430 * jQuery
431 * ------------------------------------------------------------------------
432 * add .Modal to jQuery only if jQuery is present
433 */
434
435defineJQueryPlugin(Modal)
436
437export default Modal
Note: See TracBrowser for help on using the repository browser.