talking-head-studio 0.2.6 → 0.2.8

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.
Files changed (111) hide show
  1. package/README.md +8 -7
  2. package/dist/TalkingHead.d.ts +0 -1
  3. package/dist/TalkingHead.js +78 -35
  4. package/dist/TalkingHead.web.d.ts +1 -3
  5. package/dist/TalkingHead.web.js +118 -48
  6. package/dist/appearance/apply.d.ts +0 -1
  7. package/dist/appearance/apply.js +8 -5
  8. package/dist/appearance/index.d.ts +0 -1
  9. package/dist/appearance/index.js +9 -3
  10. package/dist/appearance/matchers.d.ts +0 -1
  11. package/dist/appearance/matchers.js +4 -1
  12. package/dist/appearance/schema.d.ts +0 -1
  13. package/dist/appearance/schema.js +4 -1
  14. package/dist/editor/AvatarCanvas.d.ts +1 -2
  15. package/dist/editor/AvatarCanvas.js +44 -60
  16. package/dist/editor/AvatarCanvasErrorBoundary.d.ts +1 -2
  17. package/dist/editor/AvatarCanvasErrorBoundary.js +9 -14
  18. package/dist/editor/AvatarModel.d.ts +1 -2
  19. package/dist/editor/AvatarModel.js +17 -13
  20. package/dist/editor/RigidAccessory.d.ts +1 -2
  21. package/dist/editor/RigidAccessory.js +19 -17
  22. package/dist/editor/SkinnedClothing.d.ts +1 -2
  23. package/dist/editor/SkinnedClothing.js +46 -9
  24. package/dist/editor/index.d.ts +0 -1
  25. package/dist/editor/index.js +11 -4
  26. package/dist/editor/types.d.ts +0 -1
  27. package/dist/editor/types.js +2 -1
  28. package/dist/html.d.ts +0 -1
  29. package/dist/html.js +21 -10
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.js +20 -2
  32. package/dist/index.web.d.ts +0 -1
  33. package/dist/index.web.js +20 -2
  34. package/dist/sketchfab/api.d.ts +0 -1
  35. package/dist/sketchfab/api.js +10 -4
  36. package/dist/sketchfab/categories.d.ts +0 -1
  37. package/dist/sketchfab/categories.js +5 -2
  38. package/dist/sketchfab/index.d.ts +0 -1
  39. package/dist/sketchfab/index.js +13 -3
  40. package/dist/sketchfab/types.d.ts +0 -1
  41. package/dist/sketchfab/types.js +2 -1
  42. package/dist/sketchfab/useSketchfabSearch.d.ts +0 -1
  43. package/dist/sketchfab/useSketchfabSearch.js +20 -17
  44. package/dist/voice/convertToWav.d.ts +0 -1
  45. package/dist/voice/convertToWav.js +4 -1
  46. package/dist/voice/index.d.ts +0 -1
  47. package/dist/voice/index.js +9 -3
  48. package/dist/voice/useAudioPlayer.d.ts +0 -1
  49. package/dist/voice/useAudioPlayer.js +7 -4
  50. package/dist/voice/useAudioRecording.d.ts +0 -1
  51. package/dist/voice/useAudioRecording.js +20 -17
  52. package/package.json +10 -8
  53. package/dist/TalkingHead.d.ts.map +0 -1
  54. package/dist/TalkingHead.web.d.ts.map +0 -1
  55. package/dist/__tests__/TalkingHead.test.d.ts +0 -2
  56. package/dist/__tests__/TalkingHead.test.d.ts.map +0 -1
  57. package/dist/__tests__/TalkingHead.test.js +0 -23
  58. package/dist/__tests__/sketchfab.test.d.ts +0 -2
  59. package/dist/__tests__/sketchfab.test.d.ts.map +0 -1
  60. package/dist/__tests__/sketchfab.test.js +0 -21
  61. package/dist/appearance/apply.d.ts.map +0 -1
  62. package/dist/appearance/index.d.ts.map +0 -1
  63. package/dist/appearance/matchers.d.ts.map +0 -1
  64. package/dist/appearance/schema.d.ts.map +0 -1
  65. package/dist/editor/AvatarCanvas.d.ts.map +0 -1
  66. package/dist/editor/AvatarCanvasErrorBoundary.d.ts.map +0 -1
  67. package/dist/editor/AvatarModel.d.ts.map +0 -1
  68. package/dist/editor/RigidAccessory.d.ts.map +0 -1
  69. package/dist/editor/SkinnedClothing.d.ts.map +0 -1
  70. package/dist/editor/index.d.ts.map +0 -1
  71. package/dist/editor/types.d.ts.map +0 -1
  72. package/dist/html.d.ts.map +0 -1
  73. package/dist/index.d.ts.map +0 -1
  74. package/dist/index.web.d.ts.map +0 -1
  75. package/dist/sketchfab/api.d.ts.map +0 -1
  76. package/dist/sketchfab/categories.d.ts.map +0 -1
  77. package/dist/sketchfab/index.d.ts.map +0 -1
  78. package/dist/sketchfab/types.d.ts.map +0 -1
  79. package/dist/sketchfab/useSketchfabSearch.d.ts.map +0 -1
  80. package/dist/voice/convertToWav.d.ts.map +0 -1
  81. package/dist/voice/index.d.ts.map +0 -1
  82. package/dist/voice/useAudioPlayer.d.ts.map +0 -1
  83. package/dist/voice/useAudioRecording.d.ts.map +0 -1
  84. package/src/TalkingHead.tsx +0 -276
  85. package/src/TalkingHead.web.tsx +0 -220
  86. package/src/__tests__/TalkingHead.test.tsx +0 -32
  87. package/src/__tests__/sketchfab.test.ts +0 -24
  88. package/src/appearance/apply.ts +0 -94
  89. package/src/appearance/index.ts +0 -4
  90. package/src/appearance/matchers.ts +0 -43
  91. package/src/appearance/schema.ts +0 -35
  92. package/src/editor/AvatarCanvas.tsx +0 -167
  93. package/src/editor/AvatarCanvasErrorBoundary.tsx +0 -64
  94. package/src/editor/AvatarModel.tsx +0 -49
  95. package/src/editor/RigidAccessory.tsx +0 -130
  96. package/src/editor/SkinnedClothing.tsx +0 -114
  97. package/src/editor/index.ts +0 -5
  98. package/src/editor/r3f-shim.d.ts +0 -34
  99. package/src/editor/types.ts +0 -30
  100. package/src/html.ts +0 -678
  101. package/src/index.ts +0 -11
  102. package/src/index.web.ts +0 -8
  103. package/src/sketchfab/api.ts +0 -82
  104. package/src/sketchfab/categories.ts +0 -127
  105. package/src/sketchfab/index.ts +0 -6
  106. package/src/sketchfab/types.ts +0 -40
  107. package/src/sketchfab/useSketchfabSearch.ts +0 -110
  108. package/src/voice/convertToWav.ts +0 -87
  109. package/src/voice/index.ts +0 -7
  110. package/src/voice/useAudioPlayer.ts +0 -78
  111. package/src/voice/useAudioRecording.ts +0 -207
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/talking-head-studio.svg)](https://www.npmjs.com/package/talking-head-studio)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
- [![Build Status](https://github.com/sitebay/react-native-talking-head/actions/workflows/ci.yml/badge.svg)](https://github.com/sitebay/react-native-talking-head/actions)
7
+ [![Build Status](https://github.com/sitebay/talking-head-studio/actions/workflows/ci.yml/badge.svg)](https://github.com/sitebay/talking-head-studio/actions)
8
8
 
9
9
  ---
10
10
 
@@ -52,7 +52,7 @@ npm install talking-head-studio react-native-webview
52
52
  npm install talking-head-studio
53
53
  ```
54
54
 
55
- No WebView dependency needed. The package ships a `.web.tsx` entry point that renders via `<iframe srcdoc>` automatically when bundled for web targets.
55
+ No `react-native` or WebView runtime dependency needed. The package ships a web entry point that renders via `<iframe srcdoc>` automatically when bundled for browser targets.
56
56
 
57
57
  ---
58
58
 
@@ -100,7 +100,8 @@ The package ships five independent entry points. Import only what you need — e
100
100
  ### `talking-head-studio` — Live talking avatar
101
101
  ```tsx
102
102
  import { TalkingHead } from 'talking-head-studio';
103
- // Peer deps: react, react-native (optional), react-native-webview (optional)
103
+ // Peer deps: react
104
+ // Native-only peers: react-native (optional), react-native-webview (optional)
104
105
  ```
105
106
 
106
107
  ### `talking-head-studio/editor` — 3D editor with gizmo (web)
@@ -398,7 +399,7 @@ For detailed model authoring guidance, see the [TalkingHead documentation](https
398
399
 
399
400
  ## Plain React / Next.js
400
401
 
401
- Despite the package name, this works on the web without React Native installed. The `react-native` and `react-native-webview` peer dependencies are both marked optional.
402
+ This works on the web without `react-native` or `react-native-webview` installed at runtime.
402
403
 
403
404
  On web, the component renders an `<iframe>` with `srcdoc` containing the full Three.js scene. No WebView, no native modules, no build plugins.
404
405
 
@@ -417,7 +418,7 @@ export default function Page() {
417
418
  }
418
419
  ```
419
420
 
420
- Bundlers that support the `react-native` platform field (Metro, Expo) resolve `TalkingHead.tsx` (WebView). Standard web bundlers (webpack, Vite, esbuild) resolve `TalkingHead.web.tsx` (iframe). The API is identical.
421
+ Metro and Expo use the native entry backed by `react-native-webview`. Standard web bundlers use the browser entry backed by a plain `<iframe>`. The API is identical.
421
422
 
422
423
  ---
423
424
 
@@ -434,8 +435,8 @@ Stay tuned.
434
435
  Contributions are welcome. Please open an issue to discuss your idea before submitting a pull request.
435
436
 
436
437
  ```bash
437
- git clone https://github.com/sitebay/react-native-talking-head.git
438
- cd react-native-talking-head
438
+ git clone https://github.com/sitebay/talking-head-studio.git
439
+ cd talking-head-studio
439
440
  npm install
440
441
  npm run typecheck
441
442
  npm test
@@ -86,4 +86,3 @@ export interface TalkingHeadRef {
86
86
  setAccessories: (accessories: TalkingHeadAccessory[]) => void;
87
87
  }
88
88
  export declare const TalkingHead: React.ForwardRefExoticComponent<TalkingHeadProps & React.RefAttributes<TalkingHeadRef>>;
89
- //# sourceMappingURL=TalkingHead.d.ts.map
@@ -1,17 +1,25 @@
1
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
2
- import { StyleSheet, View } from 'react-native';
3
- import { WebView } from 'react-native-webview';
4
- import { buildAvatarHtml } from './html';
5
- export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral', cameraView = 'upper', cameraDistance = -0.5, hairColor, skinColor, eyeColor, accessories, onReady, onError, style, }, ref) => {
6
- const webViewRef = useRef(null);
7
- const readyRef = useRef(false);
8
- const accessoriesRef = useRef(accessories);
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TalkingHead = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const react_native_1 = require("react-native");
7
+ const react_native_webview_1 = require("react-native-webview");
8
+ const html_1 = require("./html");
9
+ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'neutral', cameraView = 'upper', cameraDistance = -0.5, hairColor, skinColor, eyeColor, accessories, onReady, onError, style, }, ref) => {
10
+ const webViewRef = (0, react_1.useRef)(null);
11
+ const readyRef = (0, react_1.useRef)(false);
12
+ const pendingMoodRef = (0, react_1.useRef)(mood);
13
+ const pendingHairColorRef = (0, react_1.useRef)(hairColor);
14
+ const pendingSkinColorRef = (0, react_1.useRef)(skinColor);
15
+ const pendingEyeColorRef = (0, react_1.useRef)(eyeColor);
16
+ const accessoriesRef = (0, react_1.useRef)(accessories);
9
17
  // The WebView HTML is built once from stable initial values.
10
18
  // avatarUrl + authToken changing causes a controlled key-based remount.
11
19
  // All other prop changes (mood, colors, accessories) go via postMessage.
12
- const [webViewKey, setWebViewKey] = useState(() => `${avatarUrl}__${authToken ?? ''}`);
13
- const prevAvatarRef = useRef({ avatarUrl, authToken });
14
- useEffect(() => {
20
+ const [webViewKey, setWebViewKey] = (0, react_1.useState)(() => `${avatarUrl}__${authToken ?? ''}`);
21
+ const prevAvatarRef = (0, react_1.useRef)({ avatarUrl, authToken });
22
+ (0, react_1.useEffect)(() => {
15
23
  const prev = prevAvatarRef.current;
16
24
  if (prev.avatarUrl !== avatarUrl || prev.authToken !== authToken) {
17
25
  prevAvatarRef.current = { avatarUrl, authToken };
@@ -19,50 +27,75 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
19
27
  setWebViewKey(`${avatarUrl}__${authToken ?? ''}`);
20
28
  }
21
29
  }, [avatarUrl, authToken]);
22
- const post = useCallback((msg) => {
30
+ const post = (0, react_1.useCallback)((msg) => {
23
31
  webViewRef.current?.postMessage(JSON.stringify(msg));
24
32
  }, []);
25
- useImperativeHandle(ref, () => ({
33
+ (0, react_1.useImperativeHandle)(ref, () => ({
26
34
  sendAmplitude: (amplitude) => post({ type: 'amplitude', value: amplitude }),
27
35
  sendViseme: (viseme, weight = 1.0) => post({ type: 'viseme', viseme, weight }),
28
36
  scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
29
37
  clearVisemes: () => post({ type: 'clear_visemes' }),
30
- setMood: (nextMood) => post({ type: 'mood', value: nextMood }),
31
- setHairColor: (color) => post({ type: 'hair_color', value: color }),
32
- setSkinColor: (color) => post({ type: 'skin_color', value: color }),
33
- setEyeColor: (color) => post({ type: 'eye_color', value: color }),
34
- setAccessories: (newAccessories) => post({ type: 'set_accessories', accessories: newAccessories }),
38
+ setMood: (nextMood) => {
39
+ pendingMoodRef.current = nextMood;
40
+ if (readyRef.current)
41
+ post({ type: 'mood', value: nextMood });
42
+ },
43
+ setHairColor: (color) => {
44
+ pendingHairColorRef.current = color;
45
+ if (readyRef.current)
46
+ post({ type: 'hair_color', value: color });
47
+ },
48
+ setSkinColor: (color) => {
49
+ pendingSkinColorRef.current = color;
50
+ if (readyRef.current)
51
+ post({ type: 'skin_color', value: color });
52
+ },
53
+ setEyeColor: (color) => {
54
+ pendingEyeColorRef.current = color;
55
+ if (readyRef.current)
56
+ post({ type: 'eye_color', value: color });
57
+ },
58
+ setAccessories: (newAccessories) => {
59
+ accessoriesRef.current = newAccessories;
60
+ if (readyRef.current) {
61
+ post({ type: 'set_accessories', accessories: newAccessories });
62
+ }
63
+ },
35
64
  }), [post]);
36
65
  // Sync mood via postMessage only — never causes a WebView reload
37
- useEffect(() => {
66
+ (0, react_1.useEffect)(() => {
67
+ pendingMoodRef.current = mood;
38
68
  if (readyRef.current)
39
69
  post({ type: 'mood', value: mood });
40
70
  }, [mood, post]);
41
- useEffect(() => {
71
+ (0, react_1.useEffect)(() => {
42
72
  accessoriesRef.current = accessories;
43
73
  if (accessories && readyRef.current) {
44
74
  post({ type: 'set_accessories', accessories });
45
75
  }
46
76
  }, [accessories, post]);
47
- useEffect(() => {
77
+ (0, react_1.useEffect)(() => {
78
+ pendingHairColorRef.current = hairColor;
48
79
  if (hairColor && readyRef.current)
49
80
  post({ type: 'hair_color', value: hairColor });
50
81
  }, [hairColor, post]);
51
- useEffect(() => {
82
+ (0, react_1.useEffect)(() => {
83
+ pendingSkinColorRef.current = skinColor;
52
84
  if (skinColor && readyRef.current)
53
85
  post({ type: 'skin_color', value: skinColor });
54
86
  }, [skinColor, post]);
55
- useEffect(() => {
87
+ (0, react_1.useEffect)(() => {
88
+ pendingEyeColorRef.current = eyeColor;
56
89
  if (eyeColor && readyRef.current)
57
90
  post({ type: 'eye_color', value: eyeColor });
58
91
  }, [eyeColor, post]);
59
92
  // Capture stable initial values at first mount only
60
- const [initialMood] = useState(mood);
61
- const [initialHairColor] = useState(hairColor);
62
- const [initialSkinColor] = useState(skinColor);
63
- const [initialEyeColor] = useState(eyeColor);
93
+ const [initialMood] = (0, react_1.useState)(mood);
94
+ const [initialHairColor] = (0, react_1.useState)(hairColor);
95
+ const [initialSkinColor] = (0, react_1.useState)(skinColor);
96
+ const [initialEyeColor] = (0, react_1.useState)(eyeColor);
64
97
  // html is stable — only rebuilds when webViewKey changes (avatarUrl/authToken)
65
- const html = useMemo(() => buildAvatarHtml({
98
+ const html = (0, react_1.useMemo)(() => (0, html_1.buildAvatarHtml)({
66
99
  avatarUrl,
67
100
  authToken,
68
101
  mood: initialMood,
@@ -74,12 +107,24 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
74
107
  }),
75
108
  // eslint-disable-next-line react-hooks/exhaustive-deps
76
109
  [webViewKey]);
77
- const onMessage = useCallback((event) => {
110
+ const onMessage = (0, react_1.useCallback)((event) => {
78
111
  try {
79
112
  const msg = JSON.parse(event.nativeEvent.data);
80
113
  if (msg.type === 'ready') {
81
114
  readyRef.current = true;
82
- // Flush pending props that may have arrived before WebView was ready
115
+ // Flush pending appearance updates that arrived before the WebView was ready.
116
+ if (pendingMoodRef.current) {
117
+ post({ type: 'mood', value: pendingMoodRef.current });
118
+ }
119
+ if (pendingHairColorRef.current) {
120
+ post({ type: 'hair_color', value: pendingHairColorRef.current });
121
+ }
122
+ if (pendingSkinColorRef.current) {
123
+ post({ type: 'skin_color', value: pendingSkinColorRef.current });
124
+ }
125
+ if (pendingEyeColorRef.current) {
126
+ post({ type: 'eye_color', value: pendingEyeColorRef.current });
127
+ }
83
128
  if (accessoriesRef.current?.length) {
84
129
  post({ type: 'set_accessories', accessories: accessoriesRef.current });
85
130
  }
@@ -96,12 +141,10 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
96
141
  console.warn('[TalkingHead] Invalid message from WebView:', err);
97
142
  }
98
143
  }, [onReady, onError, post]);
99
- return (<View style={[styles.container, style]}>
100
- <WebView key={webViewKey} ref={webViewRef} source={{ html }} style={styles.webview} javaScriptEnabled domStorageEnabled allowsInlineMediaPlayback mediaPlaybackRequiresUserAction={false} onMessage={onMessage} originWhitelist={['*']} mixedContentMode="always"/>
101
- </View>);
144
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.container, style], children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: webViewRef, source: { html }, style: styles.webview, javaScriptEnabled: true, domStorageEnabled: true, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, onMessage: onMessage, originWhitelist: ['*'], mixedContentMode: "always" }, webViewKey) }));
102
145
  });
103
- TalkingHead.displayName = 'TalkingHead';
104
- const styles = StyleSheet.create({
146
+ exports.TalkingHead.displayName = 'TalkingHead';
147
+ const styles = react_native_1.StyleSheet.create({
105
148
  container: {
106
149
  overflow: 'hidden',
107
150
  borderRadius: 12,
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import { type StyleProp, type ViewStyle } from 'react-native';
3
2
  import type { TalkingHeadVisemeCue, TalkingHeadVisemeSchedule } from './TalkingHead';
4
3
  export type { TalkingHeadVisemeCue, TalkingHeadVisemeSchedule };
5
4
  export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'excited' | 'thinking' | 'concerned' | 'surprised';
@@ -23,7 +22,7 @@ export interface TalkingHeadProps {
23
22
  accessories?: TalkingHeadAccessory[];
24
23
  onReady?: () => void;
25
24
  onError?: (message: string) => void;
26
- style?: StyleProp<ViewStyle>;
25
+ style?: React.CSSProperties;
27
26
  }
28
27
  export interface TalkingHeadRef {
29
28
  sendAmplitude: (amplitude: number) => void;
@@ -36,4 +35,3 @@ export interface TalkingHeadRef {
36
35
  setAccessories: (accessories: TalkingHeadAccessory[]) => void;
37
36
  }
38
37
  export declare const TalkingHead: React.ForwardRefExoticComponent<TalkingHeadProps & React.RefAttributes<TalkingHeadRef>>;
39
- //# sourceMappingURL=TalkingHead.web.d.ts.map
@@ -1,52 +1,128 @@
1
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, } from 'react';
2
- import { StyleSheet, View } from 'react-native';
3
- import { buildAvatarHtml } from './html';
4
- export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral', cameraView = 'upper', cameraDistance = -0.5, hairColor, skinColor, eyeColor, accessories, onReady, onError, style, }, ref) => {
5
- const iframeRef = useRef(null);
6
- const readyRef = useRef(false);
7
- const accessoriesRef = useRef(accessories);
8
- useEffect(() => {
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.TalkingHead = void 0;
37
+ const jsx_runtime_1 = require("react/jsx-runtime");
38
+ const react_1 = __importStar(require("react"));
39
+ const html_1 = require("./html");
40
+ const containerStyle = {
41
+ overflow: 'hidden',
42
+ borderRadius: 12,
43
+ backgroundColor: 'transparent',
44
+ };
45
+ const iframeStyle = {
46
+ width: '100%',
47
+ height: '100%',
48
+ border: 'none',
49
+ backgroundColor: 'transparent',
50
+ };
51
+ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'neutral', cameraView = 'upper', cameraDistance = -0.5, hairColor, skinColor, eyeColor, accessories, onReady, onError, style, }, ref) => {
52
+ const iframeRef = (0, react_1.useRef)(null);
53
+ const readyRef = (0, react_1.useRef)(false);
54
+ const pendingMoodRef = (0, react_1.useRef)(mood);
55
+ const pendingHairColorRef = (0, react_1.useRef)(hairColor);
56
+ const pendingSkinColorRef = (0, react_1.useRef)(skinColor);
57
+ const pendingEyeColorRef = (0, react_1.useRef)(eyeColor);
58
+ const accessoriesRef = (0, react_1.useRef)(accessories);
59
+ (0, react_1.useEffect)(() => {
9
60
  accessoriesRef.current = accessories;
10
61
  }, [accessories]);
11
- const post = useCallback((msg) => {
62
+ const post = (0, react_1.useCallback)((msg) => {
12
63
  iframeRef.current?.contentWindow?.postMessage(JSON.stringify(msg), '*');
13
64
  }, []);
14
- useImperativeHandle(ref, () => ({
65
+ (0, react_1.useImperativeHandle)(ref, () => ({
15
66
  sendAmplitude: (amplitude) => post({ type: 'amplitude', value: amplitude }),
16
67
  scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
17
68
  clearVisemes: () => post({ type: 'clear_visemes' }),
18
- setMood: (nextMood) => post({ type: 'mood', value: nextMood }),
19
- setHairColor: (color) => post({ type: 'hair_color', value: color }),
20
- setSkinColor: (color) => post({ type: 'skin_color', value: color }),
21
- setEyeColor: (color) => post({ type: 'eye_color', value: color }),
22
- setAccessories: (newAccessories) => post({ type: 'set_accessories', accessories: newAccessories }),
69
+ setMood: (nextMood) => {
70
+ pendingMoodRef.current = nextMood;
71
+ if (readyRef.current)
72
+ post({ type: 'mood', value: nextMood });
73
+ },
74
+ setHairColor: (color) => {
75
+ pendingHairColorRef.current = color;
76
+ if (readyRef.current)
77
+ post({ type: 'hair_color', value: color });
78
+ },
79
+ setSkinColor: (color) => {
80
+ pendingSkinColorRef.current = color;
81
+ if (readyRef.current)
82
+ post({ type: 'skin_color', value: color });
83
+ },
84
+ setEyeColor: (color) => {
85
+ pendingEyeColorRef.current = color;
86
+ if (readyRef.current)
87
+ post({ type: 'eye_color', value: color });
88
+ },
89
+ setAccessories: (newAccessories) => {
90
+ accessoriesRef.current = newAccessories;
91
+ if (readyRef.current) {
92
+ post({ type: 'set_accessories', accessories: newAccessories });
93
+ }
94
+ },
23
95
  }), [post]);
24
- useEffect(() => {
96
+ (0, react_1.useEffect)(() => {
97
+ pendingMoodRef.current = mood;
25
98
  if (readyRef.current)
26
99
  post({ type: 'mood', value: mood });
27
100
  }, [mood, post]);
28
- useEffect(() => {
101
+ (0, react_1.useEffect)(() => {
29
102
  if (accessories && readyRef.current) {
30
103
  post({ type: 'set_accessories', accessories });
31
104
  }
32
105
  }, [accessories, post]);
33
- useEffect(() => {
106
+ (0, react_1.useEffect)(() => {
107
+ pendingHairColorRef.current = hairColor;
34
108
  if (hairColor && readyRef.current)
35
109
  post({ type: 'hair_color', value: hairColor });
36
110
  }, [hairColor, post]);
37
- useEffect(() => {
111
+ (0, react_1.useEffect)(() => {
112
+ pendingSkinColorRef.current = skinColor;
38
113
  if (skinColor && readyRef.current)
39
114
  post({ type: 'skin_color', value: skinColor });
40
115
  }, [skinColor, post]);
41
- useEffect(() => {
116
+ (0, react_1.useEffect)(() => {
117
+ pendingEyeColorRef.current = eyeColor;
42
118
  if (eyeColor && readyRef.current)
43
119
  post({ type: 'eye_color', value: eyeColor });
44
120
  }, [eyeColor, post]);
45
- const [initialMood] = React.useState(mood);
46
- const [initialHairColor] = React.useState(hairColor);
47
- const [initialSkinColor] = React.useState(skinColor);
48
- const [initialEyeColor] = React.useState(eyeColor);
49
- const html = useMemo(() => buildAvatarHtml({
121
+ const [initialMood] = react_1.default.useState(mood);
122
+ const [initialHairColor] = react_1.default.useState(hairColor);
123
+ const [initialSkinColor] = react_1.default.useState(skinColor);
124
+ const [initialEyeColor] = react_1.default.useState(eyeColor);
125
+ const html = (0, react_1.useMemo)(() => (0, html_1.buildAvatarHtml)({
50
126
  avatarUrl,
51
127
  authToken,
52
128
  mood: initialMood,
@@ -65,22 +141,30 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
65
141
  initialSkinColor,
66
142
  initialEyeColor,
67
143
  ]);
68
- // The HTML references window.ReactNativeWebView.postMessage we need to
69
- // inject that shim so messages come back to us via window.addEventListener.
70
- const srcdoc = useMemo(() => {
144
+ const srcdoc = (0, react_1.useMemo)(() => {
71
145
  const shim = `<script>window.ReactNativeWebView = { postMessage: function(d) { window.parent.postMessage(d, '*'); } };</script>`;
72
- // Insert the shim right after <head> so it's available before the module script runs
73
146
  return html.replace('<head>', '<head>' + shim);
74
147
  }, [html]);
75
- useEffect(() => {
148
+ (0, react_1.useEffect)(() => {
76
149
  const onMessage = (event) => {
77
- // Only accept messages from our iframe
78
150
  if (iframeRef.current && event.source !== iframeRef.current.contentWindow)
79
151
  return;
80
152
  try {
81
153
  const msg = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
82
154
  if (msg.type === 'ready') {
83
155
  readyRef.current = true;
156
+ if (pendingMoodRef.current) {
157
+ post({ type: 'mood', value: pendingMoodRef.current });
158
+ }
159
+ if (pendingHairColorRef.current) {
160
+ post({ type: 'hair_color', value: pendingHairColorRef.current });
161
+ }
162
+ if (pendingSkinColorRef.current) {
163
+ post({ type: 'skin_color', value: pendingSkinColorRef.current });
164
+ }
165
+ if (pendingEyeColorRef.current) {
166
+ post({ type: 'eye_color', value: pendingEyeColorRef.current });
167
+ }
84
168
  if (accessoriesRef.current?.length) {
85
169
  post({ type: 'set_accessories', accessories: accessoriesRef.current });
86
170
  }
@@ -94,26 +178,12 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
94
178
  }
95
179
  }
96
180
  catch {
97
- // ignore non-JSON messages
181
+ // Ignore non-JSON window messages from other browser integrations.
98
182
  }
99
183
  };
100
184
  window.addEventListener('message', onMessage);
101
185
  return () => window.removeEventListener('message', onMessage);
102
186
  }, [onReady, onError, post]);
103
- return (<View style={[styles.container, style]}>
104
- <iframe ref={iframeRef} srcDoc={srcdoc} style={{
105
- width: '100%',
106
- height: '100%',
107
- border: 'none',
108
- backgroundColor: 'transparent',
109
- }} sandbox="allow-scripts allow-same-origin" title="TalkingHead Avatar"/>
110
- </View>);
111
- });
112
- TalkingHead.displayName = 'TalkingHead';
113
- const styles = StyleSheet.create({
114
- container: {
115
- overflow: 'hidden',
116
- borderRadius: 12,
117
- backgroundColor: 'transparent',
118
- },
187
+ return ((0, jsx_runtime_1.jsx)("div", { style: { ...containerStyle, ...style }, children: (0, jsx_runtime_1.jsx)("iframe", { ref: iframeRef, srcDoc: srcdoc, style: iframeStyle, sandbox: "allow-scripts allow-same-origin", title: "TalkingHead Avatar" }) }));
119
188
  });
189
+ exports.TalkingHead.displayName = 'TalkingHead';
@@ -4,4 +4,3 @@ type Object3DLike = {
4
4
  };
5
5
  export declare function applyAppearanceToObject3D(object3d: Object3DLike, appearance: AvatarAppearance): void;
6
6
  export {};
7
- //# sourceMappingURL=apply.d.ts.map
@@ -1,5 +1,8 @@
1
- import { pickTargetForMaterialName } from './matchers';
2
- import { normalizeAppearance } from './schema';
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyAppearanceToObject3D = applyAppearanceToObject3D;
4
+ const matchers_1 = require("./matchers");
5
+ const schema_1 = require("./schema");
3
6
  function asMaterialArray(material) {
4
7
  if (!material) {
5
8
  return [];
@@ -9,11 +12,11 @@ function asMaterialArray(material) {
9
12
  function getVertexCount(mesh) {
10
13
  return mesh.geometry?.attributes?.position?.count ?? 0;
11
14
  }
12
- export function applyAppearanceToObject3D(object3d, appearance) {
15
+ function applyAppearanceToObject3D(object3d, appearance) {
13
16
  if (typeof object3d.traverse !== 'function') {
14
17
  return;
15
18
  }
16
- const normalizedAppearance = normalizeAppearance(appearance);
19
+ const normalizedAppearance = (0, schema_1.normalizeAppearance)(appearance);
17
20
  // First pass: apply name-matched colors and track whether skin was matched,
18
21
  // also track the largest mesh for fallback skin coloring.
19
22
  let skinMatched = false;
@@ -29,7 +32,7 @@ export function applyAppearanceToObject3D(object3d, appearance) {
29
32
  if (!material || typeof material.name !== 'string') {
30
33
  continue;
31
34
  }
32
- const target = pickTargetForMaterialName(material.name);
35
+ const target = (0, matchers_1.pickTargetForMaterialName)(material.name);
33
36
  if (!target) {
34
37
  // Track the largest unmatched mesh as a skin fallback candidate
35
38
  if (vertexCount > largestVertexCount && material.color && typeof material.color.set === 'function') {
@@ -2,4 +2,3 @@ export { applyAppearanceToObject3D } from './apply';
2
2
  export { pickTargetForMaterialName } from './matchers';
3
3
  export { type AvatarAppearance, normalizeAppearance } from './schema';
4
4
  export type { AppearanceTarget } from './matchers';
5
- //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,9 @@
1
- export { applyAppearanceToObject3D } from './apply';
2
- export { pickTargetForMaterialName } from './matchers';
3
- export { normalizeAppearance } from './schema';
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeAppearance = exports.pickTargetForMaterialName = exports.applyAppearanceToObject3D = void 0;
4
+ var apply_1 = require("./apply");
5
+ Object.defineProperty(exports, "applyAppearanceToObject3D", { enumerable: true, get: function () { return apply_1.applyAppearanceToObject3D; } });
6
+ var matchers_1 = require("./matchers");
7
+ Object.defineProperty(exports, "pickTargetForMaterialName", { enumerable: true, get: function () { return matchers_1.pickTargetForMaterialName; } });
8
+ var schema_1 = require("./schema");
9
+ Object.defineProperty(exports, "normalizeAppearance", { enumerable: true, get: function () { return schema_1.normalizeAppearance; } });
@@ -1,3 +1,2 @@
1
1
  export type AppearanceTarget = 'hairColor' | 'skinColor' | 'eyeColor';
2
2
  export declare function pickTargetForMaterialName(name: string): AppearanceTarget | null;
3
- //# sourceMappingURL=matchers.d.ts.map
@@ -1,3 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pickTargetForMaterialName = pickTargetForMaterialName;
1
4
  function tokenizeMaterialName(name) {
2
5
  return name
3
6
  .replace(/([a-z\d])([A-Z])/g, '$1 $2')
@@ -15,7 +18,7 @@ function hasToken(tokens, candidates) {
15
18
  function hasTokenPrefix(tokens, candidates) {
16
19
  return candidates.some((candidate) => tokens.some((token) => token.startsWith(candidate) && token.length > candidate.length));
17
20
  }
18
- export function pickTargetForMaterialName(name) {
21
+ function pickTargetForMaterialName(name) {
19
22
  const tokens = tokenizeMaterialName(name);
20
23
  if (hasToken(tokens, ['hair', 'fur']) || hasTokenPrefix(tokens, ['hair', 'fur'])) {
21
24
  return 'hairColor';
@@ -6,4 +6,3 @@ export interface AvatarAppearance {
6
6
  eyeColor?: string;
7
7
  }
8
8
  export declare function normalizeAppearance(input: AvatarAppearance): AvatarAppearance;
9
- //# sourceMappingURL=schema.d.ts.map
@@ -1,3 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeAppearance = normalizeAppearance;
1
4
  const HEX_COLOR_PATTERN = /^#(?:[\dA-Fa-f]{3}|[\dA-Fa-f]{6})$/;
2
5
  function normalizeHexColor(value) {
3
6
  if (!HEX_COLOR_PATTERN.test(value)) {
@@ -7,7 +10,7 @@ function normalizeHexColor(value) {
7
10
  const expanded = raw.length === 3 ? `${raw[0]}${raw[0]}${raw[1]}${raw[1]}${raw[2]}${raw[2]}` : raw;
8
11
  return `#${expanded.toUpperCase()}`;
9
12
  }
10
- export function normalizeAppearance(input) {
13
+ function normalizeAppearance(input) {
11
14
  if (input.version !== 1) {
12
15
  throw new Error(`Unsupported appearance version: ${input.version}`);
13
16
  }
@@ -11,6 +11,5 @@ interface AvatarCanvasProps {
11
11
  style?: React.CSSProperties;
12
12
  className?: string;
13
13
  }
14
- export declare function AvatarCanvas({ avatarUrl, appearance, equipped, placements, editingAssetId, onPlacementChange, onSceneRef, style, className, }: AvatarCanvasProps): import("react").JSX.Element;
14
+ export declare function AvatarCanvas({ avatarUrl, appearance, equipped, placements, editingAssetId, onPlacementChange, onSceneRef, style, className, }: AvatarCanvasProps): import("react/jsx-runtime").JSX.Element;
15
15
  export {};
16
- //# sourceMappingURL=AvatarCanvas.d.ts.map