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 + + + + + + +
+
A
+
B
+
C
+
+ +
+ + + + +
+ + + + 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 +
+
+ +
+
+
+ + +
+

Custom Effect

+
+
+ Custom +
+
+ +
+
+
+ + +
+

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
+
+

x: 0.000 y: 0.000

+
+ +
+

Y-Axis Target

+
+
Y
+
+
+ +
+

Composite Transform (scaleX + scaleY)

+
+
XY
+
+
+ + + + 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 + + + + + + +
+ +
+

Scroll down to see animations

+
+ + +
+
View Progress
+ progress: 0 +
+ + +
+
Card 1
+
Card 2
+
Card 3
+
+
+ + + + 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