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/motion config #25

Merged
merged 17 commits into from
Dec 28, 2024
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
4 changes: 2 additions & 2 deletions docs/components/demo/layout-group/ToggleContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function handleClick() {

<template>
<Motion
class="bg-gray-100 p-4 cursor-pointer hover:bg-gray-200 transition-colors"
class="bg-secondary p-4 cursor-pointer hover:bg-primary/20 transition-colors"
:layout="true"
:initial="{ borderRadius: '8px' }"
@click="handleClick"
Expand All @@ -29,7 +29,7 @@ function handleClick() {
<Motion
v-if="isOpen"
:layout="true"
class="mt-4 text-gray-600"
class="mt-4 text-primary-foreground"
:initial="{ opacity: 0 }"
:animate="{ opacity: 1 }"
:transition="{ delay: 0.2 }"
Expand Down
52 changes: 52 additions & 0 deletions docs/content/2.components/3.layout-group.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,55 @@ navigation.icon: lucide:layout-grid
like `LayoutGroup` in framer-motion, the `LayoutGroup` will detect the layout changes of its children and apply the layout animations.

<ComponentPreview name="layout-group" />

## Props

### `id`

A unique identifier for the layout group. If not provided, the group will be identified by its context.

### `inherit`

Controls how the layout group inherits properties from its parent group. It can be:

- `true`: Inherit both id and `group`.
- `id`: Only inherit the id.
- `group`: Only inherit the `group`.

## `Slots`

### `default`: The default slot receives forceRender function:

- forceRender: A function that, when called, forces the slot Motion component to calculate its layout.

```vue
<LayoutGroup>
<template #default="{ forceRender }">
...
<Motion :layout="true" @click="forceRender">
</Motion>
...
</template>
</LayoutGroup>
```

## useLayoutGroup Hook

The `useLayoutGroup` hook provides access to the layout group context, allowing components to participate in the layoutGroup and respond to forced layout.

```vue
<script setup lang="ts">
import { useLayoutGroup } from 'motion-v'

const { forceRender } = useLayoutGroup()
</script>

<template>
<div>
<p>This component can be forced to update.</p>
<button @click="forceRender">
Force Update
</button>
</div>
</template>
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue-motion",
"version": "0.5.1",
"version": "0.5.2-beta.1",
"private": true,
"packageManager": "[email protected]+sha1.8bfdb6d72b4d5fdf87d21d27f2bfbe2b21dd2629",
"description": "",
Expand Down
5 changes: 3 additions & 2 deletions packages/motion/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "motion-v",
"version": "0.5.1",
"version": "0.5.2-beta.1",
"description": "",
"author": "",
"license": "MIT",
Expand Down Expand Up @@ -54,7 +54,7 @@
"scripts": {
"dev": "vite build --watch",
"build": "rm -rf dist && vite build",
"test": "vitest",
"test": "vitest --dom",
"coverage": "vitest run --coverage",
"pub:release": "pnpm publish --access public"
},
Expand All @@ -70,6 +70,7 @@
"@vitest/coverage-v8": "^1.4.0",
"@vue/test-utils": "^2.4.5",
"framer-motion": "^11.15.0",
"happy-dom": "^16.0.1",
"jsdom": "^24.0.0",
"vite": "^5.4.8",
"vite-plugin-dts": "^4.2.4",
Expand Down
14 changes: 11 additions & 3 deletions packages/motion/src/components/LayoutGroup.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
<script setup lang="ts">
import { type LayoutGroupProps, useLayoutGroup } from './use-layout-group'
import { onBeforeUpdate } from 'vue'
import { type LayoutGroupProps, useLayoutGroupProvider } from './use-layout-group'

const props = defineProps<LayoutGroupProps>()
useLayoutGroup(props)
const { forceRender, key, group } = useLayoutGroupProvider(props)

onBeforeUpdate(() => {
group.dirty()
})
</script>

<template>
<slot />
<slot
:render-key="key"
:force-render="forceRender"
/>
</template>
74 changes: 42 additions & 32 deletions packages/motion/src/components/Motion.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { Primitive } from './Primitive'
import { MotionState } from '@/state/motion-state'
import { injectAnimatePresence } from './presence'
import { isMotionValue } from '@/utils'
import { getMotionElement } from './utils'
import { checkMotionIsHidden, getMotionElement } from './utils'
import type { ElementType, Options, SVGAttributesWithMotionValues, SetMotionValueType } from '@/types'
import { useMotionConfig } from './motion-config/context'
</script>

<script setup lang="ts" generic="T extends ElementType = 'div', K = unknown">
import { type IntrinsicElementAttributes, getCurrentInstance, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref, useAttrs } from 'vue'
import { type IntrinsicElementAttributes, getCurrentInstance, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, useAttrs } from 'vue'
import { injectLayoutGroup, injectMotion, provideMotion } from './context'
import { convertSvgStyleToAttributes, createStyles } from '@/state/style'

Expand Down Expand Up @@ -40,18 +41,43 @@ const props = withDefaults(defineProps<ComBindProps & MotionProps<T, K>>(), {
dragElastic: 0.2,
dragMomentum: true,
whileDrag: undefined,
crossfade: true,
} as any) as MotionProps<T>
const { initial: presenceInitial, safeUnmount } = injectAnimatePresence({ initial: ref(undefined), safeUnmount: () => true })
const animatePresenceContext = injectAnimatePresence({ })
const parentState = injectMotion(null)
const attrs = useAttrs()
const layoutGroup = injectLayoutGroup({} as any)
const config = useMotionConfig()

/**
* Get the layout ID for the motion component
* If both layoutGroup.id and props.layoutId exist, combine them with a hyphen
* Otherwise return props.layoutId or undefined
*/
function getLayoutId() {
if (layoutGroup.id && props.layoutId)
return `${layoutGroup.id}-${props.layoutId}`
return props.layoutId || undefined
}

const state = new MotionState(
{
function getMotionProps() {
return {
...attrs,
...props,
layoutId: getLayoutId(),
transition: props.transition ?? config.value.transition,
layoutGroup,
},
motionConfig: config.value,
initial: animatePresenceContext.initial === false
? animatePresenceContext.initial
: (
props.initial === true ? undefined : props.initial
),
}
}

const state = new MotionState(
getMotionProps(),
parentState!,
)

Expand All @@ -64,41 +90,24 @@ onBeforeMount(() => {
})

onMounted(() => {
state.mount(getMotionElement(instance.$el), {
...attrs,
...props,
layoutGroup,
initial: presenceInitial.value === false
? presenceInitial.value
: (
props.initial === true ? undefined : props.initial
),
})
state.mount(getMotionElement(instance.$el), getMotionProps(), checkMotionIsHidden(instance))
})

onBeforeUnmount(() => {
state.beforeUnmount()
})
onBeforeUnmount(() => state.beforeUnmount())

onUnmounted(() => {
if (safeUnmount(getMotionElement(instance.$el)))
const el = getMotionElement(instance.$el)
if (!el?.isConnected) {
state.unmount()
}
})

onBeforeUpdate(() => {
state.beforeUpdate()
})

onUpdated(() => {
state.update({
...attrs,
...props,
initial: presenceInitial.value === false
? presenceInitial.value
: (
props.initial === true ? undefined : props.initial
),
})
state.update(getMotionProps())
})

function getProps() {
Expand All @@ -119,7 +128,7 @@ function getProps() {
}

if (!state.isMounted()) {
Object.assign(styleProps, state.target)
Object.assign(styleProps, state.baseTarget)
}
if (props.drag && props.dragListener !== false) {
Object.assign(styleProps, {
Expand All @@ -136,6 +145,7 @@ function getProps() {
...styleProps,
...props.style,
})

attrsProps.style = styleProps
return attrsProps
}
Expand All @@ -144,10 +154,10 @@ function getProps() {
<template>
<!-- @vue-ignore -->
<Primitive
v-bind="getProps()"
:as="as"
:as-child="asChild"
v-bind="getProps()"
:data-layout-group-key="layoutGroup?.key?.value"
:data-motion-id="state.id"
>
<slot />
</Primitive>
Expand Down
7 changes: 0 additions & 7 deletions packages/motion/src/components/MotionConfig.vue

This file was deleted.

Loading
Loading