Skip to content

Commit

Permalink
[Refactor] improve internal typings and refactor to become in sync wi…
Browse files Browse the repository at this point in the history
…th the new types (#100)

* fix(createTag.test): fix createStyles's default import mock error

* refactor(animateGroup): simplify delay's calculation

* fix(useAnimateGroup): fix cleanup function

* refactor(createStyle): simplify impl. details

* refactor: improve internal typings and refactor to address the new types

* fix: change component return types from ReactNode to ReactElement

* fix(deleteRules): cast dummy stylesheet as any

* fix(createTag): avoid reading property length of undefined

* refactor(useAnimateGroup): save refs' references for use in the cleanup function

Co-authored-by: Ali.Garajian <[email protected]>
  • Loading branch information
ali-garajian and Ali.Garajian authored Jun 15, 2022
1 parent 8383396 commit f873896
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 93 deletions.
10 changes: 5 additions & 5 deletions src/animate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import isUndefined from './utils/isUndefined';
import { ALL, DEFAULT_DURATION, DEFAULT_EASE_TYPE } from './constants';
import { AnimationProps } from './types';

export default function Animate(props: AnimationProps) {
export default function Animate(props: AnimationProps): React.ReactElement {
const {
play,
children,
Expand Down Expand Up @@ -38,16 +38,16 @@ export default function Animate(props: AnimationProps) {

setStyle({
...(play || animationState.play ? end : start),
transition: `${ALL} ${duration}s ${easeType} ${parseFloat(
animationState.delay || delay,
)}s`,
transition: `${ALL} ${duration}s ${easeType} ${
animationState.delay || delay
}s`,
});

if (play && (complete || onComplete)) {
onCompleteTimeRef.current = setTimeout(() => {
complete && setStyle(complete);
onComplete && onComplete();
}, secToMs(parseFloat(animationState.delay || delay) + duration));
}, secToMs((animationState.delay || delay) + duration));
}

return () =>
Expand Down
48 changes: 28 additions & 20 deletions src/animateGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,50 @@ export interface Props {
children?: any;
}

export const AnimateContext = React.createContext({
type SequenceId = number | string;
type PartialSequence = {
play: boolean;
pause: boolean;
delay: number;
controlled: boolean;
};
type AnimationStates = Record<SequenceId, PartialSequence>;
type Register = (data: AnimationProps | AnimateKeyframesProps) => void;

interface IAnimationContext {
animationStates: AnimationStates;
register: Register;
}
export const AnimateContext = React.createContext<IAnimationContext>({
animationStates: {},
register: (data: AnimationProps | AnimateKeyframesProps): void => {},
register: () => {},
});

export default function AnimateGroup({
play,
sequences = [],
children,
}: Props) {
const [animationStates, setAnimationStates] = React.useState({});
}: Props): React.ReactElement {
const [animationStates, setAnimationStates] = React.useState<AnimationStates>(
{},
);
const animationsRef = React.useRef<{
[key: string]: AnimationProps | AnimateKeyframesProps;
}>({});

const register = React.useCallback(
(data: AnimationProps | AnimateKeyframesProps) => {
const { sequenceIndex, sequenceId } = data;
if (!isUndefined(sequenceId) || !isUndefined(sequenceIndex)) {
animationsRef.current[getSequenceId(sequenceIndex, sequenceId)] = data;
}
},
[],
);
const register: Register = React.useCallback((data) => {
const { sequenceIndex, sequenceId } = data;
if (!isUndefined(sequenceId) || !isUndefined(sequenceIndex)) {
animationsRef.current[getSequenceId(sequenceIndex, sequenceId)] = data;
}
}, []);

React.useEffect(() => {
const sequencesToAnimate =
Array.isArray(sequences) && sequences.length
? sequences
: Object.values(animationsRef.current);
const localAnimationState = {};
const localAnimationState: AnimationStates = {};

(play ? sequencesToAnimate : [...sequencesToAnimate].reverse()).reduce(
(
Expand All @@ -66,12 +79,7 @@ export default function AnimateGroup({
localAnimationState[id] = {
play,
pause: !play,
delay:
currentIndex === 0
? delay || 0
: delay
? previous + delay
: previous,
delay: (delay || 0) + previous,
controlled: true,
};

Expand Down
19 changes: 12 additions & 7 deletions src/animateKeyframes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
} from './constants';
import { AnimateKeyframesProps } from './types';

export default function AnimateKeyframes(props: AnimateKeyframesProps) {
export default function AnimateKeyframes(
props: AnimateKeyframesProps,
): React.ReactElement {
const {
children,
play = false,
Expand All @@ -35,9 +37,11 @@ export default function AnimateKeyframes(props: AnimateKeyframesProps) {
reverse: '',
});
const controlled = React.useRef(false);
const styleTagRef = React.useRef({
forward: { sheet: {} },
reverse: { sheet: {} },
const styleTagRef = React.useRef<
Record<'forward' | 'reverse', HTMLStyleElement | null>
>({
forward: null,
reverse: null,
});
const id = getSequenceId(sequenceIndex, sequenceId);
const { register, animationStates = {} } = React.useContext(AnimateContext);
Expand All @@ -47,14 +51,15 @@ export default function AnimateKeyframes(props: AnimateKeyframesProps) {
React.useEffect(() => {
const styleTag = styleTagRef.current;
const animationName = animationNameRef.current;

animationNameRef.current.forward = createRandomName();
let result = createTag({
animationName: animationNameRef.current.forward,
keyframes,
});
styleTagRef.current.forward = result.styleTag;

animationNameRef.current.reverse = createRandomName();
styleTagRef.current.forward = result.styleTag;
result = createTag({
animationName: animationNameRef.current.reverse,
keyframes: keyframes.reverse(),
Expand All @@ -68,8 +73,8 @@ export default function AnimateKeyframes(props: AnimateKeyframesProps) {
}

return () => {
deleteRule(styleTag.forward.sheet, animationName.forward);
deleteRule(styleTag.reverse.sheet, animationName.reverse);
deleteRule(styleTag.forward?.sheet, animationName.forward);
deleteRule(styleTag.reverse?.sheet, animationName.reverse);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand Down
2 changes: 2 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const DEFAULT_EASE_TYPE = 'linear';
const DEFAULT_DIRECTION = 'normal';
const DEFAULT_FILLMODE = 'none';
const RUNNING = 'running';
const PAUSED = 'paused';
const ALL = 'all';

export {
Expand All @@ -11,5 +12,6 @@ export {
DEFAULT_DIRECTION,
DEFAULT_FILLMODE,
RUNNING,
PAUSED,
ALL,
};
15 changes: 14 additions & 1 deletion src/logic/__snapshots__/createTag.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,20 @@ Object {
1,
2,
],
"insertRule": [MockFunction],
"insertRule": [MockFunction] {
"calls": Array [
Array [
".test {}",
2,
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
},
},
}
Expand Down
10 changes: 3 additions & 7 deletions src/logic/createStyle.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { Keyframes } from '../types';
import camelCaseToDash from '../utils/camelCaseToDash';

const generateKeyframes = keyframes => {
const generateKeyframes = (keyframes) => {
const animationLength = keyframes.length;
return keyframes.reduce((previous, keyframe, currentIndex) => {
const keyframePercentage =
animationLength === 2
? currentIndex * 100
: parseFloat((100 / (animationLength - 1)).toFixed(2)) * currentIndex;
parseFloat((100 / (animationLength - 1)).toFixed(2)) * currentIndex;

if (typeof keyframe === 'string') {
return `${previous} ${keyframePercentage}% {${keyframe}}`;
Expand All @@ -21,9 +19,7 @@ const generateKeyframes = keyframes => {
);
return `${previous} ${keyframePercentage}% {${keyframeContent}}`;
}
return `${previous} ${Object.keys(keyframe)[0]}% {${
Object.values(keyframe)[0]
}}`;
return `${previous} ${keys[0]}% {${keyframe[keys[0]]}}`;
}, '');
};

Expand Down
14 changes: 11 additions & 3 deletions src/logic/createTag.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import createTag from './createTag';

jest.mock('./createStyle', () => () => 'test');
jest.mock('./createStyle', () => ({
default: () => '.test {}',
}));

const documentCreateOriginal = document.createElement;
const appendChildOriginal = document.head.appendChild;
Expand All @@ -19,7 +21,10 @@ describe('createTag', () => {
it('should the tag and total length of css rules', () => {
const insertRule = jest.fn();
// @ts-ignore
document.createElement.mockReturnValue({ sheet: { cssRules: [1, 2], insertRule }, setAttribute: () => {} });
document.createElement.mockReturnValue({
sheet: { cssRules: [1, 2], insertRule },
setAttribute: () => {},
});
// @ts-ignore
const output = createTag({ keyframes: {}, animationName: 'test' });

Expand All @@ -31,7 +36,10 @@ describe('createTag', () => {
throw new Error('failed');
};
// @ts-ignore
document.createElement.mockReturnValue({ sheet: { cssRules: [1, 2], insertRule }, setAttribute: () => {} });
document.createElement.mockReturnValue({
sheet: { cssRules: [1, 2], insertRule },
setAttribute: () => {},
});
// @ts-ignore
const output = createTag({ keyframes: '', animationName: 'test' });

Expand Down
15 changes: 4 additions & 11 deletions src/logic/createTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,21 @@ export default function createTag({
keyframes: Keyframes;
animationName: string;
}): {
styleTag: any;
styleTag: HTMLStyleElement;
index: number;
} {
let styleTag = document.querySelector('style[data-id=rsi]');
let index;
let styleTag = document.querySelector<HTMLStyleElement>('style[data-id=rsi]');

if (!styleTag) {
styleTag = document.createElement('style');
styleTag.setAttribute('data-id', 'rsi');
document.head.appendChild(styleTag);
}

try {
// @ts-ignore
index = styleTag.sheet.cssRules.length;
} catch (e) {
index = 0;
}
const index = styleTag.sheet?.cssRules?.length ?? 0;

try {
// @ts-ignore
styleTag.sheet.insertRule(
styleTag.sheet?.insertRule(
createStyle({
keyframes,
animationName,
Expand Down
4 changes: 2 additions & 2 deletions src/logic/deleteRules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('deleteRules', () => {
const data = {
cssRules: {},
deleteRule,
};
} as any;
deleteRules(data, 'whatever');
expect(deleteRule).not.toBeCalled();
});
Expand All @@ -20,7 +20,7 @@ describe('deleteRules', () => {
name: 'bill',
},
},
};
} as any;
deleteRules(data, 'bill');
expect(deleteRule).toBeCalled();
});
Expand Down
10 changes: 8 additions & 2 deletions src/logic/deleteRules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
export default (sheet: any, deleteName: string): void => {
export default (
sheet: CSSStyleSheet | null | undefined,
deleteName: string,
): void => {
if (!sheet) {
return;
}
const index = Object.values(sheet.cssRules).findIndex(
({ name }: { name: string }) => name === deleteName,
({ name }: CSSKeyframesRule) => name === deleteName,
);
if (index >= 0) {
sheet.deleteRule(index);
Expand Down
18 changes: 14 additions & 4 deletions src/useAnimate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@ import secToMs from './utils/secToMs';
import { AnimationProps } from './types';
import { ALL, DEFAULT_DURATION, DEFAULT_EASE_TYPE } from './constants';

export default function useAnimate(props: AnimationProps): {
type UseAnimateProps = Pick<
AnimationProps,
| 'start'
| 'end'
| 'complete'
| 'onComplete'
| 'delay'
| 'duration'
| 'easeType'
>;
export default function useAnimate(props: UseAnimateProps): {
isPlaying: boolean;
style: React.CSSProperties;
play: (boolean) => void;
play: (isPlaying: boolean) => void;
} {
const {
start,
Expand All @@ -33,7 +43,7 @@ export default function useAnimate(props: AnimationProps): {

React.useEffect(() => {
if ((onCompleteTimeRef.current || complete) && isPlaying) {
onCompleteTimeRef.current = setTimeout((): void => {
onCompleteTimeRef.current = setTimeout(() => {
if (onComplete) {
onComplete();
}
Expand All @@ -54,7 +64,7 @@ export default function useAnimate(props: AnimationProps): {
return {
isPlaying,
style,
play: React.useCallback((isPlaying: boolean) => {
play: React.useCallback((isPlaying) => {
setAnimate({
...animate,
style: {
Expand Down
Loading

0 comments on commit f873896

Please sign in to comment.