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

fix: motion component not using and merging custom presets #205

Merged
merged 2 commits into from
Sep 4, 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
15 changes: 9 additions & 6 deletions src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,13 @@ import * as presets from '../presets'
import { directive } from '../directive'
import { slugify } from '../utils/slugify'
import { MotionComponent, MotionGroupComponent } from '../components'
import { CUSTOM_PRESETS } from '../utils/keys'

export const MotionPlugin: Plugin = {
install(app, options: MotionPluginOptions<string>) {
// Register default `v-motion` directive
app.directive('motion', directive())

// Register <Motion> component
app.component('Motion', MotionComponent)

// Register <MotionGroup> component
app.component('MotionGroup', MotionGroupComponent)

// Register presets
if (!options || (options && !options.excludePresets)) {
for (const key in presets) {
Expand Down Expand Up @@ -45,6 +40,14 @@ export const MotionPlugin: Plugin = {
app.directive(`motion-${key}`, directive(variants, true))
}
}

app.provide(CUSTOM_PRESETS, options?.directives)

// Register <Motion> component
app.component('Motion', MotionComponent)

// Register <MotionGroup> component
app.component('MotionGroup', MotionGroupComponent)
},
}

Expand Down
40 changes: 30 additions & 10 deletions src/utils/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
type PropType,
type VNode,
computed,
inject,
nextTick,
onUpdated,
reactive,
toRaw,
} from 'vue'
import type { LooseRequired } from '@vue/shared'
import defu from 'defu'
Expand All @@ -18,20 +20,15 @@ import type {
} from '../types/variants'
import { useMotion } from '../useMotion'
import { variantToStyle } from './transform'

/**
* Type guard, checks if passed string is an existing preset
*/
const isPresetKey = (val: string): val is keyof typeof presets => val in presets
import { CUSTOM_PRESETS } from './keys'

/**
* Shared component props for <Motion> and <MotionGroup>
*/
export const MotionComponentProps = {
// Preset to be loaded
preset: {
type: String as PropType<keyof typeof presets>,
validator: (val: string) => isPresetKey(val),
type: String as PropType<string>,
required: false,
},
// Instance
Expand Down Expand Up @@ -125,10 +122,24 @@ export function setupMotionComponent(
[key: number]: MotionInstance<string, MotionVariants<string>>
}>({})

const customPresets = inject<Record<string, Variant>>(CUSTOM_PRESETS)

// Preset variant or empty object if none is provided
const preset = computed(() =>
props.preset ? structuredClone(presets[props.preset]) : {},
)
const preset = computed(() => {
if (props.preset == null) {
return {}
}

if (customPresets != null && props.preset in customPresets) {
return structuredClone(toRaw(customPresets)[props.preset])
}

if (props.preset in presets) {
return structuredClone(presets[props.preset as keyof typeof presets])
}

return {}
})

// Motion configuration using inline prop variants (`:initial` ...)
const propsConfig = computed(() => ({
Expand Down Expand Up @@ -185,6 +196,15 @@ export function setupMotionComponent(

// Replay animations on component update Vue
if (import.meta.env.DEV) {
// Validate passed preset
if (
props.preset != null
&& presets?.[props.preset as keyof typeof presets] == null
&& customPresets?.[props.preset] == null
) {
console.warn(`[@vueuse/motion]: Preset \`${props.preset}\` not found.`)
}

const replayAnimation = (instance: MotionInstance<any, any>) => {
if (instance.variants?.initial) {
instance.set('initial')
Expand Down
3 changes: 3 additions & 0 deletions src/utils/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CUSTOM_PRESETS = Symbol(
import.meta.dev ? 'motionCustomPresets' : '',
)
38 changes: 37 additions & 1 deletion tests/components.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ import { intersect } from './utils/intersectionObserver'
import { getTestComponent, useCompletionFn, waitForMockCalls } from './utils'

// Register plugin
config.global.plugins.push(MotionPlugin)
config.global.plugins.push([
MotionPlugin,
{
directives: {
'custom-preset': {
initial: { scale: 1, y: 50 },
hovered: { scale: 1.2, y: 0 },
},
},
},
])

describe.each([
{ t: 'directive', name: '`v-motion` directive (shared tests)' },
Expand Down Expand Up @@ -137,6 +147,32 @@ describe.each([
})

describe('`<Motion>` component', async () => {
it('uses and merges custom presets', async () => {
const wrapper = mount(
{ render: () => h(MotionComponent) },
{
props: {
preset: 'custom-preset',
hovered: { y: 100 },
duration: 10,
},
},
)

const el = wrapper.element as HTMLDivElement
await nextTick()

// Renders initial
expect(el.style.transform).toMatchInlineSnapshot(`"translate3d(0px,50px,0px) scale(1)"`)

// Trigger hovered
await wrapper.trigger('mouseenter')
await new Promise(resolve => setTimeout(resolve, 100))

// `custom-preset` sets scale: 1.2 and `hovered` prop sets y: 100
expect(el.style.transform).toMatchInlineSnapshot(`"translate3d(0px,100px,0px) scale(1.2)"`)
})

it('#202 - preserve variant style on rerender', async () => {
const counter = ref(0)

Expand Down
Loading