use-read-aloud 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,21 +18,47 @@ npm install use-read-aloud
18
18
 
19
19
  ```tsx
20
20
  import { useReadAloud } from "use-read-aloud";
21
+ import { useState } from "react";
22
+ import {
23
+ FastForward,
24
+ Minus,
25
+ Pause,
26
+ Play,
27
+ Plus,
28
+ Rewind,
29
+ RotateCcw,
30
+ Volume2,
31
+ } from "lucide-react";
21
32
 
22
33
  function AudioPlayer({ text }: { text: string }) {
23
- const { isReading, isPaused, toggle, start, reset } = useReadAloud(
24
- () => text,
25
- { rate: 1 }
26
- );
34
+ const [playBackSpeed, setPlayBackSpeed] = useState<number>(1);
35
+
36
+ const { isPaused, togglePlay, fastForward, seekBackward, replay } =
37
+ useReadAloud(text, {
38
+ rate: playBackSpeed,
39
+ });
27
40
 
28
41
  return (
29
42
  <div>
30
- <button onClick={toggle}>
31
- {isReading && !isPaused ? "Pause" : "Play"}
43
+ <button onClick={seekBackward}>
44
+ <Rewind />
32
45
  </button>
33
-
34
- {isReading && <button onClick={start}>Restart</button>}
35
- <button onClick={reset}>Stop</button>
46
+ <button onClick={togglePlay}>{isPaused ? <Play /> : <Pause />}</button>
47
+ <button onClick={fastForward}>
48
+ <FastForward />
49
+ </button>
50
+ <button onClick={replay}>
51
+ <RotateCcw />
52
+ </button>
53
+ <span>
54
+ <button onClick={() => setPlayBackSpeed(playBackSpeed - 0.25)}>
55
+ <Minus />
56
+ </button>
57
+ {`${playBackSpeed}x`}
58
+ <button onClick={() => setPlayBackSpeed(playBackSpeed + 0.25)}>
59
+ <Plus />
60
+ </button>
61
+ </span>
36
62
  </div>
37
63
  );
38
64
  }
@@ -41,14 +67,14 @@ function AudioPlayer({ text }: { text: string }) {
41
67
  ## API
42
68
 
43
69
  ```
44
- useReadAloud(getText, options?)
70
+ useReadAloud(text, options?)
45
71
  ```
46
72
 
47
73
  ### Parameters
48
74
 
49
- - getText - A function that returns the text to read.
75
+ - text - the text to be read.
50
76
 
51
- - options
77
+ - options - options to specify rate and pitch of the speech
52
78
 
53
79
  ```ts
54
80
  type Options = {
@@ -61,11 +87,13 @@ type Options = {
61
87
 
62
88
  ```ts
63
89
  {
64
- isReading: boolean;
65
90
  isPaused: boolean;
66
- start: () => void;
67
- toggle: () => void;
68
- reset: () => void;
91
+ play: () => void;
92
+ pause: () => void;
93
+ replay: () => void;
94
+ seekBackward: () => void;
95
+ fastForward: () => void;
96
+ togglePlay: () => void;
69
97
  }
70
98
  ```
71
99
 
@@ -93,6 +121,10 @@ Not supported:
93
121
 
94
122
  - Firefox
95
123
 
124
+ ## Changelog
125
+
126
+ See [CHANGELOG.md](./CHANGELOG.md)
127
+
96
128
  ## License
97
129
 
98
130
  MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),A=220;function I(o){const c=[];let s="";for(const n of o.split("."))(s+n+".").length>A?(c.push(s.trim()),s=n+"."):s+=n+".";return s.trim()&&c.push(s.trim()),c}function M(o,c={}){const{rate:s=1,pitch:n=1}=c,[d,k]=e.useState(!1),[b,f]=e.useState(!1),u=e.useRef(0),h=e.useRef([]),a=e.useRef(0),C=e.useCallback(()=>{h.current=[],a.current=0},[]),R=e.useCallback(()=>{const l=o();if(!l.trim())return;const t=l.split(/\n+/).map(y=>y.trim()).filter(Boolean);h.current=t.flatMap(I),a.current=0},[o]),p=e.useCallback(()=>{k(!1),f(!1)},[]),r=e.useCallback(()=>{speechSynthesis.cancel(),C(),p()},[C,p]),i=e.useCallback(()=>{const l=u.current;if(a.current>=h.current.length){p();return}const t=new SpeechSynthesisUtterance(h.current[a.current]);t.rate=s,t.pitch=n,t.onend=()=>{l===u.current&&(a.current+=1,i())},t.onerror=()=>{l===u.current&&r()},speechSynthesis.speak(t)},[p,r,n,s]),S=e.useCallback(()=>{r(),u.current+=1,k(!0),f(!1),R(),i()},[R,i]),g=e.useCallback(()=>{f(!0),speechSynthesis.pause()},[]),m=e.useCallback(()=>{f(!1),u.current+=1,speechSynthesis.cancel(),i()},[i]);return e.useEffect(()=>()=>r(),[r]),{isReading:d,isPaused:b,start:S,reset:r,pause:g,resume:m,toggle:d?b?m:g:S}}exports.useReadAloud=M;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),m=220;function P(a){const l=[];let t="";for(const c of a.split("."))(t+c+".").length>m?(l.push(t.trim()),t=c+"."):t+=c+".";return t.trim()&&l.push(t.trim()),l}function A(a,l={}){const{rate:t=1,pitch:c=1}=l,[k,h]=e.useState(!0),u=e.useRef(!0),i=e.useRef([]),r=e.useRef(0),b=e.useCallback(()=>{i.current=[],r.current=0},[]),d=e.useCallback(()=>{if(!a.trim())return;const n=a.split(/\n+/).map(g=>g.trim()).filter(Boolean);i.current=n.flatMap(P),r.current=0},[a]),o=e.useCallback(()=>{u.current=!0,h(!0)},[]),f=e.useCallback(()=>{speechSynthesis.cancel(),b(),o()},[b,o]),p=e.useCallback(()=>{if(r.current>=i.current.length){o();return}const n=new SpeechSynthesisUtterance(i.current[r.current]);n.rate=t,n.pitch=c,speechSynthesis.speak(n),n.onend=()=>{r.current+=1,p()},n.onerror=()=>{}},[o,c,t]),C=e.useCallback(()=>{h(!0),u.current=!0,speechSynthesis.pause()},[]),s=e.useCallback(()=>{h(!1),u.current=!1,i.current.length===0&&d(),speechSynthesis.cancel(),p()},[p,d]),y=e.useCallback(()=>{f(),s()},[f,s]),S=e.useCallback(()=>{r.current=Math.max(r.current-1,0),u.current||s()},[s]),R=e.useCallback(()=>{r.current=r.current+1,u.current||s()},[s]);return e.useEffect(()=>{u.current?speechSynthesis.cancel():s()},[t,s]),e.useEffect(()=>()=>f(),[f]),{isPaused:k,play:s,pause:C,replay:y,seekBackward:S,fastForward:R,togglePlay:k?s:C}}exports.useReadAloud=A;
@@ -1,55 +1,59 @@
1
- import { useState as A, useRef as d, useCallback as t, useEffect as w } from "react";
1
+ import { useState as A, useRef as d, useCallback as r, useEffect as g } from "react";
2
2
  const M = 220;
3
- function P(f) {
4
- const c = [];
3
+ function b(a) {
4
+ const i = [];
5
5
  let e = "";
6
- for (const n of f.split("."))
7
- (e + n + ".").length > M ? (c.push(e.trim()), e = n + ".") : e += n + ".";
8
- return e.trim() && c.push(e.trim()), c;
6
+ for (const c of a.split("."))
7
+ (e + c + ".").length > M ? (i.push(e.trim()), e = c + ".") : e += c + ".";
8
+ return e.trim() && i.push(e.trim()), i;
9
9
  }
10
- function q(f, c = {}) {
11
- const { rate: e = 1, pitch: n = 1 } = c, [m, R] = A(!1), [S, l] = A(!1), u = d(0), p = d([]), i = d(0), g = t(() => {
12
- p.current = [], i.current = 0;
13
- }, []), k = t(() => {
14
- const a = f();
15
- if (!a.trim()) return;
16
- const s = a.split(/\n+/).map((b) => b.trim()).filter(Boolean);
17
- p.current = s.flatMap(P), i.current = 0;
18
- }, [f]), h = t(() => {
19
- R(!1), l(!1);
20
- }, []), r = t(() => {
21
- speechSynthesis.cancel(), g(), h();
22
- }, [g, h]), o = t(() => {
23
- const a = u.current;
24
- if (i.current >= p.current.length) {
25
- h();
10
+ function I(a, i = {}) {
11
+ const { rate: e = 1, pitch: c = 1 } = i, [m, l] = A(!0), u = d(!0), o = d([]), n = d(0), y = r(() => {
12
+ o.current = [], n.current = 0;
13
+ }, []), S = r(() => {
14
+ if (!a.trim())
15
+ return;
16
+ const s = a.split(/\n+/).map((w) => w.trim()).filter(Boolean);
17
+ o.current = s.flatMap(b), n.current = 0;
18
+ }, [a]), f = r(() => {
19
+ u.current = !0, l(!0);
20
+ }, []), h = r(() => {
21
+ speechSynthesis.cancel(), y(), f();
22
+ }, [y, f]), p = r(() => {
23
+ if (n.current >= o.current.length) {
24
+ f();
26
25
  return;
27
26
  }
28
27
  const s = new SpeechSynthesisUtterance(
29
- p.current[i.current]
28
+ o.current[n.current]
30
29
  );
31
- s.rate = e, s.pitch = n, s.onend = () => {
32
- a === u.current && (i.current += 1, o());
30
+ s.rate = e, s.pitch = c, speechSynthesis.speak(s), s.onend = () => {
31
+ n.current += 1, p();
33
32
  }, s.onerror = () => {
34
- a === u.current && r();
35
- }, speechSynthesis.speak(s);
36
- }, [h, r, n, e]), y = t(() => {
37
- r(), u.current += 1, R(!0), l(!1), k(), o();
38
- }, [k, o]), C = t(() => {
39
- l(!0), speechSynthesis.pause();
40
- }, []), I = t(() => {
41
- l(!1), u.current += 1, speechSynthesis.cancel(), o();
42
- }, [o]);
43
- return w(() => () => r(), [r]), {
44
- isReading: m,
45
- isPaused: S,
46
- start: y,
47
- reset: r,
48
- pause: C,
49
- resume: I,
50
- toggle: m ? S ? I : C : y
33
+ };
34
+ }, [f, c, e]), k = r(() => {
35
+ l(!0), u.current = !0, speechSynthesis.pause();
36
+ }, []), t = r(() => {
37
+ l(!1), u.current = !1, o.current.length === 0 && S(), speechSynthesis.cancel(), p();
38
+ }, [p, S]), R = r(() => {
39
+ h(), t();
40
+ }, [h, t]), C = r(() => {
41
+ n.current = Math.max(n.current - 1, 0), u.current || t();
42
+ }, [t]), P = r(() => {
43
+ n.current = n.current + 1, u.current || t();
44
+ }, [t]);
45
+ return g(() => {
46
+ u.current ? speechSynthesis.cancel() : t();
47
+ }, [e, t]), g(() => () => h(), [h]), {
48
+ isPaused: m,
49
+ play: t,
50
+ pause: k,
51
+ replay: R,
52
+ seekBackward: C,
53
+ fastForward: P,
54
+ togglePlay: m ? t : k
51
55
  };
52
56
  }
53
57
  export {
54
- q as useReadAloud
58
+ I as useReadAloud
55
59
  };
@@ -2,12 +2,12 @@ export type Options = {
2
2
  rate?: number;
3
3
  pitch?: number;
4
4
  };
5
- export declare function useReadAloud(getText: () => string, options?: Options): {
6
- isReading: boolean;
5
+ export declare function useReadAloud(text: string, options?: Options): {
7
6
  isPaused: boolean;
8
- start: () => void;
9
- reset: () => void;
7
+ play: () => void;
10
8
  pause: () => void;
11
- resume: () => void;
12
- toggle: () => void;
9
+ replay: () => void;
10
+ seekBackward: () => void;
11
+ fastForward: () => void;
12
+ togglePlay: () => void;
13
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-read-aloud",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "description": "A React hook for reading text aloud using the Web Speech API",
6
6
  "author": "Siva Murugan",
@@ -23,13 +23,16 @@
23
23
  "dev": "vite",
24
24
  "types": "tsc --emitDeclarationOnly",
25
25
  "build": "vite build && tsc --emitDeclarationOnly",
26
- "lint": "eslint ."
26
+ "lint": "eslint .",
27
+ "test": "vitest"
27
28
  },
28
29
  "peerDependencies": {
29
30
  "react": ">=18"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@eslint/js": "^9.39.1",
34
+ "@testing-library/jest-dom": "^6.9.1",
35
+ "@testing-library/react": "^16.3.1",
33
36
  "@types/node": "^24.10.1",
34
37
  "@types/react": "^19.2.5",
35
38
  "@types/react-dom": "^19.2.3",
@@ -38,9 +41,11 @@
38
41
  "eslint-plugin-react-hooks": "^7.0.1",
39
42
  "eslint-plugin-react-refresh": "^0.4.24",
40
43
  "globals": "^16.5.0",
44
+ "happy-dom": "^20.0.11",
41
45
  "typescript": "~5.9.3",
42
46
  "typescript-eslint": "^8.46.4",
43
- "vite": "^7.2.4"
47
+ "vite": "^7.2.4",
48
+ "vitest": "^4.0.16"
44
49
  },
45
50
  "repository": {
46
51
  "type": "git",