import React, { useMemo } from 'react';
import type { IStyled, IStyledPlugin } from '@gluestack-style/react';
import { useStyled } from '@gluestack-style/react';
import {
deepMerge,
deepMergeObjects,
setObjectKeyValue,
resolvedTokenization,
} from './utils';
import { AnimatePresence } from '@legendapp/motion';
import { propertyTokenMap } from './propertyTokenMap';
function tokenizeAnimationPropsFromConfig(
props: any = {},
config: any,
animationAliases: any,
path: any = [],
tokenizedAnimatedProps: any = {}
) {
for (const prop in props) {
if (Array.isArray(props[prop])) {
path.push(prop);
setObjectKeyValue(tokenizedAnimatedProps, path, props[prop]);
path.pop();
} else if (animationAliases[prop]) {
path.push(prop);
const tokenizedValue = resolvedTokenization(props[prop], config);
setObjectKeyValue(tokenizedAnimatedProps, path, tokenizedValue);
path.pop();
} else if (typeof props[prop] === 'object') {
path.push(prop);
const tokenizedValue = resolvedTokenization(props[prop], config);
setObjectKeyValue(tokenizedAnimatedProps, path, tokenizedValue);
// path.pop();
tokenizeAnimationPropsFromConfig(
props[prop],
config,
animationAliases,
path,
tokenizedAnimatedProps
);
path.pop();
} else {
path.push(prop);
setObjectKeyValue(tokenizedAnimatedProps, path, props[prop]);
path.pop();
}
}
return tokenizedAnimatedProps;
}
function getVariantProps(props: any, theme: any) {
const variantTypes = theme?.variants ? Object.keys(theme.variants) : [];
const restProps = { ...props };
const variantProps: any = {};
variantTypes?.forEach((variant) => {
if (props[variant]) {
variantProps[variant] = props[variant];
// delete restProps[variant];
}
});
return {
variantProps,
restProps,
};
}
function resolveVariantAnimationProps(variantProps: any, styledObject: any) {
let resolvedVariant = {};
Object.keys(variantProps).forEach((variant) => {
const variantValue = variantProps[variant];
const variantObject = styledObject?.variants?.[variant]?.[variantValue];
resolvedVariant = deepMerge(resolvedVariant, variantObject);
});
return resolvedVariant;
}
export class AnimationResolver implements IStyledPlugin {
name: 'AnimationResolver';
styledUtils = {
aliases: {
':animate': 'animate',
':initial': 'initial',
':exit': 'exit',
':initialProps': 'initialProps',
':animateProps': 'animateProps',
':transition': 'transition',
':transformOrigin': 'transformOrigin',
':whileTap': 'whileTap',
':whileHover': 'whileHover',
':onAnimationComplete': 'onAnimationComplete',
} as const,
};
register(styledUtils: any) {
if (this.styledUtils) {
this.styledUtils.aliases = {
...this.styledUtils?.aliases,
...styledUtils?.aliases,
};
// @ts-ignore
this.styledUtils.tokens = {
// @ts-ignore
...this.styledUtils?.tokens,
...styledUtils?.tokens,
};
// @ts-ignore
this.styledUtils.ref = styledUtils?.ref;
}
}
constructor(styledUtils?: IStyled) {
this.register(styledUtils);
this.name = 'AnimationResolver';
}
#childrenExitPropsMap: any = {};
#extendedConfig: any = {};
inputMiddleWare(
styledObj = {},
shouldUpdateConfig: any = true
): {
// @ts-ignore
[key in keyof typeof this.styledUtils.aliases]: (typeof this.styledUtils.aliases)[key];
} {
// this.#childrenExitPropsMap = deepClone(styledObj);
const resolvedAnimatedProps = this.updateStyledObject(
styledObj,
shouldUpdateConfig
);
const resolvedStyledObjectWithAnimatedProps = deepMerge(
styledObj,
resolvedAnimatedProps
);
if (shouldUpdateConfig) {
// @ts-ignore
return styledObj;
}
return resolvedStyledObjectWithAnimatedProps;
}
updateStyledObject(
styledObject: any = {},
shouldUpdateConfig: boolean,
resolvedStyledObject: any = {},
keyPath: string[] = []
) {
const aliases = this.styledUtils?.aliases;
for (const prop in styledObject) {
if (typeof styledObject[prop] === 'object') {
keyPath.push(prop);
this.updateStyledObject(
styledObject[prop],
shouldUpdateConfig,
resolvedStyledObject,
keyPath
);
keyPath.pop();
}
// @ts-ignore
if (aliases && aliases?.[prop]) {
if (shouldUpdateConfig) {
// this.#childrenExitPropsMap[prop] = styledObject[prop];
setObjectKeyValue(
this.#childrenExitPropsMap,
[...keyPath, prop],
styledObject[prop]
);
}
const value = styledObject[prop];
// @ts-ignore
keyPath.push('props', aliases[prop]);
setObjectKeyValue(resolvedStyledObject, keyPath, value);
keyPath.pop();
keyPath.pop();
delete styledObject[prop];
}
}
return resolvedStyledObject;
}
componentMiddleWare({ Component, ExtendedConfig }: any) {
const styledConfig = this.#childrenExitPropsMap;
this.#childrenExitPropsMap = {};
const NewComponent = React.forwardRef((props: any, ref?: any) => {
const { sx, ...rest } = props;
const styledContext = useStyled();
const CONFIG = useMemo(
() => ({
...styledContext.config,
propertyTokenMap,
}),
[styledContext.config]
);
this.#extendedConfig = CONFIG;
if (ExtendedConfig) {
this.#extendedConfig = deepMerge(CONFIG, ExtendedConfig);
}
let tokenizedAnimatedProps: any = {};
const { variantProps, restProps } = getVariantProps(rest, styledConfig);
const variantStyledObject = resolveVariantAnimationProps(
variantProps,
styledConfig
);
const componentStyledObject = deepMerge(
variantStyledObject,
styledConfig
);
const animationAliases = this.styledUtils?.aliases;
const config = this.#extendedConfig;
tokenizedAnimatedProps = tokenizeAnimationPropsFromConfig(
componentStyledObject,
config,
animationAliases
);
const tokenizedSxAnimationProps: any = tokenizeAnimationPropsFromConfig(
sx,
config,
animationAliases
);
const mergedAnimatedProps = deepMerge(
tokenizedAnimatedProps,
tokenizedSxAnimationProps
);
const resolvedAnimatedStyledWithStyledObject = this.inputMiddleWare(
mergedAnimatedProps,
false
);
let isState = false;
Object.keys(restProps?.states ?? {}).forEach((state: any) => {
isState = restProps.states[state] ? true : false;
});
const animatedProps = !isState
? // @ts-ignore
resolvedAnimatedStyledWithStyledObject?.props
: {};
return (
);
});
if (NewComponent) {
//@ts-ignore
NewComponent.styled = {};
//@ts-ignore
NewComponent.styled.config = {};
//@ts-ignore
NewComponent.styled.config = styledConfig;
//@ts-ignore
NewComponent.isStyledComponent = Component?.isStyledComponent;
//@ts-ignore
NewComponent.isComposedComponent = Component?.isComposedComponent;
NewComponent.displayName = 'StyledComponent';
return NewComponent;
}
return null;
}
wrapperComponentMiddleWare() {
const AnimatedPresenceComp = React.forwardRef(
({ children, ...props }: any, ref?: any) => {
const clonedChildren: any = [];
const styledContext = useStyled();
const CONFIG = useMemo(
() => ({
...styledContext.config,
propertyTokenMap,
}),
[styledContext.config]
);
this.#extendedConfig = CONFIG;
React.Children.toArray(children).forEach((child: any) => {
if (child?.type?.displayName === 'StyledComponent') {
let tokenizedAnimatedProps: any = {};
const animationAliases = this.styledUtils?.aliases;
const componentStyledObject = child?.type?.styled?.config;
const { variantProps, restProps } = getVariantProps(
child?.props,
componentStyledObject
);
const config = CONFIG;
if (child.type.styled.resolvedProps) {
tokenizedAnimatedProps = child?.type?.styled?.resolvedProps;
} else {
const variantStyledObject = resolveVariantAnimationProps(
variantProps,
componentStyledObject
);
const componentStyledObjectWithVariants = deepMergeObjects(
componentStyledObject,
variantStyledObject
);
tokenizedAnimatedProps = tokenizeAnimationPropsFromConfig(
componentStyledObjectWithVariants,
config,
animationAliases
);
child.type.styled.resolvedProps = tokenizedAnimatedProps;
}
const tokenizedSxAnimationProps: any =
tokenizeAnimationPropsFromConfig(
child?.props?.sx,
config,
animationAliases
);
const mergedAnimatedProps = deepMergeObjects(
{},
tokenizedSxAnimationProps,
tokenizedAnimatedProps
);
const clonedChild = React.cloneElement(child, {
exit: mergedAnimatedProps?.[':exit'],
...restProps,
});
clonedChildren.push(clonedChild);
} else {
clonedChildren.push(child);
}
});
return (
{clonedChildren}
);
}
);
AnimatedPresenceComp.displayName = `AnimatePresence`;
return {
Component: AnimatedPresenceComp,
AnimatePresence: AnimatedPresenceComp,
};
}
}