source: imaps-frontend/node_modules/bootstrap/js/src/modal.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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