New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

react-text-to-speech

Package Overview
Dependencies
Maintainers
1
Versions
121
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-text-to-speech - npm Package Compare versions

Comparing version 0.7.1 to 0.8.0

11

dist/index.d.ts

@@ -11,5 +11,7 @@ import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react";

export type Children = (childrenOptions: ChildrenOptions) => ReactNode;
export type Props = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
export type SpanProps = DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;
export type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
export type SpeechProps = {
text: string;
id?: string;
pitch?: number;

@@ -24,7 +26,10 @@ rate?: number;

useStopOverPause?: boolean;
highlightText?: boolean;
highlightProps?: SpanProps;
onError?: Function;
props?: Props;
props?: DivProps;
children?: Children;
};
export type { IconProps } from "./icons.js";
export default function Speech({ text, pitch, rate, volume, lang, voiceURI, startBtn, pauseBtn, stopBtn, useStopOverPause, onError, props, children, }: SpeechProps): string | number | boolean | React.JSX.Element | Iterable<React.ReactNode> | null | undefined;
export default function Speech({ text, id, pitch, rate, volume, lang, voiceURI, startBtn, pauseBtn, stopBtn, useStopOverPause, highlightText, highlightProps, onError, props, children, }: SpeechProps): string | number | boolean | React.JSX.Element | Iterable<React.ReactNode> | null | undefined;
export declare function HighlightedText({ id, ...props }: DivProps): React.JSX.Element;

@@ -1,6 +0,54 @@

import React, { useEffect, useState } from "react";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import React, { Fragment, cloneElement, isValidElement, useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { HiMiniStop, HiVolumeOff, HiVolumeUp } from "./icons.js";
export default function Speech({ text, pitch = 1, rate = 1, volume = 1, lang, voiceURI, startBtn = React.createElement(HiVolumeUp, null), pauseBtn = React.createElement(HiVolumeOff, null), stopBtn = React.createElement(HiMiniStop, null), useStopOverPause, onError = () => alert("Browser not supported! Try some other browser."), props = {}, children, }) {
function JSXToText(element) {
if (isValidElement(element)) {
const { children } = element.props;
if (Array.isArray(children))
return children.map((child) => JSXToText(child));
return JSXToText(children);
}
if (typeof element === "string")
return element.split(" ");
if (typeof element === "number")
return [element.toString()];
return [];
}
function findWordIndex(words, index) {
let currentIndex = 0;
function recursiveSearch(subArray, parentIndex) {
for (let i = 0; i < subArray.length; i++) {
const element = subArray[i];
if (Array.isArray(element)) {
const result = recursiveSearch(element, i);
if (result !== null)
return `${parentIndex === null ? "" : parentIndex + "-"}${result}`;
}
else if (element) {
currentIndex += element.length + 1;
if (currentIndex > index)
return `${parentIndex === null ? "" : parentIndex + "-"}${i}`;
}
}
return null;
}
return recursiveSearch(words, null);
}
export default function Speech({ text, id, pitch = 1, rate = 1, volume = 1, lang, voiceURI, startBtn = React.createElement(HiVolumeUp, null), pauseBtn = React.createElement(HiVolumeOff, null), stopBtn = React.createElement(HiMiniStop, null), useStopOverPause, highlightText = false, highlightProps = { style: { fontWeight: "bold" } }, onError = () => alert("Browser not supported! Try some other browser."), props = {}, children, }) {
const [speechStatus, setSpeechStatus] = useState("stopped");
const [useStop, setUseStop] = useState();
const words = useMemo(() => JSXToText(text), [text]);
const [highlightedIndex, setHighlightedIndex] = useState(null);
const [highlightContainer, setHighlightContainer] = useState(null);
const pause = () => { var _a; return speechStatus !== "paused" && ((_a = window.speechSynthesis) === null || _a === void 0 ? void 0 : _a.pause()); };

@@ -17,3 +65,3 @@ const stop = () => { var _a; return speechStatus !== "stopped" && ((_a = window.speechSynthesis) === null || _a === void 0 ? void 0 : _a.cancel()); };

synth.cancel();
const utterance = new window.SpeechSynthesisUtterance(text === null || text === void 0 ? void 0 : text.replace(/\s/g, " "));
const utterance = new window.SpeechSynthesisUtterance(words.join(" ").replace(/\s|,/g, " "));
utterance.pitch = pitch;

@@ -39,5 +87,7 @@ utterance.rate = rate;

setSpeechStatus("stopped");
setHighlightedIndex(null);
utterance.onpause = null;
utterance.onend = null;
utterance.onerror = null;
utterance.onboundary = null;
if (synth.paused)

@@ -49,4 +99,24 @@ synth.cancel();

utterance.onerror = setStopped;
if (highlightText)
utterance.onboundary = (event) => setHighlightedIndex(findWordIndex(words, event.charIndex));
synth.speak(utterance);
}
function highlightedText(element, parentIndex = "") {
var _a;
if (!(highlightedIndex === null || highlightedIndex === void 0 ? void 0 : highlightedIndex.startsWith(parentIndex)))
return element;
if (Array.isArray(element))
return element.map((child, index) => highlightedText(child, parentIndex === "" ? `${index}` : `${parentIndex}-${index}`));
if (isValidElement(element))
return cloneElement(element, { key: (_a = element.key) !== null && _a !== void 0 ? _a : Math.random().toString() }, highlightedText(element.props.children, parentIndex));
if (typeof element === "string" || typeof element === "number") {
const words = String(element).split(" ");
const index = +highlightedIndex.split("-").at(-1);
return (React.createElement(Fragment, { key: highlightedIndex },
words.slice(0, index).join(" ") + " ",
React.createElement("span", Object.assign({}, highlightProps), words[index]),
" " + words.slice(index + 1).join(" ")));
}
return element;
}
useEffect(() => {

@@ -57,5 +127,16 @@ var _a, _b;

}, []);
useEffect(() => {
if (highlightText)
setHighlightContainer(document.getElementById(`rtts-${id}`));
else
setHighlightContainer(null);
}, [highlightText]);
return typeof children === "function" ? (children({ speechStatus, start, pause, stop })) : (React.createElement("div", Object.assign({ style: { display: "flex", columnGap: "1rem" } }, props),
speechStatus !== "started" ? (React.createElement("span", { role: "button", onClick: start }, startBtn)) : useStop === false ? (React.createElement("span", { role: "button", onClick: pause }, pauseBtn)) : (React.createElement("span", { role: "button", onClick: stop }, stopBtn)),
useStop === false && stopBtn && (React.createElement("span", { role: "button", onClick: stop }, stopBtn))));
useStop === false && stopBtn && (React.createElement("span", { role: "button", onClick: stop }, stopBtn)),
highlightContainer && createPortal(highlightedText(text), highlightContainer)));
}
export function HighlightedText(_a) {
var { id } = _a, props = __rest(_a, ["id"]);
return React.createElement("div", Object.assign({ id: `rtts-${id}` }, props));
}
{
"name": "react-text-to-speech",
"version": "0.7.1",
"version": "0.8.0",
"description": "An easy to use react component for the Web Speech API.",

@@ -34,3 +34,4 @@ "type": "module",

"devDependencies": {
"@types/react": "^18.2.52"
"@types/react": "^18.2.52",
"@types/react-dom": "^18.2.18"
},

@@ -37,0 +38,0 @@ "scripts": {

@@ -12,2 +12,3 @@ # react-text-to-speech

- Stops speech instance on page reload.
- Highlights words as they are read.
- Handles multiple speech instances easily. See [Advanced Usage](#advanced-usage)

@@ -51,6 +52,4 @@ - Fully Customizable. See [usage with FaC](#full-customization)

This is the use case where `react-text-to-speech` outshines the other text-to-speech libraries.
Let's assume that you fetch news from any News API and the API returns 3 news in response as shown below. Now if the user clicks on `startBtn` of #1 news (assuming # as id) and then clicks on `startBtn` on #2 news before the speech instance of #1 news ends, then `react-text-to-speech` will not just stop the #1 news speech instance and start the #2 news speech instance, but will also convert the `pauseBtn` of #1 news to `startBtn`, thus avoiding any inconsistency.
Let's assume that you fetch news from any News API and the API returns 3 news in response as shown below. Now if the user clicks on `startBtn` of #1 news (assuming # as id) and then clicks on `startBtn` on #2 news before the speech instance of #1 news ends, then `react-text-to-speech` will not just stop the #1 news speech instance and start the #2 news speech instance, but will also convert the `pauseBtn` of #1 news to `startBtn`, thus avoiding any confusion.
```jsx

@@ -82,2 +81,43 @@ import React from "react";

#### Highlight Text
If `highlightText` prop to `true`, the words in the text will be highlighted as they are spoken. `<HighlightedText>` component exported by `react-text-to-speech` can be used to accomplish this purpose.
NOTE: `id` of both `<Speech>` and `<HighlightedText>` should be same to link them together.
```jsx
import React from "react";
import Speech, { HighlightedText } from "react-text-to-speech";
export default function App() {
return (
<>
<Speech
id="unique-id"
highlightText={true}
highlightProps={{ style: { color: "white", backgroundColor: "blue" } }}
text={
<div>
<span>This library is awesome!</span>
<div>
<div>
<span>It can also read and highlight </span>
<span>nested text... </span>
<span>
<span>upto </span>
<span>
<span>any level.</span>
</span>
</span>
</div>
</div>
</div>
}
/>
<HighlightedText id="unique-id" />
</>
);
}
```
#### Partial Customization

@@ -158,3 +198,3 @@

| - | - | - | - | - |
| `text` | `string` | Yes | - | It contains the text to be spoken when `startBtn` is clicked. |
| `text` | `string \| JSX.Element` | Yes | - | It contains the text to be spoken when `startBtn` is clicked. |
| `pitch` | `number (0 to 2)` | No | 1 | The pitch at which the utterance will be spoken. |

@@ -169,2 +209,4 @@ | `rate` | `number (0.1 to 10)` | No | 1 | The speed at which the utterance will be spoken. |

| `useStopOverPause` | `boolean` | No | `navigator.userAgentData.mobile` | Whether the controls should display `stopBtn` instead of `pauseBtn`. In Android devices, `SpeechSynthesis.pause()` behaves like `SpeechSynthesis.cancel()`. See [details](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/pause) |
| `highlightText` | `boolean` | No | `false` | Whether the words in the text should be highlighted as they are read or not. |
| `highlightProps` | `React.DetailedHTMLProps` | No | `{ style: { fontWeight: "bold" } }` | Props to customise the highlighted word. |
| `onError` | `Function` | No | `() => alert('Browser not supported! Try some other browser.')` | Function to be executed if browser doesn't support `Web Speech API`. |

@@ -171,0 +213,0 @@ | `props` | `React.DetailedHTMLProps` | No | - | Props to customize the `<Speech>` component. |

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc