vendor: 9,470 bytes, lines 1-308
1import { E as EventHandler, S as SelectorEngine, i as isVisible, e as enableDismissTrigger, d as defineJQueryPlugin, B as BaseComponent, k as ScrollBarHelper, l as Backdrop, F as FocusTrap, r as reflow, c as isRTL } from './dom.js?5.3.8'; 2 3/** 4 * -------------------------------------------------------------------------- 5 * Bootstrap modal.js 6 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 7 * -------------------------------------------------------------------------- 8 */ 9 10 11/** 12 * Constants 13 */ 14 15const NAME = 'modal'; 16const DATA_KEY = 'bs.modal'; 17const EVENT_KEY = `.${DATA_KEY}`; 18const DATA_API_KEY = '.data-api'; 19const ESCAPE_KEY = 'Escape'; 20const EVENT_HIDE = `hide${EVENT_KEY}`; 21const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`; 22const EVENT_HIDDEN = `hidden${EVENT_KEY}`; 23const EVENT_SHOW = `show${EVENT_KEY}`; 24const EVENT_SHOWN = `shown${EVENT_KEY}`; 25const EVENT_RESIZE = `resize${EVENT_KEY}`; 26const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`; 27const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`; 28const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`; 29const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`; 30const CLASS_NAME_OPEN = 'modal-open'; 31const CLASS_NAME_FADE = 'fade'; 32const CLASS_NAME_SHOW = 'show'; 33const CLASS_NAME_STATIC = 'modal-static'; 34const OPEN_SELECTOR = '.modal.show'; 35const SELECTOR_DIALOG = '.modal-dialog'; 36const SELECTOR_MODAL_BODY = '.modal-body'; 37const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]'; 38const Default = { 39 backdrop: true, 40 focus: true, 41 keyboard: true 42}; 43const DefaultType = { 44 backdrop: '(boolean|string)', 45 focus: 'boolean', 46 keyboard: 'boolean' 47}; 48 49/** 50 * Class definition 51 */ 52 53class Modal extends BaseComponent { 54 constructor(element, config) { 55 super(element, config); 56 this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element); 57 this._backdrop = this._initializeBackDrop(); 58 this._focustrap = this._initializeFocusTrap(); 59 this._isShown = false; 60 this._isTransitioning = false; 61 this._scrollBar = new ScrollBarHelper(); 62 this._addEventListeners(); 63 } 64 65 // Getters 66 static get Default() { 67 return Default; 68 } 69 static get DefaultType() { 70 return DefaultType; 71 } 72 static get NAME() { 73 return NAME; 74 } 75 76 // Public 77 toggle(relatedTarget) { 78 return this._isShown ? this.hide() : this.show(relatedTarget); 79 } 80 show(relatedTarget) { 81 if (this._isShown || this._isTransitioning) { 82 return; 83 } 84 const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { 85 relatedTarget 86 }); 87 if (showEvent.defaultPrevented) { 88 return; 89 } 90 this._isShown = true; 91 this._isTransitioning = true; 92 this._scrollBar.hide(); 93 document.body.classList.add(CLASS_NAME_OPEN); 94 this._adjustDialog(); 95 this._backdrop.show(() => this._showElement(relatedTarget)); 96 } 97 hide() { 98 if (!this._isShown || this._isTransitioning) { 99 return; 100 } 101 const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE); 102 if (hideEvent.defaultPrevented) { 103 return; 104 } 105 this._isShown = false; 106 this._isTransitioning = true; 107 this._focustrap.deactivate(); 108 this._element.classList.remove(CLASS_NAME_SHOW); 109 this._queueCallback(() => this._hideModal(), this._element, this._isAnimated()); 110 } 111 dispose() { 112 EventHandler.off(window, EVENT_KEY); 113 EventHandler.off(this._dialog, EVENT_KEY); 114 this._backdrop.dispose(); 115 this._focustrap.deactivate(); 116 super.dispose(); 117 } 118 handleUpdate() { 119 this._adjustDialog(); 120 } 121 122 // Private 123 _initializeBackDrop() { 124 return new Backdrop({ 125 isVisible: Boolean(this._config.backdrop), 126 // 'static' option will be translated to true, and booleans will keep their value, 127 isAnimated: this._isAnimated() 128 }); 129 } 130 _initializeFocusTrap() { 131 return new FocusTrap({ 132 trapElement: this._element 133 }); 134 } 135 _showElement(relatedTarget) { 136 // try to append dynamic modal 137 if (!document.body.contains(this._element)) { 138 document.body.append(this._element); 139 } 140 this._element.style.display = 'block'; 141 this._element.removeAttribute('aria-hidden'); 142 this._element.setAttribute('aria-modal', true); 143 this._element.setAttribute('role', 'dialog'); 144 this._element.scrollTop = 0; 145 const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog); 146 if (modalBody) { 147 modalBody.scrollTop = 0; 148 } 149 reflow(this._element); 150 this._element.classList.add(CLASS_NAME_SHOW); 151 const transitionComplete = () => { 152 if (this._config.focus) { 153 this._focustrap.activate(); 154 } 155 this._isTransitioning = false; 156 EventHandler.trigger(this._element, EVENT_SHOWN, { 157 relatedTarget 158 }); 159 }; 160 this._queueCallback(transitionComplete, this._dialog, this._isAnimated()); 161 } 162 _addEventListeners() { 163 EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { 164 if (event.key !== ESCAPE_KEY) { 165 return; 166 } 167 if (this._config.keyboard) { 168 this.hide(); 169 return; 170 } 171 this._triggerBackdropTransition(); 172 }); 173 EventHandler.on(window, EVENT_RESIZE, () => { 174 if (this._isShown && !this._isTransitioning) { 175 this._adjustDialog(); 176 } 177 }); 178 EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => { 179 // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks 180 EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => { 181 if (this._element !== event.target || this._element !== event2.target) { 182 return; 183 } 184 if (this._config.backdrop === 'static') { 185 this._triggerBackdropTransition(); 186 return; 187 } 188 if (this._config.backdrop) { 189 this.hide(); 190 } 191 }); 192 }); 193 } 194 _hideModal() { 195 this._element.style.display = 'none'; 196 this._element.setAttribute('aria-hidden', true); 197 this._element.removeAttribute('aria-modal'); 198 this._element.removeAttribute('role'); 199 this._isTransitioning = false; 200 this._backdrop.hide(() => { 201 document.body.classList.remove(CLASS_NAME_OPEN); 202 this._resetAdjustments(); 203 this._scrollBar.reset(); 204 EventHandler.trigger(this._element, EVENT_HIDDEN); 205 }); 206 } 207 _isAnimated() { 208 return this._element.classList.contains(CLASS_NAME_FADE); 209 } 210 _triggerBackdropTransition() { 211 const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); 212 if (hideEvent.defaultPrevented) { 213 return; 214 } 215 const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; 216 const initialOverflowY = this._element.style.overflowY; 217 // return if the following background transition hasn't yet completed 218 if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) { 219 return; 220 } 221 if (!isModalOverflowing) { 222 this._element.style.overflowY = 'hidden'; 223 } 224 this._element.classList.add(CLASS_NAME_STATIC); 225 this._queueCallback(() => { 226 this._element.classList.remove(CLASS_NAME_STATIC); 227 this._queueCallback(() => { 228 this._element.style.overflowY = initialOverflowY; 229 }, this._dialog); 230 }, this._dialog); 231 this._element.focus(); 232 } 233 234 /** 235 * The following methods are used to handle overflowing modals 236 */ 237 238 _adjustDialog() { 239 const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; 240 const scrollbarWidth = this._scrollBar.getWidth(); 241 const isBodyOverflowing = scrollbarWidth > 0; 242 if (isBodyOverflowing && !isModalOverflowing) { 243 const property = isRTL() ? 'paddingLeft' : 'paddingRight'; 244 this._element.style[property] = `${scrollbarWidth}px`; 245 } 246 if (!isBodyOverflowing && isModalOverflowing) { 247 const property = isRTL() ? 'paddingRight' : 'paddingLeft'; 248 this._element.style[property] = `${scrollbarWidth}px`; 249 } 250 } 251 _resetAdjustments() { 252 this._element.style.paddingLeft = ''; 253 this._element.style.paddingRight = ''; 254 } 255 256 // Static 257 static jQueryInterface(config, relatedTarget) { 258 return this.each(function () { 259 const data = Modal.getOrCreateInstance(this, config); 260 if (typeof config !== 'string') { 261 return; 262 } 263 if (typeof data[config] === 'undefined') { 264 throw new TypeError(`No method named "${config}"`); 265 } 266 data[config](relatedTarget); 267 }); 268 } 269} 270 271/** 272 * Data API implementation 273 */ 274 275EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { 276 const target = SelectorEngine.getElementFromSelector(this); 277 if (['A', 'AREA'].includes(this.tagName)) { 278 event.preventDefault(); 279 } 280 EventHandler.one(target, EVENT_SHOW, showEvent => { 281 if (showEvent.defaultPrevented) { 282 // only register focus restorer if modal will actually get shown 283 return; 284 } 285 EventHandler.one(target, EVENT_HIDDEN, () => { 286 if (isVisible(this)) { 287 this.focus(); 288 } 289 }); 290 }); 291 292 // avoid conflict when clicking modal toggler while another one is open 293 const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR); 294 if (alreadyOpen) { 295 Modal.getInstance(alreadyOpen).hide(); 296 } 297 const data = Modal.getOrCreateInstance(target); 298 data.toggle(this); 299}); 300enableDismissTrigger(Modal); 301 302/** 303 * jQuery 304 */ 305 306defineJQueryPlugin(Modal); 307 308Joomla = Joomla || {};
309Joomla.Modal = Joomla.Modal || {}; 310window.bootstrap = window.bootstrap || {}; 311window.bootstrap.Modal = Modal; 312const allowed = { 313 iframe: ['src', 'name', 'width', 'height'] 314}; 315Joomla.initialiseModal = (modal, options) => { 316 if (!(modal instanceof Element)) { 317 return; 318 } 319 new window.bootstrap.Modal(modal, options); 320 321 // Comply with the Joomla API - Bound element.open/close 322 modal.open = () => { 323 window.bootstrap.Modal.getInstance(modal).show(modal); 324 }; 325 modal.close = () => { 326 window.bootstrap.Modal.getInstance(modal).hide(); 327 }; 328 329 // Do some Joomla specific changes 330 modal.addEventListener('show.bs.modal', () => { 331 // Comply with the Joomla API - Set the current Modal ID 332 Joomla.Modal.setCurrent(modal); 333 if (modal.dataset.url) { 334 const modalBody = modal.querySelector('.modal-body'); 335 const iframe = modalBody.querySelector('iframe'); 336 if (iframe) { 337 const addData = modal.querySelector('joomla-field-mediamore'); 338 if (addData) { 339 addData.parentNode.removeChild(addData); 340 } 341 iframe.parentNode.removeChild(iframe); 342 } 343 344 // @todo merge https://github.com/joomla/joomla-cms/pull/20788 345 // Hacks because com_associations and field modals use pure javascript in the url! 346 if (modal.dataset.iframe.indexOf('document.getElementById') > 0) { 347 const iframeTextArr = modal.dataset.iframe.split('+'); 348 const idFieldArr = iframeTextArr[1].split('"'); 349 let el; 350 idFieldArr[0] = idFieldArr[0].replace(/"/g, '"'); 351 if (!document.getElementById(idFieldArr[1])) { 352 const fn = new Function(`return ${idFieldArr[0]}`); // This is UNSAFE!!!! 353 el = fn.call(null); 354 } else { 355 el = document.getElementById(idFieldArr[1]).value; 356 } 357 modalBody.insertAdjacentHTML('afterbegin', Joomla.sanitizeHtml(`${iframeTextArr[0]}${el}${iframeTextArr[2]}`, allowed)); 358 } else { 359 modalBody.insertAdjacentHTML('afterbegin', Joomla.sanitizeHtml(modal.dataset.iframe, allowed)); 360 } 361 } 362 }); 363 modal.addEventListener('shown.bs.modal', () => { 364 const modalBody = modal.querySelector('.modal-body'); 365 const modalHeader = modal.querySelector('.modal-header'); 366 const modalFooter = modal.querySelector('.modal-footer'); 367 let modalHeaderHeight = 0; 368 let modalFooterHeight = 0; 369 let maxModalBodyHeight = 0; 370 let modalBodyPadding = 0; 371 let modalBodyHeightOuter = 0; 372 if (modalBody) { 373 if (modalHeader) { 374 const modalHeaderRects = modalHeader.getBoundingClientRect(); 375 modalHeaderHeight = modalHeaderRects.height; 376 modalBodyHeightOuter = modalBody.offsetHeight; 377 } 378 if (modalFooter) { 379 modalFooterHeight = parseFloat(getComputedStyle(modalFooter, null).height.replace('px', '')); 380 } 381 const modalBodyHeight = parseFloat(getComputedStyle(modalBody, null).height.replace('px', '')); 382 const padding = modalBody.offsetTop; 383 const maxModalHeight = parseFloat(getComputedStyle(document.body, null).height.replace('px', '')) - padding * 2; 384 modalBodyPadding = modalBodyHeightOuter - modalBodyHeight; 385 maxModalBodyHeight = maxModalHeight - (modalHeaderHeight + modalFooterHeight + modalBodyPadding); 386 } 387 if (modal.dataset.url) { 388 const iframeEl = modal.querySelector('iframe'); 389 const iframeHeight = parseFloat(getComputedStyle(iframeEl, null).height.replace('px', '')); 390 if (iframeHeight > maxModalBodyHeight) { 391 modalBody.style.maxHeight = maxModalBodyHeight; 392 modalBody.style.overflowY = 'auto'; 393 iframeEl.style.maxHeight = maxModalBodyHeight - modalBodyPadding; 394 } 395 } 396 }); 397 modal.addEventListener('hide.bs.modal', () => { 398 const modalBody = modal.querySelector('.modal-body'); 399 modalBody.style.maxHeight = 'initial'; 400 }); 401 modal.addEventListener('hidden.bs.modal', () => { 402 // Comply with the Joomla API - Remove the current Modal ID 403 Joomla.Modal.setCurrent(''); 404 }); 405}; 406 407/** 408 * Method to invoke a click on button inside an iframe 409 * 410 * @param {object} options Object with the css selector for the parent element of an iframe 411 * and the selector of the button in the iframe that will be clicked 412 * { iframeSelector: '', buttonSelector: '' } 413 * @returns {boolean} 414 * 415 * @since 4.0.0 416 */ 417Joomla.iframeButtonClick = options => { 418 if (!options.iframeSelector || !options.buttonSelector) { 419 throw new Error('Selector is missing'); 420 } 421 422 // Backward compatibility for older buttons 423 const old2newBtn = { 424 '#closeBtn': '#closeBtn, #toolbar-cancel>button', 425 '#saveBtn': '#saveBtn, #toolbar-save>button', 426 '#applyBtn': '#applyBtn, #toolbar-apply>button' 427 }; 428 const iframe = document.querySelector(`${options.iframeSelector} iframe`); 429 if (iframe) { 430 const selector = old2newBtn[options.buttonSelector] ? old2newBtn[options.buttonSelector] : options.buttonSelector; 431 const button = iframe.contentWindow.document.querySelector(selector); 432 if (button) { 433 button.click(); 434 } 435 } 436}; 437if (Joomla && Joomla.getOptions) { 438 // Get the elements/configurations from the PHP 439 const modals = Joomla.getOptions('bootstrap.modal'); 440 // Initialise the elements 441 if (typeof modals === 'object' && modals !== null) {
442 Object.keys(modals).forEach(modal => { 443 const opt = modals[modal]; 444 const options = { 445 backdrop: opt.backdrop ? opt.backdrop : true, 446 keyboard: opt.keyboard ? opt.keyboard : true, 447 focus: opt.focus ? opt.focus : true 448 }; 449 document.querySelectorAll(modal).forEach(modalEl => Joomla.initialiseModal(modalEl, options)); 450 }); 451 } 452} 453 454export { Modal as M };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.