diff --git a/.github/workflows/motion-e2e.yml b/.github/workflows/motion-e2e.yml
index ae956c2..1bfaae5 100644
--- a/.github/workflows/motion-e2e.yml
+++ b/.github/workflows/motion-e2e.yml
@@ -32,7 +32,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
- node-version: 24
+ node-version-file: '.nvmrc'
registry-url: https://registry.npmjs.org
- name: Cache Yarn dependencies
@@ -88,11 +88,3 @@ jobs:
if: ${{ github.event.inputs.browser == 'all' }}
working-directory: packages/motion
run: npx playwright test
-
- - name: Upload Playwright report
- if: ${{ always() }}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
- with:
- name: playwright-report
- path: packages/motion/playwright-report/
- retention-days: 14
diff --git a/.gitignore b/.gitignore
index 3666304..57dcf32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,6 @@ coverage/
.idea/
*.log
pnpm-lock.yaml
+package-lock.json
.yarn/
tmp/
diff --git a/apps/demo/package.json b/apps/demo/package.json
index 18ccbb6..e86a77f 100644
--- a/apps/demo/package.json
+++ b/apps/demo/package.json
@@ -18,7 +18,7 @@
"devDependencies": {
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
- "@vitejs/plugin-react": "^4.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.9.3",
"vite": "^7.2.2"
}
diff --git a/apps/docs/package.json b/apps/docs/package.json
index c2e120f..2183a5f 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -22,7 +22,7 @@
"devDependencies": {
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
- "@vitejs/plugin-react": "^4.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.9.3",
"vite": "^7.2.2"
}
diff --git a/packages/motion/.gitignore b/packages/motion/.gitignore
index 1d99561..04ab4ec 100644
--- a/packages/motion/.gitignore
+++ b/packages/motion/.gitignore
@@ -1,5 +1,7 @@
dist
node_modules
+test-results
+playwright-report
*npm-debug.log
target
maven
diff --git a/packages/motion/e2e/constants/animation-group.ts b/packages/motion/e2e/constants/animation-group.ts
new file mode 100644
index 0000000..07aabfc
--- /dev/null
+++ b/packages/motion/e2e/constants/animation-group.ts
@@ -0,0 +1,5 @@
+export const ANIMATION_GROUP_IDS = {
+ item1: 'group-item-1',
+ item2: 'group-item-2',
+ item3: 'group-item-3',
+} as const;
diff --git a/packages/motion/e2e/constants/effects.ts b/packages/motion/e2e/constants/effects.ts
new file mode 100644
index 0000000..88383c2
--- /dev/null
+++ b/packages/motion/e2e/constants/effects.ts
@@ -0,0 +1,20 @@
+export const EFFECTS_TARGET_IDS = {
+ namedWaapi: 'named-waapi-target',
+ namedCss: 'named-css-target',
+ keyframeWaapi: 'keyframe-waapi-target',
+ keyframeCss: 'keyframe-css-target',
+ customEffect: 'custom-effect-target',
+ playback: 'playback-target',
+} as const;
+
+export const EFFECTS_TEST_IDS = {
+ playbackStateDisplay: 'playback-state-display',
+} as const;
+
+export const EFFECTS_NAMES = {
+ namedCssEffectType: 'TestScale',
+ namedCssKeyframeName: 'test-scale',
+ keyframeCssName: 'kf-rotate',
+ keyframeWaapiName: 'kf-slide',
+ playbackName: 'playback-test',
+} as const;
diff --git a/packages/motion/e2e/constants/pointer.ts b/packages/motion/e2e/constants/pointer.ts
new file mode 100644
index 0000000..e440e43
--- /dev/null
+++ b/packages/motion/e2e/constants/pointer.ts
@@ -0,0 +1,21 @@
+export const POINTER_IDS = {
+ area: 'pointer-area',
+ yAxisArea: 'y-axis-area',
+ compositeArea: 'composite-area',
+ xAxisTarget: 'x-axis-target',
+ yAxisTarget: 'y-axis-target',
+ compositeTarget: 'composite-target',
+} as const;
+
+export const POINTER_TEST_IDS = {
+ area: 'pointer-area',
+ yAxisArea: 'y-axis-area',
+ compositeArea: 'composite-area',
+ progressDisplay: 'pointer-progress-display',
+} as const;
+
+export const POINTER_SELECTORS = {
+ area: `[data-testid="${POINTER_TEST_IDS.area}"]`,
+ yAxisArea: `[data-testid="${POINTER_TEST_IDS.yAxisArea}"]`,
+ compositeArea: `[data-testid="${POINTER_TEST_IDS.compositeArea}"]`,
+} as const;
diff --git a/packages/motion/e2e/constants/scroll.ts b/packages/motion/e2e/constants/scroll.ts
new file mode 100644
index 0000000..f83c8a1
--- /dev/null
+++ b/packages/motion/e2e/constants/scroll.ts
@@ -0,0 +1,17 @@
+export const SCROLL_IDS = {
+ viewProgressTarget: 'view-progress-target',
+ scrubCard1: 'scrub-card-1',
+ scrubCard2: 'scrub-card-2',
+ scrubCard3: 'scrub-card-3',
+} as const;
+
+export const SCROLL_TEST_IDS = {
+ viewProgressTarget: 'view-progress-target',
+ scrubCard1: 'scrub-card-1',
+ progressDisplay: 'progress-display',
+} as const;
+
+export const SCROLL_SELECTORS = {
+ viewProgressTarget: `[data-testid="${SCROLL_TEST_IDS.viewProgressTarget}"]`,
+ scrubCard1: `[data-testid="${SCROLL_TEST_IDS.scrubCard1}"]`,
+} as const;
diff --git a/packages/motion/e2e/fixtures/animation-group.html b/packages/motion/e2e/fixtures/animation-group.html
new file mode 100644
index 0000000..39e7397
--- /dev/null
+++ b/packages/motion/e2e/fixtures/animation-group.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+ AnimationGroup API
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/motion/e2e/fixtures/animation-group.ts b/packages/motion/e2e/fixtures/animation-group.ts
new file mode 100644
index 0000000..7979e63
--- /dev/null
+++ b/packages/motion/e2e/fixtures/animation-group.ts
@@ -0,0 +1,71 @@
+import { getWebAnimation } from '@wix/motion';
+import type { AnimationGroup } from '@wix/motion';
+import { ANIMATION_GROUP_IDS } from '../constants/animation-group';
+
+type AnimationGroupFixtureWindow = typeof window & {
+ animationGroup: AnimationGroup;
+ lifecycleEvents: string[];
+ play: () => Promise;
+ pause: () => void;
+ reverse: () => Promise;
+ cancel: () => void;
+};
+
+const items = [
+ document.getElementById(ANIMATION_GROUP_IDS.item1) as HTMLElement,
+ document.getElementById(ANIMATION_GROUP_IDS.item2) as HTMLElement,
+ document.getElementById(ANIMATION_GROUP_IDS.item3) as HTMLElement,
+];
+
+const lifecycleEvents: string[] = [];
+
+function recordEvent(name: string) {
+ lifecycleEvents.push(name);
+}
+
+const groups = items.map((item, i) =>
+ getWebAnimation(item, {
+ keyframeEffect: {
+ name: `group-item-${i}`,
+ keyframes: [
+ { offset: 0, opacity: 0, transform: 'scale(0.5)' },
+ { offset: 1, opacity: 1, transform: 'scale(1)' },
+ ],
+ },
+ duration: 800,
+ delay: i * 100,
+ fill: 'both',
+ easing: 'linear',
+ }),
+) as AnimationGroup[];
+
+async function play(): Promise {
+ recordEvent('play');
+ await Promise.all(groups.map((g) => g.play()));
+ recordEvent('play:ready');
+ groups[0].onFinish(() => recordEvent('finish'));
+}
+
+function pause(): void {
+ recordEvent('pause');
+ groups.forEach((g) => g.pause());
+}
+
+async function reverse(): Promise {
+ recordEvent('reverse');
+ await Promise.all(groups.map((g) => g.reverse()));
+ recordEvent('reverse:ready');
+}
+
+function cancel(): void {
+ recordEvent('cancel');
+ groups.forEach((g) => g.cancel());
+}
+
+// Expose the first group as animationGroup — tests read progress/playState from it
+(window as AnimationGroupFixtureWindow).animationGroup = groups[0];
+(window as AnimationGroupFixtureWindow).lifecycleEvents = lifecycleEvents;
+(window as AnimationGroupFixtureWindow).play = play;
+(window as AnimationGroupFixtureWindow).pause = pause;
+(window as AnimationGroupFixtureWindow).reverse = reverse;
+(window as AnimationGroupFixtureWindow).cancel = cancel;
diff --git a/packages/motion/e2e/fixtures/effects.html b/packages/motion/e2e/fixtures/effects.html
new file mode 100644
index 0000000..8a59dbc
--- /dev/null
+++ b/packages/motion/e2e/fixtures/effects.html
@@ -0,0 +1,190 @@
+
+
+
+
+
+ Effect Types
+
+
+
+
+
+
+
+
+
Named Effects
+
+
+ Named WAAPI
+
+
+
+
+
+
+
+ Named CSS
+
+
+
+
+
+
+
+
+
+
Keyframe Effects
+
+
+ Keyframe WAAPI
+
+
+
+
+
+
+
+ Keyframe CSS
+
+
+
+
+
+
+
+
+
+
+
+
+
Playback — Play / Reverse / Pause
+
+
+ Playback
+
+
+
+
+
+
+
+
+
state: idle
+
+
+
+
+
diff --git a/packages/motion/e2e/fixtures/effects.ts b/packages/motion/e2e/fixtures/effects.ts
new file mode 100644
index 0000000..409e856
--- /dev/null
+++ b/packages/motion/e2e/fixtures/effects.ts
@@ -0,0 +1,347 @@
+import { registerEffects, getWebAnimation, getCSSAnimation } from '@wix/motion';
+import type { AnimationGroup } from '@wix/motion';
+import type { CssAnimationData, CustomEffectLogEntry } from '../types';
+import { EFFECTS_NAMES, EFFECTS_TARGET_IDS, EFFECTS_TEST_IDS } from '../constants/effects';
+
+type EffectsFixtureWindow = typeof window & {
+ namedWaapiGroup: AnimationGroup;
+ namedCssData: CssAnimationData[];
+ keyframeWaapiGroup: AnimationGroup;
+ keyframeCssData: CssAnimationData[];
+ customEffectGroup: AnimationGroup;
+ customEffectLog: CustomEffectLogEntry[];
+ runNamedWaapi: () => void;
+ runNamedCss: () => void;
+ runNamedCssApplied: () => void;
+ runKeyframeWaapi: () => void;
+ runKeyframeCss: () => void;
+ runKeyframeCssApplied: () => void;
+ runCustomEffect: () => void;
+ runPlayback: () => void;
+ runPlaybackReverse: () => void;
+ runPlaybackPause: () => void;
+ runPlaybackResume: () => void;
+};
+
+type EffectOptions = Record;
+
+// ---------------------------------------------------------------------------
+// Register ad-hoc named effects (self-contained, no @wix/motion-presets dep)
+// ---------------------------------------------------------------------------
+
+registerEffects({
+ TestFadeIn: {
+ getNames: () => ['test-fadeIn'],
+ web: (options: EffectOptions) => [
+ {
+ ...options,
+ name: 'test-fadeIn',
+ easing: 'linear',
+ keyframes: [
+ { offset: 0, opacity: 0 },
+ { offset: 1, opacity: 1 },
+ ],
+ },
+ ],
+ style: (options: EffectOptions) => [
+ {
+ ...options,
+ name: 'test-fadeIn',
+ easing: 'linear',
+ keyframes: [
+ { offset: 0, opacity: 0 },
+ { offset: 1, opacity: 1 },
+ ],
+ },
+ ],
+ },
+ TestScale: {
+ getNames: () => ['test-scale'],
+ web: (options: EffectOptions) => [
+ {
+ ...options,
+ name: 'test-scale',
+ easing: 'linear',
+ keyframes: [
+ { offset: 0, transform: 'scale(0)' },
+ { offset: 1, transform: 'scale(1)' },
+ ],
+ },
+ ],
+ style: (options: EffectOptions) => [
+ {
+ ...options,
+ name: 'test-scale',
+ easing: 'linear',
+ keyframes: [
+ { offset: 0, transform: 'scale(0)' },
+ { offset: 1, transform: 'scale(1)' },
+ ],
+ },
+ ],
+ },
+});
+
+// ---------------------------------------------------------------------------
+// Element references
+// ---------------------------------------------------------------------------
+
+const namedWaapiEl = document.getElementById(EFFECTS_TARGET_IDS.namedWaapi) as HTMLElement;
+const keyframeWaapiEl = document.getElementById(EFFECTS_TARGET_IDS.keyframeWaapi) as HTMLElement;
+const customEffectEl = document.getElementById(EFFECTS_TARGET_IDS.customEffect) as HTMLElement;
+const playbackEl = document.getElementById(EFFECTS_TARGET_IDS.playback) as HTMLElement;
+const playbackStateDisplay = document.querySelector(
+ `[data-testid="${EFFECTS_TEST_IDS.playbackStateDisplay}"]`,
+) as HTMLElement;
+
+// ---------------------------------------------------------------------------
+// State
+// ---------------------------------------------------------------------------
+
+const customEffectLog: CustomEffectLogEntry[] = [];
+let namedWaapiGroup: AnimationGroup;
+let namedCssData: CssAnimationData[];
+let keyframeWaapiGroup: AnimationGroup;
+let keyframeCssData: CssAnimationData[];
+let customEffectGroup: AnimationGroup;
+let playbackGroup: AnimationGroup;
+
+// ---------------------------------------------------------------------------
+// Named Effect — WAAPI path
+// ---------------------------------------------------------------------------
+
+function runNamedWaapi() {
+ namedWaapiGroup = getWebAnimation(namedWaapiEl, {
+ namedEffect: { type: 'TestFadeIn' },
+ duration: 1000,
+ fill: 'both',
+ easing: 'linear',
+ }) as AnimationGroup;
+
+ namedWaapiGroup.play();
+ (window as EffectsFixtureWindow).namedWaapiGroup = namedWaapiGroup;
+}
+
+// ---------------------------------------------------------------------------
+// Named Effect — CSS path
+// ---------------------------------------------------------------------------
+
+function runNamedCss() {
+ namedCssData = getCSSAnimation(EFFECTS_TARGET_IDS.namedCss, {
+ namedEffect: { type: EFFECTS_NAMES.namedCssEffectType },
+ duration: 1000,
+ fill: 'both',
+ easing: 'linear',
+ }) as CssAnimationData[];
+
+ (window as EffectsFixtureWindow).namedCssData = namedCssData;
+}
+
+function toKebabCase(property: string): string {
+ return property.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
+}
+
+function getCssTargetElement(target: string): HTMLElement | null {
+ const selector =
+ target.startsWith('#') || target.startsWith('.') || target.startsWith('[')
+ ? target
+ : `#${target}`;
+ return document.querySelector(selector) as HTMLElement | null;
+}
+
+function formatKeyframeDeclaration([property, value]: [string, unknown]): string {
+ return `${toKebabCase(property)}: ${String(value)};`;
+}
+
+function formatKeyframeBlock(keyframe: Keyframe): string {
+ const declarations = Object.entries(keyframe)
+ .filter(([property, value]) => property !== 'offset' && value !== undefined)
+ .map(formatKeyframeDeclaration)
+ .join(' ');
+ const percent =
+ typeof keyframe.offset === 'number' ? `${Math.round(keyframe.offset * 100)}%` : '0%';
+ return `${percent} { ${declarations} }`;
+}
+
+/**
+ * Fixture-side adapter for browser validation.
+ * This applies getCSSAnimation() output to DOM (inject keyframes + set style.animation)
+ * so E2E can verify descriptor usability in a real browser. It does not validate
+ * internal descriptor-generation logic itself (covered by unit tests).
+ */
+function applyCssAnimationData(data: CssAnimationData[]): void {
+ const firstAnimation = data[0];
+ if (!firstAnimation?.name) {
+ return;
+ }
+
+ const target = getCssTargetElement(firstAnimation.target);
+ if (!target) {
+ return;
+ }
+
+ const styleId = `generated-css-keyframes-${firstAnimation.name}`;
+ let styleTag = document.getElementById(styleId) as HTMLStyleElement | null;
+ if (!styleTag) {
+ styleTag = document.createElement('style');
+ styleTag.id = styleId;
+ document.head.appendChild(styleTag);
+ }
+
+ styleTag.textContent = `@keyframes ${firstAnimation.name} { ${firstAnimation.keyframes.map(formatKeyframeBlock).join(' ')} }`;
+
+ target.style.animation = 'none';
+ void target.offsetWidth;
+ target.style.animation = firstAnimation.animation;
+}
+
+function runNamedCssApplied() {
+ runNamedCss();
+ applyCssAnimationData(namedCssData);
+}
+
+// ---------------------------------------------------------------------------
+// Keyframe Effect — WAAPI path
+// ---------------------------------------------------------------------------
+
+function runKeyframeWaapi() {
+ keyframeWaapiGroup = getWebAnimation(keyframeWaapiEl, {
+ keyframeEffect: {
+ name: EFFECTS_NAMES.keyframeWaapiName,
+ keyframes: [
+ { offset: 0, transform: 'translateX(-100%)' },
+ { offset: 1, transform: 'translateX(0%)' },
+ ],
+ },
+ duration: 800,
+ fill: 'both',
+ easing: 'ease-out',
+ }) as AnimationGroup;
+
+ keyframeWaapiGroup.play();
+ (window as EffectsFixtureWindow).keyframeWaapiGroup = keyframeWaapiGroup;
+}
+
+// ---------------------------------------------------------------------------
+// Keyframe Effect — CSS path
+// ---------------------------------------------------------------------------
+
+function runKeyframeCss() {
+ keyframeCssData = getCSSAnimation(EFFECTS_TARGET_IDS.keyframeCss, {
+ keyframeEffect: {
+ name: EFFECTS_NAMES.keyframeCssName,
+ keyframes: [
+ { offset: 0, transform: 'rotate(0deg)' },
+ { offset: 1, transform: 'rotate(360deg)' },
+ ],
+ },
+ duration: 800,
+ fill: 'both',
+ easing: 'linear',
+ }) as CssAnimationData[];
+
+ (window as EffectsFixtureWindow).keyframeCssData = keyframeCssData;
+}
+
+function runKeyframeCssApplied() {
+ runKeyframeCss();
+ applyCssAnimationData(keyframeCssData);
+}
+
+// ---------------------------------------------------------------------------
+// Custom Effect — WAAPI path
+// ---------------------------------------------------------------------------
+
+function runCustomEffect() {
+ customEffectLog.length = 0;
+
+ customEffectGroup = getWebAnimation(customEffectEl, {
+ customEffect: (element: Element | null, progress: number | null) => {
+ const htmlElement = element as HTMLElement | null;
+ customEffectLog.push({
+ elementId: htmlElement?.id ?? null,
+ tagName: htmlElement?.tagName ?? null,
+ progress,
+ });
+ if (element && progress !== null) {
+ (element as HTMLElement).style.opacity = String(progress);
+ (element as HTMLElement).style.transform = `scale(${0.5 + progress * 0.5})`;
+ }
+ },
+ duration: 600,
+ fill: 'both',
+ easing: 'linear',
+ }) as AnimationGroup;
+
+ customEffectGroup.play();
+ (window as EffectsFixtureWindow).customEffectGroup = customEffectGroup;
+ (window as EffectsFixtureWindow).customEffectLog = customEffectLog;
+}
+
+// ---------------------------------------------------------------------------
+// Playback controls
+// ---------------------------------------------------------------------------
+
+function ensurePlaybackGroup() {
+ if (!playbackGroup) {
+ playbackGroup = getWebAnimation(playbackEl, {
+ keyframeEffect: {
+ name: EFFECTS_NAMES.playbackName,
+ keyframes: [
+ { offset: 0, opacity: 0, transform: 'translateX(-60px)' },
+ { offset: 1, opacity: 1, transform: 'translateX(0px)' },
+ ],
+ },
+ duration: 2000,
+ fill: 'both',
+ easing: 'linear',
+ }) as AnimationGroup;
+ }
+}
+
+function updatePlaybackDisplay() {
+ if (playbackStateDisplay) {
+ playbackStateDisplay.textContent = `state: ${playbackGroup?.playState ?? 'idle'}`;
+ }
+}
+
+function runPlayback() {
+ ensurePlaybackGroup();
+ playbackGroup.play().then(updatePlaybackDisplay);
+ updatePlaybackDisplay();
+}
+
+function runPlaybackReverse() {
+ ensurePlaybackGroup();
+ playbackGroup.reverse().then(updatePlaybackDisplay);
+ updatePlaybackDisplay();
+}
+
+function runPlaybackPause() {
+ ensurePlaybackGroup();
+ playbackGroup.pause();
+ updatePlaybackDisplay();
+}
+
+function runPlaybackResume() {
+ ensurePlaybackGroup();
+ playbackGroup.play().then(updatePlaybackDisplay);
+ updatePlaybackDisplay();
+}
+
+// ---------------------------------------------------------------------------
+// Expose to tests
+// ---------------------------------------------------------------------------
+
+(window as EffectsFixtureWindow).customEffectLog = customEffectLog;
+(window as EffectsFixtureWindow).runNamedWaapi = runNamedWaapi;
+(window as EffectsFixtureWindow).runNamedCss = runNamedCss;
+(window as EffectsFixtureWindow).runNamedCssApplied = runNamedCssApplied;
+(window as EffectsFixtureWindow).runKeyframeWaapi = runKeyframeWaapi;
+(window as EffectsFixtureWindow).runKeyframeCss = runKeyframeCss;
+(window as EffectsFixtureWindow).runKeyframeCssApplied = runKeyframeCssApplied;
+(window as EffectsFixtureWindow).runCustomEffect = runCustomEffect;
+(window as EffectsFixtureWindow).runPlayback = runPlayback;
+(window as EffectsFixtureWindow).runPlaybackReverse = runPlaybackReverse;
+(window as EffectsFixtureWindow).runPlaybackPause = runPlaybackPause;
+(window as EffectsFixtureWindow).runPlaybackResume = runPlaybackResume;
diff --git a/packages/motion/e2e/fixtures/index.html b/packages/motion/e2e/fixtures/index.html
new file mode 100644
index 0000000..60ca9c8
--- /dev/null
+++ b/packages/motion/e2e/fixtures/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+ @wix/motion E2E Fixtures
+
+
+
+
+
+ Test Fixture Pages
+
+
+
diff --git a/packages/motion/e2e/fixtures/pointer.html b/packages/motion/e2e/fixtures/pointer.html
new file mode 100644
index 0000000..745ae16
--- /dev/null
+++ b/packages/motion/e2e/fixtures/pointer.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+ Pointer-Driven Animations
+
+
+
+
+
+
+
+
Pointer Area (move mouse inside)
+
+
x: 0.000 y: 0.000
+
+
+
+
+
+
Composite Transform (scaleX + scaleY)
+
+
+
+
+
+
diff --git a/packages/motion/e2e/fixtures/pointer.ts b/packages/motion/e2e/fixtures/pointer.ts
new file mode 100644
index 0000000..273b52a
--- /dev/null
+++ b/packages/motion/e2e/fixtures/pointer.ts
@@ -0,0 +1,125 @@
+import { getScrubScene } from '@wix/motion';
+import type { ScrubPointerScene } from '@wix/motion';
+import { POINTER_IDS, POINTER_TEST_IDS } from '../constants/pointer';
+
+type PointerFixtureWindow = typeof window & {
+ pointerScene: {
+ cancel(): void;
+ playState: 'idle' | 'running';
+ };
+};
+
+const pointerArea = document.getElementById(POINTER_IDS.area) as HTMLElement;
+const xAxisTarget = document.getElementById(POINTER_IDS.xAxisTarget) as HTMLElement;
+const yAxisTarget = document.getElementById(POINTER_IDS.yAxisTarget) as HTMLElement;
+const compositeTarget = document.getElementById(POINTER_IDS.compositeTarget) as HTMLElement;
+const progressDisplay = document.querySelector(
+ `[data-testid="${POINTER_TEST_IDS.progressDisplay}"]`,
+) as HTMLElement;
+
+function createPointerTrigger(element: HTMLElement, axis: 'x' | 'y') {
+ return {
+ trigger: 'pointer-move',
+ element,
+ axis,
+ } as Parameters[2];
+}
+
+const xAxisScene = getScrubScene(
+ xAxisTarget,
+ {
+ keyframeEffect: {
+ name: 'pointer-x',
+ keyframes: [
+ { offset: 0, transform: 'translateX(-80px)' },
+ { offset: 1, transform: 'translateX(80px)' },
+ ],
+ },
+ duration: 1000,
+ fill: 'both',
+ easing: 'linear',
+ },
+ createPointerTrigger(pointerArea, 'x'),
+) as ScrubPointerScene | null;
+
+const yAxisArea = document.getElementById(POINTER_IDS.yAxisArea) as HTMLElement;
+const yAxisScene = getScrubScene(
+ yAxisTarget,
+ {
+ keyframeEffect: {
+ name: 'pointer-y',
+ keyframes: [
+ { offset: 0, transform: 'translateY(-40px)' },
+ { offset: 1, transform: 'translateY(40px)' },
+ ],
+ },
+ duration: 1000,
+ fill: 'both',
+ easing: 'linear',
+ },
+ createPointerTrigger(yAxisArea, 'y'),
+) as ScrubPointerScene | null;
+
+const compositeArea = document.getElementById(POINTER_IDS.compositeArea) as HTMLElement;
+const compositeScaleXScene = getScrubScene(
+ compositeTarget,
+ {
+ keyframeEffect: {
+ name: 'pointer-scale-x',
+ keyframes: [
+ { offset: 0, transform: 'scaleX(0.5)' },
+ { offset: 1, transform: 'scaleX(1.5)' },
+ ],
+ },
+ duration: 1000,
+ fill: 'both',
+ easing: 'linear',
+ },
+ createPointerTrigger(compositeArea, 'x'),
+) as ScrubPointerScene | null;
+
+const compositeScaleYScene = getScrubScene(
+ compositeTarget,
+ {
+ keyframeEffect: {
+ name: 'pointer-scale-y',
+ keyframes: [
+ { offset: 0, transform: 'scaleY(0.5)' },
+ { offset: 1, transform: 'scaleY(1.5)' },
+ ],
+ },
+ duration: 1000,
+ fill: 'both',
+ easing: 'linear',
+ },
+ createPointerTrigger(compositeArea, 'y'),
+) as ScrubPointerScene | null;
+
+function getRelativeProgress(area: HTMLElement, clientX: number, clientY: number) {
+ const rect = area.getBoundingClientRect();
+ const x = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
+ const y = Math.max(0, Math.min(1, (clientY - rect.top) / rect.height));
+ return { x, y };
+}
+
+// Pointer area readout (scene driving is handled by getScrubScene)
+pointerArea.addEventListener('pointermove', (e) => {
+ const progress = getRelativeProgress(pointerArea, e.clientX, e.clientY);
+
+ if (progressDisplay) {
+ progressDisplay.textContent = `x: ${progress.x.toFixed(3)} y: ${progress.y.toFixed(3)}`;
+ }
+});
+
+const pointerSceneHandle: PointerFixtureWindow['pointerScene'] = {
+ playState: 'running',
+ cancel() {
+ xAxisScene?.destroy();
+ yAxisScene?.destroy();
+ compositeScaleXScene?.destroy();
+ compositeScaleYScene?.destroy();
+ this.playState = 'idle';
+ },
+};
+
+(window as PointerFixtureWindow).pointerScene = pointerSceneHandle;
diff --git a/packages/motion/e2e/fixtures/scroll.html b/packages/motion/e2e/fixtures/scroll.html
new file mode 100644
index 0000000..9e87282
--- /dev/null
+++ b/packages/motion/e2e/fixtures/scroll.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+ Scroll-Driven Animations
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/motion/e2e/fixtures/scroll.ts b/packages/motion/e2e/fixtures/scroll.ts
new file mode 100644
index 0000000..d282c7a
--- /dev/null
+++ b/packages/motion/e2e/fixtures/scroll.ts
@@ -0,0 +1,110 @@
+import { getWebAnimation, getScrubScene } from '@wix/motion';
+import type { AnimationGroup, RangeOffset, ScrubScrollScene } from '@wix/motion';
+import { SCROLL_IDS, SCROLL_TEST_IDS } from '../constants/scroll';
+
+type ScrollFixtureWindow = typeof window & {
+ scrubScene: AnimationGroup;
+ getScrollProgress: () => number;
+ rangeScene: ScrubScrollScene | null;
+ rangeConfig: { startOffset: RangeOffset; endOffset: RangeOffset };
+};
+
+const target = document.getElementById(SCROLL_IDS.viewProgressTarget) as HTMLElement;
+const progressDisplay = document.querySelector(
+ `[data-testid="${SCROLL_TEST_IDS.progressDisplay}"]`,
+) as HTMLElement;
+
+function calculateProgress(el: HTMLElement): number {
+ const rect = el.getBoundingClientRect();
+ const progress = (window.innerHeight - rect.top) / (window.innerHeight + rect.height);
+ return Math.max(0, Math.min(1, progress));
+}
+
+const animationGroup = getWebAnimation(target, {
+ keyframeEffect: {
+ name: 'scroll-fade-slide',
+ keyframes: [
+ { offset: 0, opacity: 0, transform: 'translateY(60px)' },
+ { offset: 1, opacity: 1, transform: 'translateY(0px)' },
+ ],
+ },
+ duration: 1000,
+ fill: 'both',
+ easing: 'linear',
+}) as AnimationGroup;
+
+animationGroup.ready.then(() => {
+ function onScroll() {
+ const p = calculateProgress(target);
+ animationGroup.progress(p);
+ if (progressDisplay) {
+ progressDisplay.textContent = `progress: ${p.toFixed(3)}`;
+ }
+ }
+
+ window.addEventListener('scroll', onScroll, { passive: true });
+ onScroll();
+});
+
+// Staggered scrub cards
+const cards = [SCROLL_IDS.scrubCard1, SCROLL_IDS.scrubCard2, SCROLL_IDS.scrubCard3];
+cards.forEach((id, i) => {
+ const card = document.getElementById(id) as HTMLElement;
+
+ const cardAnimation = getWebAnimation(card, {
+ keyframeEffect: {
+ name: `card-enter-${i}`,
+ keyframes: [
+ { offset: 0, opacity: 0, transform: 'translateX(-40px)' },
+ { offset: 1, opacity: 1, transform: 'translateX(0px)' },
+ ],
+ },
+ duration: 600,
+ delay: i * 80,
+ fill: 'both',
+ easing: 'ease-out',
+ }) as AnimationGroup;
+
+ cardAnimation.ready.then(() => {
+ function onCardScroll() {
+ cardAnimation.progress(calculateProgress(card));
+ }
+ window.addEventListener('scroll', onCardScroll, { passive: true });
+ onCardScroll();
+ });
+});
+
+(window as ScrollFixtureWindow).scrubScene = animationGroup;
+(window as ScrollFixtureWindow).getScrollProgress = () => calculateProgress(target);
+
+// ---------------------------------------------------------------------------
+// Range offset scene — tests that startOffset/endOffset flow through the pipeline
+// ---------------------------------------------------------------------------
+
+const RANGE_START: RangeOffset = { name: 'entry' };
+const RANGE_END: RangeOffset = { name: 'exit' };
+
+const rangeSceneResult = getScrubScene(
+ target,
+ {
+ keyframeEffect: {
+ name: 'scroll-range-test',
+ keyframes: [
+ { offset: 0, opacity: 0 },
+ { offset: 1, opacity: 1 },
+ ],
+ },
+ startOffset: RANGE_START,
+ endOffset: RANGE_END,
+ fill: 'both',
+ },
+ { trigger: 'view-progress', element: target },
+);
+
+// getScrubScene returns ScrubScrollScene[] in the fallback path (no native ViewTimeline)
+const rangeScene = Array.isArray(rangeSceneResult)
+ ? (rangeSceneResult[0] as ScrubScrollScene)
+ : null;
+
+(window as ScrollFixtureWindow).rangeScene = rangeScene;
+(window as ScrollFixtureWindow).rangeConfig = { startOffset: RANGE_START, endOffset: RANGE_END };
diff --git a/packages/motion/e2e/fixtures/styles.css b/packages/motion/e2e/fixtures/styles.css
new file mode 100644
index 0000000..ecf3413
--- /dev/null
+++ b/packages/motion/e2e/fixtures/styles.css
@@ -0,0 +1,59 @@
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: sans-serif;
+ background: #f5f5f5;
+}
+
+.fixture-nav {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 8px 16px;
+ background: #1a1a2e;
+ color: #fff;
+ font-size: 13px;
+ z-index: 9999;
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.fixture-nav a {
+ color: #a0c4ff;
+ text-decoration: none;
+}
+
+.fixture-nav a:hover {
+ text-decoration: underline;
+}
+
+.fixture-box {
+ width: 120px;
+ height: 120px;
+ background: #4a90d9;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 12px;
+ font-weight: bold;
+}
+
+.fixture-label {
+ font-size: 11px;
+ color: #666;
+ margin-top: 4px;
+}
+
+.spacer {
+ height: 100vh;
+}
diff --git a/packages/motion/e2e/fixtures/vite.config.ts b/packages/motion/e2e/fixtures/vite.config.ts
new file mode 100644
index 0000000..ac7c7b5
--- /dev/null
+++ b/packages/motion/e2e/fixtures/vite.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig } from 'vite';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const motionSrc = path.resolve(__dirname, '../../src/index.ts');
+
+export default defineConfig({
+ root: __dirname,
+ resolve: {
+ alias: {
+ '@wix/motion': motionSrc,
+ },
+ },
+ build: {
+ rollupOptions: {
+ input: {
+ index: path.resolve(__dirname, 'index.html'),
+ scroll: path.resolve(__dirname, 'scroll.html'),
+ pointer: path.resolve(__dirname, 'pointer.html'),
+ 'animation-group': path.resolve(__dirname, 'animation-group.html'),
+ effects: path.resolve(__dirname, 'effects.html'),
+ },
+ },
+ },
+ server: {
+ port: 5174,
+ },
+});
diff --git a/packages/motion/e2e/pages/animation-group-page.ts b/packages/motion/e2e/pages/animation-group-page.ts
new file mode 100644
index 0000000..00009fa
--- /dev/null
+++ b/packages/motion/e2e/pages/animation-group-page.ts
@@ -0,0 +1,80 @@
+import type { Page } from '@playwright/test';
+import { BaseFixturePage } from './base-fixture-page';
+import { ANIMATION_GROUP_IDS } from '../constants/animation-group';
+
+type FixtureWindow = {
+ play(): Promise;
+ pause(): void;
+ reverse(): Promise;
+ cancel(): void;
+ animationGroup: {
+ getProgress(): number;
+ playState: string;
+ progress(p: number): void;
+ };
+ lifecycleEvents: string[];
+};
+
+export class AnimationGroupPage extends BaseFixturePage {
+ constructor(page: Page) {
+ super(page);
+ }
+
+ async goto(): Promise {
+ await this.navigate('animation-group');
+ }
+
+ play() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).play());
+ }
+
+ pause() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).pause());
+ }
+
+ reverse() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).reverse());
+ }
+
+ cancel() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).cancel());
+ }
+
+ getProgress() {
+ return this.page.evaluate(() =>
+ (window as unknown as FixtureWindow).animationGroup.getProgress(),
+ );
+ }
+
+ setProgress(p: number) {
+ return this.page.evaluate(
+ (progress) => (window as unknown as FixtureWindow).animationGroup.progress(progress),
+ p,
+ );
+ }
+
+ getGroupItemOpacity() {
+ return this.page.evaluate((targetId) => {
+ const el = document.getElementById(targetId);
+ return el ? parseFloat(getComputedStyle(el).opacity) : 0;
+ }, ANIMATION_GROUP_IDS.item1);
+ }
+
+ getPlayState() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).animationGroup.playState);
+ }
+
+ getPlaybackRate() {
+ return this.page.evaluate(() => {
+ const fixtureWindow = window as unknown as {
+ animationGroup: { animations: Array<{ playbackRate: number }> };
+ };
+
+ return fixtureWindow.animationGroup.animations[0]?.playbackRate;
+ });
+ }
+
+ getLifecycleEvents() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).lifecycleEvents);
+ }
+}
diff --git a/packages/motion/e2e/pages/base-fixture-page.ts b/packages/motion/e2e/pages/base-fixture-page.ts
new file mode 100644
index 0000000..ef37915
--- /dev/null
+++ b/packages/motion/e2e/pages/base-fixture-page.ts
@@ -0,0 +1,9 @@
+import type { Page } from '@playwright/test';
+
+export class BaseFixturePage {
+ constructor(protected readonly page: Page) {}
+
+ async navigate(fixture: string): Promise {
+ await this.page.goto(`/${fixture}.html`);
+ }
+}
diff --git a/packages/motion/e2e/pages/effects-page.ts b/packages/motion/e2e/pages/effects-page.ts
new file mode 100644
index 0000000..0a73b24
--- /dev/null
+++ b/packages/motion/e2e/pages/effects-page.ts
@@ -0,0 +1,126 @@
+import type { Page } from '@playwright/test';
+import { BaseFixturePage } from './base-fixture-page';
+import type { CssAnimationData, CustomEffectLogEntry } from '../types';
+import { EFFECTS_TARGET_IDS } from '../constants/effects';
+
+type FixtureWindow = {
+ namedWaapiGroup: { playState: string };
+ namedCssData: CssAnimationData[];
+ keyframeWaapiGroup: { playState: string };
+ keyframeCssData: CssAnimationData[];
+ customEffectGroup: { playState: string; cancel(): void };
+ customEffectLog: CustomEffectLogEntry[];
+ runNamedWaapi(): void;
+ runNamedCss(): void;
+ runNamedCssApplied(): void;
+ runKeyframeWaapi(): void;
+ runKeyframeCss(): void;
+ runKeyframeCssApplied(): void;
+ runCustomEffect(): void;
+ runPlayback(): void;
+ runPlaybackReverse(): void;
+ runPlaybackPause(): void;
+ runPlaybackResume(): void;
+};
+
+export class EffectsPage extends BaseFixturePage {
+ constructor(page: Page) {
+ super(page);
+ }
+
+ async goto(): Promise {
+ await this.navigate('effects');
+ }
+
+ runNamedWaapi() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runNamedWaapi());
+ }
+
+ runNamedCss() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runNamedCss());
+ }
+
+ runNamedCssApplied() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runNamedCssApplied());
+ }
+
+ runKeyframeWaapi() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runKeyframeWaapi());
+ }
+
+ runKeyframeCss() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runKeyframeCss());
+ }
+
+ runKeyframeCssApplied() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runKeyframeCssApplied());
+ }
+
+ runCustomEffect() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runCustomEffect());
+ }
+
+ runPlayback() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runPlayback());
+ }
+
+ runPlaybackReverse() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runPlaybackReverse());
+ }
+
+ runPlaybackPause() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runPlaybackPause());
+ }
+
+ runPlaybackResume() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).runPlaybackResume());
+ }
+
+ getNamedCssData(): Promise {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).namedCssData);
+ }
+
+ getKeyframeCssData(): Promise {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).keyframeCssData);
+ }
+
+ getCustomEffectLog(): Promise {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).customEffectLog);
+ }
+
+ getNamedWaapiPlayState(): Promise {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).namedWaapiGroup.playState);
+ }
+
+ getCustomEffectPlayState(): Promise {
+ return this.page.evaluate(
+ () => (window as unknown as FixtureWindow).customEffectGroup.playState,
+ );
+ }
+
+ getKeyframeWaapiPlayState(): Promise {
+ return this.page.evaluate(
+ () => (window as unknown as FixtureWindow).keyframeWaapiGroup.playState,
+ );
+ }
+
+ cancelCustomEffect() {
+ return this.page.evaluate(() =>
+ (window as unknown as FixtureWindow).customEffectGroup.cancel(),
+ );
+ }
+
+ getPlaybackPlayState(): Promise {
+ return this.page.evaluate((playbackId) => {
+ const el = document.getElementById(playbackId);
+ return el?.getAnimations()[0]?.playState ?? 'idle';
+ }, EFFECTS_TARGET_IDS.playback);
+ }
+
+ getPlaybackOpacity(): Promise {
+ return this.page.evaluate((playbackId) => {
+ const el = document.getElementById(playbackId);
+ return el ? getComputedStyle(el).opacity : '1';
+ }, EFFECTS_TARGET_IDS.playback);
+ }
+}
diff --git a/packages/motion/e2e/pages/pointer-page.ts b/packages/motion/e2e/pages/pointer-page.ts
new file mode 100644
index 0000000..fd0be6d
--- /dev/null
+++ b/packages/motion/e2e/pages/pointer-page.ts
@@ -0,0 +1,29 @@
+import type { Page } from '@playwright/test';
+import { BaseFixturePage } from './base-fixture-page';
+import { movePointerWithinElement } from '../utils/pointer-helpers';
+
+export class PointerPage extends BaseFixturePage {
+ constructor(page: Page) {
+ super(page);
+ }
+
+ async goto(): Promise {
+ await this.navigate('pointer');
+ }
+
+ movePointerWithinElement(containerSelector: string, ratioX: number, ratioY: number) {
+ return movePointerWithinElement(this.page, containerSelector, ratioX, ratioY);
+ }
+
+ cancelPointerScene() {
+ return this.page.evaluate(() =>
+ (window as unknown as { pointerScene: { cancel(): void } }).pointerScene.cancel(),
+ );
+ }
+
+ getPointerScenePlayState() {
+ return this.page.evaluate(
+ () => (window as unknown as { pointerScene: { playState: string } }).pointerScene.playState,
+ );
+ }
+}
diff --git a/packages/motion/e2e/pages/scroll-page.ts b/packages/motion/e2e/pages/scroll-page.ts
new file mode 100644
index 0000000..f428562
--- /dev/null
+++ b/packages/motion/e2e/pages/scroll-page.ts
@@ -0,0 +1,63 @@
+import type { Page } from '@playwright/test';
+import { BaseFixturePage } from './base-fixture-page';
+import {
+ scrollTo,
+ scrollElementIntoView,
+ getScrollProgress,
+ getScrollY,
+} from '../utils/scroll-helpers';
+
+type RangeOffset = { name?: string; offset?: number };
+
+type FixtureWindow = {
+ scrubScene: { cancel(): void; playState: string };
+ rangeScene: { start?: RangeOffset; end?: RangeOffset } | null;
+ rangeConfig: { startOffset: RangeOffset; endOffset: RangeOffset };
+ getScrollProgress(): number;
+};
+
+export class ScrollPage extends BaseFixturePage {
+ constructor(page: Page) {
+ super(page);
+ }
+
+ async goto(): Promise {
+ await this.navigate('scroll');
+ }
+
+ scrollTo(y: number) {
+ return scrollTo(this.page, y);
+ }
+
+ scrollElementIntoView(selector: string) {
+ return scrollElementIntoView(this.page, selector);
+ }
+
+ getScrollProgress() {
+ return getScrollProgress(this.page);
+ }
+
+ getScrollY() {
+ return getScrollY(this.page);
+ }
+
+ cancelScrubScene() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).scrubScene.cancel());
+ }
+
+ getScrubScenePlayState() {
+ return this.page.evaluate(() => (window as unknown as FixtureWindow).scrubScene.playState);
+ }
+
+ getRangeOffsets() {
+ return this.page.evaluate(() => {
+ const win = window as unknown as FixtureWindow;
+ return {
+ config: win.rangeConfig,
+ // only populated in the non-ViewTimeline fallback path
+ sceneStart: win.rangeScene?.start ?? null,
+ sceneEnd: win.rangeScene?.end ?? null,
+ };
+ });
+ }
+}
diff --git a/packages/motion/e2e/tests/animation-group.spec.ts b/packages/motion/e2e/tests/animation-group.spec.ts
new file mode 100644
index 0000000..e0cae1a
--- /dev/null
+++ b/packages/motion/e2e/tests/animation-group.spec.ts
@@ -0,0 +1,117 @@
+import { test, expect } from '@playwright/test';
+import { AnimationGroupPage } from '../pages/animation-group-page';
+
+test.describe('AnimationGroup API', () => {
+ let animationGroupPage: AnimationGroupPage;
+
+ test.beforeEach(async ({ page }) => {
+ animationGroupPage = new AnimationGroupPage(page);
+ await animationGroupPage.goto();
+ });
+
+ test.describe('Lifecycle Methods', () => {
+ test('should play animation and resolve ready promise', async () => {
+ await animationGroupPage.play();
+
+ const playState = await animationGroupPage.getPlayState();
+ expect(['running', 'finished']).toContain(playState);
+
+ const events = await animationGroupPage.getLifecycleEvents();
+ expect(events).toContain('play:ready');
+ });
+
+ test('should pause animation immediately', async () => {
+ await animationGroupPage.play();
+ await animationGroupPage.pause();
+
+ const playState = await animationGroupPage.getPlayState();
+ expect(playState).toBe('paused');
+
+ const events = await animationGroupPage.getLifecycleEvents();
+ expect(events).toContain('pause');
+ });
+
+ test('should reverse animation direction', async () => {
+ await animationGroupPage.play();
+ await animationGroupPage.reverse();
+ const playbackRate = await animationGroupPage.getPlaybackRate();
+
+ const events = await animationGroupPage.getLifecycleEvents();
+ expect(playbackRate).toBe(-1);
+ expect(events).toContain('reverse:ready');
+ });
+
+ test('should cancel animation and reset', async () => {
+ await animationGroupPage.play();
+ await animationGroupPage.cancel();
+
+ const playState = await animationGroupPage.getPlayState();
+ expect(playState).toBe('idle');
+
+ const events = await animationGroupPage.getLifecycleEvents();
+ expect(events).toContain('cancel');
+ });
+ });
+
+ test.describe('Progress Control', () => {
+ test('should set progress manually', async ({ page }) => {
+ await page.evaluate(
+ () =>
+ (window as unknown as { animationGroup: { ready: Promise } }).animationGroup.ready,
+ );
+ await animationGroupPage.setProgress(0.5);
+
+ const progress = await animationGroupPage.getProgress();
+ expect(progress).toBeCloseTo(0.5, 1);
+
+ const opacity = await animationGroupPage.getGroupItemOpacity();
+ expect(opacity).toBeCloseTo(0.5, 1);
+ });
+
+ test('should report accurate progress percentage', async () => {
+ for (const p of [0, 0.25, 0.5, 0.75, 1]) {
+ await animationGroupPage.setProgress(p);
+ const reported = await animationGroupPage.getProgress();
+ expect(reported).toBeCloseTo(p, 1);
+ }
+ });
+ });
+
+ test.describe('Callbacks', () => {
+ test('should fire onFinish callback when animation completes', async ({ page }) => {
+ await animationGroupPage.play();
+
+ await page.waitForFunction(
+ () =>
+ (window as unknown as { lifecycleEvents: string[] }).lifecycleEvents.includes('finish'),
+ { timeout: 3000 },
+ );
+
+ expect(await animationGroupPage.getLifecycleEvents()).toContain('finish');
+ });
+
+ test('should handle multiple onFinish subscribers', async ({ page }) => {
+ await animationGroupPage.play();
+
+ await page.waitForFunction(
+ () =>
+ (window as unknown as { lifecycleEvents: string[] }).lifecycleEvents.includes('finish'),
+ { timeout: 3000 },
+ );
+
+ // Second play() registers a new onFinish listener independently
+ await animationGroupPage.play();
+
+ await page.waitForFunction(
+ () =>
+ (window as unknown as { lifecycleEvents: string[] }).lifecycleEvents.filter(
+ (e: string) => e === 'finish',
+ ).length >= 2,
+ { timeout: 3000 },
+ );
+
+ const events = await animationGroupPage.getLifecycleEvents();
+ expect(events.filter((e) => e === 'finish').length).toBeGreaterThanOrEqual(2);
+ });
+ });
+});
diff --git a/packages/motion/e2e/tests/effects.spec.ts b/packages/motion/e2e/tests/effects.spec.ts
new file mode 100644
index 0000000..020d985
--- /dev/null
+++ b/packages/motion/e2e/tests/effects.spec.ts
@@ -0,0 +1,316 @@
+import { test, expect } from '@playwright/test';
+import { EffectsPage } from '../pages/effects-page';
+import { waitForWindowPlayState, waitForElementAnimationState } from '../utils/animation-helpers';
+import { EFFECTS_NAMES, EFFECTS_TARGET_IDS } from '../constants/effects';
+
+test.describe('Effect Types', () => {
+ let effectsPage: EffectsPage;
+
+ test.beforeEach(async ({ page }) => {
+ effectsPage = new EffectsPage(page);
+ await effectsPage.goto();
+ });
+
+ test.describe('Named Effects — WAAPI', () => {
+ test('should create AnimationGroup via getWebAnimation with registered named effect', async ({
+ page,
+ }) => {
+ await effectsPage.runNamedWaapi();
+
+ await waitForWindowPlayState(page, 'namedWaapiGroup', ['running', 'finished']);
+
+ const playState = await effectsPage.getNamedWaapiPlayState();
+ expect(['running', 'finished']).toContain(playState);
+ });
+
+ test('should apply correct keyframes from named effect web() method', async ({ page }) => {
+ await effectsPage.runNamedWaapi();
+
+ await waitForWindowPlayState(page, 'namedWaapiGroup', ['finished'], 3000);
+
+ const opacity = await page.evaluate((targetId) => {
+ const el = document.getElementById(targetId);
+ return el ? getComputedStyle(el).opacity : null;
+ }, EFFECTS_TARGET_IDS.namedWaapi);
+ expect(opacity).toBe('1');
+ });
+ });
+
+ test.describe('Named Effects — CSS', () => {
+ test('should generate CSS animation data via getCSSAnimation with registered named effect', async () => {
+ await effectsPage.runNamedCss();
+
+ const data = await effectsPage.getNamedCssData();
+ expect(Array.isArray(data)).toBe(true);
+ expect(data.length).toBeGreaterThan(0);
+ });
+
+ test('should produce correct keyframe name from style() method', async () => {
+ await effectsPage.runNamedCss();
+
+ const data = await effectsPage.getNamedCssData();
+ expect(data[0].name).toBe(EFFECTS_NAMES.namedCssKeyframeName);
+ });
+
+ test('should return animation data with correct keyframes', async () => {
+ await effectsPage.runNamedCss();
+
+ const data = await effectsPage.getNamedCssData();
+ const keyframes = data[0].keyframes;
+ expect(keyframes.length).toBeGreaterThan(0);
+ // scale(0) → scale(1) as defined in TestScale
+ expect(String(keyframes[0].transform)).toContain('scale(0)');
+ expect(String(keyframes[keyframes.length - 1].transform)).toContain('scale(1)');
+ });
+ });
+
+ test.describe('Named Effects — CSS Runtime Consumption', () => {
+ test('should apply generated named CSS descriptor to element in browser', async ({ page }) => {
+ await effectsPage.runNamedCssApplied();
+ await waitForElementAnimationState(
+ page,
+ EFFECTS_TARGET_IDS.namedCss,
+ ['paused', 'running', 'finished'],
+ 5000,
+ );
+
+ const result = await page.evaluate((targetId) => {
+ const target = document.getElementById(targetId) as HTMLElement;
+ const animation = target.getAnimations()[0];
+ animation.pause();
+ animation.currentTime = 0;
+ const startTransform = getComputedStyle(target).transform;
+ animation.currentTime = 700;
+ const endTransform = getComputedStyle(target).transform;
+ return {
+ animationName: getComputedStyle(target).animationName,
+ startTransform,
+ endTransform,
+ };
+ }, EFFECTS_TARGET_IDS.namedCss);
+
+ expect(result.animationName).toContain(EFFECTS_NAMES.namedCssKeyframeName);
+ expect(result.startTransform).not.toBe(result.endTransform);
+ });
+ });
+
+ test.describe('Keyframe Effects — WAAPI', () => {
+ test('should create AnimationGroup via getWebAnimation with inline keyframeEffect', async ({
+ page,
+ }) => {
+ await effectsPage.runKeyframeWaapi();
+
+ await waitForWindowPlayState(page, 'keyframeWaapiGroup', ['running', 'finished']);
+
+ const playState = await effectsPage.getKeyframeWaapiPlayState();
+ expect(['running', 'finished']).toContain(playState);
+ });
+
+ test('should apply keyframeEffect keyframes to the element', async ({ page }) => {
+ await effectsPage.runKeyframeWaapi();
+
+ await waitForWindowPlayState(page, 'keyframeWaapiGroup', ['running', 'finished']);
+
+ const hasExpectedKeyframes = await page.evaluate((targetId) => {
+ const el = document.getElementById(targetId);
+ const keyframes =
+ (el?.getAnimations()[0]?.effect as KeyframeEffect)?.getKeyframes?.() ?? [];
+ return keyframes.some((kf) => String(kf.transform).includes('translateX'));
+ }, EFFECTS_TARGET_IDS.keyframeWaapi);
+
+ expect(hasExpectedKeyframes).toBe(true);
+ });
+ });
+
+ test.describe('Keyframe Effects — CSS', () => {
+ test('should generate CSS animation data via getCSSAnimation with inline keyframeEffect', async () => {
+ await effectsPage.runKeyframeCss();
+
+ const data = await effectsPage.getKeyframeCssData();
+ expect(Array.isArray(data)).toBe(true);
+ expect(data.length).toBeGreaterThan(0);
+ });
+
+ test('should produce correct CSS keyframe name from keyframeEffect.name', async () => {
+ await effectsPage.runKeyframeCss();
+
+ const data = await effectsPage.getKeyframeCssData();
+ expect(data[0].name).toBe(EFFECTS_NAMES.keyframeCssName);
+ });
+
+ test('should include keyframeEffect keyframes in CSS output', async () => {
+ await effectsPage.runKeyframeCss();
+
+ const data = await effectsPage.getKeyframeCssData();
+ const keyframes = data[0].keyframes;
+ expect(keyframes.length).toBeGreaterThan(0);
+ // rotate(0deg) → rotate(360deg)
+ expect(String(keyframes[0].transform)).toContain('rotate(0deg)');
+ });
+ });
+
+ test.describe('Keyframe Effects — CSS Runtime Consumption', () => {
+ test('should apply generated keyframe CSS descriptor to element in browser', async ({
+ page,
+ }) => {
+ await effectsPage.runKeyframeCssApplied();
+ await waitForElementAnimationState(
+ page,
+ EFFECTS_TARGET_IDS.keyframeCss,
+ ['paused', 'running', 'finished'],
+ 5000,
+ );
+
+ const result = await page.evaluate((targetId) => {
+ const target = document.getElementById(targetId) as HTMLElement;
+ const animation = target.getAnimations()[0];
+ animation.pause();
+ animation.currentTime = 0;
+ const startTransform = getComputedStyle(target).transform;
+ animation.currentTime = 400;
+ const midTransform = getComputedStyle(target).transform;
+ return {
+ animationName: getComputedStyle(target).animationName,
+ startTransform,
+ midTransform,
+ };
+ }, EFFECTS_TARGET_IDS.keyframeCss);
+
+ expect(result.animationName).toContain(EFFECTS_NAMES.keyframeCssName);
+ expect(result.startTransform).not.toBe(result.midTransform);
+ });
+ });
+
+ test.describe('Custom Effects', () => {
+ test('should create animation via getWebAnimation with customEffect function', async ({
+ page,
+ }) => {
+ await effectsPage.runCustomEffect();
+
+ await waitForWindowPlayState(page, 'customEffectGroup', ['running', 'finished']);
+
+ const playState = await effectsPage.getCustomEffectPlayState();
+ expect(['running', 'finished']).toContain(playState);
+ });
+
+ test('should call customEffect function with (element, progress) during playback', async ({
+ page,
+ }) => {
+ await effectsPage.runCustomEffect();
+
+ // Wait for at least some log entries to accumulate
+ await page.waitForFunction(
+ () => (window as unknown as { customEffectLog: unknown[] }).customEffectLog?.length > 2,
+ { timeout: 3000 },
+ );
+
+ const log = await effectsPage.getCustomEffectLog();
+ const progressEntries = log.filter((e) => e.progress !== null && e.progress !== undefined);
+ expect(progressEntries.length).toBeGreaterThan(0);
+ expect(progressEntries[0].tagName).toBeTruthy();
+ expect(progressEntries[0].elementId).toBe(EFFECTS_TARGET_IDS.customEffect);
+ });
+
+ test('should call customEffect with null progress on cancel', async ({ page }) => {
+ await effectsPage.runCustomEffect();
+
+ // Wait for animation to start
+ await page.waitForFunction(
+ () => (window as unknown as { customEffectLog: unknown[] }).customEffectLog?.length > 0,
+ { timeout: 3000 },
+ );
+
+ await effectsPage.cancelCustomEffect();
+
+ const log = await effectsPage.getCustomEffectLog();
+ const nullEntries = log.filter((e) => e.progress === null);
+ expect(nullEntries.length).toBeGreaterThan(0);
+ });
+
+ test('should track progress updates through customEffect callback', async ({ page }) => {
+ await effectsPage.runCustomEffect();
+
+ // Wait for animation to complete — use progress log instead of playState for Firefox/WebKit compatibility
+ await page.waitForFunction(
+ () => {
+ const log = (window as unknown as { customEffectLog: { progress: number | null }[] })
+ .customEffectLog;
+ return log?.some((e) => e.progress !== null && e.progress >= 0.9) ?? false;
+ },
+ { timeout: 3000 },
+ );
+
+ const log = await effectsPage.getCustomEffectLog();
+ const progressValues = log
+ .filter((e) => e.progress !== null)
+ .map((e) => e.progress as number);
+ // Browsers may not deliver progress=1; 0.9+ confirms the callback tracked the full animation
+ expect(Math.max(...progressValues)).toBeGreaterThanOrEqual(0.9);
+ });
+ });
+
+ test.describe('Playback — Play/Reverse', () => {
+ test('should return to initial state after reverse completes', async ({ page }) => {
+ await effectsPage.runPlayback();
+ await waitForElementAnimationState(page, EFFECTS_TARGET_IDS.playback, ['running']);
+ await effectsPage.runPlaybackReverse();
+
+ await waitForElementAnimationState(page, EFFECTS_TARGET_IDS.playback, ['finished'], 5000);
+
+ const opacity = await effectsPage.getPlaybackOpacity();
+ // fill: both + reversed → element at first keyframe: opacity 0
+ expect(parseFloat(opacity)).toBeCloseTo(0, 1);
+ });
+ });
+
+ test.describe('Playback — Play/Pause', () => {
+ test('should pause animation mid-playback and hold current state', async ({ page }) => {
+ await effectsPage.runPlayback();
+ await waitForElementAnimationState(page, EFFECTS_TARGET_IDS.playback, ['running']);
+ await page.waitForFunction(
+ (targetId) => {
+ const target = document.getElementById(targetId);
+ return target ? parseFloat(getComputedStyle(target).opacity) > 0.1 : false;
+ },
+ EFFECTS_TARGET_IDS.playback,
+ { timeout: 2000 },
+ );
+ await effectsPage.runPlaybackPause();
+
+ const playState = await effectsPage.getPlaybackPlayState();
+ expect(playState).toBe('paused');
+
+ const opacityBefore = await effectsPage.getPlaybackOpacity();
+ await page.waitForTimeout(300);
+ const opacityAfter = await effectsPage.getPlaybackOpacity();
+
+ // Precision 1 (< 0.05 diff) — WebKit compositor can drift opacity slightly even when paused
+ expect(parseFloat(opacityBefore)).toBeCloseTo(parseFloat(opacityAfter), 1);
+ });
+
+ test('should resume from paused position when played again', async ({ page }) => {
+ await effectsPage.runPlayback();
+ await waitForElementAnimationState(page, EFFECTS_TARGET_IDS.playback, ['running']);
+ await page.waitForFunction(
+ (targetId) => {
+ const target = document.getElementById(targetId);
+ return target ? parseFloat(getComputedStyle(target).opacity) > 0.1 : false;
+ },
+ EFFECTS_TARGET_IDS.playback,
+ { timeout: 2000 },
+ );
+ await effectsPage.runPlaybackPause();
+
+ const opacityAtPause = await effectsPage.getPlaybackOpacity();
+
+ await effectsPage.runPlaybackResume();
+ const playState = await effectsPage.getPlaybackPlayState();
+ expect(['running', 'finished']).toContain(playState);
+
+ await waitForElementAnimationState(page, EFFECTS_TARGET_IDS.playback, ['finished'], 5000);
+
+ const opacityAfterFinish = await effectsPage.getPlaybackOpacity();
+ expect(parseFloat(opacityAfterFinish)).toBeGreaterThan(parseFloat(opacityAtPause));
+ });
+ });
+});
diff --git a/packages/motion/e2e/tests/pointer-animations.spec.ts b/packages/motion/e2e/tests/pointer-animations.spec.ts
new file mode 100644
index 0000000..7091973
--- /dev/null
+++ b/packages/motion/e2e/tests/pointer-animations.spec.ts
@@ -0,0 +1,87 @@
+import { test, expect } from '@playwright/test';
+import { PointerPage } from '../pages/pointer-page';
+import { POINTER_IDS, POINTER_SELECTORS } from '../constants/pointer';
+
+test.describe('Pointer-Driven Animations', () => {
+ let pointerPage: PointerPage;
+
+ test.beforeEach(async ({ page }) => {
+ pointerPage = new PointerPage(page);
+ await pointerPage.goto();
+ });
+
+ test.describe('Pointer Move Trigger', () => {
+ test('should drive x-axis animation based on horizontal position', async ({ page }) => {
+ await pointerPage.movePointerWithinElement(POINTER_SELECTORS.area, 0.05, 0.5);
+ const transformLeft = await page.evaluate(
+ (targetId) => getComputedStyle(document.getElementById(targetId)!).transform,
+ POINTER_IDS.xAxisTarget,
+ );
+
+ await pointerPage.movePointerWithinElement(POINTER_SELECTORS.area, 0.95, 0.5);
+ const transformRight = await page.evaluate(
+ (targetId) => getComputedStyle(document.getElementById(targetId)!).transform,
+ POINTER_IDS.xAxisTarget,
+ );
+
+ expect(transformLeft).not.toBe(transformRight);
+ });
+
+ test('should drive y-axis animation based on vertical position', async ({ page }) => {
+ await pointerPage.movePointerWithinElement(POINTER_SELECTORS.yAxisArea, 0.5, 0.05);
+ const transformTop = await page.evaluate(
+ (targetId) => getComputedStyle(document.getElementById(targetId)!).transform,
+ POINTER_IDS.yAxisTarget,
+ );
+
+ await pointerPage.movePointerWithinElement(POINTER_SELECTORS.yAxisArea, 0.5, 0.95);
+ const transformBottom = await page.evaluate(
+ (targetId) => getComputedStyle(document.getElementById(targetId)!).transform,
+ POINTER_IDS.yAxisTarget,
+ );
+
+ expect(transformTop).not.toBe(transformBottom);
+ });
+ });
+
+ test.describe('Composite Operations', () => {
+ test('should create two independent AnimationGroup instances on the same element', async ({
+ page,
+ }) => {
+ // Trigger progress on both groups so animations leave idle state and become visible to getAnimations()
+ await pointerPage.movePointerWithinElement(POINTER_SELECTORS.compositeArea, 0.5, 0.5);
+
+ const animationCount = await page.evaluate(
+ (targetId) => document.getElementById(targetId)?.getAnimations().length ?? 0,
+ POINTER_IDS.compositeTarget,
+ );
+
+ expect(animationCount).toBeGreaterThanOrEqual(2);
+ });
+
+ test('should respond to both x and y pointer axes independently', async ({ page }) => {
+ await pointerPage.movePointerWithinElement(POINTER_SELECTORS.compositeArea, 0.05, 0.05);
+ const transformAtOrigin = await page.evaluate(
+ (targetId) => getComputedStyle(document.getElementById(targetId)!).transform,
+ POINTER_IDS.compositeTarget,
+ );
+
+ await pointerPage.movePointerWithinElement(POINTER_SELECTORS.compositeArea, 0.95, 0.95);
+ const transformAtEnd = await page.evaluate(
+ (targetId) => getComputedStyle(document.getElementById(targetId)!).transform,
+ POINTER_IDS.compositeTarget,
+ );
+
+ expect(transformAtOrigin).not.toBe(transformAtEnd);
+ });
+ });
+
+ test.describe('Cleanup', () => {
+ test('should move AnimationGroup to idle state after cancel', async () => {
+ await pointerPage.cancelPointerScene();
+
+ const playState = await pointerPage.getPointerScenePlayState();
+ expect(playState).toBe('idle');
+ });
+ });
+});
diff --git a/packages/motion/e2e/tests/scroll-animations.spec.ts b/packages/motion/e2e/tests/scroll-animations.spec.ts
new file mode 100644
index 0000000..05fa19c
--- /dev/null
+++ b/packages/motion/e2e/tests/scroll-animations.spec.ts
@@ -0,0 +1,90 @@
+import { test, expect } from '@playwright/test';
+import { ScrollPage } from '../pages/scroll-page';
+import { waitForWindowPlayState } from '../utils/animation-helpers';
+import { SCROLL_SELECTORS } from '../constants/scroll';
+
+test.describe('Scroll-Driven Animations', () => {
+ let scrollPage: ScrollPage;
+
+ test.beforeEach(async ({ page }) => {
+ scrollPage = new ScrollPage(page);
+ await scrollPage.goto();
+ });
+
+ test.describe('View Progress Trigger', () => {
+ test('should animate based on scroll progress', async () => {
+ // Target starts below the fold (first section is 100vh spacer)
+ const initialProgress = await scrollPage.getScrollProgress();
+ expect(initialProgress).toBe(0);
+
+ // Scroll down to bring the target into view
+ await scrollPage.scrollElementIntoView(SCROLL_SELECTORS.viewProgressTarget);
+
+ const progressAfterScroll = await scrollPage.getScrollProgress();
+ expect(progressAfterScroll).toBeGreaterThan(0);
+ });
+
+ test('should respect rangeStart and rangeEnd boundaries', async () => {
+ // At the very top: target is below fold → progress is 0 (clamped)
+ const startProgress = await scrollPage.getScrollProgress();
+ expect(startProgress).toBe(0);
+
+ // Scroll past the target so it is above the viewport → progress reaches 1 (clamped)
+ await scrollPage.scrollElementIntoView(SCROLL_SELECTORS.scrubCard1);
+
+ const endProgress = await scrollPage.getScrollProgress();
+ expect(endProgress).toBe(1);
+ });
+
+ test('should update progress on scroll direction change', async () => {
+ // Scroll target into partial view
+ await scrollPage.scrollElementIntoView(SCROLL_SELECTORS.viewProgressTarget);
+ const progressDown = await scrollPage.getScrollProgress();
+ expect(progressDown).toBeGreaterThan(0);
+
+ // Scroll back toward top
+ await scrollPage.scrollTo(0);
+ const progressUp = await scrollPage.getScrollProgress();
+
+ // Progress decreases when scrolling back up
+ expect(progressUp).toBeLessThan(progressDown);
+ });
+ });
+
+ test.describe('Scrub Scene', () => {
+ test('should create scrub scene with correct range offsets', async () => {
+ const { config, sceneStart, sceneEnd } = await scrollPage.getRangeOffsets();
+
+ // The configured offsets should always be accessible
+ expect(config.startOffset.name).toBe('entry');
+ expect(config.endOffset.name).toBe('exit');
+
+ // In the non-native-ViewTimeline fallback path, offsets are also readable
+ // directly from the scene object (scene.start / scene.end)
+ if (sceneStart !== null) {
+ expect(sceneStart.name).toBe('entry');
+ expect(sceneEnd?.name).toBe('exit');
+ }
+ });
+
+ test('should report accurate progress percentage', async () => {
+ // Scroll to bring target partially into view
+ await scrollPage.scrollElementIntoView(SCROLL_SELECTORS.viewProgressTarget);
+ await scrollPage.scrollTo((await scrollPage.getScrollY()) - 100);
+
+ const progress = await scrollPage.getScrollProgress();
+ expect(progress).toBeGreaterThan(0);
+ expect(progress).toBeLessThanOrEqual(1);
+ });
+
+ test('should handle destroy cleanup properly', async ({ page }) => {
+ await scrollPage.cancelScrubScene();
+
+ // In this progress-driven path, browsers can settle on either paused or idle after cancel.
+ await waitForWindowPlayState(page, 'scrubScene', ['idle', 'paused'], 5000);
+
+ const playState = await scrollPage.getScrubScenePlayState();
+ expect(['idle', 'paused']).toContain(playState);
+ });
+ });
+});
diff --git a/packages/motion/e2e/types.ts b/packages/motion/e2e/types.ts
new file mode 100644
index 0000000..1176c3e
--- /dev/null
+++ b/packages/motion/e2e/types.ts
@@ -0,0 +1,9 @@
+export type CssAnimationData = ReturnType<
+ (typeof import('@wix/motion'))['getCSSAnimation']
+>[number];
+
+export type CustomEffectLogEntry = {
+ elementId: string | null;
+ tagName: string | null;
+ progress: number | null;
+};
diff --git a/packages/motion/e2e/utils/animation-helpers.ts b/packages/motion/e2e/utils/animation-helpers.ts
new file mode 100644
index 0000000..bf7b8b3
--- /dev/null
+++ b/packages/motion/e2e/utils/animation-helpers.ts
@@ -0,0 +1,87 @@
+import type { Page } from '@playwright/test';
+
+/**
+ * Wait until a window-exposed AnimationGroup's playState matches one of the target states.
+ * Covers the common pattern of polling `(window as any)[key].playState`.
+ */
+export async function waitForWindowPlayState(
+ page: Page,
+ windowKey: string,
+ states: string[],
+ timeout = 2000,
+): Promise {
+ await page.waitForFunction(
+ ({ key, targetStates }) => {
+ const obj = (window as unknown as Record)[key];
+ return targetStates.includes(obj?.playState ?? '');
+ },
+ { key: windowKey, targetStates: states },
+ { timeout },
+ );
+}
+
+/**
+ * Wait until a DOM element's first WAAPI animation reaches one of the target playStates.
+ */
+export async function waitForElementAnimationState(
+ page: Page,
+ elementId: string,
+ states: string[],
+ timeout = 2000,
+): Promise {
+ try {
+ await page.waitForFunction(
+ ({ id, targetStates }) => {
+ const el = document.getElementById(id);
+ const state = el?.getAnimations()[0]?.playState;
+ return targetStates.includes(state ?? '');
+ },
+ { id: elementId, targetStates: states },
+ { timeout },
+ );
+ } catch (error) {
+ const debug = await page.evaluate((id) => {
+ const el = document.getElementById(id);
+ return {
+ exists: !!el,
+ animationCount: el?.getAnimations().length ?? 0,
+ playState: el?.getAnimations()[0]?.playState ?? 'none',
+ };
+ }, elementId);
+
+ throw new Error(
+ [
+ `Timed out waiting for #${elementId} animation state.`,
+ `Expected: [${states.join(', ')}]`,
+ `Actual: ${debug.playState}`,
+ `Element exists: ${debug.exists}`,
+ `Animation count: ${debug.animationCount}`,
+ `Original error: ${String(error)}`,
+ ].join('\n'),
+ );
+ }
+}
+
+/** Return the playState of a DOM element's first WAAPI animation (or 'none'). */
+export function getElementAnimationPlayState(page: Page, elementId: string): Promise {
+ return page.evaluate(
+ (id) => document.getElementById(id)?.getAnimations()[0]?.playState ?? 'none',
+ elementId,
+ );
+}
+
+/** Wait until the first element matching `selector` has at least one active animation. */
+export async function waitForElementAnimation(
+ page: Page,
+ selector: string,
+ timeout = 2000,
+): Promise {
+ await page.waitForFunction(
+ (sel) => {
+ const el = document.querySelectorAll(sel)[0];
+ return el ? el.getAnimations().length > 0 : false;
+ },
+ selector,
+ { timeout },
+ );
+}
diff --git a/packages/motion/e2e/utils/pointer-helpers.ts b/packages/motion/e2e/utils/pointer-helpers.ts
new file mode 100644
index 0000000..9cefdb3
--- /dev/null
+++ b/packages/motion/e2e/utils/pointer-helpers.ts
@@ -0,0 +1,28 @@
+import type { Page } from '@playwright/test';
+
+/**
+ * Move the mouse to a position expressed as a ratio (0–1) within
+ * the bounding rect of the given container element.
+ */
+export async function movePointerWithinElement(
+ page: Page,
+ containerSelector: string,
+ ratioX: number,
+ ratioY: number,
+): Promise {
+ // Ensure the element is in the viewport before interacting
+ await page.locator(containerSelector).scrollIntoViewIfNeeded();
+
+ const rect = await page.evaluate((sel) => {
+ const el = document.querySelector(sel);
+ if (!el) throw new Error(`Element not found: ${sel}`);
+ const { left, top, width, height } = el.getBoundingClientRect();
+ return { left, top, width, height };
+ }, containerSelector);
+
+ const x = rect.left + rect.width * ratioX;
+ const y = rect.top + rect.height * ratioY;
+ await page.mouse.move(x, y);
+ // Allow pointermove handlers to settle
+ await new Promise((r) => setTimeout(r, 50));
+}
diff --git a/packages/motion/e2e/utils/scroll-helpers.ts b/packages/motion/e2e/utils/scroll-helpers.ts
new file mode 100644
index 0000000..b5e7ae2
--- /dev/null
+++ b/packages/motion/e2e/utils/scroll-helpers.ts
@@ -0,0 +1,27 @@
+import type { Page } from '@playwright/test';
+
+/** Scroll the window to an absolute Y position. */
+export async function scrollTo(page: Page, y: number): Promise {
+ await page.evaluate((scrollY) => window.scrollTo({ top: scrollY }), y);
+ await new Promise((r) => setTimeout(r, 100));
+}
+
+/** Scroll until the given element is fully in the viewport. */
+export async function scrollElementIntoView(page: Page, selector: string): Promise {
+ await page.evaluate((sel) => {
+ document.querySelector(sel)?.scrollIntoView({ block: 'center' });
+ }, selector);
+ await new Promise((r) => setTimeout(r, 100));
+}
+
+/** Return the element's scroll progress exposed on window by the fixture. */
+export function getScrollProgress(page: Page): Promise {
+ return page.evaluate(() =>
+ (window as unknown as { getScrollProgress(): number }).getScrollProgress(),
+ );
+}
+
+/** Return the current window scroll position. */
+export function getScrollY(page: Page): Promise {
+ return page.evaluate(() => window.scrollY);
+}
diff --git a/packages/motion/package.json b/packages/motion/package.json
index 58b197a..9526963 100644
--- a/packages/motion/package.json
+++ b/packages/motion/package.json
@@ -24,7 +24,12 @@
"build:types": "tsc -p tsconfig.build.json",
"lint": "tsc -p tsconfig.build.json --noEmit",
"test": "vitest run",
- "coverage": "vitest run --coverage"
+ "coverage": "vitest run --coverage",
+ "test:e2e": "playwright test",
+ "test:e2e:ui": "playwright test --ui",
+ "test:e2e:debug": "playwright test --headed",
+ "test:e2e:report": "playwright test --reporter=html",
+ "test:e2e:fixtures": "vite --config e2e/fixtures/vite.config.ts"
},
"keywords": [
"animation",
@@ -53,6 +58,7 @@
"fastdom": "^1.0.11"
},
"devDependencies": {
+ "@playwright/test": "^1.58.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitest/coverage-v8": "^4.0.14",
diff --git a/packages/motion/playwright.config.ts b/packages/motion/playwright.config.ts
new file mode 100644
index 0000000..a03f6bf
--- /dev/null
+++ b/packages/motion/playwright.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ testDir: 'e2e/tests',
+ fullyParallel: true,
+ forbidOnly: !!process.env.CI,
+ retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : undefined,
+ reporter: [['list']],
+
+ use: {
+ baseURL: 'http://localhost:5174',
+ trace: 'off',
+ screenshot: 'off',
+ video: 'off',
+ },
+
+ projects: [
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
+ { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
+ { name: 'webkit', use: { ...devices['Desktop Safari'] } },
+ ],
+
+ webServer: {
+ command: 'yarn test:e2e:fixtures',
+ url: 'http://localhost:5174',
+ reuseExistingServer: !process.env.CI,
+ timeout: 60_000,
+ },
+});
diff --git a/packages/motion/vitest.config.ts b/packages/motion/vitest.config.ts
index c21f67c..2f0ea5d 100644
--- a/packages/motion/vitest.config.ts
+++ b/packages/motion/vitest.config.ts
@@ -6,6 +6,6 @@ export default defineConfig({
test: {
environment: 'jsdom',
setupFiles: [],
- exclude: ['dist/*'],
+ exclude: ['dist/*', 'e2e/**'],
},
});
diff --git a/yarn.lock b/yarn.lock
index c104a8d..e665b52 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -18,57 +18,57 @@ __metadata:
languageName: node
linkType: hard
-"@babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.28.6":
- version: 7.28.6
- resolution: "@babel/code-frame@npm:7.28.6"
+"@babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0":
+ version: 7.29.0
+ resolution: "@babel/code-frame@npm:7.29.0"
dependencies:
"@babel/helper-validator-identifier": "npm:^7.28.5"
js-tokens: "npm:^4.0.0"
picocolors: "npm:^1.1.1"
- checksum: 10/93e7ed9e039e3cb661bdb97c26feebafacc6ec13d745881dae5c7e2708f579475daebe7a3b5d23b183bb940b30744f52f4a5bcb65b4df03b79d82fcb38495784
+ checksum: 10/199e15ff89007dd30675655eec52481cb245c9fdf4f81e4dc1f866603b0217b57aff25f5ffa0a95bbc8e31eb861695330cd7869ad52cc211aa63016320ef72c5
languageName: node
linkType: hard
"@babel/compat-data@npm:^7.28.6":
- version: 7.28.6
- resolution: "@babel/compat-data@npm:7.28.6"
- checksum: 10/dc17dfb55711a15f006e34c4610c49b7335fc11b23e192f9e5f625e8ea0f48805e61a57b6b4f5550879332782c93af0b5d6952825fffbb8d4e604b14d698249f
+ version: 7.29.0
+ resolution: "@babel/compat-data@npm:7.29.0"
+ checksum: 10/7f21beedb930ed8fbf7eabafc60e6e6521c1d905646bf1317a61b2163339157fe797efeb85962bf55136e166b01fd1a6b526a15974b92a8b877d564dcb6c9580
languageName: node
linkType: hard
"@babel/core@npm:^7.24.4, @babel/core@npm:^7.28.0":
- version: 7.28.6
- resolution: "@babel/core@npm:7.28.6"
+ version: 7.29.0
+ resolution: "@babel/core@npm:7.29.0"
dependencies:
- "@babel/code-frame": "npm:^7.28.6"
- "@babel/generator": "npm:^7.28.6"
+ "@babel/code-frame": "npm:^7.29.0"
+ "@babel/generator": "npm:^7.29.0"
"@babel/helper-compilation-targets": "npm:^7.28.6"
"@babel/helper-module-transforms": "npm:^7.28.6"
"@babel/helpers": "npm:^7.28.6"
- "@babel/parser": "npm:^7.28.6"
+ "@babel/parser": "npm:^7.29.0"
"@babel/template": "npm:^7.28.6"
- "@babel/traverse": "npm:^7.28.6"
- "@babel/types": "npm:^7.28.6"
+ "@babel/traverse": "npm:^7.29.0"
+ "@babel/types": "npm:^7.29.0"
"@jridgewell/remapping": "npm:^2.3.5"
convert-source-map: "npm:^2.0.0"
debug: "npm:^4.1.0"
gensync: "npm:^1.0.0-beta.2"
json5: "npm:^2.2.3"
semver: "npm:^6.3.1"
- checksum: 10/1a150a69c547daf13c457be1fdaf1a0935d02b94605e777e049537ec2f279b4bb442ffbe1c2d8ff62c688878b1d5530a5784daf72ece950d1917fb78717f51d2
+ checksum: 10/25f4e91688cdfbaf1365831f4f245b436cdaabe63d59389b75752013b8d61819ee4257101b52fc328b0546159fd7d0e74457ed7cf12c365fea54be4fb0a40229
languageName: node
linkType: hard
-"@babel/generator@npm:^7.28.6":
- version: 7.28.6
- resolution: "@babel/generator@npm:7.28.6"
+"@babel/generator@npm:^7.29.0":
+ version: 7.29.1
+ resolution: "@babel/generator@npm:7.29.1"
dependencies:
- "@babel/parser": "npm:^7.28.6"
- "@babel/types": "npm:^7.28.6"
+ "@babel/parser": "npm:^7.29.0"
+ "@babel/types": "npm:^7.29.0"
"@jridgewell/gen-mapping": "npm:^0.3.12"
"@jridgewell/trace-mapping": "npm:^0.3.28"
jsesc: "npm:^3.0.2"
- checksum: 10/ef2af927e8e0985d02ec4321a242da761a934e927539147c59fdd544034dc7f0e9846f6bf86209aca7a28aee2243ed0fad668adccd48f96d7d6866215173f9af
+ checksum: 10/61fe4ddd6e817aa312a14963ccdbb5c9a8c57e8b97b98d19a8a99ccab2215fda1a5f52bc8dd8d2e3c064497ddeb3ab8ceb55c76fa0f58f8169c34679d2256fe0
languageName: node
linkType: hard
@@ -153,14 +153,14 @@ __metadata:
languageName: node
linkType: hard
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.28.6":
- version: 7.28.6
- resolution: "@babel/parser@npm:7.28.6"
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0":
+ version: 7.29.0
+ resolution: "@babel/parser@npm:7.29.0"
dependencies:
- "@babel/types": "npm:^7.28.6"
+ "@babel/types": "npm:^7.29.0"
bin:
parser: ./bin/babel-parser.js
- checksum: 10/483a6fb5f9876ec9cbbb98816f2c94f39ae4d1158d35f87e1c4bf19a1f56027c96a1a3962ff0c8c46e8322a6d9e1c80d26b7f9668410df13d5b5769d9447b010
+ checksum: 10/b1576dca41074997a33ee740d87b330ae2e647f4b7da9e8d2abd3772b18385d303b0cee962b9b88425e0f30d58358dbb8d63792c1a2d005c823d335f6a029747
languageName: node
linkType: hard
@@ -204,28 +204,28 @@ __metadata:
languageName: node
linkType: hard
-"@babel/traverse@npm:^7.28.6":
- version: 7.28.6
- resolution: "@babel/traverse@npm:7.28.6"
+"@babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0":
+ version: 7.29.0
+ resolution: "@babel/traverse@npm:7.29.0"
dependencies:
- "@babel/code-frame": "npm:^7.28.6"
- "@babel/generator": "npm:^7.28.6"
+ "@babel/code-frame": "npm:^7.29.0"
+ "@babel/generator": "npm:^7.29.0"
"@babel/helper-globals": "npm:^7.28.0"
- "@babel/parser": "npm:^7.28.6"
+ "@babel/parser": "npm:^7.29.0"
"@babel/template": "npm:^7.28.6"
- "@babel/types": "npm:^7.28.6"
+ "@babel/types": "npm:^7.29.0"
debug: "npm:^4.3.1"
- checksum: 10/dd71efe9412433169b805d5c346a6473e539ce30f605752a0d40a0733feba37259bd72bb4ad2ab591e2eaff1ee56633de160c1e98efdc8f373cf33a4a8660275
+ checksum: 10/3a0d0438f1ba9fed4fbe1706ea598a865f9af655a16ca9517ab57bda526e224569ca1b980b473fb68feea5e08deafbbf2cf9febb941f92f2d2533310c3fc4abc
languageName: node
linkType: hard
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6":
- version: 7.28.6
- resolution: "@babel/types@npm:7.28.6"
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0":
+ version: 7.29.0
+ resolution: "@babel/types@npm:7.29.0"
dependencies:
"@babel/helper-string-parser": "npm:^7.27.1"
"@babel/helper-validator-identifier": "npm:^7.28.5"
- checksum: 10/f9c6e52b451065aae5654686ecfc7de2d27dd0fbbc204ee2bd912a71daa359521a32f378981b1cf333ace6c8f86928814452cb9f388a7da59ad468038deb6b5f
+ checksum: 10/bfc2b211210f3894dcd7e6a33b2d1c32c93495dc1e36b547376aa33441abe551ab4bc1640d4154ee2acd8e46d3bbc925c7224caae02fcaf0e6a771e97fccc661
languageName: node
linkType: hard
@@ -282,184 +282,184 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/aix-ppc64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/aix-ppc64@npm:0.27.2"
+"@esbuild/aix-ppc64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/aix-ppc64@npm:0.27.3"
conditions: os=aix & cpu=ppc64
languageName: node
linkType: hard
-"@esbuild/android-arm64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/android-arm64@npm:0.27.2"
+"@esbuild/android-arm64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/android-arm64@npm:0.27.3"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/android-arm@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/android-arm@npm:0.27.2"
+"@esbuild/android-arm@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/android-arm@npm:0.27.3"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
-"@esbuild/android-x64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/android-x64@npm:0.27.2"
+"@esbuild/android-x64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/android-x64@npm:0.27.3"
conditions: os=android & cpu=x64
languageName: node
linkType: hard
-"@esbuild/darwin-arm64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/darwin-arm64@npm:0.27.2"
+"@esbuild/darwin-arm64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/darwin-arm64@npm:0.27.3"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/darwin-x64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/darwin-x64@npm:0.27.2"
+"@esbuild/darwin-x64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/darwin-x64@npm:0.27.3"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@esbuild/freebsd-arm64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/freebsd-arm64@npm:0.27.2"
+"@esbuild/freebsd-arm64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/freebsd-arm64@npm:0.27.3"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/freebsd-x64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/freebsd-x64@npm:0.27.2"
+"@esbuild/freebsd-x64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/freebsd-x64@npm:0.27.3"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/linux-arm64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-arm64@npm:0.27.2"
+"@esbuild/linux-arm64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-arm64@npm:0.27.3"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/linux-arm@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-arm@npm:0.27.2"
+"@esbuild/linux-arm@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-arm@npm:0.27.3"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@esbuild/linux-ia32@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-ia32@npm:0.27.2"
+"@esbuild/linux-ia32@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-ia32@npm:0.27.3"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard
-"@esbuild/linux-loong64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-loong64@npm:0.27.2"
+"@esbuild/linux-loong64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-loong64@npm:0.27.3"
conditions: os=linux & cpu=loong64
languageName: node
linkType: hard
-"@esbuild/linux-mips64el@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-mips64el@npm:0.27.2"
+"@esbuild/linux-mips64el@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-mips64el@npm:0.27.3"
conditions: os=linux & cpu=mips64el
languageName: node
linkType: hard
-"@esbuild/linux-ppc64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-ppc64@npm:0.27.2"
+"@esbuild/linux-ppc64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-ppc64@npm:0.27.3"
conditions: os=linux & cpu=ppc64
languageName: node
linkType: hard
-"@esbuild/linux-riscv64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-riscv64@npm:0.27.2"
+"@esbuild/linux-riscv64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-riscv64@npm:0.27.3"
conditions: os=linux & cpu=riscv64
languageName: node
linkType: hard
-"@esbuild/linux-s390x@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-s390x@npm:0.27.2"
+"@esbuild/linux-s390x@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-s390x@npm:0.27.3"
conditions: os=linux & cpu=s390x
languageName: node
linkType: hard
-"@esbuild/linux-x64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/linux-x64@npm:0.27.2"
+"@esbuild/linux-x64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/linux-x64@npm:0.27.3"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
-"@esbuild/netbsd-arm64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/netbsd-arm64@npm:0.27.2"
+"@esbuild/netbsd-arm64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/netbsd-arm64@npm:0.27.3"
conditions: os=netbsd & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/netbsd-x64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/netbsd-x64@npm:0.27.2"
+"@esbuild/netbsd-x64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/netbsd-x64@npm:0.27.3"
conditions: os=netbsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/openbsd-arm64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/openbsd-arm64@npm:0.27.2"
+"@esbuild/openbsd-arm64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/openbsd-arm64@npm:0.27.3"
conditions: os=openbsd & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/openbsd-x64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/openbsd-x64@npm:0.27.2"
+"@esbuild/openbsd-x64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/openbsd-x64@npm:0.27.3"
conditions: os=openbsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/openharmony-arm64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/openharmony-arm64@npm:0.27.2"
+"@esbuild/openharmony-arm64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/openharmony-arm64@npm:0.27.3"
conditions: os=openharmony & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/sunos-x64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/sunos-x64@npm:0.27.2"
+"@esbuild/sunos-x64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/sunos-x64@npm:0.27.3"
conditions: os=sunos & cpu=x64
languageName: node
linkType: hard
-"@esbuild/win32-arm64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/win32-arm64@npm:0.27.2"
+"@esbuild/win32-arm64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/win32-arm64@npm:0.27.3"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/win32-ia32@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/win32-ia32@npm:0.27.2"
+"@esbuild/win32-ia32@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/win32-ia32@npm:0.27.3"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@esbuild/win32-x64@npm:0.27.2":
- version: 0.27.2
- resolution: "@esbuild/win32-x64@npm:0.27.2"
+"@esbuild/win32-x64@npm:0.27.3":
+ version: 0.27.3
+ resolution: "@esbuild/win32-x64@npm:0.27.3"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -583,22 +583,6 @@ __metadata:
languageName: node
linkType: hard
-"@isaacs/balanced-match@npm:^4.0.1":
- version: 4.0.1
- resolution: "@isaacs/balanced-match@npm:4.0.1"
- checksum: 10/102fbc6d2c0d5edf8f6dbf2b3feb21695a21bc850f11bc47c4f06aa83bd8884fde3fe9d6d797d619901d96865fdcb4569ac2a54c937992c48885c5e3d9967fe8
- languageName: node
- linkType: hard
-
-"@isaacs/brace-expansion@npm:^5.0.0":
- version: 5.0.1
- resolution: "@isaacs/brace-expansion@npm:5.0.1"
- dependencies:
- "@isaacs/balanced-match": "npm:^4.0.1"
- checksum: 10/aec226065bc4285436a27379e08cc35bf94ef59f5098ac1c026495c9ba4ab33d851964082d3648d56d63eb90f2642867bd15a3e1b810b98beb1a8c14efce6a94
- languageName: node
- linkType: hard
-
"@isaacs/fs-minipass@npm:^4.0.0":
version: 4.0.1
resolution: "@isaacs/fs-minipass@npm:4.0.1"
@@ -674,6 +658,17 @@ __metadata:
languageName: node
linkType: hard
+"@playwright/test@npm:^1.58.2":
+ version: 1.58.2
+ resolution: "@playwright/test@npm:1.58.2"
+ dependencies:
+ playwright: "npm:1.58.2"
+ bin:
+ playwright: cli.js
+ checksum: 10/58bf90139280a0235eeeb6049e9fb4db6425e98be1bf0cc17913b068eef616cf67be57bfb36dc4cb56bcf116f498ffd0225c4916e85db404b343ea6c5efdae13
+ languageName: node
+ linkType: hard
+
"@rolldown/pluginutils@npm:1.0.0-beta.27":
version: 1.0.0-beta.27
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27"
@@ -681,177 +676,177 @@ __metadata:
languageName: node
linkType: hard
-"@rollup/rollup-android-arm-eabi@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-android-arm-eabi@npm:4.55.3"
+"@rollup/rollup-android-arm-eabi@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-android-arm-eabi@npm:4.59.0"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
-"@rollup/rollup-android-arm64@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-android-arm64@npm:4.55.3"
+"@rollup/rollup-android-arm64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-android-arm64@npm:4.59.0"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-darwin-arm64@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-darwin-arm64@npm:4.55.3"
+"@rollup/rollup-darwin-arm64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-darwin-arm64@npm:4.59.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-darwin-x64@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-darwin-x64@npm:4.55.3"
+"@rollup/rollup-darwin-x64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-darwin-x64@npm:4.59.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@rollup/rollup-freebsd-arm64@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-freebsd-arm64@npm:4.55.3"
+"@rollup/rollup-freebsd-arm64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-freebsd-arm64@npm:4.59.0"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-freebsd-x64@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-freebsd-x64@npm:4.55.3"
+"@rollup/rollup-freebsd-x64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-freebsd-x64@npm:4.59.0"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm-gnueabihf@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.55.3"
+"@rollup/rollup-linux-arm-gnueabihf@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.59.0"
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm-musleabihf@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.55.3"
+"@rollup/rollup-linux-arm-musleabihf@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.59.0"
conditions: os=linux & cpu=arm & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm64-gnu@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.55.3"
+"@rollup/rollup-linux-arm64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.59.0"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-arm64-musl@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-arm64-musl@npm:4.55.3"
+"@rollup/rollup-linux-arm64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-arm64-musl@npm:4.59.0"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-linux-loong64-gnu@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.55.3"
+"@rollup/rollup-linux-loong64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.59.0"
conditions: os=linux & cpu=loong64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-loong64-musl@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-loong64-musl@npm:4.55.3"
+"@rollup/rollup-linux-loong64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-loong64-musl@npm:4.59.0"
conditions: os=linux & cpu=loong64 & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-linux-ppc64-gnu@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.55.3"
+"@rollup/rollup-linux-ppc64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.59.0"
conditions: os=linux & cpu=ppc64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-ppc64-musl@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.55.3"
+"@rollup/rollup-linux-ppc64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.59.0"
conditions: os=linux & cpu=ppc64 & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-linux-riscv64-gnu@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.55.3"
+"@rollup/rollup-linux-riscv64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.59.0"
conditions: os=linux & cpu=riscv64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-riscv64-musl@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.55.3"
+"@rollup/rollup-linux-riscv64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.59.0"
conditions: os=linux & cpu=riscv64 & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-linux-s390x-gnu@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.55.3"
+"@rollup/rollup-linux-s390x-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.59.0"
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-x64-gnu@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-x64-gnu@npm:4.55.3"
+"@rollup/rollup-linux-x64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-x64-gnu@npm:4.59.0"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@rollup/rollup-linux-x64-musl@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-linux-x64-musl@npm:4.55.3"
+"@rollup/rollup-linux-x64-musl@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-linux-x64-musl@npm:4.59.0"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@rollup/rollup-openbsd-x64@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-openbsd-x64@npm:4.55.3"
+"@rollup/rollup-openbsd-x64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-openbsd-x64@npm:4.59.0"
conditions: os=openbsd & cpu=x64
languageName: node
linkType: hard
-"@rollup/rollup-openharmony-arm64@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-openharmony-arm64@npm:4.55.3"
+"@rollup/rollup-openharmony-arm64@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-openharmony-arm64@npm:4.59.0"
conditions: os=openharmony & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-win32-arm64-msvc@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.55.3"
+"@rollup/rollup-win32-arm64-msvc@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.59.0"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@rollup/rollup-win32-ia32-msvc@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.55.3"
+"@rollup/rollup-win32-ia32-msvc@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.59.0"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@rollup/rollup-win32-x64-gnu@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-win32-x64-gnu@npm:4.55.3"
+"@rollup/rollup-win32-x64-gnu@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-win32-x64-gnu@npm:4.59.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"@rollup/rollup-win32-x64-msvc@npm:4.55.3":
- version: 4.55.3
- resolution: "@rollup/rollup-win32-x64-msvc@npm:4.55.3"
+"@rollup/rollup-win32-x64-msvc@npm:4.59.0":
+ version: 4.59.0
+ resolution: "@rollup/rollup-win32-x64-msvc@npm:4.59.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -1038,12 +1033,12 @@ __metadata:
linkType: hard
"@types/react@npm:^18.3.2, @types/react@npm:^18.3.3":
- version: 18.3.27
- resolution: "@types/react@npm:18.3.27"
+ version: 18.3.28
+ resolution: "@types/react@npm:18.3.28"
dependencies:
"@types/prop-types": "npm:*"
csstype: "npm:^3.2.2"
- checksum: 10/90155820a2af315cad1ff47df695f3f2f568c12ad641a7805746a6a9a9aa6c40b1374e819e50d39afe0e375a6b9160a73176cbdb4e09807262bc6fcdc06e67db
+ checksum: 10/6db7bb7f19957ee9f530baa7d143527f8befedad1585b064eb80777be0d84621157de75aba4f499ff429b10c5ef0c7d13e89be6bca3296ef71c80472894ff0eb
languageName: node
linkType: hard
@@ -1130,11 +1125,11 @@ __metadata:
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:^8.51.0":
- version: 8.54.0
- resolution: "@typescript-eslint/tsconfig-utils@npm:8.54.0"
+ version: 8.56.0
+ resolution: "@typescript-eslint/tsconfig-utils@npm:8.56.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
- checksum: 10/e9d6b29538716f007919bfcee94f09b7f8e7d2b684ad43d1a3c8d43afb9f0539c7707f84a34f42054e31c8c056b0ccf06575d89e860b4d34632ffefaefafe1fc
+ checksum: 10/b1834aeffcdc07835eae0bf52aca573cba7e6528b5c1483e9b1f7f4f9e1f6450a8650796be11140e0437caf7eb1b0f9711c22989c8294547534f12614a759760
languageName: node
linkType: hard
@@ -1162,9 +1157,9 @@ __metadata:
linkType: hard
"@typescript-eslint/types@npm:^8.51.0":
- version: 8.54.0
- resolution: "@typescript-eslint/types@npm:8.54.0"
- checksum: 10/c25cc0bdf90fb150cf6ce498897f43fe3adf9e872562159118f34bd91a9bfab5f720cb1a41f3cdf253b2e840145d7d372089b7cef5156624ef31e98d34f91b31
+ version: 8.56.0
+ resolution: "@typescript-eslint/types@npm:8.56.0"
+ checksum: 10/d7549535c99d9202742bf0191bcc2822c2d18a03e206be9ad5a6f6b0902de7381c93e8c238754fe5d1dfdcc22d7e3bbafa032f63ba165d6dc03e180f84b138f9
languageName: node
linkType: hard
@@ -1236,11 +1231,11 @@ __metadata:
linkType: hard
"@vitest/coverage-v8@npm:^4.0.14":
- version: 4.0.17
- resolution: "@vitest/coverage-v8@npm:4.0.17"
+ version: 4.0.18
+ resolution: "@vitest/coverage-v8@npm:4.0.18"
dependencies:
"@bcoe/v8-coverage": "npm:^1.0.2"
- "@vitest/utils": "npm:4.0.17"
+ "@vitest/utils": "npm:4.0.18"
ast-v8-to-istanbul: "npm:^0.3.10"
istanbul-lib-coverage: "npm:^3.2.2"
istanbul-lib-report: "npm:^3.0.1"
@@ -1250,34 +1245,34 @@ __metadata:
std-env: "npm:^3.10.0"
tinyrainbow: "npm:^3.0.3"
peerDependencies:
- "@vitest/browser": 4.0.17
- vitest: 4.0.17
+ "@vitest/browser": 4.0.18
+ vitest: 4.0.18
peerDependenciesMeta:
"@vitest/browser":
optional: true
- checksum: 10/aab6340670dbf42a5bf4a28b49a4d4c8819e842edac45567bae50af27b9e89264406945e57dd115b833190a6c25ba8f716c2eabaa23d2e249a185e3acc97ec1a
+ checksum: 10/33bd54aa8ea1c4b0acae77722b34460408325793d4d74159f7d73aedf2d1c4aa940c8666baf31c4b19b3760d68bbc268dd8c9265ebc2088cece428d26568afb4
languageName: node
linkType: hard
-"@vitest/expect@npm:4.0.17":
- version: 4.0.17
- resolution: "@vitest/expect@npm:4.0.17"
+"@vitest/expect@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/expect@npm:4.0.18"
dependencies:
"@standard-schema/spec": "npm:^1.0.0"
"@types/chai": "npm:^5.2.2"
- "@vitest/spy": "npm:4.0.17"
- "@vitest/utils": "npm:4.0.17"
+ "@vitest/spy": "npm:4.0.18"
+ "@vitest/utils": "npm:4.0.18"
chai: "npm:^6.2.1"
tinyrainbow: "npm:^3.0.3"
- checksum: 10/f260fefea527aae652be8d71ff188d45f958b7299a4577d1c3ed15bc87e6b20a6abb30ec6419c826259863d8bdbc1122e82cc499fb9eb63aaa43d3a5be1b7f76
+ checksum: 10/2115bff1bbcad460ce72032022e4dbcf8572c4b0fe07ca60f5644a8d96dd0dfa112986b5a1a5c5705f4548119b3b829c45d1de0838879211e0d6bb276b4ece73
languageName: node
linkType: hard
-"@vitest/mocker@npm:4.0.17":
- version: 4.0.17
- resolution: "@vitest/mocker@npm:4.0.17"
+"@vitest/mocker@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/mocker@npm:4.0.18"
dependencies:
- "@vitest/spy": "npm:4.0.17"
+ "@vitest/spy": "npm:4.0.18"
estree-walker: "npm:^3.0.3"
magic-string: "npm:^0.30.21"
peerDependencies:
@@ -1288,54 +1283,54 @@ __metadata:
optional: true
vite:
optional: true
- checksum: 10/4d938c298dd7e63d23efc56a81e254a8a453b0157b378d4b7af57a40dd2687c24a0e1f2e2499f8d17fe302e6d6d515e67c6a5fbfbff75dee2cfd51c37cf4c7dc
+ checksum: 10/46f584a4c1180dfb513137bc8db6e2e3b53e141adfe964307297e98321652d86a3f2a52d80cda1f810205bd5fdcab789bb8b52a532e68f175ef1e20be398218d
languageName: node
linkType: hard
-"@vitest/pretty-format@npm:4.0.17":
- version: 4.0.17
- resolution: "@vitest/pretty-format@npm:4.0.17"
+"@vitest/pretty-format@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/pretty-format@npm:4.0.18"
dependencies:
tinyrainbow: "npm:^3.0.3"
- checksum: 10/e50925f44168b8108a5094e44fd739b7183457c101eb020e88b5556a2f857808d0c9d045113aec83815a20d4aaaf9b7a522a1c651ce111de18daa686891b37a0
+ checksum: 10/4cafc7c9853097345bd94e8761bf47c2c04e00d366ac56d79928182787ff83c512c96f1dc2ce9b6aeed4d3a8c23ce12254da203783108d3c096bc398eed2a62d
languageName: node
linkType: hard
-"@vitest/runner@npm:4.0.17":
- version: 4.0.17
- resolution: "@vitest/runner@npm:4.0.17"
+"@vitest/runner@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/runner@npm:4.0.18"
dependencies:
- "@vitest/utils": "npm:4.0.17"
+ "@vitest/utils": "npm:4.0.18"
pathe: "npm:^2.0.3"
- checksum: 10/75c62ac09b506d2707baad72c9a8ca6addb9bb179548d9ec9af3f7f2303b2e03f4001480c9657325718b15f2997fc39168c027d8d88794c0f8c04800c640c055
+ checksum: 10/d7deebf086d7e084f449733ecea6c9c81737a18aafece318cbe7500e45debea00fa9dbf9315fd38aa88550dd5240a791b885ac71665f89b154d71a6c63da5836
languageName: node
linkType: hard
-"@vitest/snapshot@npm:4.0.17":
- version: 4.0.17
- resolution: "@vitest/snapshot@npm:4.0.17"
+"@vitest/snapshot@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/snapshot@npm:4.0.18"
dependencies:
- "@vitest/pretty-format": "npm:4.0.17"
+ "@vitest/pretty-format": "npm:4.0.18"
magic-string: "npm:^0.30.21"
pathe: "npm:^2.0.3"
- checksum: 10/0cda8970f484bdc5777347cc317f020dc7773ddf0cea996ab5fff453966310c64e9a97854b04998cf0635e8118c12e2235c7a5f921fdfc288dc63dc27c3116d8
+ checksum: 10/50aa5fb7fca45c499c145cc2f20e53b8afb0990b53ff4a4e6447dd6f147437edc5316f22e2d82119e154c3cf7c59d44898e7b2faf7ba614ac1051cbe4d662a77
languageName: node
linkType: hard
-"@vitest/spy@npm:4.0.17":
- version: 4.0.17
- resolution: "@vitest/spy@npm:4.0.17"
- checksum: 10/23313980c512b00c08a1c64f6ed15dc7c295bb7b09feab571a3cc96536de2f07432109256717f9deb7f1b8c9ba9ac28f7e617cf639654bc564f6ea5a341ad8f4
+"@vitest/spy@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/spy@npm:4.0.18"
+ checksum: 10/f7b1618ae13790105771dd2a8c973c63c018366fcc69b50f15ce5d12f9ac552efd3c1e6e5ae4ebdb6023d0b8d8f31fef2a0b1b77334284928db45c80c63de456
languageName: node
linkType: hard
-"@vitest/utils@npm:4.0.17":
- version: 4.0.17
- resolution: "@vitest/utils@npm:4.0.17"
+"@vitest/utils@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/utils@npm:4.0.18"
dependencies:
- "@vitest/pretty-format": "npm:4.0.17"
+ "@vitest/pretty-format": "npm:4.0.18"
tinyrainbow: "npm:^3.0.3"
- checksum: 10/b8b96f8c2c4fee13f4ef4927e56bbf98c2d4f3a61428d9721c5578c96e2a0953892dfccfad3e0c1a7b3105e3d24f93f826f8338c82c72b9f8bc32b50bc9072a1
+ checksum: 10/e8b2ad7bc35b2bc5590f9dc1d1a67644755da416b47ab7099a6f26792903fa0aacb81e6ba99f0f03858d9d3a1d76eeba65150a1a0849690a40817424e749c367
languageName: node
linkType: hard
@@ -1422,6 +1417,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@wix/motion@workspace:packages/motion"
dependencies:
+ "@playwright/test": "npm:^1.58.2"
"@types/react": "npm:^18.3.3"
"@types/react-dom": "npm:^18.3.0"
"@vitest/coverage-v8": "npm:^4.0.14"
@@ -1452,11 +1448,11 @@ __metadata:
linkType: hard
"acorn@npm:^8.15.0":
- version: 8.15.0
- resolution: "acorn@npm:8.15.0"
+ version: 8.16.0
+ resolution: "acorn@npm:8.16.0"
bin:
acorn: bin/acorn
- checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4
+ checksum: 10/690c673bb4d61b38ef82795fab58526471ad7f7e67c0e40c4ff1e10ecd80ce5312554ef633c9995bfc4e6d170cef165711f9ca9e49040b62c0c66fbf2dd3df2b
languageName: node
linkType: hard
@@ -1468,14 +1464,14 @@ __metadata:
linkType: hard
"ajv@npm:^6.12.4":
- version: 6.12.6
- resolution: "ajv@npm:6.12.6"
+ version: 6.14.0
+ resolution: "ajv@npm:6.14.0"
dependencies:
fast-deep-equal: "npm:^3.1.1"
fast-json-stable-stringify: "npm:^2.0.0"
json-schema-traverse: "npm:^0.4.1"
uri-js: "npm:^4.2.2"
- checksum: 10/48d6ad21138d12eb4d16d878d630079a2bda25a04e745c07846a4ad768319533031e28872a9b3c5790fa1ec41aabdf2abed30a56e5a03ebc2cf92184b8ee306c
+ checksum: 10/c71f14dd2b6f2535d043f74019c8169f7aeb1106bafbb741af96f34fdbf932255c919ddd46344043d03b62ea0ccb319f83667ec5eedf612393f29054fe5ce4a5
languageName: node
linkType: hard
@@ -1632,13 +1628,13 @@ __metadata:
linkType: hard
"ast-v8-to-istanbul@npm:^0.3.10":
- version: 0.3.10
- resolution: "ast-v8-to-istanbul@npm:0.3.10"
+ version: 0.3.11
+ resolution: "ast-v8-to-istanbul@npm:0.3.11"
dependencies:
"@jridgewell/trace-mapping": "npm:^0.3.31"
estree-walker: "npm:^3.0.3"
- js-tokens: "npm:^9.0.1"
- checksum: 10/240a5e2c24776b355f2442fa93564a528b8df4b8d94e9bc3234f25020ffac745886865a3a92e5e9dc67ee9720739ec078f04790a3607a7ad98d8349cf75ddf04
+ js-tokens: "npm:^10.0.0"
+ checksum: 10/3209a099194d41a9504383b598bfa21a51dc09e3938f4d1fb5dd868ce2c092e9800d783bf8f9527e0dc88a90e6b03d5ab1fedbe9bc7a70485fa7b497190694c7
languageName: node
linkType: hard
@@ -1700,12 +1696,19 @@ __metadata:
languageName: node
linkType: hard
+"balanced-match@npm:^4.0.2":
+ version: 4.0.4
+ resolution: "balanced-match@npm:4.0.4"
+ checksum: 10/fb07bb66a0959c2843fc055838047e2a95ccebb837c519614afb067ebfdf2fa967ca8d712c35ced07f2cd26fc6f07964230b094891315ad74f11eba3d53178a0
+ languageName: node
+ linkType: hard
+
"baseline-browser-mapping@npm:^2.9.0":
- version: 2.9.19
- resolution: "baseline-browser-mapping@npm:2.9.19"
+ version: 2.10.0
+ resolution: "baseline-browser-mapping@npm:2.10.0"
bin:
- baseline-browser-mapping: dist/cli.js
- checksum: 10/8d7bbb7fe3d1ad50e04b127c819ba6d059c01ed0d2a7a5fc3327e23a8c42855fa3a8b510550c1fe1e37916147e6a390243566d3ef85bf6130c8ddfe5cc3db530
+ baseline-browser-mapping: dist/cli.cjs
+ checksum: 10/8145e076e4299f04c7a412e6ea63803e330153cd89c47b5303f9b56b58078f4c3d5a5b5332c1069da889e76facacca4d43f8940375f7e73ce0a4d96214332953
languageName: node
linkType: hard
@@ -1719,12 +1722,12 @@ __metadata:
languageName: node
linkType: hard
-"brace-expansion@npm:^2.0.1":
- version: 2.0.2
- resolution: "brace-expansion@npm:2.0.2"
+"brace-expansion@npm:^5.0.2":
+ version: 5.0.3
+ resolution: "brace-expansion@npm:5.0.3"
dependencies:
- balanced-match: "npm:^1.0.0"
- checksum: 10/01dff195e3646bc4b0d27b63d9bab84d2ebc06121ff5013ad6e5356daa5a9d6b60fa26cf73c74797f2dc3fbec112af13578d51f75228c1112b26c790a87b0488
+ balanced-match: "npm:^4.0.2"
+ checksum: 10/8ba7deae4ca333d52418d2cde3287ac23f44f7330d92c3ecd96a8941597bea8aab02227bd990944d6711dd549bcc6e550fe70be5d94aa02e2fdc88942f480c9b
languageName: node
linkType: hard
@@ -1802,9 +1805,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001759":
- version: 1.0.30001765
- resolution: "caniuse-lite@npm:1.0.30001765"
- checksum: 10/d7e90e3c02bbab9aa69c28c8c609284d51275396b8e5646ad9fd89b7bc7adcfcd1f5414be25399adde985eadf8d0041fda6d86c1e58384b69b5a9f03a916eb18
+ version: 1.0.30001772
+ resolution: "caniuse-lite@npm:1.0.30001772"
+ checksum: 10/94f0cdb55fb17271435ad5622be2d422d160a7a13cab3006aab1972b15cf699245772922ff2570b5be6ddc1708a4ac9eedb6989cdabfb2de3ef25f294231409a
languageName: node
linkType: hard
@@ -2106,9 +2109,9 @@ __metadata:
linkType: hard
"electron-to-chromium@npm:^1.5.263":
- version: 1.5.286
- resolution: "electron-to-chromium@npm:1.5.286"
- checksum: 10/530ae36571f3f737431dc1f97ab176d9ec38d78e7a14a78fff78540769ef139e9011200a886864111ee26d64e647136531ff004f368f5df8cdd755c45ad97649
+ version: 1.5.302
+ resolution: "electron-to-chromium@npm:1.5.302"
+ checksum: 10/0d31470d04a0d1ea046dd363370081b67e6fe822949b10cfece0a64fd2f8180afb5ccaf14f4294251e444a0af627eb0dc0156242b714c0f10561adf2a21aa5f7
languageName: node
linkType: hard
@@ -2298,35 +2301,35 @@ __metadata:
linkType: hard
"esbuild@npm:^0.27.0":
- version: 0.27.2
- resolution: "esbuild@npm:0.27.2"
- dependencies:
- "@esbuild/aix-ppc64": "npm:0.27.2"
- "@esbuild/android-arm": "npm:0.27.2"
- "@esbuild/android-arm64": "npm:0.27.2"
- "@esbuild/android-x64": "npm:0.27.2"
- "@esbuild/darwin-arm64": "npm:0.27.2"
- "@esbuild/darwin-x64": "npm:0.27.2"
- "@esbuild/freebsd-arm64": "npm:0.27.2"
- "@esbuild/freebsd-x64": "npm:0.27.2"
- "@esbuild/linux-arm": "npm:0.27.2"
- "@esbuild/linux-arm64": "npm:0.27.2"
- "@esbuild/linux-ia32": "npm:0.27.2"
- "@esbuild/linux-loong64": "npm:0.27.2"
- "@esbuild/linux-mips64el": "npm:0.27.2"
- "@esbuild/linux-ppc64": "npm:0.27.2"
- "@esbuild/linux-riscv64": "npm:0.27.2"
- "@esbuild/linux-s390x": "npm:0.27.2"
- "@esbuild/linux-x64": "npm:0.27.2"
- "@esbuild/netbsd-arm64": "npm:0.27.2"
- "@esbuild/netbsd-x64": "npm:0.27.2"
- "@esbuild/openbsd-arm64": "npm:0.27.2"
- "@esbuild/openbsd-x64": "npm:0.27.2"
- "@esbuild/openharmony-arm64": "npm:0.27.2"
- "@esbuild/sunos-x64": "npm:0.27.2"
- "@esbuild/win32-arm64": "npm:0.27.2"
- "@esbuild/win32-ia32": "npm:0.27.2"
- "@esbuild/win32-x64": "npm:0.27.2"
+ version: 0.27.3
+ resolution: "esbuild@npm:0.27.3"
+ dependencies:
+ "@esbuild/aix-ppc64": "npm:0.27.3"
+ "@esbuild/android-arm": "npm:0.27.3"
+ "@esbuild/android-arm64": "npm:0.27.3"
+ "@esbuild/android-x64": "npm:0.27.3"
+ "@esbuild/darwin-arm64": "npm:0.27.3"
+ "@esbuild/darwin-x64": "npm:0.27.3"
+ "@esbuild/freebsd-arm64": "npm:0.27.3"
+ "@esbuild/freebsd-x64": "npm:0.27.3"
+ "@esbuild/linux-arm": "npm:0.27.3"
+ "@esbuild/linux-arm64": "npm:0.27.3"
+ "@esbuild/linux-ia32": "npm:0.27.3"
+ "@esbuild/linux-loong64": "npm:0.27.3"
+ "@esbuild/linux-mips64el": "npm:0.27.3"
+ "@esbuild/linux-ppc64": "npm:0.27.3"
+ "@esbuild/linux-riscv64": "npm:0.27.3"
+ "@esbuild/linux-s390x": "npm:0.27.3"
+ "@esbuild/linux-x64": "npm:0.27.3"
+ "@esbuild/netbsd-arm64": "npm:0.27.3"
+ "@esbuild/netbsd-x64": "npm:0.27.3"
+ "@esbuild/openbsd-arm64": "npm:0.27.3"
+ "@esbuild/openbsd-x64": "npm:0.27.3"
+ "@esbuild/openharmony-arm64": "npm:0.27.3"
+ "@esbuild/sunos-x64": "npm:0.27.3"
+ "@esbuild/win32-arm64": "npm:0.27.3"
+ "@esbuild/win32-ia32": "npm:0.27.3"
+ "@esbuild/win32-x64": "npm:0.27.3"
dependenciesMeta:
"@esbuild/aix-ppc64":
optional: true
@@ -2382,7 +2385,7 @@ __metadata:
optional: true
bin:
esbuild: bin/esbuild
- checksum: 10/7f1229328b0efc63c4184a61a7eb303df1e99818cc1d9e309fb92600703008e69821e8e984e9e9f54a627da14e0960d561db3a93029482ef96dc82dd267a60c2
+ checksum: 10/aa74b8d8a3ed8e2eea4d8421737b322f4d21215244e8fa2156c6402d49b5bda01343c220196f1e3f830a7ce92b54ef653c6c723a8cc2e912bb4d17b7398b51ae
languageName: node
linkType: hard
@@ -2755,6 +2758,16 @@ __metadata:
languageName: node
linkType: hard
+"fsevents@npm:2.3.2":
+ version: 2.3.2
+ resolution: "fsevents@npm:2.3.2"
+ dependencies:
+ node-gyp: "npm:latest"
+ checksum: 10/6b5b6f5692372446ff81cf9501c76e3e0459a4852b3b5f1fc72c103198c125a6b8c72f5f166bdd76ffb2fca261e7f6ee5565daf80dca6e571e55bcc589cc1256
+ conditions: os=darwin
+ languageName: node
+ linkType: hard
+
"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3":
version: 2.3.3
resolution: "fsevents@npm:2.3.3"
@@ -2765,6 +2778,15 @@ __metadata:
languageName: node
linkType: hard
+"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin":
+ version: 2.3.2
+ resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1"
+ dependencies:
+ node-gyp: "npm:latest"
+ conditions: os=darwin
+ languageName: node
+ linkType: hard
+
"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin":
version: 2.3.3
resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1"
@@ -2867,14 +2889,14 @@ __metadata:
languageName: node
linkType: hard
-"glob@npm:^13.0.0":
- version: 13.0.0
- resolution: "glob@npm:13.0.0"
+"glob@npm:^13.0.0, glob@npm:^13.0.3":
+ version: 13.0.6
+ resolution: "glob@npm:13.0.6"
dependencies:
- minimatch: "npm:^10.1.1"
- minipass: "npm:^7.1.2"
- path-scurry: "npm:^2.0.0"
- checksum: 10/de390721d29ee1c9ea41e40ec2aa0de2cabafa68022e237dc4297665a5e4d650776f2573191984ea1640aba1bf0ea34eddef2d8cbfbfc2ad24b5fb0af41d8846
+ minimatch: "npm:^10.2.2"
+ minipass: "npm:^7.1.3"
+ path-scurry: "npm:^2.0.2"
+ checksum: 10/201ad69e5f0aa74e1d8c00a481581f8b8c804b6a4fbfabeeb8541f5d756932800331daeba99b58fb9e4cd67e12ba5a7eba5b82fb476691588418060b84353214
languageName: node
linkType: hard
@@ -3240,7 +3262,7 @@ __metadata:
languageName: node
linkType: hard
-"is-core-module@npm:^2.13.0":
+"is-core-module@npm:^2.16.1":
version: 2.16.1
resolution: "is-core-module@npm:2.16.1"
dependencies:
@@ -3458,10 +3480,10 @@ __metadata:
languageName: node
linkType: hard
-"isexe@npm:^3.1.1":
- version: 3.1.1
- resolution: "isexe@npm:3.1.1"
- checksum: 10/7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e
+"isexe@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "isexe@npm:4.0.0"
+ checksum: 10/2ead327ef596042ef9c9ec5f236b316acfaedb87f4bb61b3c3d574fb2e9c8a04b67305e04733bde52c24d9622fdebd3270aadb632adfbf9cadef88fe30f479e5
languageName: node
linkType: hard
@@ -3507,6 +3529,13 @@ __metadata:
languageName: node
linkType: hard
+"js-tokens@npm:^10.0.0":
+ version: 10.0.0
+ resolution: "js-tokens@npm:10.0.0"
+ checksum: 10/88f536ec89f076fc230d29df255b3c55531237669d746d1868fca716b1e3f5f2e4abf8e5b8701903216e3f00d2dc3918d078b35da87772d433ab6a513c3bf76d
+ languageName: node
+ linkType: hard
+
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
@@ -3514,13 +3543,6 @@ __metadata:
languageName: node
linkType: hard
-"js-tokens@npm:^9.0.1":
- version: 9.0.1
- resolution: "js-tokens@npm:9.0.1"
- checksum: 10/3288ba73bb2023adf59501979fb4890feb6669cc167b13771b226814fde96a1583de3989249880e3f4d674040d1815685db9a9880db9153307480d39dc760365
- languageName: node
- linkType: hard
-
"js-yaml@npm:^4.1.1":
version: 4.1.1
resolution: "js-yaml@npm:4.1.1"
@@ -3712,9 +3734,9 @@ __metadata:
linkType: hard
"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1":
- version: 11.2.4
- resolution: "lru-cache@npm:11.2.4"
- checksum: 10/3b2da74c0b6653767f8164c38c4c4f4d7f0cc10c62bfa512663d94a830191ae6a5af742a8d88a8b30d5f9974652d3adae53931f32069139ad24fa2a18a199aca
+ version: 11.2.6
+ resolution: "lru-cache@npm:11.2.6"
+ checksum: 10/91222bbd59f793a0a0ad57789388f06b34ac9bb1613433c1d1810457d09db5cd3ec8943227ce2e1f5d6a0a15d6f1a9f129cb2c49ae9b6b10e82d4965fddecbef
languageName: node
linkType: hard
@@ -3746,13 +3768,13 @@ __metadata:
linkType: hard
"magicast@npm:^0.5.1":
- version: 0.5.1
- resolution: "magicast@npm:0.5.1"
+ version: 0.5.2
+ resolution: "magicast@npm:0.5.2"
dependencies:
- "@babel/parser": "npm:^7.28.5"
- "@babel/types": "npm:^7.28.5"
+ "@babel/parser": "npm:^7.29.0"
+ "@babel/types": "npm:^7.29.0"
source-map-js: "npm:^1.2.1"
- checksum: 10/ee6149994760f0b539a07f1d36631fed366ae19b9fc82e338c1cdd2a2e0b33a773635327514a6aa73faca9dc0ca37df5e5376b7b0687fb56353f431f299714c4
+ checksum: 10/724d47bfa70cc5046992cf6defae51a3cb701307b35e5637faede1b109fb19ccb47d3f3886df569f5b1281deb6a1ae6993f4542e7c7c6312f70d7be0f4194833
languageName: node
linkType: hard
@@ -3811,8 +3833,8 @@ __metadata:
linkType: hard
"mdast-util-from-markdown@npm:^2.0.0":
- version: 2.0.2
- resolution: "mdast-util-from-markdown@npm:2.0.2"
+ version: 2.0.3
+ resolution: "mdast-util-from-markdown@npm:2.0.3"
dependencies:
"@types/mdast": "npm:^4.0.0"
"@types/unist": "npm:^3.0.0"
@@ -3826,7 +3848,7 @@ __metadata:
micromark-util-symbol: "npm:^2.0.0"
micromark-util-types: "npm:^2.0.0"
unist-util-stringify-position: "npm:^4.0.0"
- checksum: 10/69b207913fbcc0469f8c59d922af4d5509b79e809d77c9bd4781543a907fe2ecc8e6433ce0707066a27b117b13f38af3aae4f2d085e18ebd2d3ad5f1a5647902
+ checksum: 10/96f2bfb3b240c3d20a57db5d215faed795abf495c65ca2a4d61c0cf796011bc980619aa032d7984b05b67c15edc0eccd12a004a848952d3a598d108cf14901ab
languageName: node
linkType: hard
@@ -4353,30 +4375,30 @@ __metadata:
languageName: node
linkType: hard
-"minimatch@npm:^10.1.1":
- version: 10.1.1
- resolution: "minimatch@npm:10.1.1"
+"minimatch@npm:^10.2.2":
+ version: 10.2.2
+ resolution: "minimatch@npm:10.2.2"
dependencies:
- "@isaacs/brace-expansion": "npm:^5.0.0"
- checksum: 10/110f38921ea527022e90f7a5f43721838ac740d0a0c26881c03b57c261354fb9a0430e40b2c56dfcea2ef3c773768f27210d1106f1f2be19cde3eea93f26f45e
+ brace-expansion: "npm:^5.0.2"
+ checksum: 10/e135be7b502ac97c02bcee42ccc1c55dc26dbac036c0f4acde69e42fe339d7fb53fae711e57b3546cb533426382ea492c73a073c7f78832e0453d120d48dd015
languageName: node
linkType: hard
"minimatch@npm:^3.1.2":
- version: 3.1.2
- resolution: "minimatch@npm:3.1.2"
+ version: 3.1.3
+ resolution: "minimatch@npm:3.1.3"
dependencies:
brace-expansion: "npm:^1.1.7"
- checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634
+ checksum: 10/d430c40785fdb42c9fc1a36b9c9e3b08146044bd0f2fd043b05afb436aa4eaf6cdcb7756939ab8fc01664cd75d6335fd5043b2fe2aa084756927ffc77c1e3597
languageName: node
linkType: hard
"minimatch@npm:^9.0.4":
- version: 9.0.5
- resolution: "minimatch@npm:9.0.5"
+ version: 9.0.6
+ resolution: "minimatch@npm:9.0.6"
dependencies:
- brace-expansion: "npm:^2.0.1"
- checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348
+ brace-expansion: "npm:^5.0.2"
+ checksum: 10/c7a46134aaf349f386de9a3f6c5b48c53bc3a4e2ef4b8b6365184504e28cc31cc261a388e181648cbc756b40e213dbce115c8087a47eff8f54ee28d62bc17b08
languageName: node
linkType: hard
@@ -4390,17 +4412,17 @@ __metadata:
linkType: hard
"minipass-fetch@npm:^5.0.0":
- version: 5.0.0
- resolution: "minipass-fetch@npm:5.0.0"
+ version: 5.0.1
+ resolution: "minipass-fetch@npm:5.0.1"
dependencies:
encoding: "npm:^0.1.13"
minipass: "npm:^7.0.3"
- minipass-sized: "npm:^1.0.3"
+ minipass-sized: "npm:^2.0.0"
minizlib: "npm:^3.0.1"
dependenciesMeta:
encoding:
optional: true
- checksum: 10/4fb7dca630a64e6970a8211dade505bfe260d0b8d60beb348dcdfb95fe35ef91d977b29963929c9017ae0805686aa3f413107dc6bc5deac9b9e26b0b41c3b86c
+ checksum: 10/08bf0c9866e7f344bf1863ce0d99c0a6fe96b43ef5a4119e23d84a21e613a3f55ecf302adf28d9e228b4ebd50e81d5e84c397e0535089090427319379f478d94
languageName: node
linkType: hard
@@ -4422,12 +4444,12 @@ __metadata:
languageName: node
linkType: hard
-"minipass-sized@npm:^1.0.3":
- version: 1.0.3
- resolution: "minipass-sized@npm:1.0.3"
+"minipass-sized@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "minipass-sized@npm:2.0.0"
dependencies:
- minipass: "npm:^3.0.0"
- checksum: 10/40982d8d836a52b0f37049a0a7e5d0f089637298e6d9b45df9c115d4f0520682a78258905e5c8b180fb41b593b0a82cc1361d2c74b45f7ada66334f84d1ecfdd
+ minipass: "npm:^7.1.2"
+ checksum: 10/3b89adf64ca705662f77481e278eff5ec0a57aeffb5feba7cc8843722b1e7770efc880f2a17d1d4877b2d7bf227873cd46afb4da44c0fd18088b601ea50f96bb
languageName: node
linkType: hard
@@ -4440,10 +4462,10 @@ __metadata:
languageName: node
linkType: hard
-"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2":
- version: 7.1.2
- resolution: "minipass@npm:7.1.2"
- checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950
+"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3":
+ version: 7.1.3
+ resolution: "minipass@npm:7.1.3"
+ checksum: 10/175e4d5e20980c3cd316ae82d2c031c42f6c746467d8b1905b51060a0ba4461441a0c25bb67c025fd9617f9a3873e152c7b543c6b5ac83a1846be8ade80dffd6
languageName: node
linkType: hard
@@ -4486,9 +4508,21 @@ __metadata:
languageName: node
linkType: hard
+"node-exports-info@npm:^1.6.0":
+ version: 1.6.0
+ resolution: "node-exports-info@npm:1.6.0"
+ dependencies:
+ array.prototype.flatmap: "npm:^1.3.3"
+ es-errors: "npm:^1.3.0"
+ object.entries: "npm:^1.1.9"
+ semver: "npm:^6.3.1"
+ checksum: 10/0a1667d535f499ac1fe6c6d22f8146bc8b68abc76fa355856219202f6cf5f386027e0ff054e66a22d08be02acbc63fcdc9f98d0fbc97993f5eabc66408fdadad
+ languageName: node
+ linkType: hard
+
"node-gyp@npm:latest":
- version: 12.1.0
- resolution: "node-gyp@npm:12.1.0"
+ version: 12.2.0
+ resolution: "node-gyp@npm:12.2.0"
dependencies:
env-paths: "npm:^2.2.0"
exponential-backoff: "npm:^3.1.1"
@@ -4497,12 +4531,12 @@ __metadata:
nopt: "npm:^9.0.0"
proc-log: "npm:^6.0.0"
semver: "npm:^7.3.5"
- tar: "npm:^7.5.2"
+ tar: "npm:^7.5.4"
tinyglobby: "npm:^0.2.12"
which: "npm:^6.0.0"
bin:
node-gyp: bin/node-gyp.js
- checksum: 10/d93079236cef1dd7fa4df683708d8708ad255c55865f6656664c8959e4d3963d908ac48e8f9f341705432e979dbbf502a40d68d65a17fe35956a5a05ba6c1cb4
+ checksum: 10/4ebab5b77585a637315e969c2274b5520562473fe75de850639a580c2599652fb9f33959ec782ea45a2e149d8f04b548030f472eeeb3dbdf19a7f2ccbc30b908
languageName: node
linkType: hard
@@ -4720,13 +4754,13 @@ __metadata:
languageName: node
linkType: hard
-"path-scurry@npm:^2.0.0":
- version: 2.0.1
- resolution: "path-scurry@npm:2.0.1"
+"path-scurry@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "path-scurry@npm:2.0.2"
dependencies:
lru-cache: "npm:^11.0.0"
minipass: "npm:^7.1.2"
- checksum: 10/1e9c74e9ccf94d7c16056a5cb2dba9fa23eec1bc221ab15c44765486b9b9975b4cd9a4d55da15b96eadf67d5202e9a2f1cec9023fbb35fe7d9ccd0ff1891f88b
+ checksum: 10/2b4257422bcb870a4c2d205b3acdbb213a72f5e2250f61c80f79c9d014d010f82bdf8584441612c8e1fa4eb098678f5704a66fa8377d72646bad4be38e57a2c3
languageName: node
linkType: hard
@@ -4751,6 +4785,30 @@ __metadata:
languageName: node
linkType: hard
+"playwright-core@npm:1.58.2":
+ version: 1.58.2
+ resolution: "playwright-core@npm:1.58.2"
+ bin:
+ playwright-core: cli.js
+ checksum: 10/8a98fcf122167e8703d525db2252de0e3da4ab9110ab6ea9951247e52d846310eb25ea2c805e1b7ccb54b4010c44e5adc3a76aae6da02f34324ccc3e76683bb1
+ languageName: node
+ linkType: hard
+
+"playwright@npm:1.58.2":
+ version: 1.58.2
+ resolution: "playwright@npm:1.58.2"
+ dependencies:
+ fsevents: "npm:2.3.2"
+ playwright-core: "npm:1.58.2"
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ bin:
+ playwright: cli.js
+ checksum: 10/d89d6c8a32388911b9aff9ee0f1a90076219f15c804f2b287db048b9e9cde182aea3131fac1959051d25189ed4218ec4272b137c83cd7f9cd24781cbc77edd86
+ languageName: node
+ linkType: hard
+
"possible-typed-array-names@npm:^1.0.0":
version: 1.1.0
resolution: "possible-typed-array-names@npm:1.1.0"
@@ -4910,20 +4968,20 @@ __metadata:
linkType: hard
"react-router-dom@npm:^7.1.1":
- version: 7.12.0
- resolution: "react-router-dom@npm:7.12.0"
+ version: 7.13.0
+ resolution: "react-router-dom@npm:7.13.0"
dependencies:
- react-router: "npm:7.12.0"
+ react-router: "npm:7.13.0"
peerDependencies:
react: ">=18"
react-dom: ">=18"
- checksum: 10/d2733cbb00f00617a227a454b3fb76645433ce8564b02a0ff05064a2da782ce192a8d87a2f2ab0c92f899689c0b562096ec8217b6af483b97c17e540257ed5e9
+ checksum: 10/7d0f283302710b54a24254aecdfc680832e0f28256a1743f7190e5742ac6072be70c30f731ee9350dcc4ef832d645e701aeef501b9a075da53ce1668d050322c
languageName: node
linkType: hard
-"react-router@npm:7.12.0":
- version: 7.12.0
- resolution: "react-router@npm:7.12.0"
+"react-router@npm:7.13.0":
+ version: 7.13.0
+ resolution: "react-router@npm:7.13.0"
dependencies:
cookie: "npm:^1.0.1"
set-cookie-parser: "npm:^2.6.0"
@@ -4933,7 +4991,7 @@ __metadata:
peerDependenciesMeta:
react-dom:
optional: true
- checksum: 10/578324f792721200bd57a220c7931af692613943051c9bb0c6303613849ec9a2c2365a3a6afe1b3976c13edc8f71616bb9cfdb13c0ac501f239ad11a6884e3f8
+ checksum: 10/8bc0a1eebf37136851ee345e53b137b5a7ec0de10c842861a06810bb54c0a389826734a7454f4afe8731daf39499948ba73657e60a6bd2acb93b3e567fd79127
languageName: node
linkType: hard
@@ -5054,28 +5112,34 @@ __metadata:
linkType: hard
"resolve@npm:^2.0.0-next.5":
- version: 2.0.0-next.5
- resolution: "resolve@npm:2.0.0-next.5"
+ version: 2.0.0-next.6
+ resolution: "resolve@npm:2.0.0-next.6"
dependencies:
- is-core-module: "npm:^2.13.0"
+ es-errors: "npm:^1.3.0"
+ is-core-module: "npm:^2.16.1"
+ node-exports-info: "npm:^1.6.0"
+ object-keys: "npm:^1.1.1"
path-parse: "npm:^1.0.7"
supports-preserve-symlinks-flag: "npm:^1.0.0"
bin:
resolve: bin/resolve
- checksum: 10/2d6fd28699f901744368e6f2032b4268b4c7b9185fd8beb64f68c93ac6b22e52ae13560ceefc96241a665b985edf9ffd393ae26d2946a7d3a07b7007b7d51e79
+ checksum: 10/c95cb98b8d3f9e2a979e6eb8b7e0b0e13f08da62607a45207275f151d640152244568a9a9cd01662a21e3746792177cbf9be1dacb88f7355edf4db49d9ee27e5
languageName: node
linkType: hard
"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin":
- version: 2.0.0-next.5
- resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d"
+ version: 2.0.0-next.6
+ resolution: "resolve@patch:resolve@npm%3A2.0.0-next.6#optional!builtin::version=2.0.0-next.6&hash=c3c19d"
dependencies:
- is-core-module: "npm:^2.13.0"
+ es-errors: "npm:^1.3.0"
+ is-core-module: "npm:^2.16.1"
+ node-exports-info: "npm:^1.6.0"
+ object-keys: "npm:^1.1.1"
path-parse: "npm:^1.0.7"
supports-preserve-symlinks-flag: "npm:^1.0.0"
bin:
resolve: bin/resolve
- checksum: 10/05fa778de9d0347c8b889eb7a18f1f06bf0f801b0eb4610b4871a4b2f22e220900cf0ad525e94f990bb8d8921c07754ab2122c0c225ab4cdcea98f36e64fa4c2
+ checksum: 10/1b26738af76c80b341075e6bf4b202ef85f85f4a2cbf2934246c3b5f20c682cf352833fc6e32579c6967419226d3ab63e8d321328da052c87a31eaad91e3571a
languageName: node
linkType: hard
@@ -5087,46 +5151,46 @@ __metadata:
linkType: hard
"rimraf@npm:^6.0.1":
- version: 6.1.2
- resolution: "rimraf@npm:6.1.2"
+ version: 6.1.3
+ resolution: "rimraf@npm:6.1.3"
dependencies:
- glob: "npm:^13.0.0"
+ glob: "npm:^13.0.3"
package-json-from-dist: "npm:^1.0.1"
bin:
rimraf: dist/esm/bin.mjs
- checksum: 10/add8e566fe903f59d7b55c6c2382320c48302778640d1951baf247b3b451af496c2dee7195c204a8c646fd6327feadd1f5b61ce68c1362d4898075a726d83cc6
+ checksum: 10/dd98ec2ad7cd2cccae1c7110754d472eac8edb2bab8a8b057dce04edfe1433dab246a889b3fd85a66c78ca81caa1429caa0e736c7647f6832b04fd5d4dfb8ab8
languageName: node
linkType: hard
"rollup@npm:^4.43.0":
- version: 4.55.3
- resolution: "rollup@npm:4.55.3"
- dependencies:
- "@rollup/rollup-android-arm-eabi": "npm:4.55.3"
- "@rollup/rollup-android-arm64": "npm:4.55.3"
- "@rollup/rollup-darwin-arm64": "npm:4.55.3"
- "@rollup/rollup-darwin-x64": "npm:4.55.3"
- "@rollup/rollup-freebsd-arm64": "npm:4.55.3"
- "@rollup/rollup-freebsd-x64": "npm:4.55.3"
- "@rollup/rollup-linux-arm-gnueabihf": "npm:4.55.3"
- "@rollup/rollup-linux-arm-musleabihf": "npm:4.55.3"
- "@rollup/rollup-linux-arm64-gnu": "npm:4.55.3"
- "@rollup/rollup-linux-arm64-musl": "npm:4.55.3"
- "@rollup/rollup-linux-loong64-gnu": "npm:4.55.3"
- "@rollup/rollup-linux-loong64-musl": "npm:4.55.3"
- "@rollup/rollup-linux-ppc64-gnu": "npm:4.55.3"
- "@rollup/rollup-linux-ppc64-musl": "npm:4.55.3"
- "@rollup/rollup-linux-riscv64-gnu": "npm:4.55.3"
- "@rollup/rollup-linux-riscv64-musl": "npm:4.55.3"
- "@rollup/rollup-linux-s390x-gnu": "npm:4.55.3"
- "@rollup/rollup-linux-x64-gnu": "npm:4.55.3"
- "@rollup/rollup-linux-x64-musl": "npm:4.55.3"
- "@rollup/rollup-openbsd-x64": "npm:4.55.3"
- "@rollup/rollup-openharmony-arm64": "npm:4.55.3"
- "@rollup/rollup-win32-arm64-msvc": "npm:4.55.3"
- "@rollup/rollup-win32-ia32-msvc": "npm:4.55.3"
- "@rollup/rollup-win32-x64-gnu": "npm:4.55.3"
- "@rollup/rollup-win32-x64-msvc": "npm:4.55.3"
+ version: 4.59.0
+ resolution: "rollup@npm:4.59.0"
+ dependencies:
+ "@rollup/rollup-android-arm-eabi": "npm:4.59.0"
+ "@rollup/rollup-android-arm64": "npm:4.59.0"
+ "@rollup/rollup-darwin-arm64": "npm:4.59.0"
+ "@rollup/rollup-darwin-x64": "npm:4.59.0"
+ "@rollup/rollup-freebsd-arm64": "npm:4.59.0"
+ "@rollup/rollup-freebsd-x64": "npm:4.59.0"
+ "@rollup/rollup-linux-arm-gnueabihf": "npm:4.59.0"
+ "@rollup/rollup-linux-arm-musleabihf": "npm:4.59.0"
+ "@rollup/rollup-linux-arm64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-arm64-musl": "npm:4.59.0"
+ "@rollup/rollup-linux-loong64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-loong64-musl": "npm:4.59.0"
+ "@rollup/rollup-linux-ppc64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-ppc64-musl": "npm:4.59.0"
+ "@rollup/rollup-linux-riscv64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-riscv64-musl": "npm:4.59.0"
+ "@rollup/rollup-linux-s390x-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-x64-gnu": "npm:4.59.0"
+ "@rollup/rollup-linux-x64-musl": "npm:4.59.0"
+ "@rollup/rollup-openbsd-x64": "npm:4.59.0"
+ "@rollup/rollup-openharmony-arm64": "npm:4.59.0"
+ "@rollup/rollup-win32-arm64-msvc": "npm:4.59.0"
+ "@rollup/rollup-win32-ia32-msvc": "npm:4.59.0"
+ "@rollup/rollup-win32-x64-gnu": "npm:4.59.0"
+ "@rollup/rollup-win32-x64-msvc": "npm:4.59.0"
"@types/estree": "npm:1.0.8"
fsevents: "npm:~2.3.2"
dependenciesMeta:
@@ -5184,7 +5248,7 @@ __metadata:
optional: true
bin:
rollup: dist/bin/rollup
- checksum: 10/9abdb43e96e24309cad838aeb0a97055d93c1b6820cb1d55041cdf414a07e3fe377564b711476e511f2d373484b04dd68b6129c42efc201deb8f761bf4c2de7e
+ checksum: 10/728237932aad7022c0640cd126b9fe5285f2578099f22a0542229a17785320a6553b74582fa5977877541c1faf27de65ed2750bc89dbb55b525405244a46d9f1
languageName: node
linkType: hard
@@ -5271,11 +5335,11 @@ __metadata:
linkType: hard
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0":
- version: 7.7.3
- resolution: "semver@npm:7.7.3"
+ version: 7.7.4
+ resolution: "semver@npm:7.7.4"
bin:
semver: bin/semver.js
- checksum: 10/8dbc3168e057a38fc322af909c7f5617483c50caddba135439ff09a754b20bdd6482a5123ff543dad4affa488ecf46ec5fb56d61312ad20bb140199b88dfaea9
+ checksum: 10/26bdc6d58b29528f4142d29afb8526bc335f4fc04c4a10f2b98b217f277a031c66736bf82d3d3bb354a2f6a3ae50f18fd62b053c4ac3f294a3d10a61f5075b75
languageName: node
linkType: hard
@@ -5437,11 +5501,11 @@ __metadata:
linkType: hard
"ssri@npm:^13.0.0":
- version: 13.0.0
- resolution: "ssri@npm:13.0.0"
+ version: 13.0.1
+ resolution: "ssri@npm:13.0.1"
dependencies:
minipass: "npm:^7.0.3"
- checksum: 10/fd59bfedf0659c1b83f6e15459162da021f08ec0f5834dd9163296f8b77ee82f9656aa1d415c3d3848484293e0e6aefdd482e863e52ddb53d520bb73da1eeec1
+ checksum: 10/ae560d0378d074006a71b06af71bfbe84a3fe1ac6e16c1f07575f69e670d40170507fe52b21bcc23399429bc6a15f4bc3ea8d9bc88e9dfd7e87de564e6da6a72
languageName: node
linkType: hard
@@ -5614,16 +5678,16 @@ __metadata:
languageName: node
linkType: hard
-"tar@npm:^7.5.2":
- version: 7.5.7
- resolution: "tar@npm:7.5.7"
+"tar@npm:^7.5.4":
+ version: 7.5.9
+ resolution: "tar@npm:7.5.9"
dependencies:
"@isaacs/fs-minipass": "npm:^4.0.0"
chownr: "npm:^3.0.0"
minipass: "npm:^7.1.2"
minizlib: "npm:^3.1.0"
yallist: "npm:^5.0.0"
- checksum: 10/0d6938dd32fe5c0f17c8098d92bd9889ee0ed9d11f12381b8146b6e8c87bb5aa49feec7abc42463f0597503d8e89e4c4c0b42bff1a5a38444e918b4878b7fd21
+ checksum: 10/1213cdde9c22d6acf8809ba5d2a025212ce3517bc99c4a4c6981b7dc0489bf3b164db9c826c9517680889194c9ba57448c8ff0da35eca9a60bb7689bf0b3897d
languageName: node
linkType: hard
@@ -5877,13 +5941,13 @@ __metadata:
linkType: hard
"unist-util-visit@npm:^5.0.0":
- version: 5.0.0
- resolution: "unist-util-visit@npm:5.0.0"
+ version: 5.1.0
+ resolution: "unist-util-visit@npm:5.1.0"
dependencies:
"@types/unist": "npm:^3.0.0"
unist-util-is: "npm:^6.0.0"
unist-util-visit-parents: "npm:^6.0.0"
- checksum: 10/f2bbde23641e9ade7640358c06ddeec0f38342322eb8e7819d9ee380b0f859d25d084dde22bf63db0280b3b2f36575f15aa1d6c23acf276c91c2493cf799e3b0
+ checksum: 10/340fc1929062d21156200284105caad79cc188bd98f285b60aba887492a70e6e6cadbc7e383a68909c7e0fdd83f855cb9f4184ad8e5aa153eb2d810445aea8e5
languageName: node
linkType: hard
@@ -6003,16 +6067,16 @@ __metadata:
linkType: hard
"vitest@npm:^4.0.14":
- version: 4.0.17
- resolution: "vitest@npm:4.0.17"
- dependencies:
- "@vitest/expect": "npm:4.0.17"
- "@vitest/mocker": "npm:4.0.17"
- "@vitest/pretty-format": "npm:4.0.17"
- "@vitest/runner": "npm:4.0.17"
- "@vitest/snapshot": "npm:4.0.17"
- "@vitest/spy": "npm:4.0.17"
- "@vitest/utils": "npm:4.0.17"
+ version: 4.0.18
+ resolution: "vitest@npm:4.0.18"
+ dependencies:
+ "@vitest/expect": "npm:4.0.18"
+ "@vitest/mocker": "npm:4.0.18"
+ "@vitest/pretty-format": "npm:4.0.18"
+ "@vitest/runner": "npm:4.0.18"
+ "@vitest/snapshot": "npm:4.0.18"
+ "@vitest/spy": "npm:4.0.18"
+ "@vitest/utils": "npm:4.0.18"
es-module-lexer: "npm:^1.7.0"
expect-type: "npm:^1.2.2"
magic-string: "npm:^0.30.21"
@@ -6030,10 +6094,10 @@ __metadata:
"@edge-runtime/vm": "*"
"@opentelemetry/api": ^1.9.0
"@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0
- "@vitest/browser-playwright": 4.0.17
- "@vitest/browser-preview": 4.0.17
- "@vitest/browser-webdriverio": 4.0.17
- "@vitest/ui": 4.0.17
+ "@vitest/browser-playwright": 4.0.18
+ "@vitest/browser-preview": 4.0.18
+ "@vitest/browser-webdriverio": 4.0.18
+ "@vitest/ui": 4.0.18
happy-dom: "*"
jsdom: "*"
peerDependenciesMeta:
@@ -6057,7 +6121,7 @@ __metadata:
optional: true
bin:
vitest: vitest.mjs
- checksum: 10/792cf5ecdb2c0c2a61fc7beacec800413dcc5b68ad5e18f74795cdbfe513d58e3b6e437571c728c9992920f52d0640a5264aaf8c3702454b2637ff93451cf567
+ checksum: 10/6c6464ebcf3af83546862896fd1b5f10cb6607261bffce39df60033a288b8c1687ae1dd20002b6e4997a7a05303376d1eb58ce20afe63be052529a4378a8c165
languageName: node
linkType: hard
@@ -6176,13 +6240,13 @@ __metadata:
linkType: hard
"which@npm:^6.0.0":
- version: 6.0.0
- resolution: "which@npm:6.0.0"
+ version: 6.0.1
+ resolution: "which@npm:6.0.1"
dependencies:
- isexe: "npm:^3.1.1"
+ isexe: "npm:^4.0.0"
bin:
node-which: bin/which.js
- checksum: 10/df19b2cd8aac94b333fa29b42e8e371a21e634a742a3b156716f7752a5afe1d73fb5d8bce9b89326f453d96879e8fe626eb421e0117eb1a3ce9fd8c97f6b7db9
+ checksum: 10/dbea77c7d3058bf6c78bf9659d2dce4d2b57d39a15b826b2af6ac2e5a219b99dc8a831b79fdbc453c0598adb4f3f84cf9c2491fd52beb9f5d2dececcad117f68
languageName: node
linkType: hard