react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
179 lines (156 loc) • 5.47 kB
text/typescript
import { ReanimatedError } from '../../../../../common';
import type {
AnyRecord,
CSSTransitionProperties,
CSSTransitionProperty,
} from '../../../../types';
import {
areArraysEqual,
convertPropertyToArray,
deepEqual,
} from '../../../../utils';
import type {
NormalizedCSSTransitionConfig,
NormalizedCSSTransitionConfigUpdates,
NormalizedSingleCSSTransitionSettings,
} from '../../types';
import {
normalizeDelay,
normalizeDuration,
normalizeTimingFunction,
} from '../common';
import { normalizeTransitionBehavior } from './settings';
import type { ExpandedCSSTransitionConfigProperties } from './shorthand';
import {
createEmptyTransitionConfig,
parseTransitionShorthand,
} from './shorthand';
export const ERROR_MESSAGES = {
invalidTransitionProperty: (
transitionProperty: CSSTransitionProperty | undefined | string[]
) => `Invalid transition property "${JSON.stringify(transitionProperty)}"`,
};
function getExpandedConfigProperties(
config: CSSTransitionProperties
): ExpandedCSSTransitionConfigProperties {
const result: AnyRecord = config.transition
? parseTransitionShorthand(config.transition)
: createEmptyTransitionConfig();
for (const [key, value] of Object.entries(config)) {
result[key] = convertPropertyToArray(value);
}
return result as ExpandedCSSTransitionConfigProperties;
}
const hasTransition = ({
transitionProperty,
...rest
}: ExpandedCSSTransitionConfigProperties) => {
if (transitionProperty.length) {
const hasNone = transitionProperty[0] === 'none';
// We allow either all values to be 'none' or none of them to be 'none'
if (transitionProperty.some((prop) => (prop === 'none') !== hasNone)) {
throw new ReanimatedError(
ERROR_MESSAGES.invalidTransitionProperty(transitionProperty)
);
}
return !hasNone;
}
// transitionProperty defaults to 'all' if not specified but there are
// other transition properties
return Object.values(rest).some((value) => value.length);
};
export function normalizeCSSTransitionProperties(
config: CSSTransitionProperties
): NormalizedCSSTransitionConfig | null {
const expandedProperties = getExpandedConfigProperties(config);
if (!hasTransition(expandedProperties)) {
return null;
}
const {
transitionProperty,
transitionDuration,
transitionTimingFunction,
transitionDelay,
transitionBehavior,
} = expandedProperties;
const specificProperties: string[] = [];
let allPropertiesTransition = false;
const settings: Record<string, NormalizedSingleCSSTransitionSettings> = {};
if (!transitionProperty.length) {
// For cases when transition property hasn't been explicitly specified
// (e.g. when only the transitionDuration is set)
transitionProperty.push('all');
}
// Go from the last to the first property to ensure that the last
// one entry for the same property is used without having to override
// it multiple times if specified more than once (we just take the last
// occurrence and ignore remaining ones)
for (let i = transitionProperty.length - 1; i >= 0; i--) {
const property = transitionProperty[i];
// Continue if there was a prop with the same name specified later
// (we don't want to override the last occurrence of the property)
if (settings?.[property]) {
continue;
}
if (property === 'all') {
allPropertiesTransition = true;
} else {
specificProperties.push(property);
}
settings[property] = {
duration: normalizeDuration(
transitionDuration[i % transitionDuration.length]
),
timingFunction: normalizeTimingFunction(
transitionTimingFunction[i % transitionTimingFunction.length]
),
delay: normalizeDelay(transitionDelay[i % transitionDelay.length]),
allowDiscrete: normalizeTransitionBehavior(
transitionBehavior[i % transitionBehavior.length]
),
};
// 'all' transition property overrides all properties before it,
// so we don't need to process them
if (allPropertiesTransition) {
break;
}
}
return {
properties: allPropertiesTransition ? 'all' : specificProperties.reverse(),
settings,
};
}
export function getNormalizedCSSTransitionConfigUpdates(
oldConfig: NormalizedCSSTransitionConfig,
newConfig: NormalizedCSSTransitionConfig
): NormalizedCSSTransitionConfigUpdates {
const configUpdates: NormalizedCSSTransitionConfigUpdates = {};
if (
oldConfig.properties !== newConfig.properties &&
(!Array.isArray(oldConfig.properties) ||
!Array.isArray(newConfig.properties) ||
!areArraysEqual(oldConfig.properties, newConfig.properties))
) {
configUpdates.properties = newConfig.properties;
}
const newSettingsKeys = Object.keys(newConfig.settings);
const oldSettingsKeys = Object.keys(oldConfig.settings);
if (newSettingsKeys.length !== oldSettingsKeys.length) {
configUpdates.settings = newConfig.settings;
} else {
for (const key of newSettingsKeys) {
if (
!oldConfig.settings[key] ||
// TODO - think of a better way to compare settings (necessary for
// timing functions comparison). Maybe add some custom way instead
// of deepEqual
!deepEqual(oldConfig.settings[key], newConfig.settings[key])
) {
configUpdates.settings = newConfig.settings;
break;
}
}
}
return configUpdates;
}
;