From 4a5029ea29ac75243dfec68153051292fc70f5cf Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Thu, 3 Jun 2021 13:44:16 +0200 Subject: [PATCH] Fix handling of transitionend events dispatched by nested elements(#33845) Fix handling of transitionend events dispatched by nested elements Properly handle events from nested elements Change `emulateTransitionEnd` to `executeAfterTransition` && --- js/src/base-component.js | 16 +--- js/src/modal.js | 33 +++---- js/src/util/backdrop.js | 11 +-- js/src/util/index.js | 51 +++++++---- js/tests/unit/modal.spec.js | 23 +++++ js/tests/unit/tooltip.spec.js | 2 +- js/tests/unit/util/index.spec.js | 153 +++++++++++++++++++++++++------ 7 files changed, 201 insertions(+), 88 deletions(-) diff --git a/js/src/base-component.js b/js/src/base-component.js index a5f1b36a00..368cc99c88 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -7,10 +7,8 @@ import Data from './dom/data' import { - emulateTransitionEnd, - execute, - getElement, - getTransitionDurationFromElement + executeAfterTransition, + getElement } from './util/index' import EventHandler from './dom/event-handler' @@ -44,15 +42,7 @@ class BaseComponent { } _queueCallback(callback, element, isAnimated = true) { - if (!isAnimated) { - execute(callback) - return - } - - const transitionDuration = getTransitionDurationFromElement(element) - EventHandler.one(element, 'transitionend', () => execute(callback)) - - emulateTransitionEnd(element, transitionDuration) + executeAfterTransition(callback, element, isAnimated) } /** Static */ diff --git a/js/src/modal.js b/js/src/modal.js index b05fe8de75..e8eee3b4d0 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -7,9 +7,7 @@ import { defineJQueryPlugin, - emulateTransitionEnd, getElementFromSelector, - getTransitionDurationFromElement, isRTL, isVisible, reflow, @@ -339,25 +337,28 @@ class Modal extends BaseComponent { return } - const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight + const { classList, scrollHeight, style } = this._element + const isModalOverflowing = scrollHeight > document.documentElement.clientHeight - if (!isModalOverflowing) { - this._element.style.overflowY = 'hidden' + // return if the following background transition hasn't yet completed + if ((!isModalOverflowing && style.overflowY === 'hidden') || classList.contains(CLASS_NAME_STATIC)) { + return } - this._element.classList.add(CLASS_NAME_STATIC) - const modalTransitionDuration = getTransitionDurationFromElement(this._dialog) - EventHandler.off(this._element, 'transitionend') - EventHandler.one(this._element, 'transitionend', () => { - this._element.classList.remove(CLASS_NAME_STATIC) + if (!isModalOverflowing) { + style.overflowY = 'hidden' + } + + classList.add(CLASS_NAME_STATIC) + this._queueCallback(() => { + classList.remove(CLASS_NAME_STATIC) if (!isModalOverflowing) { - EventHandler.one(this._element, 'transitionend', () => { - this._element.style.overflowY = '' - }) - emulateTransitionEnd(this._element, modalTransitionDuration) + this._queueCallback(() => { + style.overflowY = '' + }, this._dialog) } - }) - emulateTransitionEnd(this._element, modalTransitionDuration) + }, this._dialog) + this._element.focus() } diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 07ad20fab7..028325d118 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -6,7 +6,7 @@ */ import EventHandler from '../dom/event-handler' -import { emulateTransitionEnd, execute, getElement, getTransitionDurationFromElement, reflow, typeCheckConfig } from './index' +import { execute, executeAfterTransition, getElement, reflow, typeCheckConfig } from './index' const Default = { isVisible: true, // if false, we use the backdrop helper without adding any element to the dom @@ -122,14 +122,7 @@ class Backdrop { } _emulateAnimation(callback) { - if (!this._config.isAnimated) { - execute(callback) - return - } - - const backdropTransitionDuration = getTransitionDurationFromElement(this._getElement()) - EventHandler.one(this._getElement(), 'transitionend', () => execute(callback)) - emulateTransitionEnd(this._getElement(), backdropTransitionDuration) + executeAfterTransition(callback, this._getElement(), this._config.isAnimated) } } diff --git a/js/src/util/index.js b/js/src/util/index.js index 4d077b21f9..6edfaa580d 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -126,24 +126,6 @@ const getElement = obj => { return null } -const emulateTransitionEnd = (element, duration) => { - let called = false - const durationPadding = 5 - const emulatedDuration = duration + durationPadding - - function listener() { - called = true - element.removeEventListener(TRANSITION_END, listener) - } - - element.addEventListener(TRANSITION_END, listener) - setTimeout(() => { - if (!called) { - triggerTransitionEnd(element) - } - }, emulatedDuration) -} - const typeCheckConfig = (componentName, config, configTypes) => { Object.keys(configTypes).forEach(property => { const expectedTypes = configTypes[property] @@ -252,6 +234,35 @@ const execute = callback => { } } +const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { + if (!waitForTransition) { + execute(callback) + return + } + + const durationPadding = 5 + const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding + + let called = false + + const handler = ({ target }) => { + if (target !== transitionElement) { + return + } + + called = true + transitionElement.removeEventListener(TRANSITION_END, handler) + execute(callback) + } + + transitionElement.addEventListener(TRANSITION_END, handler) + setTimeout(() => { + if (!called) { + triggerTransitionEnd(transitionElement) + } + }, emulatedDuration) +} + /** * Return the previous/next element of a list. * @@ -288,7 +299,6 @@ export { getTransitionDurationFromElement, triggerTransitionEnd, isElement, - emulateTransitionEnd, typeCheckConfig, isVisible, isDisabled, @@ -300,5 +310,6 @@ export { onDOMContentLoaded, isRTL, defineJQueryPlugin, - execute + execute, + executeAfterTransition } diff --git a/js/tests/unit/modal.spec.js b/js/tests/unit/modal.spec.js index f73ac40b55..2974495cac 100644 --- a/js/tests/unit/modal.spec.js +++ b/js/tests/unit/modal.spec.js @@ -539,6 +539,29 @@ describe('Modal', () => { modal.show() }) + it('should not queue multiple callbacks when clicking outside of modal-content and backdrop = static', done => { + fixtureEl.innerHTML = '