Skip to content

Commit c23efca

Browse files
authored
fix: motion component not using and merging custom presets (#205)
* fix: motion component not using and merging custom presets * fix: always provide custom presets even if none were configured
1 parent 5f0119e commit c23efca

File tree

4 files changed

+79
-17
lines changed

4 files changed

+79
-17
lines changed

src/plugin/index.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,13 @@ import * as presets from '../presets'
55
import { directive } from '../directive'
66
import { slugify } from '../utils/slugify'
77
import { MotionComponent, MotionGroupComponent } from '../components'
8+
import { CUSTOM_PRESETS } from '../utils/keys'
89

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

14-
// Register <Motion> component
15-
app.component('Motion', MotionComponent)
16-
17-
// Register <MotionGroup> component
18-
app.component('MotionGroup', MotionGroupComponent)
19-
2015
// Register presets
2116
if (!options || (options && !options.excludePresets)) {
2217
for (const key in presets) {
@@ -45,6 +40,14 @@ export const MotionPlugin: Plugin = {
4540
app.directive(`motion-${key}`, directive(variants, true))
4641
}
4742
}
43+
44+
app.provide(CUSTOM_PRESETS, options?.directives)
45+
46+
// Register <Motion> component
47+
app.component('Motion', MotionComponent)
48+
49+
// Register <MotionGroup> component
50+
app.component('MotionGroup', MotionGroupComponent)
4851
},
4952
}
5053

src/utils/component.ts

+30-10
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import {
33
type PropType,
44
type VNode,
55
computed,
6+
inject,
67
nextTick,
78
onUpdated,
89
reactive,
10+
toRaw,
911
} from 'vue'
1012
import type { LooseRequired } from '@vue/shared'
1113
import defu from 'defu'
@@ -18,20 +20,15 @@ import type {
1820
} from '../types/variants'
1921
import { useMotion } from '../useMotion'
2022
import { variantToStyle } from './transform'
21-
22-
/**
23-
* Type guard, checks if passed string is an existing preset
24-
*/
25-
const isPresetKey = (val: string): val is keyof typeof presets => val in presets
23+
import { CUSTOM_PRESETS } from './keys'
2624

2725
/**
2826
* Shared component props for <Motion> and <MotionGroup>
2927
*/
3028
export const MotionComponentProps = {
3129
// Preset to be loaded
3230
preset: {
33-
type: String as PropType<keyof typeof presets>,
34-
validator: (val: string) => isPresetKey(val),
31+
type: String as PropType<string>,
3532
required: false,
3633
},
3734
// Instance
@@ -125,10 +122,24 @@ export function setupMotionComponent(
125122
[key: number]: MotionInstance<string, MotionVariants<string>>
126123
}>({})
127124

125+
const customPresets = inject<Record<string, Variant>>(CUSTOM_PRESETS)
126+
128127
// Preset variant or empty object if none is provided
129-
const preset = computed(() =>
130-
props.preset ? structuredClone(presets[props.preset]) : {},
131-
)
128+
const preset = computed(() => {
129+
if (props.preset == null) {
130+
return {}
131+
}
132+
133+
if (customPresets != null && props.preset in customPresets) {
134+
return structuredClone(toRaw(customPresets)[props.preset])
135+
}
136+
137+
if (props.preset in presets) {
138+
return structuredClone(presets[props.preset as keyof typeof presets])
139+
}
140+
141+
return {}
142+
})
132143

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

186197
// Replay animations on component update Vue
187198
if (import.meta.env.DEV) {
199+
// Validate passed preset
200+
if (
201+
props.preset != null
202+
&& presets?.[props.preset as keyof typeof presets] == null
203+
&& customPresets?.[props.preset] == null
204+
) {
205+
console.warn(`[@vueuse/motion]: Preset \`${props.preset}\` not found.`)
206+
}
207+
188208
const replayAnimation = (instance: MotionInstance<any, any>) => {
189209
if (instance.variants?.initial) {
190210
instance.set('initial')

src/utils/keys.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const CUSTOM_PRESETS = Symbol(
2+
import.meta.dev ? 'motionCustomPresets' : '',
3+
)

tests/components.spec.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@ import { intersect } from './utils/intersectionObserver'
77
import { getTestComponent, useCompletionFn, waitForMockCalls } from './utils'
88

99
// Register plugin
10-
config.global.plugins.push(MotionPlugin)
10+
config.global.plugins.push([
11+
MotionPlugin,
12+
{
13+
directives: {
14+
'custom-preset': {
15+
initial: { scale: 1, y: 50 },
16+
hovered: { scale: 1.2, y: 0 },
17+
},
18+
},
19+
},
20+
])
1121

1222
describe.each([
1323
{ t: 'directive', name: '`v-motion` directive (shared tests)' },
@@ -137,6 +147,32 @@ describe.each([
137147
})
138148

139149
describe('`<Motion>` component', async () => {
150+
it('uses and merges custom presets', async () => {
151+
const wrapper = mount(
152+
{ render: () => h(MotionComponent) },
153+
{
154+
props: {
155+
preset: 'custom-preset',
156+
hovered: { y: 100 },
157+
duration: 10,
158+
},
159+
},
160+
)
161+
162+
const el = wrapper.element as HTMLDivElement
163+
await nextTick()
164+
165+
// Renders initial
166+
expect(el.style.transform).toMatchInlineSnapshot(`"translate3d(0px,50px,0px) scale(1)"`)
167+
168+
// Trigger hovered
169+
await wrapper.trigger('mouseenter')
170+
await new Promise(resolve => setTimeout(resolve, 100))
171+
172+
// `custom-preset` sets scale: 1.2 and `hovered` prop sets y: 100
173+
expect(el.style.transform).toMatchInlineSnapshot(`"translate3d(0px,100px,0px) scale(1.2)"`)
174+
})
175+
140176
it('#202 - preserve variant style on rerender', async () => {
141177
const counter = ref(0)
142178

0 commit comments

Comments
 (0)