From 6f39cb22690561376fe4bd5a9946783cf136d3fd Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 20 Jun 2024 15:16:52 +0100 Subject: [PATCH] feat: add onCharTyped and onCharDeleted callback add typeSpeed and deletionSpeed prop to TypeAnimation component --- README.md | 22 ++++++++- example/src/App.tsx | 33 +++++++++++++ package.json | 2 +- src/TypeAnimation.tsx | 111 +++++++++++++++++++++++++++++++++++------- 4 files changed, 148 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 0dd4648..03fb5e3 100644 --- a/README.md +++ b/README.md @@ -81,11 +81,15 @@ The following props allows you to configure the properties for the type animatio | `direction` | no | String | front | Specifies the direction in which to perform the typing/deleting animation. It accepts two possible values: 'front' and 'back'. | | `initialDelay` | no | Number | 0 | The delay before the animation begins (in milliseconds). | | `loop` | no | Boolean | false | Whether to loop the typing animation indefinitely. | +| `onCharTyped` | no | Function | | Callback function triggered when a character is typed. | +| `onCharDeleted` | no | Function | | Callback function triggered when a character is deleted. | | `preRenderText` | no | String | None | Specifies the initial text to display. | | `repeat` | no | Number | 1 | The number of times to repeat the sequence. | | `sequence` | yes | Array | | An array of objects defining the text to be typed and animation options. | | `splitter` | no | Function | | A function to split text into individual characters or chunks for typing. | | `style` | no | TextStyle | | Additional styles for the typewriter animation container. | +| `typeSpeed` | no | Number | 100 | The speed at which characters are typed. | +| `deletionSpeed` | no | Number | 100 | The speed at which characters are deleted. | #### `sequence` Array @@ -99,9 +103,9 @@ The `sequence` prop is an array of objects, where each object defines a part of - `deleteCount`: The number of characters to delete before typing (backspacing). -- `deletionSpeed`: The speed at which characters are deleted (backspace speed, in milliseconds). The default is 100, but you can specify a custom value for individual sequences. +- `deletionSpeed`: The speed at which characters are deleted from this sequence (backspace speed, in milliseconds). The default is 100, but you can specify a custom value for individual sequences. -- `typeSpeed`: The speed at which characters are typed (typing speed, in milliseconds). The default is 100, but you can specify a custom value for individual sequences. +- `typeSpeed`: The speed at which characters are typed in this sequence(typing speed, in milliseconds). The default is 100, but you can specify a custom value for individual sequences. #### `direction` String @@ -135,6 +139,20 @@ The `cursorStyle` prop is used to apply additional styles to the cursor element. The `cursor` prop determines whether the cursor is displayed during the animation. By default, it is set to `true`, but you can set it to `false` if you don't want to display the cursor. +#### `onCharTyped` Callback + +This callback function is triggered each time a character is typed. It receives an object as an argument containing two properties: + +- character: The character that was just typed. +- currentText: The current state of the text after the character has been typed. + +#### `onCharDeleted` Callback + +This callback function is triggered each time a character is deleted. It receives an object as an argument containing two properties: + +- character: The character that was just deleted. +- currentText: The current state of the text after the character has been deleted. + **Note:** When using the `sequence` prop, you can define complex typing animations with different text, delays, and actions. Each object in the `sequence` array represents a step in the animation. Example usage of `TypeAnimationProps`: diff --git a/example/src/App.tsx b/example/src/App.tsx index 3016bd2..bc41c12 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -22,9 +22,42 @@ export default function App() { }, { text: 'One' }, ]} + onCharDeleted={({ character, currentText }) => { + console.log('char deleted', { character, currentText }); + }} + onCharTyped={({ character, currentText }) => { + console.log('char typed', { character, currentText }); + }} loop style={styles.text} /> + { + console.log('Finished first two sequences'); + }, + }, + { text: 'One Two Three' }, + { + text: 'One Two', + }, + { text: 'One' }, + ]} + loop + direction="back" + style={styles.text} + onCharDeleted={({ character, currentText }) => { + console.log('char deleted', { character, currentText }); + }} + onCharTyped={({ character, currentText }) => { + console.log('char typed', { character, currentText }); + }} + /> ); } diff --git a/package.json b/package.json index 908af13..4b4a511 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-type-animation", - "version": "0.3.0", + "version": "0.3.1", "description": "React Native Type Animation is a component library that allows you to create engaging typewriter-style text animations in your React Native applications.", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/src/TypeAnimation.tsx b/src/TypeAnimation.tsx index 42f0ce5..760cab2 100644 --- a/src/TypeAnimation.tsx +++ b/src/TypeAnimation.tsx @@ -1,4 +1,10 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import type { TextStyle } from 'react-native'; import { Animated, Text } from 'react-native'; import { @@ -45,7 +51,7 @@ const Cursor: React.FC = ({ Animated.delay(blinkSpeed), ]) ).start(); - }, []); + }, [blinkSpeed, opacity]); return ( @@ -80,12 +86,12 @@ type TypeAnimationProps = { */ deleteCount?: number; /** - * The speed at which characters are deleted (backspace speed, in milliseconds). Default: 100 + * The speed at which characters are deleted from this sequence (backspace speed, in milliseconds). Default: 100 */ deletionSpeed?: number; /** - * The speed at which characters are typed (typing speed, in milliseconds). Default: 100 + * The speed at which characters are typed in this sequence (typing speed, in milliseconds). Default: 100 */ typeSpeed?: number; } & ( @@ -147,6 +153,23 @@ type TypeAnimationProps = { * The delay before the animation begins (in milliseconds). Default: 0 */ initialDelay?: number; + /** + * Callback function triggered when a character is typed. + */ + onCharTyped?: (data: { character: string; currentText: string }) => void; + /** + * Callback function triggered when a character is deleted. + */ + onCharDeleted?: (data: { character: string; currentText: string }) => void; + /** + * The speed at which characters are deleted (backspace speed, in milliseconds). Default: 100 + */ + deletionSpeed?: number; + + /** + * The speed at which characters are typed (typing speed, in milliseconds). Default: 100 + */ + typeSpeed?: number; }; /** @@ -165,6 +188,10 @@ const TypeAnimation: React.FC = ({ direction = 'front', preRenderText = '', initialDelay = 0, + onCharTyped, + onCharDeleted, + typeSpeed = 100, + deletionSpeed = 100, }) => { const [text, setText] = useState(preRenderText); let currentText = preRenderText; @@ -174,7 +201,7 @@ const TypeAnimation: React.FC = ({ * @param textToType - The text to type. * @param speed - The typing speed. */ - const typeLetters = (textToType: string, speed = 100) => { + const typeLetters = (textToType: string, speed = typeSpeed) => { return new Promise((resolve) => { let i = 0; const textArray = splitter(textToType); @@ -184,11 +211,35 @@ const TypeAnimation: React.FC = ({ resolve(); } else { if (direction === 'front') { - setText((currText) => `${currText}${textArray[i]}`); + setText((currText) => { + const character = textArray[i]; + const word = `${currText}${character}`; + if (character) { + const data = { + character, + currentText: word, + }; + if (onCharTyped) { + onCharTyped(data); + } + } + return word; + }); } else { - setText( - (currText) => `${textArray[textArray.length - i - 1]}${currText}` - ); + setText((currText) => { + const character = textArray[textArray.length - i - 1]; + const word = `${character}${currText}`; + if (character) { + const data = { + character, + currentText: word, + }; + if (onCharTyped) { + onCharTyped(data); + } + } + return word; + }); } } i++; @@ -201,7 +252,7 @@ const TypeAnimation: React.FC = ({ * @param count - The number of letters to delete. * @param speed - The deletion speed. */ - const deleteLetters = (count: number, speed = 100) => { + const deleteLetters = (count: number, speed = deletionSpeed) => { return new Promise((resolve) => { let i = 0; const interval = setInterval(() => { @@ -210,11 +261,35 @@ const TypeAnimation: React.FC = ({ resolve(); } else { if (direction === 'front') { - setText( - (currtext) => `${currtext.substring(0, currtext.length - 1)}` - ); + setText((currtext) => { + const word = `${currtext.substring(0, currtext.length - 1)}`; + const character = currtext[currtext.length - 1]; + if (character) { + const data = { + character, + currentText: word, + }; + if (onCharDeleted) { + onCharDeleted(data); + } + } + return word; + }); } else { - setText((currtext) => `${currtext.substring(1, currtext.length)}`); + setText((currtext) => { + const word = `${currtext.substring(1, currtext.length)}`; + const character = currtext[0]; + if (character) { + const data = { + character, + currentText: word, + }; + if (onCharDeleted) { + onCharDeleted(data); + } + } + return word; + }); } } i++; @@ -267,7 +342,7 @@ const TypeAnimation: React.FC = ({ } }; - const firstFunction = () => { + const firstFunction = useCallback(() => { if (loop) { const run = async () => { await runSequence(); @@ -277,7 +352,9 @@ const TypeAnimation: React.FC = ({ } else { repeatFunctionNTimes(runSequence, repeat); } - }; + /* eslint-disable react-hooks/exhaustive-deps */ + }, []); + useEffect(() => { if (initialDelay) { setTimeout(() => { @@ -286,7 +363,7 @@ const TypeAnimation: React.FC = ({ } else { firstFunction(); } - }, []); + }, [initialDelay, firstFunction]); const cursorComponent = useMemo(() => { return cursor ? (