Skip to content

fix: emit syntheticHostListener for legacy animation host listeners#256

Merged
Brooooooklyn merged 1 commit intovoidzero-dev:mainfrom
ashley-hunter:animations-fix
May 2, 2026
Merged

fix: emit syntheticHostListener for legacy animation host listeners#256
Brooooooklyn merged 1 commit intovoidzero-dev:mainfrom
ashley-hunter:animations-fix

Conversation

@ashley-hunter
Copy link
Copy Markdown
Contributor

Summary

  • Legacy animation host listeners (@HostListener('@trigger.phase') and host: { '(@trigger.phase)': '...' }) were compiled to plain ɵɵlistener, so @trigger events never routed through Angular's animation renderer.
  • Classify @-prefixed host event names as ParsedEventType::LegacyAnimation in both component/transform.rs and directive/compiler.rs so the pipeline routes them through the synthetic-host path.
  • Order legacy animation host listeners before regular listeners (priority 0 vs 1) to match Angular's CREATE_ORDERING.
  • Emit ɵɵsyntheticHostListener("@trigger.phase", fn ComponentName_animation_<trigger>_<phase>_HostBindingHandler) from the reify phase, including correct chaining.
  • Replace ListenerOp.animation_phase: Option<AnimationKind> with legacy_animation_phase: Option<Ident<'a>> so the raw phase string round-trips through the IR (matches Angular's parseLegacyAnimationEventName + splitAtPeriod semantics: trim both halves, lowercase the phase, preserve unknown phases verbatim).
  • Switch sanitize_identifier to ASCII-only (is_ascii_alphanumeric) to match Angular's sanitizeIdentifier regex (/\W/g is ASCII-only in JavaScript).

Reproduction

@Component({
  selector: 'my-comp',
  template: '',
  host: { '(@animation.done)': 'done()' },
})
export class MyComponent {
  @HostListener('@animation.start')
  start() {}
  done() {}
}

Before: emitted ɵɵlistener(\"@animation.done\", ...).
After: emits the chained ɵɵsyntheticHostListener(\"@animation.done\", fn)(\"@animation.start\", fn) — byte-for-byte match against Angular's chain_synthetic_listeners.js compliance fixture.

Test plan

  • Verified output against Angular's chain_synthetic_listeners, chain_synthetic_listeners_mixed, and animation_host_bindings compliance fixtures (all match)
  • Added vitest cases covering @HostListener and host: { ... } syntax, ordering, @Directive parity, uppercase phase normalization, whitespace trimming, bogus-phase preservation, no-phase fallback to plain ɵɵlistener, and ASCII sanitize
  • Added Rust unit tests for ListenerOp::is_legacy_animation and create_op_priority
  • Full cargo test passes
  • Full vitest suite passes
  • cargo fmt --check, oxfmt --check, oxlint --type-aware --type-check all clean

🤖 Generated with Claude Code

Legacy animation host listeners (`@HostListener('@trigger.phase')` and
`host: { '(@trigger.phase)': '...' }`) were being compiled as plain
`ɵɵlistener` calls, so `@trigger` events were never routed through the
animation renderer. Match Angular's reference compiler by classifying
these as `LegacyAnimation`, ordering them before regular listeners, and
emitting `ɵɵsyntheticHostListener` with the correct
`ComponentName_animation_<trigger>_<phase>_HostBindingHandler` name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ashley-hunter ashley-hunter marked this pull request as ready for review April 30, 2026 21:08
@Brooooooklyn Brooooooklyn merged commit ca3c458 into voidzero-dev:main May 2, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants