( script: string, animationId: string, percentage: number, )
| 2259 | * remaining keyframe's properties. |
| 2260 | */ |
| 2261 | export function removeKeyframeFromScript( |
| 2262 | script: string, |
| 2263 | animationId: string, |
| 2264 | percentage: number, |
| 2265 | ): string { |
| 2266 | // Array-form keyframes (`keyframes: [{x,y}, …]`) have no explicit percentages — |
| 2267 | // GSAP distributes them evenly. The object-form path below can't see them |
| 2268 | // (findKeyframesObjectNode only matches ObjectExpression), so removing from an |
| 2269 | // array-form tween silently no-op'd. Resolve the element by its implicit |
| 2270 | // percentage and splice it; collapse to a flat tween when fewer than two remain. |
| 2271 | const arrLoc = locateAnimationWithFallback(script, animationId); |
| 2272 | // findPropertyNode here returns the property's VALUE node directly. |
| 2273 | const arrVal = arrLoc && findPropertyNode(arrLoc.target.call.varsArg, "keyframes"); |
| 2274 | if (arrLoc && arrVal?.type === "ArrayExpression") { |
| 2275 | const elements: AstNode[] = (arrVal.elements ?? []).filter( |
| 2276 | (e: AstNode | null): e is AstNode => !!e && e.type === "ObjectExpression", |
| 2277 | ); |
| 2278 | const n = elements.length; |
| 2279 | if (n === 0) return script; |
| 2280 | let matchIdx = -1; |
| 2281 | let bestDist = Number.POSITIVE_INFINITY; |
| 2282 | for (let i = 0; i < n; i++) { |
| 2283 | const pct = n > 1 ? (i / (n - 1)) * 100 : 0; |
| 2284 | const dist = Math.abs(pct - percentage); |
| 2285 | if (dist <= PCT_TOLERANCE && dist < bestDist) { |
| 2286 | matchIdx = i; |
| 2287 | bestDist = dist; |
| 2288 | } |
| 2289 | } |
| 2290 | if (matchIdx === -1) return script; |
| 2291 | const remaining = elements.filter((_, i) => i !== matchIdx); |
| 2292 | if (remaining.length < 2) { |
| 2293 | const sole = remaining[0]; |
| 2294 | const record = sole ? objectExpressionToRecord(sole, arrLoc.parsed.scope) : {}; |
| 2295 | collapseKeyframesToFlat(arrLoc.target.call.varsArg, record); |
| 2296 | } else { |
| 2297 | const realIdx = arrVal.elements.indexOf(elements[matchIdx]); |
| 2298 | arrVal.elements.splice(realIdx, 1); |
| 2299 | } |
| 2300 | return recast.print(arrLoc.parsed.ast).code; |
| 2301 | } |
| 2302 | |
| 2303 | const ctx = locateKeyframeCtx(script, animationId, percentage); |
| 2304 | if (!ctx) return script; |
| 2305 | const { loc, kfNode } = ctx; |
| 2306 | |
| 2307 | const match = findKeyframePropByPct(kfNode, percentage); |
| 2308 | if (!match) return script; |
| 2309 | const removeIdx = match.idx; |
| 2310 | |
| 2311 | kfNode.properties.splice(removeIdx, 1); |
| 2312 | |
| 2313 | const remainingKfs = filterPercentageProps(kfNode); |
| 2314 | if (remainingKfs.length < 2) { |
| 2315 | const record = |
| 2316 | remainingKfs.length === 1 |
| 2317 | ? objectExpressionToRecord(remainingKfs[0]!.value, loc.parsed.scope) |
| 2318 | : {}; |
no test coverage detected