-
-
Notifications
You must be signed in to change notification settings - Fork 18
Timing issue when typing after clearing sometimes causes container to be stuck with broken styles #43
Description
Version: 0.0.9
Description
When a TextMorph container transitions to an empty string and the user types a new character quickly (NOT immediately) the container sometimes permanently freezes at width: 0px; height: 0px.
The computed styles on the stuck element:
transition-duration: 400ms;
transition-timing-function: cubic-bezier(0.19, 1, 0.22, 1);
width: 0px;
height: 0px;Steps to reproduce
- Render a
TextMorphinside an input overlay (transparent<input>+ morphing text overlay). - Type a value, e.g.
"100". - Hold backspace to clear the field to
"". - Type a new character, slightly before fade animation ends (may need a few tries to trigger it).
Example (near the end stucked in weird position because container dimensions are stuck at 0);
demo-torph.mp4
Root cause
transitionContainerSize in animate.ts manages the width/height CSS transition on the torph-root element. When text becomes empty it animates the container shut, setting style.width and style.height to "0px", and stores a cancel callback in the module-level pendingCleanup:
pendingCleanup = () => {
element.removeEventListener("transitionend", onEnd);
clearTimeout(fallbackTimer);
pendingCleanup = null;
// ⚠️ does NOT reset style.width / style.height
};CSS transitions are committed at frame boundaries, not synchronously during script execution. If a new keystroke arrives before the first transition frame renders, element.offsetWidth (forced reflow) returns 0 — the CSS target value — rather than the animated-from value.
The next call to transitionContainerSize then:
- Calls
pendingCleanup()— removes thetransitionendlistener and clears the fallback timer, without resettingstyle.widthorstyle.height. - Hits the early-return guard because
oldWidth === 0 || oldHeight === 0. - Returns, leaving
style.width = "0px"andstyle.height = "0px"permanently on the element.
The normal completion path (cleanup()) does reset both properties to "auto", but it is never reached because pendingCleanup() already removed the listener and cleared the timer.
Fix
Reset style.width and style.height to "auto" in the early-return branch, so the container is always unblocked when source dimensions are unknown:
export function transitionContainerSize(
element: HTMLElement,
oldWidth: number,
oldHeight: number,
duration: number,
onComplete?: () => void,
) {
if (pendingCleanup) {
pendingCleanup();
pendingCleanup = null;
}
if (oldWidth === 0 || oldHeight === 0) {
element.style.width = "auto"; // ← add these two lines
element.style.height = "auto"; // ←
return;
}
// ... rest unchanged
}This ensures that even when transitionContainerSize exits early, any previously-pinned "0px" inline styles are cleared and the container reverts to natural content sizing.
The corresponding patch-package fixes the issue.