Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve pan interaction and animation state management #51

Merged
merged 1 commit into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/components/demo/pan/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ function template({ rotateY, rotateX }: TransformProperties) {

const controls = useAnimationControls()
function handlePan(_: PointerEvent, info: PanInfo) {
_.preventDefault()
controls.set({
rotateY: info.offset.x / 2,
rotateX: -info.offset.y / 2,
Expand All @@ -26,7 +25,7 @@ function handlePanEnd() {
<template>
<div class="flex flex-col items-center justify-center ">
<Motion
class="card"
class="card touch-none"
:transform-template="template"
:animate="controls"
@pan="handlePan"
Expand Down
13 changes: 12 additions & 1 deletion packages/motion/src/animation/hooks/animation-controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@ import type { Options } from '@/types'
import { invariant } from 'hey-listen'
import { setTarget } from 'framer-motion/dist/es/render/utils/setters.mjs'
import type { VisualElement } from 'framer-motion'
import { resolveVariant } from '@/state/utils'

function stopAnimation(visualElement: VisualElement) {
visualElement.values.forEach(value => value.stop())
}

function setStateTarget(state: MotionState, definition: Options['animate']) {
const resolvedVariant = resolveVariant(definition, state.options.variants, state.options.custom)
Object.entries(resolvedVariant).forEach(([key, value]) => {
if (key === 'transition')
return
state.target[key] = value
})
}

/**
* @public
*/
Expand Down Expand Up @@ -85,6 +95,7 @@ export function setValues(
return setVariants(state, [definition])
}
else {
setStateTarget(state, definition)
setTarget(state.visualElement, definition as any)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of definition as any in the setTarget function call is a type assertion that bypasses TypeScript's static type checking. This could lead to runtime errors if definition does not meet the expected structure required by setTarget. To improve type safety and maintainability, consider validating definition or refining its type definition to ensure it aligns with what setTarget expects.

}
}
Expand All @@ -95,7 +106,7 @@ function setVariants(state: MotionState, variantLabels: string[]) {
reversedLabels.forEach((key) => {
const variant = visualElement.getVariant(key)
variant && setTarget(visualElement, variant)

setStateTarget(state, variant)
if (visualElement.variantChildren) {
visualElement.variantChildren.forEach((child) => {
setVariants(mountedStates.get(child.current as HTMLElement), variantLabels)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recursive call to setVariants within the forEach loop handling variantChildren could lead to performance issues or stack overflow errors, especially with a deep or cyclic structure of children. To prevent potential issues, consider implementing a non-recursive approach or adding safeguards such as depth limits or cycle detection.

Expand Down
4 changes: 2 additions & 2 deletions packages/motion/src/state/animate-updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ function createAnimationFactories(
): AnimationFactory[] {
const factories: AnimationFactory[] = []

new Set(Object.keys(this.target)).forEach((key: any) => {
if (!hasChanged(this.visualElement.getValue(key), this.target[key]))
Object.keys(this.target).forEach((key: any) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable key is typed as any in the forEach loop, which undermines TypeScript's type safety. To enhance type safety and prevent potential runtime errors, it's recommended to specify a more precise type for key that aligns with the properties expected in this.target.

Suggested Change:

Object.keys(this.target).forEach((key: keyof typeof this.target) => {...})

if (!hasChanged(prevTarget[key], this.target[key]))
return

this.baseTarget[key] ??= style.get(this.element, key) as string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line assumes that style.get(this.element, key) will always return a string, which might not be the case, potentially leading to type inconsistencies or errors. It's safer to handle the possibility of different return types explicitly.

Suggested Change:

this.baseTarget[key] ??= style.get(this.element, key) as string | undefined

Expand Down
2 changes: 1 addition & 1 deletion playground/nuxt/pages/pan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function handlePanEnd() {
<template>
<div class="flex flex-col items-center justify-center ">
<Motion
class="card"
class="card touch-none"
:transform-template="template"
:animate="controls"
@pan="handlePan"
Expand Down
Loading