|
4 | 4 | */ |
5 | 5 | import type { GsapAnimation } from "@hyperframes/core/gsap-parser"; |
6 | 6 | import { usePlayerStore, type KeyframeCacheEntry } from "../player/store/playerStore"; |
7 | | -import { idFromSelector, resolveClipTimingBasis, toClipKeyframes } from "./gsapShared"; |
| 7 | +import { resolveClipTimingBasis, resolveSelectorElementIds, toClipKeyframes } from "./gsapShared"; |
8 | 8 | import { deduplicateKeyframes, synthesizeFlatTweenKeyframes } from "./gsapTweenSynth"; |
9 | 9 |
|
10 | 10 | export function updateKeyframeCacheFromParsed( |
11 | 11 | animations: GsapAnimation[], |
12 | 12 | targetPath: string, |
13 | 13 | selectionId: string | undefined, |
14 | 14 | mutation: Record<string, unknown>, |
| 15 | + doc?: Document | null, |
15 | 16 | ): void { |
16 | 17 | const { setKeyframeCache, elements, domClipChildren } = usePlayerStore.getState(); |
17 | 18 | const idsWithKeyframes = new Set<string>(); |
18 | 19 | const merged = new Map<string, KeyframeCacheEntry>(); |
19 | 20 | const sourceAnimations = new Map<string, GsapAnimation[]>(); |
20 | 21 | for (const anim of animations) { |
21 | | - const id = idFromSelector(anim.targetSelector); |
22 | 22 | const kfSource = |
23 | 23 | anim.keyframes?.keyframes ?? synthesizeFlatTweenKeyframes(anim)?.keyframes ?? []; |
24 | | - if (!id || kfSource.length === 0) continue; |
25 | | - idsWithKeyframes.add(id); |
26 | | - // Every tween that fed keyframeCache also lands in gsapAnimations, group or |
27 | | - // not: a mixed-group tween (`{ x, opacity }` classifies to undefined) used to |
28 | | - // cache diamonds with no source animation behind them, so the collapsed row |
29 | | - // drew keyframes the expanded lanes couldn't render. Lane consumers do the |
30 | | - // group filtering themselves (animationContributesLane). |
31 | | - sourceAnimations.set(id, [...(sourceAnimations.get(id) ?? []), anim]); |
| 24 | + if (kfSource.length === 0) continue; |
| 25 | + // Attribute the tween to every element it actually animates. A leading-id |
| 26 | + // match filed `#stat3 .block` under `#stat3`: the child's diamonds landed on |
| 27 | + // its ancestor AND collided with the ancestor's own tween at the shared |
| 28 | + // percentage, which the same-% merge then resolved by dropping the ease. |
| 29 | + for (const id of resolveSelectorElementIds(anim.targetSelector, doc)) { |
| 30 | + idsWithKeyframes.add(id); |
| 31 | + // Every tween that fed keyframeCache also lands in gsapAnimations, group or |
| 32 | + // not: a mixed-group tween (`{ x, opacity }` classifies to undefined) used to |
| 33 | + // cache diamonds with no source animation behind them, so the collapsed row |
| 34 | + // drew keyframes the expanded lanes couldn't render. Lane consumers do the |
| 35 | + // group filtering themselves (animationContributesLane). |
| 36 | + sourceAnimations.set(id, [...(sourceAnimations.get(id) ?? []), anim]); |
32 | 37 |
|
33 | | - // Convert tween-relative percentages to clip-relative so diamonds |
34 | | - // render at the correct position within the timeline clip. The basis comes |
35 | | - // from the shared resolver, so this writer agrees with the AST load on both |
36 | | - // the sub-comp host fallback and the tween's own time frame. |
37 | | - const { elStart, elDuration } = resolveClipTimingBasis( |
38 | | - id, |
39 | | - targetPath, |
40 | | - elements, |
41 | | - domClipChildren, |
42 | | - ); |
43 | | - const clipKeyframes = toClipKeyframes(kfSource, anim, elStart, elDuration); |
| 38 | + // Convert tween-relative percentages to clip-relative so diamonds |
| 39 | + // render at the correct position within the timeline clip. The basis comes |
| 40 | + // from the shared resolver, so this writer agrees with the AST load on both |
| 41 | + // the sub-comp host fallback and the tween's own time frame. |
| 42 | + const { elStart, elDuration } = resolveClipTimingBasis( |
| 43 | + id, |
| 44 | + targetPath, |
| 45 | + elements, |
| 46 | + domClipChildren, |
| 47 | + ); |
| 48 | + const clipKeyframes = toClipKeyframes(kfSource, anim, elStart, elDuration); |
44 | 49 |
|
45 | | - const existing = merged.get(id); |
46 | | - if (existing) { |
47 | | - // deduplicateKeyframes owns the same-% merge (including the easeAmbiguous |
48 | | - // flag downstream lanes read); a second copy of that rule here is how the |
49 | | - // two writers drift. |
50 | | - existing.keyframes = deduplicateKeyframes([...existing.keyframes, ...clipKeyframes]); |
51 | | - } else { |
52 | | - merged.set(id, { |
53 | | - ...anim.keyframes, |
54 | | - format: anim.keyframes?.format ?? "percentage", |
55 | | - keyframes: clipKeyframes, |
56 | | - }); |
| 50 | + const existing = merged.get(id); |
| 51 | + if (existing) { |
| 52 | + // deduplicateKeyframes owns the same-% merge (including the easeAmbiguous |
| 53 | + // flag downstream lanes read); a second copy of that rule here is how the |
| 54 | + // two writers drift. |
| 55 | + existing.keyframes = deduplicateKeyframes([...existing.keyframes, ...clipKeyframes]); |
| 56 | + } else { |
| 57 | + merged.set(id, { |
| 58 | + ...anim.keyframes, |
| 59 | + format: anim.keyframes?.format ?? "percentage", |
| 60 | + keyframes: clipKeyframes, |
| 61 | + }); |
| 62 | + } |
57 | 63 | } |
58 | 64 | } |
59 | 65 | for (const [id, entry] of merged) { |
60 | 66 | for (const key of elementCacheKeys(targetPath, id)) setKeyframeCache(key, entry); |
61 | 67 | writeGsapAnimationsForElement(targetPath, id, sourceAnimations.get(id)); |
62 | 68 | } |
63 | | - const targetId = |
64 | | - idFromSelector((mutation as { targetSelector?: string }).targetSelector) ?? selectionId; |
65 | | - if (targetId && !idsWithKeyframes.has(targetId)) { |
66 | | - clearKeyframeCacheForElement(targetPath, targetId); |
| 69 | + const mutationSelector = (mutation as { targetSelector?: string }).targetSelector; |
| 70 | + const mutated = mutationSelector ? resolveSelectorElementIds(mutationSelector, doc) : []; |
| 71 | + const targetIds = mutated.length > 0 ? mutated : selectionId ? [selectionId] : []; |
| 72 | + for (const targetId of targetIds) { |
| 73 | + if (!idsWithKeyframes.has(targetId)) clearKeyframeCacheForElement(targetPath, targetId); |
67 | 74 | } |
68 | 75 | } |
69 | 76 |
|
|
0 commit comments