theme/boost/amd/src/bootstrap/toast.js

  1. /**
  2. * --------------------------------------------------------------------------
  3. * Bootstrap toast.js
  4. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
  5. * --------------------------------------------------------------------------
  6. */
  7. import BaseComponent from './base-component'
  8. import EventHandler from './dom/event-handler'
  9. import { enableDismissTrigger } from './util/component-functions'
  10. import { defineJQueryPlugin, reflow } from './util/index'
  11. /**
  12. * Constants
  13. */
  14. const NAME = 'toast'
  15. const DATA_KEY = 'bs.toast'
  16. const EVENT_KEY = `.${DATA_KEY}`
  17. const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`
  18. const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`
  19. const EVENT_FOCUSIN = `focusin${EVENT_KEY}`
  20. const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`
  21. const EVENT_HIDE = `hide${EVENT_KEY}`
  22. const EVENT_HIDDEN = `hidden${EVENT_KEY}`
  23. const EVENT_SHOW = `show${EVENT_KEY}`
  24. const EVENT_SHOWN = `shown${EVENT_KEY}`
  25. const CLASS_NAME_FADE = 'fade'
  26. const CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility
  27. const CLASS_NAME_SHOW = 'show'
  28. const CLASS_NAME_SHOWING = 'showing'
  29. const DefaultType = {
  30. animation: 'boolean',
  31. autohide: 'boolean',
  32. delay: 'number'
  33. }
  34. const Default = {
  35. animation: true,
  36. autohide: true,
  37. delay: 5000
  38. }
  39. /**
  40. * Class definition
  41. */
  42. class Toast extends BaseComponent {
  43. constructor(element, config) {
  44. super(element, config)
  45. this._timeout = null
  46. this._hasMouseInteraction = false
  47. this._hasKeyboardInteraction = false
  48. this._setListeners()
  49. }
  50. // Getters
  51. static get Default() {
  52. return Default
  53. }
  54. static get DefaultType() {
  55. return DefaultType
  56. }
  57. static get NAME() {
  58. return NAME
  59. }
  60. // Public
  61. show() {
  62. const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)
  63. if (showEvent.defaultPrevented) {
  64. return
  65. }
  66. this._clearTimeout()
  67. if (this._config.animation) {
  68. this._element.classList.add(CLASS_NAME_FADE)
  69. }
  70. const complete = () => {
  71. this._element.classList.remove(CLASS_NAME_SHOWING)
  72. EventHandler.trigger(this._element, EVENT_SHOWN)
  73. this._maybeScheduleHide()
  74. }
  75. this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated
  76. reflow(this._element)
  77. this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)
  78. this._queueCallback(complete, this._element, this._config.animation)
  79. }
  80. hide() {
  81. if (!this.isShown()) {
  82. return
  83. }
  84. const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)
  85. if (hideEvent.defaultPrevented) {
  86. return
  87. }
  88. const complete = () => {
  89. this._element.classList.add(CLASS_NAME_HIDE) // @deprecated
  90. this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)
  91. EventHandler.trigger(this._element, EVENT_HIDDEN)
  92. }
  93. this._element.classList.add(CLASS_NAME_SHOWING)
  94. this._queueCallback(complete, this._element, this._config.animation)
  95. }
  96. dispose() {
  97. this._clearTimeout()
  98. if (this.isShown()) {
  99. this._element.classList.remove(CLASS_NAME_SHOW)
  100. }
  101. super.dispose()
  102. }
  103. isShown() {
  104. return this._element.classList.contains(CLASS_NAME_SHOW)
  105. }
  106. // Private
  107. _maybeScheduleHide() {
  108. if (!this._config.autohide) {
  109. return
  110. }
  111. if (this._hasMouseInteraction || this._hasKeyboardInteraction) {
  112. return
  113. }
  114. this._timeout = setTimeout(() => {
  115. this.hide()
  116. }, this._config.delay)
  117. }
  118. _onInteraction(event, isInteracting) {
  119. switch (event.type) {
  120. case 'mouseover':
  121. case 'mouseout': {
  122. this._hasMouseInteraction = isInteracting
  123. break
  124. }
  125. case 'focusin':
  126. case 'focusout': {
  127. this._hasKeyboardInteraction = isInteracting
  128. break
  129. }
  130. default: {
  131. break
  132. }
  133. }
  134. if (isInteracting) {
  135. this._clearTimeout()
  136. return
  137. }
  138. const nextElement = event.relatedTarget
  139. if (this._element === nextElement || this._element.contains(nextElement)) {
  140. return
  141. }
  142. this._maybeScheduleHide()
  143. }
  144. _setListeners() {
  145. EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))
  146. EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))
  147. EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))
  148. EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))
  149. }
  150. _clearTimeout() {
  151. clearTimeout(this._timeout)
  152. this._timeout = null
  153. }
  154. // Static
  155. static jQueryInterface(config) {
  156. return this.each(function () {
  157. const data = Toast.getOrCreateInstance(this, config)
  158. if (typeof config === 'string') {
  159. if (typeof data[config] === 'undefined') {
  160. throw new TypeError(`No method named "${config}"`)
  161. }
  162. data[config](this)
  163. }
  164. })
  165. }
  166. }
  167. /**
  168. * Data API implementation
  169. */
  170. enableDismissTrigger(Toast)
  171. /**
  172. * jQuery
  173. */
  174. defineJQueryPlugin(Toast)
  175. export default Toast