108 lines
3.5 KiB
JavaScript
108 lines
3.5 KiB
JavaScript
// Walks the React fiber tree (via __REACT_DEVTOOLS_GLOBAL_HOOK__) to find framer-motion
|
|
// components and dump their animate/variants/transition/whileInView/whileHover props.
|
|
// Called from __CLONE_FINALIZE__.
|
|
(() => {
|
|
function cssPath(el) {
|
|
if (!el || el.nodeType !== 1) return null;
|
|
if (el.id) return `#${el.id}`;
|
|
const parts = [];
|
|
while (el && el.nodeType === 1 && parts.length < 10) {
|
|
let s = el.tagName.toLowerCase();
|
|
const p = el.parentNode;
|
|
if (p) {
|
|
const sibs = Array.from(p.children).filter((c) => c.tagName === el.tagName);
|
|
if (sibs.length > 1) s += `:nth-of-type(${sibs.indexOf(el) + 1})`;
|
|
}
|
|
parts.unshift(s);
|
|
el = el.parentNode;
|
|
}
|
|
return parts.join(' > ');
|
|
}
|
|
|
|
function isMotionComponent(fiber) {
|
|
const type = fiber?.type;
|
|
if (!type) return false;
|
|
if (type.$$typeof && type.render && type.render.displayName?.startsWith('motion.')) return true;
|
|
const dn = type.displayName || type.name;
|
|
if (typeof dn === 'string' && (dn.startsWith('motion.') || dn === 'MotionComponent')) return true;
|
|
return false;
|
|
}
|
|
|
|
function serializeProp(v) {
|
|
try {
|
|
if (v == null) return v;
|
|
if (typeof v === 'function') return null;
|
|
if (typeof v === 'object') {
|
|
const out = {};
|
|
for (const k of Object.keys(v)) {
|
|
const val = v[k];
|
|
if (typeof val === 'function') continue;
|
|
if (val && typeof val === 'object' && val.nodeType) continue;
|
|
out[k] = val;
|
|
}
|
|
return out;
|
|
}
|
|
return v;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function findFibers(fiber, out) {
|
|
if (!fiber) return;
|
|
if (isMotionComponent(fiber)) {
|
|
const props = fiber.memoizedProps || {};
|
|
let node = fiber.stateNode;
|
|
if (!(node instanceof Element)) {
|
|
// Walk down to the first DOM host for selector extraction.
|
|
let cursor = fiber.child;
|
|
while (cursor) {
|
|
if (cursor.stateNode instanceof Element) {
|
|
node = cursor.stateNode;
|
|
break;
|
|
}
|
|
cursor = cursor.child;
|
|
}
|
|
}
|
|
out.push({
|
|
selector: node instanceof Element ? cssPath(node) : null,
|
|
initial: serializeProp(props.initial),
|
|
animate: serializeProp(props.animate),
|
|
exit: serializeProp(props.exit),
|
|
variants: serializeProp(props.variants),
|
|
transition: serializeProp(props.transition),
|
|
whileHover: serializeProp(props.whileHover),
|
|
whileTap: serializeProp(props.whileTap),
|
|
whileInView: serializeProp(props.whileInView),
|
|
viewport: serializeProp(props.viewport),
|
|
});
|
|
}
|
|
if (fiber.child) findFibers(fiber.child, out);
|
|
if (fiber.sibling) findFibers(fiber.sibling, out);
|
|
}
|
|
|
|
const prevFinalize = window.__CLONE_FINALIZE__;
|
|
window.__CLONE_FINALIZE__ = function () {
|
|
if (prevFinalize)
|
|
try {
|
|
prevFinalize();
|
|
} catch {}
|
|
try {
|
|
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
if (!hook || !hook.renderers) return;
|
|
for (const [, renderer] of hook.renderers) {
|
|
if (!renderer) continue;
|
|
const roots = renderer.findFiberByHostInstance ? null : renderer.roots || null;
|
|
const scanFrom = [];
|
|
if (hook.getFiberRoots) {
|
|
const rootSet = hook.getFiberRoots(1) || new Set();
|
|
for (const r of rootSet) scanFrom.push(r.current);
|
|
}
|
|
for (const fiber of scanFrom) findFibers(fiber, window.__CLONE_CAPTURE__.framer);
|
|
}
|
|
} catch (e) {
|
|
window.__CLONE_CAPTURE__.framer.push({ error: String(e) });
|
|
}
|
|
};
|
|
})();
|