diff --git a/js/src/carousel.js b/js/src/carousel.js index e56d4f0f29..7fda8f6152 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -116,9 +116,10 @@ const Carousel = (($) => { // public next() { - if (!this._isSliding) { - this._slide(Direction.NEXT) + if (this._isSliding) { + throw new Error('Carousel is sliding') } + this._slide(Direction.NEXT) } nextWhenVisible() { @@ -129,9 +130,10 @@ const Carousel = (($) => { } prev() { - if (!this._isSliding) { - this._slide(Direction.PREVIOUS) + if (this._isSliding) { + throw new Error('Carousel is sliding') } + this._slide(Direction.PREVIOUS) } pause(event) { diff --git a/js/src/collapse.js b/js/src/collapse.js index ebc3e24cfe..1e4730ff73 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -112,8 +112,11 @@ const Collapse = (($) => { } show() { - if (this._isTransitioning || - $(this._element).hasClass(ClassName.ACTIVE)) { + if (this._isTransitioning) { + throw new Error('Collapse is transitioning') + } + + if ($(this._element).hasClass(ClassName.ACTIVE)) { return } @@ -193,8 +196,11 @@ const Collapse = (($) => { } hide() { - if (this._isTransitioning || - !$(this._element).hasClass(ClassName.ACTIVE)) { + if (this._isTransitioning) { + throw new Error('Collapse is transitioning') + } + + if (!$(this._element).hasClass(ClassName.ACTIVE)) { return } diff --git a/js/src/modal.js b/js/src/modal.js index 61a28dbf5d..70bb68e42a 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -87,6 +87,7 @@ const Modal = (($) => { this._isShown = false this._isBodyOverflowing = false this._ignoreBackdropClick = false + this._isTransitioning = false this._originalBodyPadding = 0 this._scrollbarWidth = 0 } @@ -110,6 +111,14 @@ const Modal = (($) => { } show(relatedTarget) { + if (this._isTransitioning) { + throw new Error('Modal is transitioning') + } + + if (Util.supportsTransitionEnd() && + $(this._element).hasClass(ClassName.FADE)) { + this._isTransitioning = true + } const showEvent = $.Event(Event.SHOW, { relatedTarget }) @@ -152,8 +161,17 @@ const Modal = (($) => { event.preventDefault() } - const hideEvent = $.Event(Event.HIDE) + if (this._isTransitioning) { + throw new Error('Modal is transitioning') + } + const transition = Util.supportsTransitionEnd() && + $(this._element).hasClass(ClassName.FADE) + if (transition) { + this._isTransitioning = true + } + + const hideEvent = $.Event(Event.HIDE) $(this._element).trigger(hideEvent) if (!this._isShown || hideEvent.isDefaultPrevented()) { @@ -172,9 +190,7 @@ const Modal = (($) => { $(this._element).off(Event.CLICK_DISMISS) $(this._dialog).off(Event.MOUSEDOWN_DISMISS) - if (Util.supportsTransitionEnd() && - $(this._element).hasClass(ClassName.FADE)) { - + if (transition) { $(this._element) .one(Util.TRANSITION_END, (event) => this._hideModal(event)) .emulateTransitionEnd(TRANSITION_DURATION) @@ -240,6 +256,7 @@ const Modal = (($) => { if (this._config.focus) { this._element.focus() } + this._isTransitioning = false $(this._element).trigger(shownEvent) } @@ -287,7 +304,8 @@ const Modal = (($) => { _hideModal() { this._element.style.display = 'none' - this._element.setAttribute('aria-hidden', true) + this._element.setAttribute('aria-hidden', 'true') + this._isTransitioning = false this._showBackdrop(() => { $(document.body).removeClass(ClassName.OPEN) this._resetAdjustments() diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 2b659b8854..dc291a72ca 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -123,11 +123,12 @@ const Tooltip = (($) => { constructor(element, config) { // private - this._isEnabled = true - this._timeout = 0 - this._hoverState = '' - this._activeTrigger = {} - this._tether = null + this._isEnabled = true + this._timeout = 0 + this._hoverState = '' + this._activeTrigger = {} + this._isTransitioning = false + this._tether = null // protected this.element = element @@ -245,9 +246,12 @@ const Tooltip = (($) => { if ($(this.element).css('display') === 'none') { throw new Error('Please use show on visible elements') } - const showEvent = $.Event(this.constructor.Event.SHOW) + const showEvent = $.Event(this.constructor.Event.SHOW) if (this.isWithContent() && this._isEnabled) { + if (this._isTransitioning) { + throw new Error('Tooltip is transitioning') + } $(this.element).trigger(showEvent) const isInTheDom = $.contains( @@ -303,7 +307,8 @@ const Tooltip = (($) => { const complete = () => { const prevHoverState = this._hoverState - this._hoverState = null + this._hoverState = null + this._isTransitioning = false $(this.element).trigger(this.constructor.Event.SHOWN) @@ -313,6 +318,7 @@ const Tooltip = (($) => { } if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) { + this._isTransitioning = true $(this.tip) .one(Util.TRANSITION_END, complete) .emulateTransitionEnd(Tooltip._TRANSITION_DURATION) @@ -326,6 +332,9 @@ const Tooltip = (($) => { hide(callback) { const tip = this.getTipElement() const hideEvent = $.Event(this.constructor.Event.HIDE) + if (this._isTransitioning) { + throw new Error('Tooltip is transitioning') + } const complete = () => { if (this._hoverState !== HoverState.ACTIVE && tip.parentNode) { tip.parentNode.removeChild(tip) @@ -333,6 +342,7 @@ const Tooltip = (($) => { this.element.removeAttribute('aria-describedby') $(this.element).trigger(this.constructor.Event.HIDDEN) + this._isTransitioning = false this.cleanupTether() if (callback) { @@ -350,7 +360,7 @@ const Tooltip = (($) => { if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) { - + this._isTransitioning = true $(tip) .one(Util.TRANSITION_END, complete) .emulateTransitionEnd(TRANSITION_DURATION) diff --git a/js/tests/visual/carousel.html b/js/tests/visual/carousel.html index 2017f338b2..b26fb4a0d4 100644 --- a/js/tests/visual/carousel.html +++ b/js/tests/visual/carousel.html @@ -46,11 +46,31 @@ diff --git a/js/tests/visual/collapse.html b/js/tests/visual/collapse.html index e13597984a..973d3c5ee6 100644 --- a/js/tests/visual/collapse.html +++ b/js/tests/visual/collapse.html @@ -61,5 +61,30 @@ + diff --git a/js/tests/visual/modal.html b/js/tests/visual/modal.html index fa5bd368aa..69d3923506 100644 --- a/js/tests/visual/modal.html +++ b/js/tests/visual/modal.html @@ -188,6 +188,26 @@ } } + // Should throw an error because modal is in transition + function testModalTransitionError() { + var err = false + // Close #myModal + $('#myModal').on('shown.bs.modal', function () { + $('#myModal').modal('hide').off('shown.bs.modal') + if (!err) { + alert('No error thrown for : testModalTransitionError') + } + }) + + try { + $('#myModal').modal('show').modal('hide') + } + catch (e) { + err = true + console.error(e.message) + } + } + $(function () { $('[data-toggle="popover"]').popover() $('[data-toggle="tooltip"]').tooltip() @@ -200,6 +220,7 @@ $('#firefoxModal').on('focus', reportFirefoxTestResult.bind(false)) $('#ff-bug-input').on('focus', reportFirefoxTestResult.bind(true)) }) + testModalTransitionError() }) diff --git a/js/tests/visual/tooltip.html b/js/tests/visual/tooltip.html index 999e7eda62..6cd33e7e68 100644 --- a/js/tests/visual/tooltip.html +++ b/js/tests/visual/tooltip.html @@ -42,7 +42,26 @@