motion-v
Version:
<h1 align="center"> <img width="35" height="35" alt="Motion logo" src="https://github.com/user-attachments/assets/00d6d1c3-72c4-4c2f-a664-69da13182ffc" /><br />Motion for Vue</h1>
171 lines (170 loc) • 6.3 kB
JavaScript
import { resolveMotionProps } from "../utils/resolve-motion-props.mjs";
import { layoutGroupInjectionKey, motionInjectionKey } from "../components/context.mjs";
import { defaultConfig, motionConfigInjectionKey } from "../components/motion-config/context.mjs";
import { animatePresenceInjectionKey } from "../components/animate-presence/presence.mjs";
import { createSVGStyles, createStyles } from "../state/style.mjs";
import { updateLazyFeatures } from "../features/lazy-features.mjs";
import { isSVGElement, resolveInitialValues } from "../state/utils.mjs";
import { MotionState, mountedStates } from "../state/motion-state.mjs";
import { createVisualElement } from "../state/create-visual-element.mjs";
import { domMax } from "../features/dom-max.mjs";
import { warning } from "hey-listen";
function extractMotionProps(vnode, bindingValue) {
const vnodeProps = vnode?.props;
if (!vnodeProps) return bindingValue || {};
return {
...bindingValue || {},
...vnodeProps
};
}
function cleanVNodeProps(el, vnodeProps) {
if (!vnodeProps) return;
for (const key in vnodeProps) {
const value = vnodeProps[key];
if (typeof value !== "function" && key in Element.prototype) delete el[key];
if (value != null && typeof value === "object" && key !== "style") el.removeAttribute(key);
}
}
function resolveTag(source) {
if (source instanceof Element) return source.tagName.toLowerCase();
return typeof source.type === "string" ? source.type : "div";
}
function computeStyles(values, tag, styleProp) {
if (isSVGElement(tag)) {
const { attrs, style } = createSVGStyles({ ...values }, tag, styleProp);
return {
styles: createStyles(style),
attrs
};
}
return { styles: createStyles({
...styleProp,
...values
}) };
}
function resolveSSRStyles(options) {
if (!options) return null;
const latestValues = resolveInitialValues(options);
if (Object.keys(latestValues).length === 0) return null;
return computeStyles(latestValues, options.as || "div", options.style).styles;
}
function applyInitialStyles(el, state) {
const { styles, attrs } = computeStyles(state.latestValues, resolveTag(el), state.options.style);
if (attrs) for (const key in attrs) el.setAttribute(key, String(attrs[key]));
if (styles) for (const key in styles) el.style[key] = styles[key];
}
function findComponentParent(vnode, root) {
const stack = /* @__PURE__ */ new Set();
const walk = (children) => {
for (const child of children) {
if (!child) continue;
if (child === vnode || child.el && vnode.el && child.el === vnode.el) return true;
stack.add(child);
let result$1;
if (child.suspense) result$1 = walk([child.ssContent]);
else if (Array.isArray(child.children)) result$1 = walk(child.children);
else if (child.component?.vnode) result$1 = walk([child.component?.subTree]);
if (result$1) return result$1;
stack.delete(child);
}
return false;
};
if (!walk([root.subTree])) {
warning(false, "Could not find original vnode, component will not inherit provides");
return root;
}
const result = Array.from(stack).reverse();
for (const child of result) if (child.component) return child.component;
return root;
}
function resolveProvides(vnode, binding) {
return (vnode.ctx === binding.instance.$ ? findComponentParent(vnode, binding.instance.$)?.provides : vnode.ctx?.provides) ?? binding.instance.$.provides;
}
function buildMotionOptions(motionProps, provides, tag) {
const parentState = provides[motionInjectionKey] ?? null;
const layoutGroup = provides[layoutGroupInjectionKey] ?? {};
const presenceContext = provides[animatePresenceInjectionKey] ?? {};
const config = (provides[motionConfigInjectionKey] ?? null)?.value ?? defaultConfig;
return {
parentState,
options: resolveMotionProps({
...motionProps,
as: tag
}, {
layoutGroup,
presenceContext,
config
})
};
}
function createMotionDirective(featureBundle, defaultOptions) {
const renderer = featureBundle?.renderer ?? createVisualElement;
if (featureBundle?.features) updateLazyFeatures(featureBundle.features);
function mergeMotionProps(vnode, bindingValue) {
const userProps = extractMotionProps(vnode, bindingValue);
return defaultOptions ? {
...defaultOptions,
...userProps
} : userProps;
}
return {
created(el, binding, vnode) {
const provides = resolveProvides(vnode, binding);
const { options, parentState } = buildMotionOptions(mergeMotionProps(vnode, binding.value), provides, resolveTag(el));
const state = new MotionState(options, parentState);
state.initVisualElement(renderer);
mountedStates.set(el, state);
},
mounted(el, binding, vnode) {
const state = mountedStates.get(el);
if (!state) return;
cleanVNodeProps(el, vnode.props);
applyInitialStyles(el, state);
state.mount(el);
state.updateFeatures();
},
beforeUpdate(el, binding, vnode) {
const state = mountedStates.get(el);
if (!state) return;
const provides = resolveProvides(vnode, binding);
const { options } = buildMotionOptions(mergeMotionProps(vnode, binding.value), provides, resolveTag(el));
state.beforeUpdate();
state.updateOptions(options);
},
updated(el, binding, vnode) {
const state = mountedStates.get(el);
if (!state) return;
cleanVNodeProps(el, vnode.props);
state.update();
},
beforeUnmount(el) {
const state = mountedStates.get(el);
if (!state) return;
state.beforeUnmount();
},
unmounted(el) {
const state = mountedStates.get(el);
if (!state) return;
if (!el.isConnected) state.unmount();
},
getSSRProps(binding, vnode) {
const motionProps = mergeMotionProps(vnode, binding.value);
const tag = vnode ? resolveTag(vnode) : "div";
const ssrStyles = resolveSSRStyles({
...motionProps,
as: tag
});
if (!ssrStyles) return {};
return { style: ssrStyles };
}
};
}
function createPresetDirective(preset, featureBundle) {
return createMotionDirective(featureBundle ?? domMax, preset);
}
const vMotion = createMotionDirective(domMax);
const MotionPlugin = { install(app, options) {
app.directive("motion", vMotion);
if (options?.presets) for (const [name, preset] of Object.entries(options.presets)) app.directive(name, createPresetDirective(preset));
} };
export { MotionPlugin, createMotionDirective, createPresetDirective, vMotion };