-
-
Notifications
You must be signed in to change notification settings - Fork 36
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/html content #71
Changes from all commits
424e603
0901377
4924fd8
1f09d2d
dcde25e
d8fdc88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<script setup lang="ts"> | ||
import { RowValue, animate, useMotionValue, useTransform } from 'motion-v' | ||
import { onUnmounted } from 'vue' | ||
|
||
const count = useMotionValue(0) | ||
const rounded = useTransform(() => Math.round(count.get())) | ||
|
||
let controls: any | ||
watchEffect((cleanup) => { | ||
controls = animate(count, 100, { duration: 5 }) | ||
cleanup(() => { | ||
controls?.stop() | ||
}) | ||
}) | ||
|
||
onUnmounted(() => { | ||
controls?.stop() | ||
}) | ||
</script> | ||
|
||
<template> | ||
<pre | ||
class="!bg-transparent flex items-center justify-center" | ||
style="font-size: 64px; color: #4ff0b7;" | ||
> | ||
<RowValue :value="rounded" /> | ||
</pre> | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<script setup lang="ts"> | ||
import { AnimatePresence, motion } from 'motion-v' | ||
import { Popover } from 'radix-vue/namespaced' | ||
</script> | ||
|
||
<template> | ||
<div class="px-4"> | ||
<Popover.Root> | ||
<Popover.Trigger class="cursor-pointer"> | ||
Toggle popover | ||
</Popover.Trigger> | ||
<Popover.Portal> | ||
<AnimatePresence :unwrap-element="true"> | ||
<Popover.Content | ||
as-child | ||
class="px-4 py-2 bg-pink-400" | ||
> | ||
<motion.div | ||
:initial="{ opacity: 0, scale: 0.95 }" | ||
:animate="{ opacity: 1, scale: 1 }" | ||
:exit="{ opacity: 0, scale: 0.95 }" | ||
:transition="{ duration: 0.3, ease: 'easeInOut' }" | ||
> | ||
I'm a popover! | ||
</motion.div> | ||
</Popover.Content> | ||
</AnimatePresence> | ||
</Popover.Portal> | ||
</Popover.Root> | ||
</div> | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<script setup generic="T" lang="ts"> | ||
import type { MotionValue } from 'framer-motion/dom' | ||
import { getCurrentInstance, watchEffect } from 'vue' | ||
|
||
const props = defineProps<{ | ||
value: MotionValue<T> | ||
}>() | ||
|
||
const instance = getCurrentInstance().proxy | ||
watchEffect((cleanup) => { | ||
const unSub = props.value.on('change', (value) => { | ||
if (instance.$el) { | ||
instance.$el.textContent = value | ||
} | ||
}) | ||
cleanup(unSub) | ||
}) | ||
</script> | ||
|
||
<template> | ||
{{ value.get() }} | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { describe, expect, it, vi } from 'vitest' | ||
import { mount } from '@vue/test-utils' | ||
import { motionValue } from 'framer-motion/dom' | ||
import RowValue from '../RowValue.vue' | ||
|
||
describe('row-value', () => { | ||
it('should render initial value', () => { | ||
const value = motionValue(10) | ||
const wrapper = mount(RowValue, { | ||
props: { | ||
value, | ||
}, | ||
}) | ||
|
||
expect(wrapper.text()).toBe('10') | ||
}) | ||
|
||
it('should update when value changes', async () => { | ||
const value = motionValue('initial') | ||
const wrapper = mount(RowValue, { | ||
props: { | ||
value, | ||
}, | ||
}) | ||
|
||
expect(wrapper.text()).toBe('initial') | ||
|
||
value.set('updated') | ||
await wrapper.vm.$nextTick() | ||
|
||
expect(wrapper.text()).toBe('updated') | ||
}) | ||
|
||
it('should cleanup subscription on unmount', async () => { | ||
const value = motionValue('test') | ||
const unsubscribe = vi.fn() | ||
vi.spyOn(value, 'on').mockImplementation(() => unsubscribe) | ||
|
||
const wrapper = mount(RowValue, { | ||
props: { | ||
value, | ||
}, | ||
}) | ||
|
||
await wrapper.unmount() | ||
expect(unsubscribe).toHaveBeenCalled() | ||
}) | ||
|
||
it('should handle different value types', () => { | ||
const numberValue = motionValue(42) | ||
const numberWrapper = mount(RowValue, { | ||
props: { value: numberValue }, | ||
}) | ||
expect(numberWrapper.text()).toBe('42') | ||
|
||
const stringValue = motionValue('hello') | ||
const stringWrapper = mount(RowValue, { | ||
props: { value: stringValue }, | ||
}) | ||
expect(stringWrapper.text()).toBe('hello') | ||
|
||
const boolValue = motionValue(true) | ||
const boolWrapper = mount(RowValue, { | ||
props: { value: boolValue }, | ||
}) | ||
expect(boolWrapper.text()).toBe('true') | ||
}) | ||
|
||
it('should update DOM element directly when value changes', async () => { | ||
const value = motionValue('initial') | ||
const wrapper = mount(RowValue, { | ||
props: { value }, | ||
}) | ||
|
||
const el = wrapper.element | ||
value.set('updated via DOM') | ||
await wrapper.vm.$nextTick() | ||
|
||
expect(el.textContent).toBe('updated via DOM') | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,5 @@ export interface AnimatePresenceProps { | |
as?: string | ||
custom?: any | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Recommended Change: custom?: unknown |
||
onExitComplete?: VoidFunction | ||
unwrapElement?: boolean | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,7 +31,7 @@ export function nodeGroup(): NodeGroup { | |
unsubscribe() | ||
subscriptions.delete(node) | ||
} | ||
Comment on lines
31
to
33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lack of Error HandlingThe unsubscribe function (lines 31-33) is called without any error handling. If this function throws an error, it could lead to unhandled exceptions which might crash the application or lead to unexpected behavior. Recommendation: |
||
// dirtyAll() | ||
dirtyAll() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performance ConcernCalling Recommendation: |
||
}, | ||
dirty: dirtyAll, | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ export { Motion, motion, type MotionProps } from './motion' | |
export * from './animate-presence' | ||
export * from './motion-config' | ||
Comment on lines
2
to
3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using wildcard exports ( Recommendation: Explicitly list the exports from each module. This approach improves clarity, makes the code easier to refactor, and helps in maintaining a clear API surface. |
||
export * from './reorder' | ||
export { default as RowValue } from './RowValue.vue' |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,20 +23,17 @@ export class LayoutFeature extends Feature { | |
this.state.visualElement.projection?.root?.didUpdate() | ||
} | ||
|
||
beforeMount() { | ||
} | ||
|
||
mount() { | ||
const options = this.state.options | ||
const layoutGroup = this.state.options.layoutGroup | ||
if (options.layout || options.layoutId) { | ||
if (options.layout || options.layoutId || options.drag) { | ||
const projection = this.state.visualElement.projection | ||
|
||
if (projection) { | ||
projection.promote() | ||
layoutGroup?.group?.add(projection) | ||
} | ||
globalProjectionState.hasEverUpdated = true | ||
this.didUpdate() | ||
globalProjectionState.hasEverUpdated = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Direct manipulation of a global state ( |
||
} | ||
} | ||
|
||
|
@@ -57,8 +54,12 @@ export class LayoutFeature extends Feature { | |
unmount() { | ||
const layoutGroup = this.state.options.layoutGroup | ||
const projection = this.state.visualElement.projection | ||
if (layoutGroup?.group && projection) | ||
|
||
if (layoutGroup?.group && projection) { | ||
layoutGroup.group.remove(projection) | ||
this.didUpdate() | ||
} | ||
else { | ||
this.didUpdate() | ||
} | ||
Comment on lines
+61
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The property
as
is ambiguously named, which might cause confusion about its purpose. It's better to use a more descriptive name that clearly indicates its role, such aselementType
orcomponentType
, especially if it's used to specify the type of component or HTML tag to render.Recommended Change:
elementType?: string