Skip to content

Commit 9aab8c8

Browse files
committed
docs(changeset): Fix step and children updates.
1 parent 704bd9b commit 9aab8c8

File tree

4 files changed

+72
-62
lines changed

4 files changed

+72
-62
lines changed

.changeset/gold-gifts-draw.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"typingfx": patch
3+
---
4+
5+
Fix step and children updates.

lib/src/client/type-out/type-out.tsx

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { HTMLProps, ReactNode, useEffect, useMemo, useRef, useState } from "react";
22
import styles from "./type-out.module.scss";
33
import { Optional } from "@m2d/core/utils";
4-
import { addAnimationListeners, setupTypingFX } from "./utils";
4+
import { addAnimationListeners, listElements, setupTypingFX } from "./utils";
55

66
/**
77
* Props for the TypeOut component.
@@ -63,8 +63,10 @@ const TypingAnimation = ({
6363
...props
6464
}: DefaultTypeOutProps) => {
6565
const [processing, setProcessing] = useState(true);
66+
6667
const animatedSteps = useMemo(() => {
6768
const newSteps = children ? [...steps, children] : steps;
69+
console.log({ children, steps, newSteps });
6870
if (newSteps.length < 2) newSteps.unshift("", "");
6971
return newSteps.map(setupTypingFX);
7072
}, [children, steps]);
@@ -73,48 +75,34 @@ const TypingAnimation = ({
7375

7476
// Trigger animations on mount or changes
7577
useEffect(() => {
76-
if (!containerRef.current) return;
7778
setProcessing(true);
7879

79-
const enqueue = (node: Element, els: Element[]) => {
80-
els.push(node);
81-
const el = node as HTMLElement;
82-
if (el.classList.contains(styles.hk)) el.style.setProperty("--n", "0");
83-
else if (el.classList.contains(styles.word)) {
84-
el.style.setProperty("--n", `${el.textContent?.length ?? 0}`);
85-
el.style.setProperty("--w", `${el.offsetWidth}px`);
80+
requestAnimationFrame(() => {
81+
if (!containerRef.current) return;
82+
const elements = listElements(containerRef.current);
83+
84+
for (let i = 0; i < elements[0].length; i++) {
85+
const el = elements[0][i] as HTMLElement;
86+
const nextEl = elements[0][i + 1] as HTMLElement;
87+
el.onanimationend = (e: AnimationEvent) => {
88+
e.stopPropagation();
89+
el.style.width = el.style.getPropertyValue("--w");
90+
el.classList.remove(styles.type, styles.hk);
91+
92+
// skipcq: JS-0354
93+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
94+
nextEl
95+
? nextEl.classList.add(styles.type)
96+
: (addAnimationListeners(elements as HTMLElement[][], repeat, noCursorAfterAnimEnd),
97+
el.classList.add(styles.del));
98+
};
8699
}
87-
Array.from(node.children).forEach(child => enqueue(child, els));
88-
};
89-
90-
const elements: Element[][] = [];
91-
Array.from(containerRef.current.children).forEach(child => {
92-
const els: Element[] = [];
93-
enqueue(child, els);
94-
elements.push(els);
95-
});
96100

97-
for (let i = 0; i < elements[0].length; i++) {
98-
const el = elements[0][i] as HTMLElement;
99-
const nextEl = elements[0][i + 1] as HTMLElement;
100-
const animListener = (e: AnimationEvent) => {
101-
e.stopPropagation();
102-
el.removeEventListener("animationend", animListener);
103-
el.style.width = el.style.getPropertyValue("--w");
104-
el.classList.remove(styles.type, styles.hk);
105-
106-
// skipcq: JS-0354
107-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
108-
nextEl
109-
? nextEl.classList.add(styles.type)
110-
: (addAnimationListeners(elements as HTMLElement[][], repeat, noCursorAfterAnimEnd),
111-
el.classList.add(styles.del));
112-
};
113-
el.addEventListener("animationend", animListener);
114-
}
101+
console.log({ elements });
115102

116-
requestAnimationFrame(() => elements[0][0].classList.add(styles.type));
117-
setProcessing(false);
103+
requestAnimationFrame(() => elements[0][0].classList.add(styles.type));
104+
setProcessing(false);
105+
});
118106
}, [animatedSteps, repeat, noCursorAfterAnimEnd]);
119107

120108
// Respect pause and pause on visibility hidden

lib/src/client/type-out/utils.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,30 @@ export const setupTypingFX = (children: ReactNode): ReactNode => {
5353
return handleNode(children);
5454
};
5555

56+
/**
57+
* list elements and apply custom CSS vars
58+
*/
59+
const enqueue = (node: Element, els: Element[]) => {
60+
els.push(node);
61+
const el = node as HTMLElement;
62+
if (el.classList.contains(styles.hk)) el.style.setProperty("--n", "0");
63+
else if (el.classList.contains(styles.word)) {
64+
el.style.setProperty("--n", `${el.textContent?.length ?? 0}`);
65+
el.style.setProperty("--w", `${el.offsetWidth}px`);
66+
}
67+
Array.from(node.children).forEach(child => enqueue(child, els));
68+
};
69+
/**
70+
* list elements and apply custom CSS vars
71+
*/
72+
export const listElements = (root: HTMLElement): HTMLElement[][] => {
73+
return Array.from(root.children).map(child => {
74+
const els: Element[] = [];
75+
enqueue(child, els);
76+
return els;
77+
}) as HTMLElement[][];
78+
};
79+
5680
/**
5781
* Computes differing start index for each pair of steps to know what to animate.
5882
*/
@@ -119,7 +143,7 @@ export const addAnimationListeners = (
119143
const nextEl = elements[i][j + 1];
120144
const prevEl = elements[i][j - 1];
121145

122-
const animListener = (e: AnimationEvent) => {
146+
el.onanimationend = (e: AnimationEvent) => {
123147
e.stopPropagation();
124148
if (el.classList.contains(styles.type)) {
125149
updateAfterTypeAnim(el);
@@ -144,7 +168,6 @@ export const addAnimationListeners = (
144168
} else if (prevEl) prevEl.classList.add(styles.del);
145169
}
146170
};
147-
el.addEventListener("animationend", animListener);
148171
}
149172
}
150173
};

packages/shared/src/client/demo/demo.tsx

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,34 @@ import { TypeOut } from "typingfx";
44
import styles from "./demo.module.scss";
55
import { useEffect, useMemo, useState } from "react";
66

7-
const steps = [
8-
<div key={0}>Hare</div>,
9-
<div key={1}>
10-
Hare <span style={{ color: "green" }}>Krishna</span> Hare Krishna Hare Krishna
11-
<p>Hare Krishna</p>
12-
<p>{3000}Hare Rama</p>
13-
</div>,
14-
<p key={2}>Hare Krishna {3000} Hare Krishna Krishna Krishna Hare Hare</p>,
15-
<p key={3}>Hare Krishna Hare Krishna Hare Rama Hare Rama</p>,
16-
];
17-
187
/** React live demo */
198
export function Demo() {
209
const [paused, setPaused] = useState(false);
2110
const [user, setUser] = useState<{ name: string }>();
2211
useEffect(() => {
2312
setTimeout(() => {
24-
// setUser({ name: "Mayank" });
13+
setUser({ name: "Mayank" });
2514
}, 3000);
2615
}, []);
27-
const welcome = useMemo(
28-
() => (
29-
<>
30-
{1000}Welcome <i>{`${user?.name},`}</i> {500}How can I help you?{-3000}
31-
</>
32-
),
33-
[user],
16+
const steps = [
17+
<div key={0}>Hare</div>,
18+
<div key={1}>
19+
Hare <span style={{ color: "green" }}>Krishna</span> Hare Krishna Hare Krishna
20+
<p>Hare Krishna</p>
21+
<p>{3000}Hare Rama</p>
22+
</div>,
23+
<p key={2}>Hare Krishna {3000} Hare Krishna Krishna Krishna Hare Hare</p>,
24+
<p key={3}>Hare Krishna Hare Krishna Hare Rama Hare Rama</p>,
25+
];
26+
const welcome = (
27+
<>
28+
{1000}Welcome <i>{`${user?.name},`}</i> {500}How can I help you?{-3000}
29+
</>
3430
);
3531
return (
3632
<div className={styles.demo}>
3733
<button onClick={() => setPaused(!paused)}>{paused ? "Resume" : "Pause"}</button>
38-
<TypeOut paused={paused} repeat={3}>
39-
{welcome}
40-
</TypeOut>
34+
<TypeOut paused={paused}>{welcome}</TypeOut>
4135
</div>
4236
);
4337
}

0 commit comments

Comments
 (0)