feat(web-platform): Implement __ElementAnimate PAPI#2329
Conversation
- Add ElementAnimatePAPI type to web-constants MainThreadGlobalThis - Implement __ElementAnimate in createMainThreadGlobalThis with animation map tracking - Add mapTimingOptions() to convert Lynx animation timing to Web Animations API - Add __ElementAnimate to testing-environment ElementPAPI with state tracking - Add comprehensive unit tests for all animation operations (START/PLAY/PAUSE/CANCEL/FINISH) - Add REPL example demonstrating element animation with UI controls Handles all animation operations: START (keyframes + options), PLAY, PAUSE, CANCEL, FINISH.
🦋 Changeset detectedLatest commit: 12264f4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds element-level animation support via a new __ElementAnimate PAPI: main-thread Web Animations implementation and wiring, testing-environment FSM and animationMap, unit and E2E tests, a REPL example UI, and a changeset entry. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Implements the __ElementAnimate PAPI for the web platform layer to bridge Lynx worklet animation calls to the browser’s Web Animations API, and adds a testing-environment implementation plus REPL demo coverage.
Changes:
- Added
ElementAnimatePAPIto the main-thread globals type surface (MainThreadGlobalThis). - Implemented
__ElementAnimatein the web main-thread global with timing-option mapping and animation instance tracking. - Added testing-environment behavior + unit tests and a new REPL sample demonstrating pause/play/cancel/restart.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Lockfile updates (notably Rsbuild peer resolutions). |
| packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts | Adds __ElementAnimate implementation and timing option mapping. |
| packages/web-platform/web-constants/src/types/MainThreadGlobalThis.ts | Exposes __ElementAnimate on MainThreadGlobalThis typings. |
| packages/testing-library/testing-environment/src/lynx/ElementPAPI.ts | Adds testing-environment __ElementAnimate state tracking. |
| packages/testing-library/testing-environment/src/tests/element-papi.test.js | Adds unit tests for START/PLAY/PAUSE/CANCEL/FINISH operations. |
| packages/repl/src/samples.ts | Registers a new “Element Animate” REPL sample. |
| packages/repl/src/examples/interactivity-element-animate/main-thread.js | New interactive demo for the __ElementAnimate bridge. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| case AnimationOperation.CANCEL: | ||
| animationMap.get(name)?.cancel(); | ||
| break; | ||
| case AnimationOperation.FINISH: | ||
| animationMap.get(name)?.finish(); | ||
| break; |
There was a problem hiding this comment.
CANCEL calls animation.cancel() but does not remove the entry from animationMap, leaving a stale reference and causing the map to grow unbounded over time. Delete the map entry after cancelling (and consider also removing entries on finish/onfinish to avoid leaks when animations end naturally).
| export type ElementAnimatePAPI = ( | ||
| element: HTMLElement, | ||
| args: | ||
| | [ | ||
| operation: number, | ||
| name: string, | ||
| keyframes: Record<string, string | number>[], | ||
| options?: Record<string, string | number>, | ||
| ] | ||
| | [operation: number, name: string], |
There was a problem hiding this comment.
ElementAnimatePAPI currently uses operation: number for both tuple variants, which prevents TypeScript from narrowing args based on the operation and makes invalid operation values type-check. Consider using the shared AnimationOperation enum (and a discriminated union keyed on specific enum members) so callers and implementations get proper type safety.
| export type ElementAnimatePAPI = ( | |
| element: HTMLElement, | |
| args: | |
| | [ | |
| operation: number, | |
| name: string, | |
| keyframes: Record<string, string | number>[], | |
| options?: Record<string, string | number>, | |
| ] | |
| | [operation: number, name: string], | |
| export enum AnimationOperation { | |
| Start = 0, | |
| Cancel = 1, | |
| } | |
| export type ElementAnimatePAPI = ( | |
| element: HTMLElement, | |
| args: | |
| | [ | |
| operation: AnimationOperation.Start, | |
| name: string, | |
| keyframes: Record<string, string | number>[], | |
| options?: Record<string, string | number>, | |
| ] | |
| | [operation: AnimationOperation.Cancel, name: string], |
| switch (operation) { | ||
| case 0 /* START */: { | ||
| const keyframes = args[2]; | ||
| const options = args[3]; | ||
| this.animationMap.set(name, { | ||
| element, | ||
| state: 'running', | ||
| keyframes, | ||
| options, | ||
| }); | ||
| break; | ||
| } | ||
| case 1 /* PLAY */: { | ||
| const anim = this.animationMap.get(name); | ||
| if (anim) anim.state = 'running'; | ||
| break; | ||
| } | ||
| case 2 /* PAUSE */: { | ||
| const anim = this.animationMap.get(name); | ||
| if (anim) anim.state = 'paused'; | ||
| break; | ||
| } | ||
| case 3 /* CANCEL */: { | ||
| this.animationMap.delete(name); | ||
| break; | ||
| } | ||
| case 4 /* FINISH */: { | ||
| const anim = this.animationMap.get(name); | ||
| if (anim) anim.state = 'finished'; | ||
| break; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The testing environment hard-codes operation numbers (0..4) inside __ElementAnimate. Since the web platform uses the AnimationOperation enum, importing and using the same enum here will prevent drift when operations are added/renumbered and make the tests more self-documenting.
| version: 3.7.0 | ||
| '@rsbuild/plugin-babel': | ||
| specifier: 1.1.0 | ||
| version: 1.1.0(@rsbuild/core@1.7.3) | ||
| version: 1.1.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) | ||
| '@types/react': |
There was a problem hiding this comment.
This PR’s lockfile changes include resolving several Rsbuild plugins against @rsbuild/core@2.0.0-beta.3(...) (a major beta) whereas the workspace catalogs pin @rsbuild/core to 1.7.3. If this beta upgrade isn't intentional for this feature PR, consider regenerating the lockfile with the intended catalog resolution or splitting dependency upgrades into a separate PR to reduce risk.
| return el; | ||
| }; | ||
|
|
||
| const animationMap = new Map<string, globalThis.Animation | undefined>(); |
There was a problem hiding this comment.
animationMap is typed as Map<string, globalThis.Animation | undefined>, but globalThis.Animation refers to the global constructor type (typeof Animation) rather than the Animation instance returned by element.animate(). This will either fail type-checking or make the map type incorrect. Use the DOM Animation instance type (e.g. Map<string, Animation>), or ReturnType<HTMLElement['animate']> if you want to derive it from the API.
| const animationMap = new Map<string, globalThis.Animation | undefined>(); | |
| const animationMap = new Map<string, Animation | undefined>(); |
| animationMap.set( | ||
| name, | ||
| element.animate( | ||
| keyframes as Keyframe[], | ||
| mapTimingOptions(options), | ||
| ), | ||
| ); |
There was a problem hiding this comment.
On START, an existing animation with the same name is overwritten in animationMap without being cancelled first. This means repeated START calls (e.g. the REPL sample's Restart button) will leave the previous animation running and create multiple concurrent animations for the same logical id. Cancel any existing animation for name before starting the new one (and consider replacing the entry only after that).
| animationMap.set( | |
| name, | |
| element.animate( | |
| keyframes as Keyframe[], | |
| mapTimingOptions(options), | |
| ), | |
| ); | |
| const existingAnimation = animationMap.get(name); | |
| if (existingAnimation) { | |
| existingAnimation.cancel(); | |
| } | |
| const newAnimation = element.animate( | |
| keyframes as Keyframe[], | |
| mapTimingOptions(options), | |
| ); | |
| animationMap.set(name, newAnimation); |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web-platform/web-constants/src/types/MainThreadGlobalThis.ts`:
- Around line 331-341: The ElementAnimatePAPI type allows any numeric operation
and both tuple shapes, so callers can pass invalid combinations; tighten it into
a discriminated union where the START operation is the literal 0 and requires
the 4-arg tuple ([0, name, keyframes, options?]) while control operations are
literal unions 1|2|3|4 and use the 2-arg tuple ([1|2|3|4, name]); update the
ElementAnimatePAPI type to reflect these two specific tuple shapes (START with
mandatory keyframes and optional options, other ops only name) so consumers and
implementors get compile-time checks.
In `@packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts`:
- Around line 67-68: The animation lifecycle handlers leave stale Animation
objects in animationMap: before handling the START case (in the
AnimationOperation switch that sets animationMap[name]) ensure any existing
animation for the same name is retired/terminated (call the same retire/cleanup
logic used elsewhere or invoke cancel/finish and remove it from animationMap) so
the new START overwrites a retired instance; in the CANCEL handler delete
animationMap[name] after cancelling so the entry is removed (mirror
testing-environment/ElementPAPI.ts behavior); and in the FINISH handler perform
cleanup by removing the map entry once the finish sequence completes so no stale
handles remain (ensure you reference animationMap, START, CANCEL, FINISH and the
retire/cleanup function used for animations).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c1aae284-2618-4753-82f0-6faa959d3981
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
packages/repl/src/examples/interactivity-element-animate/main-thread.jspackages/repl/src/samples.tspackages/testing-library/testing-environment/src/__tests__/element-papi.test.jspackages/testing-library/testing-environment/src/lynx/ElementPAPI.tspackages/web-platform/web-constants/src/types/MainThreadGlobalThis.tspackages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts
Merging this PR will degrade performance by 5.87%
Performance Changes
Comparing Footnotes
|
Web Explorer#8178 Bundle Size — 385.21KiB (+0.19%).12264f4(current) vs 0991136 main#8171(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch Huxpro/fix-background-only-new Project dashboard Generated by RelativeCI Documentation Report issue |
- Fix animation lifecycle: cancel existing animation before START, delete map entry on CANCEL, auto-cleanup on finish/cancel via handlers - Fix type: globalThis.Animation → Animation (correct instance type) - Tighten ElementAnimatePAPI discriminated union with literal operation types - Run pnpm dedupe to fix stale @rsbuild/core@2.0.0-beta.3 lockfile resolutions - Update testing-environment API report
Cover all animation operations (START, PAUSE, PLAY, CANCEL, FINISH) and the replace-on-duplicate-name behavior via the Web Animations API.
The e2e tests call globalThis.runtime.__ElementAnimate but the web-core-wasm variant was missing this PAPI. Add the implementation to the client createElementAPI and a no-op to the server variant.
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @lynx-js/react@0.117.0 ### Minor Changes - feat: export `GlobalPropsProvider`, `GlobalPropsConsumer`, `useGlobalProps` and `useGlobalPropsChanged` for `__globalProps` ([#2346](#2346)) - `GlobalPropsProvider`: A Provider component that accepts `children`. It is used to provide the `lynx.__globalProps` context. - `GlobalPropsConsumer`: A Consumer component that accepts a function as a child. It is used to consume the `lynx.__globalProps` context. - `useGlobalProps`: A hook that returns the `lynx.__globalProps` object. It triggers a re-render when `lynx.__globalProps` changes. - `useGlobalPropsChanged`: A hook that accepts a callback function. The callback is invoked when `lynx.__globalProps` changes. Note: When `globalPropsMode` is not set to `'event'` (default is `'reactive'`), these APIs will be ineffective (pass-through) and will log a warning in development mode, as updates are triggered automatically by full re-render. - **BREAKING CHANGE**: ([#2319](#2319)) Change preact package from `@hongzhiyuan/preact` to `@lynx-js/internal-preact`. Upgrade preact from [f7693b72](preactjs/preact@f7693b7) to [55254ef7](preactjs/preact@55254ef), see diffs at [f7693b72...55254ef7](https://github.com/preactjs/preact/compare/f7693b72ecb4a40c66e6e47f54e2d4edc374c9f0...preactjs:preact:55254ef7021e563cc1a86fb816058964a1b6a29a?expand=1). - feat: add `globalPropsMode` option to `PluginReactLynxOptions` ([#2346](#2346)) - When configured to `"event"`, `updateGlobalProps` will only trigger a global event and skip the `runWithForce` flow. - Defaults to `"reactive"`, which means `updateGlobalProps` will trigger re-render automatically. ### Patch Changes - Add `__BACKGROUND__` guard on `onBackgroundSnapshotInstanceUpdateId` event to prevent bundling to main-thread on dev environment. ([#2332](#2332)) - refactor: extract static string in template literal ([#2334](#2334)) - fix: avoid crash when spread undefined ref ([#2333](#2333)) - Avoid registering lifecycle refs for main-thread functions (MTF) that have not received an `execId` during `renderPage()` first-screen binding. ([#2320](#2320)) ## @lynx-js/react-umd@0.117.0 ### Minor Changes - Add standalone UMD build of the ReactLynx runtime. ([#2331](#2331)) ## @lynx-js/react-rsbuild-plugin@0.13.0 ### Minor Changes - **BREAKING CHANGE**: ([#2319](#2319)) Change preact package from `@hongzhiyuan/preact` to `@lynx-js/internal-preact`. Upgrade preact from [f7693b72](preactjs/preact@f7693b7) to [55254ef7](preactjs/preact@55254ef), see diffs at [f7693b72...55254ef7](https://github.com/preactjs/preact/compare/f7693b72ecb4a40c66e6e47f54e2d4edc374c9f0...preactjs:preact:55254ef7021e563cc1a86fb816058964a1b6a29a?expand=1). - feat: add `globalPropsMode` option to `PluginReactLynxOptions` ([#2346](#2346)) - When configured to `"event"`, `updateGlobalProps` will only trigger a global event and skip the `runWithForce` flow. - Defaults to `"reactive"`, which means `updateGlobalProps` will trigger re-render automatically. ### Patch Changes - Updated dependencies \[[`f1129ea`](f1129ea), [`27f1cff`](27f1cff), [`ed566f0`](ed566f0), [`402ec2b`](402ec2b)]: - @lynx-js/react-webpack-plugin@0.8.0 - @lynx-js/react-refresh-webpack-plugin@0.3.5 - @lynx-js/react-alias-rsbuild-plugin@0.13.0 - @lynx-js/use-sync-external-store@1.5.0 - @lynx-js/template-webpack-plugin@0.10.6 - @lynx-js/css-extract-webpack-plugin@0.7.0 ## @lynx-js/react-webpack-plugin@0.8.0 ### Minor Changes - feat: add `globalPropsMode` option to `PluginReactLynxOptions` ([#2346](#2346)) - When configured to `"event"`, `updateGlobalProps` will only trigger a global event and skip the `runWithForce` flow. - Defaults to `"reactive"`, which means `updateGlobalProps` will trigger re-render automatically. ### Patch Changes - Fix sourcemap misalignment when wrapping lazy bundle main-thread chunks. ([#2361](#2361)) The lazy bundle IIFE wrapper is now injected in `processAssets` at `PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 1` by walking chunk groups instead of patching assets in `beforeEncode`. - With `experimental_isLazyBundle: true`, the wrapper is applied to lazy-bundle chunk groups. - Without lazy bundle mode, the wrapper is applied to async main-thread chunk groups generated by dynamic import. Injecting the wrapper in this stage keeps the emitted JS stable after optimization while still running before `DEV_TOOLING` sourcemap finalization, so the generated `.js` and `.js.map` stay aligned. - Set `__DEV__` and `__PROFILE__` to `true` on `NODE_ENV === 'development'`. ([#2324](#2324)) ## @lynx-js/rspeedy@0.13.6 ### Patch Changes - Rename Web Preview label to fix URL alignment ([#2355](#2355)) - Updated dependencies \[[`799fda8`](799fda8)]: - @lynx-js/cache-events-webpack-plugin@0.0.3 - @lynx-js/web-rsbuild-server-middleware@0.19.9 ## @lynx-js/lynx-bundle-rslib-config@0.2.3 ### Patch Changes - Fix snapshot not found error when dev with external bundle ([#2316](#2316)) ## @lynx-js/external-bundle-rsbuild-plugin@0.0.4 ### Patch Changes - Updated dependencies \[[`ed566f0`](ed566f0)]: - @lynx-js/externals-loading-webpack-plugin@0.0.5 ## @lynx-js/kitten-lynx-test-infra@0.1.1 ### Patch Changes - feat: support page.screenshot() ([#2364](#2364)) - feat: initial commit ([#2272](#2272)) ## @lynx-js/testing-environment@0.1.12 ### Patch Changes - Implement `__ElementAnimate` PAPI for web platform animation lifecycle ([#2329](#2329)) ## @lynx-js/web-constants@0.19.9 ### Patch Changes - Implement `__ElementAnimate` PAPI for web platform animation lifecycle ([#2329](#2329)) - Updated dependencies \[]: - @lynx-js/web-worker-rpc@0.19.9 ## @lynx-js/web-core@0.19.9 ### Patch Changes - Updated dependencies \[[`2efecc2`](2efecc2)]: - @lynx-js/web-constants@0.19.9 - @lynx-js/web-mainthread-apis@0.19.9 - @lynx-js/web-worker-runtime@0.19.9 - @lynx-js/web-worker-rpc@0.19.9 ## @lynx-js/web-core-wasm@0.0.6 ### Patch Changes - reexports essential utils & types in @lynx-js/web-elements from @lynx-js/web-core-wasm/client ([#2321](#2321)) - Updated dependencies \[]: - @lynx-js/web-worker-rpc@0.19.9 ## @lynx-js/web-mainthread-apis@0.19.9 ### Patch Changes - Updated dependencies \[[`2efecc2`](2efecc2)]: - @lynx-js/web-constants@0.19.9 ## @lynx-js/web-worker-runtime@0.19.9 ### Patch Changes - Updated dependencies \[[`2efecc2`](2efecc2)]: - @lynx-js/web-constants@0.19.9 - @lynx-js/web-mainthread-apis@0.19.9 - @lynx-js/web-worker-rpc@0.19.9 ## @lynx-js/cache-events-webpack-plugin@0.0.3 ### Patch Changes - Cache `globalThis.loadDynamicComponent` in the cache events runtime and add tests covering tt methods, performance events, and globalThis replay behavior. ([#2343](#2343)) ## @lynx-js/externals-loading-webpack-plugin@0.0.5 ### Patch Changes - Fix snapshot not found error when dev with external bundle ([#2316](#2316)) ## @lynx-js/react-refresh-webpack-plugin@0.3.5 ### Patch Changes - Fix snapshot not found error when dev with external bundle ([#2316](#2316)) ## @lynx-js/template-webpack-plugin@0.10.6 ### Patch Changes - Updated dependencies \[[`d034dae`](d034dae)]: - @lynx-js/web-core-wasm@0.0.6 ## create-rspeedy@0.13.6 ## @lynx-js/react-alias-rsbuild-plugin@0.13.0 ## upgrade-rspeedy@0.13.6 ## @lynx-js/web-core-server@0.19.9 ## @lynx-js/web-rsbuild-server-middleware@0.19.9 ## @lynx-js/web-worker-rpc@0.19.9 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Implements
__ElementAnimatePAPI for the web platform layer, enabling Web Animations API support in the REPL and web environment. Previously, this function was undefined causingReferenceErrorwhen worklet code called animations.Changes
ElementAnimatePAPItype toMainThreadGlobalThis__ElementAnimatewith animation map tracking and timing option mapping (Lynx → Web Animations API)__ElementAnimateimplementation with state tracking for unit testsVerification
✅ Rebased onto main with upstream REPL site
✅ Added comprehensive unit tests (testing-environment)
✅ Created manual UI test in REPL at http://localhost:3003/
✅ All pre-commit hooks passed (eslint, biome, dprint)
Summary by CodeRabbit
New Features
Tests
Documentation