diff --git a/.gitignore b/.gitignore
index fd10abd9..e61b718d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.DS_Store
node_modules
dist/
+.idea/
diff --git a/README.md b/README.md
index 2469249c..aff79537 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ For more advanced, controlled typing effects, TypeIt comes with companion functi
- Define strings programmatically or directly in the HTML (a useful fallback in case user doesn't have JavaScript enabled, as well as for SEO).
- Handle HTML (even nested tags!) with ease, preserving all of its attributes (classes, ids, etc.).
- Offered as an ES module for modern bundlers, or a UMD library for loading via a traditional `
diff --git a/packages/typeit/package-lock.json b/packages/typeit/package-lock.json
index a32327dc..f1b11be4 100644
--- a/packages/typeit/package-lock.json
+++ b/packages/typeit/package-lock.json
@@ -1,18 +1,19 @@
{
"name": "typeit",
- "version": "8.7.0",
+ "version": "8.7.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "typeit",
- "version": "8.7.0",
+ "version": "8.7.1",
"hasInstallScript": true,
"license": "GPL-3.0",
"devDependencies": {
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@types/web-animations-js": "^2.2.12",
+ "grapheme-splitter": "^1.0.4",
"jest": "^29.3.1",
"jest-cli": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
@@ -3969,6 +3970,12 @@
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
"dev": true
},
+ "node_modules/grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -10207,6 +10214,12 @@
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
"dev": true
},
+ "grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
diff --git a/packages/typeit/package.json b/packages/typeit/package.json
index bd9f1a16..52a8dfaa 100644
--- a/packages/typeit/package.json
+++ b/packages/typeit/package.json
@@ -42,6 +42,7 @@
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@types/web-animations-js": "^2.2.12",
+ "grapheme-splitter": "^1.0.4",
"jest": "^29.3.1",
"jest-cli": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
@@ -52,10 +53,12 @@
"jest": {
"clearMocks": true,
"testPathIgnorePatterns": [
- "/__tests__/setup.js"
+ "/__tests__/setup.js",
+ "/__tests__/helpers/util.js"
],
"setupFilesAfterEnv": [
- "./__tests__/setup.js"
+ "./__tests__/setup.js",
+ "./__tests__/helpers/util.js"
],
"testEnvironment": "jsdom"
}
diff --git a/packages/typeit/src/constants.ts b/packages/typeit/src/constants.ts
index 968fa535..886dc763 100644
--- a/packages/typeit/src/constants.ts
+++ b/packages/typeit/src/constants.ts
@@ -1,4 +1,5 @@
import { CursorOptions, Options } from "./types";
+import toArray from "./helpers/toArray";
export const DATA_ATTRIBUTE = "data-typeit-id";
export const CURSOR_CLASS = "ti-cursor";
@@ -41,6 +42,7 @@ export const DEFAULT_OPTIONS: Options & {
startDelay: 250,
startDelete: false,
strings: [],
+ stringSpliterator: (str) => toArray(str),
waitUntilVisible: false,
beforeString: () => {},
afterString: () => {},
diff --git a/packages/typeit/src/helpers/chunkStrings.ts b/packages/typeit/src/helpers/chunkStrings.ts
index 470bde6a..58a05aeb 100644
--- a/packages/typeit/src/helpers/chunkStrings.ts
+++ b/packages/typeit/src/helpers/chunkStrings.ts
@@ -53,8 +53,11 @@ export function walkElementNodes(
* Convert string to array of chunks that will be later
* used to construct a TypeIt queue.
*/
-export function chunkStringAsHtml(string: string): El[] {
- return walkElementNodes(getParsedBody(string));
+export function chunkStringAsHtml(
+ string: string,
+ stringSpliterator: (str: string) => string[]
+): El[] {
+ return walkElementNodes(getParsedBody(string, stringSpliterator));
}
/**
@@ -63,11 +66,15 @@ export function chunkStringAsHtml(string: string): El[] {
*
* @param {string} str
* @param {boolean} asHtml
+ * @param {(string) => string[]}stringSpliterator
* @return {array}
*/
export function maybeChunkStringAsHtml(
str: string,
- asHtml = true
+ asHtml = true,
+ stringSpliterator: (str: string) => string[]
): Partial[] {
- return asHtml ? chunkStringAsHtml(str) : toArray(str).map(createTextNode);
+ return asHtml
+ ? chunkStringAsHtml(str, stringSpliterator)
+ : toArray(stringSpliterator(str)).map(createTextNode);
}
diff --git a/packages/typeit/src/helpers/expandTextNodes.ts b/packages/typeit/src/helpers/expandTextNodes.ts
index fab067cf..52dfa37f 100644
--- a/packages/typeit/src/helpers/expandTextNodes.ts
+++ b/packages/typeit/src/helpers/expandTextNodes.ts
@@ -1,10 +1,13 @@
import createTextNode from "./createTextNode";
import { El } from "../types";
-let expandTextNodes = (element: El): El => {
+const expandTextNodes = (
+ element: El,
+ stringSpliterator: (str: string) => string[]
+): El => {
[...element.childNodes].forEach((child) => {
if (child.nodeValue) {
- [...child.nodeValue].forEach((c) => {
+ stringSpliterator(child.nodeValue).forEach((c) => {
child.parentNode.insertBefore(createTextNode(c), child);
});
@@ -12,7 +15,7 @@ let expandTextNodes = (element: El): El => {
return;
}
- expandTextNodes(child as El);
+ expandTextNodes(child as El, stringSpliterator);
});
return element;
diff --git a/packages/typeit/src/helpers/getAllChars.ts b/packages/typeit/src/helpers/getAllChars.ts
index 08c6942a..6f7f4980 100644
--- a/packages/typeit/src/helpers/getAllChars.ts
+++ b/packages/typeit/src/helpers/getAllChars.ts
@@ -7,9 +7,16 @@ import { walkElementNodes } from "./chunkStrings";
* Get a flattened array of text nodes that have been typed.
* This excludes any cursor character that might exist.
*/
-let getAllChars = (element: El) => {
+let getAllChars = (
+ element: El,
+ stringSpliterator: (str: string) => string[]
+) => {
if (isInput(element)) {
- return toArray(element.value);
+ if (typeof element.value === "string") {
+ return toArray(stringSpliterator(element.value));
+ } else {
+ return toArray(element.value);
+ }
}
return walkElementNodes(element, true).filter(
diff --git a/packages/typeit/src/helpers/getParsedBody.ts b/packages/typeit/src/helpers/getParsedBody.ts
index defb0348..96833c22 100644
--- a/packages/typeit/src/helpers/getParsedBody.ts
+++ b/packages/typeit/src/helpers/getParsedBody.ts
@@ -5,9 +5,9 @@ import expandTextNodes from "./expandTextNodes";
* Parse a string as HTML and return the body
* of the parsed document, with all text nodes expanded.
*/
-export default (content): El => {
+export default (content, stringSpliterator: (str: string) => string[]): El => {
let doc = document.implementation.createHTMLDocument();
doc.body.innerHTML = content;
- return expandTextNodes(doc.body as El);
+ return expandTextNodes(doc.body as El, stringSpliterator);
};
diff --git a/packages/typeit/src/index.ts b/packages/typeit/src/index.ts
index f05cc81e..f60bee9b 100644
--- a/packages/typeit/src/index.ts
+++ b/packages/typeit/src/index.ts
@@ -88,7 +88,7 @@ const TypeIt: TypeItInstance = function (element, options = {}) {
let _getPace = (index: number = 0): number => calculatePace(_opts)[index];
- let _getAllChars = (): El[] => getAllChars(_element);
+ let _getAllChars = (): El[] => getAllChars(_element, _opts.stringSpliterator);
let _maybeAppendPause = (opts: ActionOpts = {}) => {
let delay = opts.delay;
@@ -145,7 +145,10 @@ const TypeIt: TypeItInstance = function (element, options = {}) {
return cursor as El;
}
- cursor.innerHTML = getParsedBody(_opts.cursorChar).innerHTML;
+ cursor.innerHTML = getParsedBody(
+ _opts.cursorChar,
+ _opts.stringSpliterator
+ ).innerHTML;
return cursor as El;
};
@@ -253,7 +256,7 @@ const TypeIt: TypeItInstance = function (element, options = {}) {
if (_opts.startDelete) {
_element.innerHTML = existingMarkup;
- expandTextNodes(_element);
+ expandTextNodes(_element, _opts.stringSpliterator);
_addSplitPause(
duplicate(
@@ -537,7 +540,11 @@ const TypeIt: TypeItInstance = function (element, options = {}) {
let { instant } = actionOpts;
let bookEndQueueItems = _generateTemporaryOptionQueueItems(actionOpts);
- let chars = maybeChunkStringAsHtml(string, _opts.html);
+ let chars = maybeChunkStringAsHtml(
+ string,
+ _opts.html,
+ _opts.stringSpliterator
+ );
let charsAsQueueItems = chars.map((char): QueueItem => {
return {
diff --git a/packages/typeit/src/types.ts b/packages/typeit/src/types.ts
index 8e12e225..b0ca7909 100644
--- a/packages/typeit/src/types.ts
+++ b/packages/typeit/src/types.ts
@@ -33,6 +33,22 @@ export interface Options {
startDelay?: number;
startDelete?: boolean;
strings?: string[] | string;
+ /**
+ * String splitter function, can be used to split emoji's or graphemes.
+ *
+ * @example
+ * ```js
+ * import GraphemeSplitter from "grapheme-splitter";
+ * const splitter = new GraphemeSplitter();
+ * new TypeIt("#element", {
+ * strings: "👋🏻👋🏼👋🏽👋🏾👋🏿",
+ * stringSpliterator: (str) => splitter.splitGraphemes(str),
+ * });
+ * ```
+ * @see https://www.npmjs.com/package/grapheme-splitter
+ * @default null
+ */
+ stringSpliterator?: (str: string) => string[];
waitUntilVisible?: boolean;
beforeString?: Function;
afterString?: Function;