1/** 2 * Author and copyright: Stefan Haack (https://shaack.com) 3 * Repository: https://github.com/shaack/bootstrap-show-toast 4 * License: MIT, see file 'LICENSE' 5 */ 6; (function (bootstrap) { 7 "use strict" 8 9 function createElement(html) { 10 const template = document.createElement('template') 11 template.innerHTML = html.trim() 12 return template.content.firstChild 13 } 14 15 function Toast(props) { 16 // see https://getbootstrap.com/docs/5.2/components/toasts/ 17 this.props = { 18 header: "", // the header text 19 headerSmall: "", // additional text in the header, aligns right 20 body: "", // the body text of the toast 21 closeButton: true, // show a close button 22 closeButtonLabel: "close", // the label of the close button 23 closeButtonClass: "", // set to "btn-close-white" for dark backgrounds 24 toastClass: "", // the appearance 25 animation: true, // apply a CSS fade transition to the toast 26 delay: 5000, // delay in milliseconds before hiding the toast, set delay to `Infinity` to make it sticky 27 position: "top-0 end-0", // top right 28 topOffset: "", // override top position with an explicit CSS value (e.g. "80px") â omit top-0 from position when using this 29 rightOffset: "", // override right padding with a CSS value (e.g. "16px") or "container" to auto-align with the Bootstrap .container right edge 30 direction: "append", // or "prepend", the stack direction 31 zIndex: 1100, // the z-index of the container 32 ariaLive: "assertive", 33 type: "info", // "info", "success", "warning", "error" 34 width: "", // override toast width with an explicit CSS value (e.g. "250px") 35 } 36 this.containerId = "bootstrap-show-toast-container-" + this.props.position.replace(" ", "_") 37 for (let prop in props) { 38 // noinspection JSUnfilteredForInLoop 39 this.props[prop] = props[prop] 40 } 41 const cssClass = ("toast " + this.props.toastClass).trim() 42 let toastHeader = "" 43 const showHeader = this.props.header || this.props.headerSmall 44 let colors = { 45 info: "primary", 46 success: "success", 47 warning: "warning", 48 error: "danger" 49 } 50 if (showHeader) { 51 toastHeader = `<div class="toast-header bg-white border-0 border-start border-5 border-${colors[this.props.type]} rounded"> 52 <div class="mb-0 me-auto">${this.props.header}</div> 53 ${this.props.headerSmall ? `<small>${this.props.headerSmall}</small>` : ""} 54 ${this.props.closeButton ? `<button type="button" class="btn-close ${this.props.closeButtonClass}" data-bs-dismiss="toast" aria-label="${this.props.closeButtonLabel}"></button>` : ""} 55 </div>` 56 } 57 this.template = 58 `<div class="${cssClass} border-0 shadow-lg" role="alert" aria-live="${this.props.ariaLive}" aria-atomic="true"> 59 ${toastHeader} 60 </div>` 61 // <div class="d-flex bg-white"> 62 // <div class="toast-body"> 63 // ${this.props.body} 64 // </div> 65 // ${(!showHeader && this.props.closeButton) ? `<button type="button" class="btn-close me-2 mx-auto ${this.props.closeButtonClass}" style="margin-top: 0.69rem" data-bs-dismiss="toast" aria-label="${this.props.closeButtonLabel}"></button>` : ""} 66 // </div> 67 this.container = document.getElementById(this.containerId) 68 if (!this.container) { 69 this.container = document.createElement("div") 70 this.container.id = this.containerId 71 this.container.setAttribute("class", "toast-container position-fixed p-3 " + this.props.position) 72 this.container.style.zIndex = "" + this.props.zIndex 73 this.container.style.transition = "margin-top 0.3s ease" 74 if (this.props.topOffset) { 75 const applyTopMargin = (container, topOffsetProp) => { 76 if (topOffsetProp === 'navbar') { 77 const navbarEl = document.querySelector('nav.navbar') 78 if (!navbarEl) { 79 container.style.setProperty('margin-top', '0px', 'important') 80 return 81 } 82 const isNavbarVisible = !navbarEl.classList.contains('navbar--hidden') 83 if (isNavbarVisible) { 84 const topMargin = (navbarEl.getBoundingClientRect().bottom - 10) + 'px' 85 container.style.setProperty('margin-top', topMargin, 'important') 86 } else { 87 container.style.setProperty('margin-top', '0px', 'important') 88 } 89 return 90 } 91 container.style.setProperty('margin-top', topOffsetProp, 'important') 92 } 93 94 applyTopMargin(this.container, this.props.topOffset) 95 96 if (this.props.topOffset === 'navbar') { 97 // Re-apply on resize 98 window.addEventListener('resize', () => { 99 applyTopMargin(this.container, this.props.topOffset) 100 }) 101 102 // Re-apply on scroll to track navbar show/hide 103 window.addEventListener('scroll', () => { 104 applyTopMargin(this.container, this.props.topOffset) 105 }, { passive: true }) 106 107 // Also observe class changes on the navbar for immediate reaction 108 const navbarEl = document.querySelector('nav.navbar') 109 if (navbarEl) { 110 const observer = new MutationObserver(() => { 111 applyTopMargin(this.container, this.props.topOffset) 112 }) 113 observer.observe(navbarEl, { attributes: true, attributeFilter: ['class'] }) 114 } 115 } 116 } 117 if (this.props.rightOffset) { 118 const applyRightPadding = (container, rightOffsetProp) => { 119 if (rightOffsetProp === 'container') { 120 // Look for the specific page container: catalogue or product detail 121 const targetEl = document.querySelector('.catalogue-container') 122 || document.querySelector('.top-area') 123 if (!targetEl) { 124 container.style.setProperty('padding-right', '0px', 'important') 125 return 126 } 127 const targetRect = targetEl.getBoundingClientRect() 128 const rightGap = window.innerWidth - targetRect.right 129 container.style.setProperty('padding-right', Math.max(rightGap, 0) + 'px', 'important') 130 return 131 } 132 container.style.setProperty('padding-right', rightOffsetProp, 'important') 133 } 134 135 applyRightPadding(this.container, this.props.rightOffset) 136 137 if (this.props.rightOffset === 'container') { 138 window.addEventListener('resize', () => { 139 applyRightPadding(this.container, this.props.rightOffset) 140 }) 141 } 142 } 143 document.body.appendChild(this.container) 144 } 145 this.element = createElement(this.template) 146 if (this.props.width) { 147 this.element.style.width = this.props.width 148 } 149 this.toast = this.showToast(this.element) 150 } 151 152 Toast.prototype.showToast = function (toastElement) {
153 if (this.props.direction === "prepend") { 154 this.container.prepend(toastElement) 155 } else { 156 this.container.append(toastElement) 157 } 158 this.toast = new bootstrap.Toast(toastElement, { 159 animation: this.props.animation, 160 autohide: this.props.delay > 0 && this.props.delay !== Infinity, // TODO remove 0 for infinity 2022-09-02 161 delay: this.props.delay 162 163 }) 164 this.toast.show() 165 166 return toastElement 167 } 168 169 bootstrap.showToast = function (props) { 170 return new Toast(props) 171 } 172 173}(bootstrap)) 174 175window.addEventListener("showToast", function (event) { 176 bootstrap.showToast(event.detail) 177})
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.