-
-
Notifications
You must be signed in to change notification settings - Fork 78.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix handling of transitionend events dispatched by nested elements #33845
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flag variable ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although, there is no test covering the use of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about omitting this. I mean in theory we could, since the event handler unregisters itself, thus the callback is only executed once. But there would be another
I added another test that I hope will satisfy your objection. (I can't really verify the state of called since it's scoped to the function.) |
||
|
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although the comment was not present here before, 5 seems the magic number here thus it is better to put a nice comment 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what to comment here.. I tried to find out why it's 5 but I have no idea.. Considering that one frame of a 60fps animation is about 16ms - 5ms seems basically nothing.
AFAICT the idea behind this emulation code was to dispatch a
transitionend
event if no othertransitionend
event had been triggered. Likely as a fallback for legacy browsers that don't support them.I guess the intention was to add some additional duration so the timeout will finish after the native transition event in case the browser dispatch it. That's hard to enforce tho since we're not starting any transition here (or tracking its state) but only adding some listener and a timeout that would trigger after the calculated duration no matter what.
Also
getTransitionDurationFromElement
only respects the duration of one (the first) CSS transition. If there are multiple transitions defined and one that takes longer (which isn't the first one)setTimeout
might finish too early. (This shouldn't be the case with our defaults from what I can tell.)In general since the timeout triggers a (custom)
transitionend
event and we don't match or verify them, this could lead to inconsistencies. For example the browser takes longer for the transition,setTimeout
will trigger an event, then the browser triggers another event for the same transition.On a sidenote, native
transitionend
events in the test environment seem to be somewhat unreliable. No idea if this is some optimization the browser is doing because it's headless or the elements are rendered offscreen (because of the fixture) or what exactly is causing this.setTimeout
on the other hand works pretty reliable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/CC @Johann-S for the opinion ❤️