runTransition method

void runTransition(
  1. String propertyName,
  2. dynamic begin,
  3. dynamic end
)

Implementation

void runTransition(String propertyName, begin, end) {
  if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
    cssLogger.info('[transition][run] property=$propertyName begin=${begin ?? 'null'} end=$end');
  }

  // For box-shadow, prefer the current computed shadow list as the
  // transition begin value when the previous CSS text is var()-based.
  // This avoids snapping when variables are updated before the
  // shorthand (e.g., Tailwind's --tw-shadow patterns).
  if (propertyName == BOX_SHADOW && begin is String && begin.contains('var(')) {
    final dynamic current = getProperty(propertyName);
    if (current is List<CSSBoxShadow> && current.isNotEmpty) {
      try {
        begin = _stringifyBoxShadowForTransition(current);
        if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
          cssLogger.info('[transition][run] property=$propertyName override-begin-from-computed "$begin"');
        }
      } catch (_) {
        // Fallback to string-based begin if serialization fails.
      }
    }
  }
  if (_hasRunningTransition(propertyName)) {
    Animation animation = _propertyRunningTransition[propertyName]!;
    if (cssTransitionHandlers.containsKey(propertyName) && animation.effect is KeyframeEffect) {
      KeyframeEffect effect = animation.effect as KeyframeEffect;
      var interpolation = effect.interpolations.firstWhere((interpolation) => interpolation.property == propertyName);
      var stringifyFunc = cssTransitionHandlers[propertyName]![2];
      // Matrix4 begin, Matrix4 end, double t, String property, CSSRenderStyle renderStyle
      begin = stringifyFunc(
          interpolation.lerp(interpolation.begin, interpolation.end, animation.progress, propertyName, this));
    }

    if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
      cssLogger.info('[transition][run] cancel-existing property=$propertyName progress=${animation.progress.toStringAsFixed(3)}');
    }
    animation.cancel();
    // An Event fired when a CSS transition has been cancelled.
    target.dispatchEvent(Event(EVENT_TRANSITION_CANCEL));
  }

  if (begin == null || (begin is String && begin.isEmpty)) {
    begin = cssInitialValues[propertyName] ?? '';
    if (begin == CURRENT_COLOR) {
      begin = currentColor;
    }
  }

  if (end == null || (end is String && end.isEmpty)) {
    end = cssInitialValues[propertyName] ?? '';
  }

  // Keyframe.value is typed as String; ensure our transition endpoints
  // are always serialized strings before constructing keyframes.
  if (begin is! String) {
    begin = begin.toString();
  }
  if (end is! String) {
    end = end.toString();
  }

  EffectTiming? options = getTransitionEffectTiming(propertyName);

  // Fallback: if effective duration is 0, apply end immediately rather than
  // creating a no-op animation that never fires finish.
  final double durationMs = options?.duration ?? 0;
  if (durationMs <= 0) {
    if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
      cssLogger.info('[transition][run] property=$propertyName duration=0; direct-apply "$end"');
    }
    target.setRenderStyle(propertyName, end);
    return;
  }

  List<Keyframe> keyframes = [
    Keyframe(propertyName, begin, 0, LINEAR),
    Keyframe(propertyName, end, 1, LINEAR),
  ];
  KeyframeEffect effect = KeyframeEffect(this, keyframes, options, isTransition: true);
  Animation animation = Animation(effect, target.ownerDocument.animationTimeline);
  _propertyRunningTransition[propertyName] = animation;

  animation.onstart = () {
    // An Event fired when a CSS transition is created,
    // when it is added to a set of running transitions,
    // though not necessarily started.
    target.dispatchEvent(TransitionEvent(
      EVENT_TRANSITION_START,
      propertyName: _toHyphenatedCSSProperty(propertyName),
      elapsedTime: 0.0,
    ));
  };

  animation.onfinish = (AnimationPlaybackEvent event) {
    _propertyRunningTransition.remove(propertyName);
    target.setRenderStyle(propertyName, end);
    // An Event fired when a CSS transition has finished playing.
    if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
      cssLogger.info('[transition][finish] property=$propertyName applied-end "$end"');
    }
    // elapsedTime is the time the transition has run, in seconds,
    // excluding any transition-delay. Our EffectTiming stores duration
    // in milliseconds.
    final double durationMs = options?.duration ?? 0.0;
    final double elapsedSec = durationMs > 0 ? durationMs / 1000.0 : 0.0;
    target.dispatchEvent(TransitionEvent(
      EVENT_TRANSITION_END,
      propertyName: _toHyphenatedCSSProperty(propertyName),
      elapsedTime: elapsedSec,
    ));
  };

  // For transitionrun, elapsedTime is always 0.
  target.dispatchEvent(TransitionEvent(
    EVENT_TRANSITION_RUN,
    propertyName: _toHyphenatedCSSProperty(propertyName),
    elapsedTime: 0.0,
  ));
  if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
    cssLogger.info('[transition][run] play property=$propertyName duration=${options?.duration} easing=${options?.easing} delay=${options?.delay}');
  }

  animation.play();
}