(script: string, animationId: string)
| 2601 | * last keyframe's properties. |
| 2602 | */ |
| 2603 | export function removeAllKeyframesFromScript(script: string, animationId: string): string { |
| 2604 | let loc = locateAnimationWithFallback(script, animationId); |
| 2605 | if (!loc) return script; |
| 2606 | // Array-form keyframes have no percentage-keyed props for |
| 2607 | // filterPercentageProps to read — normalize to object form first (mirrors |
| 2608 | // addKeyframeToScript/moveKeyframeInScript), otherwise this silently no-ops |
| 2609 | // on every array-form tween. |
| 2610 | const kfNode = |
| 2611 | findKeyframesObjectNode(loc.target.call.varsArg) ?? |
| 2612 | convertArrayKeyframesToObjectNode(loc.target.call.varsArg); |
| 2613 | if (!kfNode) return script; |
| 2614 | |
| 2615 | const kfEntries = filterPercentageProps(kfNode) |
| 2616 | .map((p: AstNode) => ({ pct: percentageFromKey(propKeyName(p)!), prop: p })) |
| 2617 | .filter((e) => !Number.isNaN(e.pct)) |
| 2618 | .sort((a, b) => a.pct - b.pct); |
| 2619 | if (kfEntries.length === 0) return script; |
| 2620 | |
| 2621 | // For to()/set(): collapse to last keyframe (the destination = visible state). |
| 2622 | // For from(): collapse to first keyframe (the starting state). |
| 2623 | const method = loc.target.call.method; |
| 2624 | const collapseEntry = method === "from" ? kfEntries[0]! : kfEntries[kfEntries.length - 1]!; |
| 2625 | const record = objectExpressionToRecord(collapseEntry.prop.value, loc.parsed.scope); |
| 2626 | collapseKeyframesToFlat(loc.target.call.varsArg, record); |
| 2627 | // Removing ALL keyframes HOLDS the element statically — collapse to a |
| 2628 | // zero-duration immediateRender tween (a `gsap.set` equivalent), dropping the |
| 2629 | // original duration/ease so the element does not re-animate from its base |
| 2630 | // toward the collapsed value (which moved it out from under the selection). |
| 2631 | removeVarsKey(loc.target.call.varsArg, "ease"); |
| 2632 | setVarsKey(loc.target.call.varsArg, "duration", 0); |
| 2633 | setVarsKey(loc.target.call.varsArg, "immediateRender", true); |
| 2634 | |
| 2635 | // Removing all keyframes of a POSITION tween leaves exactly ONE held state: |
| 2636 | // strip every sibling position write for the same selector (stray gsap.set or a |
| 2637 | // second tl.to/tl.set) so the collapsed hold is the lone position source. Only |
| 2638 | // position siblings are stripped; rotation/opacity/etc. for the selector remain. |
| 2639 | if (loc.target.animation.propertyGroup === "position") { |
| 2640 | for (const l of loc.parsed.located) { |
| 2641 | if (l === loc.target) continue; |
| 2642 | if ( |
| 2643 | l.animation.targetSelector === loc.target.animation.targetSelector && |
| 2644 | l.animation.propertyGroup === "position" |
| 2645 | ) { |
| 2646 | removeCallFromAst(l.call); |
| 2647 | } |
| 2648 | } |
| 2649 | } |
| 2650 | |
| 2651 | return recast.print(loc.parsed.ast).code; |
| 2652 | } |
| 2653 | |
| 2654 | /** |
| 2655 | * Replace a dynamic `keyframes: <expr>` with a static percentage-keyframes object. |
no test coverage detected