From 4c63bba86573708383f9e4cc747c6b4429d64bea Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 11 Mar 2026 15:34:27 -0400 Subject: [PATCH 1/3] fix: use executeOnUIRuntimeSync for registry setup runOnUI is async, so the registry may not be set up on the UI thread by the time NativeTransformerTextInputModule.install() runs, causing a crash when native code tries to access __rntti_registerTransformerRegistry. Switch to executeOnUIRuntimeSync to guarantee the registry exists before install() is called. --- src/registry.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/registry.ts b/src/registry.ts index ed3ee03..00a975f 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -1,4 +1,4 @@ -import { runOnUI } from 'react-native-worklets'; +import { runOnUI, executeOnUIRuntimeSync } from 'react-native-worklets'; import NativeTransformerTextInputModule from './NativeTransformerTextInputModule'; import { type Selection, type Transformer } from './Transformer'; import { computeUncontrolledSelection, validateSelection } from './selection'; @@ -29,8 +29,9 @@ function initializeIfNeeded() { return; } - // Important that `runOnUI` is called first to make sure the UI runtime is initialized. - runOnUI(() => { + // Set up registry on UI runtime synchronously so it is guaranteed to exist + // when native code accesses it after install(). + executeOnUIRuntimeSync(() => { 'worklet'; const transformersMap = new Map(); From f1ddd6d84426db8de5629f02a69f1bcc12f8f256 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 11 Mar 2026 15:38:56 -0400 Subject: [PATCH 2/3] fix: pre-transform defaultValue for correct initial layout When defaultValue is set, Yoga measures the untransformed text for layout. The native-side transformation happens after layout and doesn't trigger a remeasure, resulting in incorrect sizing. Run the transformer worklet on the JS thread to pre-transform the defaultValue before passing it to the native TextInput, so Yoga measures the correct text from the start. --- src/TransformerTextInput.tsx | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/TransformerTextInput.tsx b/src/TransformerTextInput.tsx index fd6a7fd..cf33d64 100644 --- a/src/TransformerTextInput.tsx +++ b/src/TransformerTextInput.tsx @@ -61,13 +61,34 @@ export type TransformerTextInputProps = Omit & { export const TransformerTextInput = forwardRef( ( - { transformer, onChangeText, ...others }: TransformerTextInputProps, + { + transformer, + onChangeText, + defaultValue, + ...others + }: TransformerTextInputProps, forwardedRef: Ref, ) => { const transformerId = useMemo(() => { return registerTransformer(transformer); }, [transformer]); + // Pre-transform defaultValue on the JS thread so Yoga measures the correct + // text from the start. Without this the native-side transformation happens + // after layout and doesn't trigger a remeasure. + const transformedDefaultValue = useMemo(() => { + if (defaultValue == null) { + return undefined; + } + const result = transformer.worklet({ + value: defaultValue, + previousValue: '', + selection: { start: defaultValue.length, end: defaultValue.length }, + previousSelection: { start: 0, end: 0 }, + }); + return result?.value ?? defaultValue; + }, [defaultValue, transformer]); + useEffect(() => { return () => { unregisterTransformer(transformerId); @@ -135,6 +156,7 @@ export const TransformerTextInput = forwardRef( // @ts-expect-error ref={inputRef} onChangeText={handleChangeText} + defaultValue={transformedDefaultValue} {...others} /> From c1971fbef3f4cfb022351c84e367c9ea2fa4a633 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 11 Mar 2026 15:44:15 -0400 Subject: [PATCH 3/3] fix: use defaultValue as previousValue in pre-transform --- src/TransformerTextInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TransformerTextInput.tsx b/src/TransformerTextInput.tsx index cf33d64..d9a175a 100644 --- a/src/TransformerTextInput.tsx +++ b/src/TransformerTextInput.tsx @@ -82,7 +82,7 @@ export const TransformerTextInput = forwardRef( } const result = transformer.worklet({ value: defaultValue, - previousValue: '', + previousValue: defaultValue, selection: { start: defaultValue.length, end: defaultValue.length }, previousSelection: { start: 0, end: 0 }, });