feat: add web support for TransformerTextInput#12
Merged
Conversation
Add a web implementation (TransformerTextInput.web.tsx): a controlled TextInput that runs the transformer synchronously in JS and restores the caret after each format. Web is single-threaded, so value and selection update in one commit (no flicker) without the native decorator, TurboModule, or codegenNativeComponent — none of which exist under react-native-web. Shared types move to TransformerTextInput.types.ts so the .web file can import them without resolving to itself. Run the example on web (yarn web / yarn build:web) via expo-router + react-native-web, mirroring the react-native-ease example.
Make the example a full Expo app so native and web build from one setup (mirroring react-native-ease): native runs via `expo run:ios` / `expo run:android` with continuous native generation — `ios/` and `android/` are gitignored and regenerated by `expo prebuild` from app.json. Drops the bare react-native CLI entry (index.js); the Expo entry is used on all platforms. Verified: `expo run:android` builds (the library's native code, reanimated, worklets, screens) and the demo renders on an emulator.
Write the formatted value and caret straight to the DOM node in the change handler instead of going through React state. Web is single-threaded, so value and selection land together synchronously — this drops the per-keystroke re-render and removes the controlled-input round-trip (set value -> caret collapses to end -> layout effect restores it), which had a one-frame window where the caret could jump. Mirrors the native side's imperative update.
With the example using Expo continuous native generation, ios/ and android/ aren't checked in, which broke two CI jobs: - lint ran `lint:android` (`cd example/android`), which no longer exists in a clean checkout. Drop it from the `lint` chain; `lint:ts` already typechecks the example (the root tsconfig has no `include`). - build-ios had a separate "Install cocoapods" step doing `pod install` in example/ios, which isn't there until prebuild. Remove it — `build:ios` (`expo prebuild --clean`) generates the project and installs pods, matching react-native-ease. Target the x86_64 simulator slice on the macOS runner.
The CNG example pulls expo-modules-core from Expo SDK 55, whose Swift uses strict-concurrency / MainActor features that Xcode 16.4 can't compile. Match react-native-ease and build with Xcode 26.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a web implementation of
TransformerTextInputso the component works under react-native-web (Expo web, etc.). The native component is a Fabric decorator view plus worklets that run the transformer on the UI thread — neither exists on web, so importing the package on web previously broke the bundle (codegenNativeComponent/ TurboModule aren't available under react-native-web).What's in it
TransformerTextInput.web.tsx— an uncontrolledTextInputthat runstransformer.worklet(...)synchronously in JS on each change and writes the formatted value + caret straight to the DOM node (mirroring the native side's imperative update). Web is single-threaded, so value and selection land together — no React re-render, and no controlled-input round-trip where the caret briefly jumps to the end. Reuses the existingcomputeUncontrolledSelection/validateSelectionlogic and exposes the same instance API (getValue/update/clear). No native decorator, TurboModule, orcodegenNativeComponent.TransformerTextInput.types.ts— the sharedTransformerTextInputInstance/TransformerTextInputPropstypes extracted out, so the.webfile can import them without resolving to itself; the native file re-exports them so the public API is unchanged.yarn web/yarn build:web(expo-router + react-native-web), and native viaexpo run:ios/expo run:androidwith continuous native generation (ios/andandroid/are gitignored and regenerated byexpo prebuild). The bare RN CLI entry (index.js) is dropped in favor of the Expo entry on all platforms.Test plan
yarn lint:ts(tsc) clean;yarn test(jest) — 122/122 pass, including the native snapshot.yarn build:webexports cleanly; loaded the export in Chrome and typed into the phone field: formats as you type (4155550142→+1 (415) 555-0142), deletes reformat correctly, all demo formatters render.expo run:androidbuilds the full native stack (the library's C++/JSI, reanimated, worklets, react-native-screens) and the demo renders on an emulator.build-iosjob, Xcode 26 — required by Expo SDK 55): the library + example native build compiles. I didn't get a clean local iOS build (CocoaPods / Clang-module env issues on my machine), so there's no local render — CI is the iOS check.Notes
ios/andandroid/are gitignored, regenerated byexpo prebuild), so CI builds it on both platforms. Expo SDK 55 requires Xcode 26, so thebuild-iosjob was bumped from 16.4.