diff --git a/change/@react-native-windows-automation-b73cb600-5f2e-4554-a855-be5d9602c57d.json b/change/@react-native-windows-automation-b73cb600-5f2e-4554-a855-be5d9602c57d.json
new file mode 100644
index 00000000000..7b3567abe69
--- /dev/null
+++ b/change/@react-native-windows-automation-b73cb600-5f2e-4554-a855-be5d9602c57d.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Fix broken web links in markdown documentation files",
+ "packageName": "@react-native-windows/automation",
+ "email": "email not defined",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@react-native-windows-automation-channel-3ffc8278-e1ff-4f57-9524-71cc056243b1.json b/change/@react-native-windows-automation-channel-3ffc8278-e1ff-4f57-9524-71cc056243b1.json
new file mode 100644
index 00000000000..eb3e4d59257
--- /dev/null
+++ b/change/@react-native-windows-automation-channel-3ffc8278-e1ff-4f57-9524-71cc056243b1.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Fix broken web links in markdown documentation files",
+ "packageName": "@react-native-windows/automation-channel",
+ "email": "email not defined",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@react-native-windows-automation-commands-d302e6bd-1720-4f3a-9d72-4273739dae25.json b/change/@react-native-windows-automation-commands-d302e6bd-1720-4f3a-9d72-4273739dae25.json
new file mode 100644
index 00000000000..61a5d35eaab
--- /dev/null
+++ b/change/@react-native-windows-automation-commands-d302e6bd-1720-4f3a-9d72-4273739dae25.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Fix broken web links in markdown documentation files",
+ "packageName": "@react-native-windows/automation-commands",
+ "email": "email not defined",
+ "dependentChangeType": "patch"
+}
diff --git a/change/react-native-windows-8951004c-2587-4d8c-952a-f673c722f2a2.json b/change/react-native-windows-8951004c-2587-4d8c-952a-f673c722f2a2.json
new file mode 100644
index 00000000000..cdaee7ea26d
--- /dev/null
+++ b/change/react-native-windows-8951004c-2587-4d8c-952a-f673c722f2a2.json
@@ -0,0 +1,7 @@
+{
+ "comment": "Add keyboardType prop support to Fabric TextInput for parity with Paper",
+ "type": "prerelease",
+ "packageName": "react-native-windows",
+ "email": "nitchaudhary@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/playground/Samples/KeyboardTypeTest.tsx b/packages/playground/Samples/KeyboardTypeTest.tsx
new file mode 100644
index 00000000000..e813dd2670d
--- /dev/null
+++ b/packages/playground/Samples/KeyboardTypeTest.tsx
@@ -0,0 +1,230 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ * Licensed under the MIT License.
+ * @format
+ */
+
+import React from 'react';
+import {
+ AppRegistry,
+ StyleSheet,
+ ScrollView,
+ Text,
+ View,
+ TextInput,
+} from 'react-native';
+
+class KeyboardTypeTest extends React.Component<
+ {},
+ {
+ defaultValue: string;
+ numericValue: string;
+ numberPadValue: string;
+ decimalPadValue: string;
+ emailValue: string;
+ phonePadValue: string;
+ urlValue: string;
+ webSearchValue: string;
+ secureNumericValue: string;
+ }
+> {
+ constructor(props: {}) {
+ super(props);
+ this.state = {
+ defaultValue: '',
+ numericValue: '',
+ numberPadValue: '',
+ decimalPadValue: '',
+ emailValue: '',
+ phonePadValue: '',
+ urlValue: '',
+ webSearchValue: '',
+ secureNumericValue: '',
+ };
+ }
+
+ render() {
+ return (
+
+ Keyboard Type Test (Fabric)
+ Test SetInputScopes on Parent HWND
+
+
+ Default Keyboard:
+ this.setState({defaultValue: text})}
+ placeholder="default keyboard"
+ />
+
+
+
+ Numeric Keyboard:
+ this.setState({numericValue: text})}
+ placeholder="numeric keyboard"
+ />
+
+
+
+ Number Pad:
+ this.setState({numberPadValue: text})}
+ placeholder="number-pad"
+ />
+
+
+
+ Decimal Pad:
+ this.setState({decimalPadValue: text})}
+ placeholder="decimal-pad"
+ />
+
+
+
+ Email Address:
+ this.setState({emailValue: text})}
+ placeholder="email-address"
+ />
+
+
+
+ Phone Pad:
+ this.setState({phonePadValue: text})}
+ placeholder="phone-pad"
+ />
+
+
+
+ URL Keyboard:
+ this.setState({urlValue: text})}
+ placeholder="url"
+ />
+
+
+
+ Web Search:
+ this.setState({webSearchValue: text})}
+ placeholder="web-search"
+ />
+
+
+
+ Secure + Numeric:
+ this.setState({secureNumericValue: text})}
+ placeholder="numeric password"
+ />
+
+
+
+
+ Instructions for Testing on Windows:{'\n'}
+ {'\n'}
+ This test uses SetInputScopes on the parent HWND.{'\n'}
+ {'\n'}
+ To test with Windows Touch Keyboard:{'\n'}
+ 1. Right-click taskbar → Show touch keyboard button{'\n'}
+ 2. Click the keyboard icon in system tray{'\n'}
+ 3. Tap/click each TextInput field to focus it{'\n'}
+ 4. Observe the touch keyboard layout changes{'\n'}
+ {'\n'}
+ Expected keyboard layouts:{'\n'}• default: Standard QWERTY{'\n'}•
+ numeric/number-pad: Number keys (IS_NUMBER/IS_DIGITS){'\n'}•
+ decimal-pad: Numbers with decimal point{'\n'}• email-address: QWERTY
+ with @ and .com keys{'\n'}• phone-pad: Phone dial pad layout{'\n'}•
+ url: QWERTY with .com/.net buttons{'\n'}• web-search:
+ Search-optimized layout{'\n'}• secure+numeric: PIN entry layout
+ {'\n'}
+ {'\n'}
+ Note: Physical keyboard allows all input (matches iOS/Android).
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ padding: 20,
+ backgroundColor: '#f5f5f5',
+ },
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ marginBottom: 5,
+ color: '#333',
+ },
+ subtitle: {
+ fontSize: 14,
+ marginBottom: 20,
+ color: '#666',
+ fontStyle: 'italic',
+ },
+ inputContainer: {
+ marginBottom: 15,
+ },
+ label: {
+ fontSize: 14,
+ fontWeight: '600',
+ marginBottom: 5,
+ color: '#444',
+ },
+ input: {
+ borderWidth: 1,
+ borderColor: '#ccc',
+ borderRadius: 4,
+ padding: 10,
+ fontSize: 16,
+ backgroundColor: '#fff',
+ },
+ instructions: {
+ marginTop: 30,
+ padding: 15,
+ backgroundColor: '#e3f2fd',
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: '#90caf9',
+ },
+ instructionText: {
+ fontSize: 13,
+ color: '#1565c0',
+ lineHeight: 20,
+ },
+});
+
+AppRegistry.registerComponent('KeyboardTypeTest', () => KeyboardTypeTest);
diff --git a/packages/playground/windows/playground-composition/Playground-Composition.cpp b/packages/playground/windows/playground-composition/Playground-Composition.cpp
index 0ca8491496f..f238c1b1471 100644
--- a/packages/playground/windows/playground-composition/Playground-Composition.cpp
+++ b/packages/playground/windows/playground-composition/Playground-Composition.cpp
@@ -245,7 +245,13 @@ struct WindowData {
DialogBox(s_instance, MAKEINTRESOURCE(IDD_OPENJSBUNDLEBOX), hwnd, &Bundle);
if (!m_bundleFile.empty()) {
- m_appName = (m_bundleFile == LR"(Samples\rntester)") ? L"RNTesterApp" : L"Bootstrap";
+ if (m_bundleFile == LR"(Samples\rntester)") {
+ m_appName = L"RNTesterApp";
+ } else if (m_bundleFile == LR"(Samples\KeyboardTypeTest)") {
+ m_appName = L"KeyboardTypeTest";
+ } else {
+ m_appName = L"Bootstrap";
+ }
WCHAR appDirectory[MAX_PATH];
GetModuleFileNameW(NULL, appDirectory, MAX_PATH);
@@ -370,16 +376,28 @@ struct WindowData {
return FALSE;
}
- static constexpr std::wstring_view g_bundleFiles[] = {LR"(Samples\rntester)", LR"(Samples\accessible)",
- LR"(Samples\callbackTest)", LR"(Samples\calculator)",
- LR"(Samples\click)", LR"(Samples\control)",
- LR"(Samples\flexbox)", LR"(Samples\focusTest)",
- LR"(Samples\geosample)", LR"(Samples\image)",
- LR"(Samples\index)", LR"(Samples\nativeFabricComponent)",
- LR"(Samples\mouse)", LR"(Samples\scrollViewSnapSample)",
- LR"(Samples\simple)", LR"(Samples\text)",
- LR"(Samples\textinput)", LR"(Samples\ticTacToe)",
- LR"(Samples\view)", LR"(Samples\debugTest01)"};
+ static constexpr std::wstring_view g_bundleFiles[] = {
+ LR"(Samples\rntester)",
+ LR"(Samples\accessible)",
+ LR"(Samples\callbackTest)",
+ LR"(Samples\calculator)",
+ LR"(Samples\click)",
+ LR"(Samples\control)",
+ LR"(Samples\flexbox)",
+ LR"(Samples\focusTest)",
+ LR"(Samples\geosample)",
+ LR"(Samples\image)",
+ LR"(Samples\index)",
+ LR"(Samples\KeyboardTypeTest)",
+ LR"(Samples\nativeFabricComponent)",
+ LR"(Samples\mouse)",
+ LR"(Samples\scrollViewSnapSample)",
+ LR"(Samples\simple)",
+ LR"(Samples\text)",
+ LR"(Samples\textinput)",
+ LR"(Samples\ticTacToe)",
+ LR"(Samples\view)",
+ LR"(Samples\debugTest01)"};
static INT_PTR CALLBACK Bundle(HWND hwnd, UINT message, WPARAM wparam, LPARAM /*lparam*/) noexcept {
switch (message) {
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp
index 3ee3ca3708e..55294dd47ed 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp
@@ -1,15 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-#pragma once
-
#include "WindowsTextInputComponentView.h"
#include
#include
#include
+#include
+#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -25,7 +27,31 @@
#include "WindowsTextInputShadowNode.h"
#include "guid/msoGuid.h"
-#include
+#pragma comment(lib, "Shlwapi.lib")
+
+// Dynamic loading of SetInputScopes from msctf.dll
+typedef HRESULT(WINAPI *PFN_SetInputScopes)(
+ HWND hwnd,
+ const InputScope *pInputScopes,
+ UINT cInputScopes,
+ PWSTR *ppszPhraseList,
+ UINT cPhrases,
+ PWSTR pszRegExp,
+ PWSTR pszSRGS);
+
+static PFN_SetInputScopes g_pfnSetInputScopes = nullptr;
+static bool g_bSetInputScopesInitialized = false;
+
+static PFN_SetInputScopes GetSetInputScopesProc() {
+ if (!g_bSetInputScopesInitialized) {
+ g_bSetInputScopesInitialized = true;
+ HMODULE hMsctf = LoadLibraryW(L"msctf.dll");
+ if (hMsctf) {
+ g_pfnSetInputScopes = (PFN_SetInputScopes)GetProcAddress(hMsctf, "SetInputScopes");
+ }
+ }
+ return g_pfnSetInputScopes;
+}
// convert a BSTR to a std::string.
std::string &BstrToStdString(const BSTR bstr, std::string &dst, int cp = CP_UTF8) {
@@ -61,6 +87,63 @@ MSO_CLASS_GUID(ITextServices2, "8D33F741-CF58-11CE-A89D-00AA006CADC5") // IID_IT
namespace winrt::Microsoft::ReactNative::Composition::implementation {
+// Static members for proxy EDIT control
+HWND WindowsTextInputComponentView::s_proxyEditHwnd = nullptr;
+WNDPROC WindowsTextInputComponentView::s_originalProxyEditWndProc = nullptr;
+WindowsTextInputComponentView *WindowsTextInputComponentView::s_currentFocusedTextInput = nullptr;
+
+// Proxy EDIT control window procedure - forwards input to the active TextInput
+LRESULT CALLBACK WindowsTextInputComponentView::ProxyEditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
+ // Forward character and key messages to the actual TextInput's RichEdit
+ if (s_currentFocusedTextInput && s_currentFocusedTextInput->m_textServices) {
+ if (msg == WM_CHAR || msg == WM_KEYDOWN || msg == WM_KEYUP || msg == WM_IME_CHAR || msg == WM_IME_COMPOSITION ||
+ msg == WM_IME_STARTCOMPOSITION || msg == WM_IME_ENDCOMPOSITION) {
+ LRESULT result;
+ s_currentFocusedTextInput->m_textServices->TxSendMessage(msg, wParam, lParam, &result);
+ if (msg == WM_CHAR || msg == WM_IME_CHAR) {
+ // Emit onKeyPress event (this is what OnCharacterReceived normally does)
+ s_currentFocusedTextInput->EmitOnKeyPress(static_cast(wParam));
+ s_currentFocusedTextInput->OnTextUpdated();
+ }
+ return result;
+ }
+ }
+
+ // For other messages, call the original window procedure
+ return CallWindowProcW(s_originalProxyEditWndProc, hwnd, msg, wParam, lParam);
+}
+
+// Create a hidden EDIT control for InputScope support
+void WindowsTextInputComponentView::EnsureProxyEditControl(HWND parentHwnd) {
+ if (s_proxyEditHwnd) {
+ return; // Already created
+ }
+
+ // Create an EDIT control - position at 0,0 initially, we'll move it when focused
+ // WS_VISIBLE is needed for TSF to properly integrate
+ s_proxyEditHwnd = CreateWindowExW(
+ WS_EX_TRANSPARENT | WS_EX_LAYERED, // Transparent and layered for invisibility
+ L"EDIT",
+ L"",
+ WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
+ 0,
+ 0,
+ 1,
+ 1, // Small but not off-screen
+ parentHwnd,
+ nullptr,
+ GetModuleHandle(nullptr),
+ nullptr);
+
+ if (s_proxyEditHwnd) {
+ // Make it fully transparent
+ SetLayeredWindowAttributes(s_proxyEditHwnd, 0, 0, LWA_ALPHA);
+
+ // Subclass the EDIT control to forward input to our RichEdit
+ s_originalProxyEditWndProc = (WNDPROC)SetWindowLongPtrW(s_proxyEditHwnd, GWLP_WNDPROC, (LONG_PTR)ProxyEditWndProc);
+ }
+}
+
// RichEdit doesn't handle us calling Draw during the middle of a TxTranslateMessage call.
WindowsTextInputComponentView::DrawBlock::DrawBlock(WindowsTextInputComponentView &view) : m_view(view) {
m_view.m_cDrawBlock++;
@@ -722,7 +805,8 @@ void WindowsTextInputComponentView::OnPointerPressed(
pressInArgs.pagePoint = {position.X, position.Y};
pressInArgs.offsetPoint = {offsetX, offsetY}; //{LocationX,LocationY}
pressInArgs.timestamp = static_cast(pp.Timestamp()) / 1000.0;
- pressInArgs.identifier = pp.PointerId();
+ // Normalize pointer ID to 0-20 range to avoid React touch system performance warning
+ pressInArgs.identifier = pp.PointerId() % 21;
emitter->onPressIn(pressInArgs);
}
@@ -787,7 +871,8 @@ void WindowsTextInputComponentView::OnPointerReleased(
pressOutArgs.pagePoint = {position.X, position.Y};
pressOutArgs.offsetPoint = {offsetX, offsetY}; //{LocationX,LocationY}
pressOutArgs.timestamp = static_cast(pp.Timestamp()) / 1000.0;
- pressOutArgs.identifier = pp.PointerId();
+ // Normalize pointer ID to 0-20 range to avoid React touch system performance warning
+ pressOutArgs.identifier = pp.PointerId() % 21;
emitter->onPressOut(pressOutArgs);
}
@@ -947,6 +1032,28 @@ bool WindowsTextInputComponentView::ShouldSubmit(
return shouldSubmit;
}
+// Helper method to emit onKeyPress event - used by both OnCharacterReceived and ProxyEditWndProc
+void WindowsTextInputComponentView::EmitOnKeyPress(wchar_t keyChar) noexcept {
+ if (!m_eventEmitter) {
+ return;
+ }
+
+ // Convert wchar_t to std::string
+ wchar_t key[2] = {keyChar, L'\0'};
+ std::string keyString = ::Microsoft::Common::Unicode::Utf16ToUtf8(key, 1);
+
+ auto emitter = std::static_pointer_cast(m_eventEmitter);
+ facebook::react::WindowsTextInputEventEmitter::OnKeyPress onKeyPressArgs;
+ if (keyString.compare("\r") == 0) {
+ onKeyPressArgs.key = "Enter";
+ } else if (keyString.compare("\b") == 0) {
+ onKeyPressArgs.key = "Backspace";
+ } else {
+ onKeyPressArgs.key = keyString;
+ }
+ emitter->onKeyPress(onKeyPressArgs);
+}
+
void WindowsTextInputComponentView::OnCharacterReceived(
const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept {
// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
@@ -975,21 +1082,8 @@ void WindowsTextInputComponentView::OnCharacterReceived(
return;
}
- // convert keyCode to std::string
- wchar_t key[2] = L" ";
- key[0] = static_cast(args.KeyCode());
- std::string keyString = ::Microsoft::Common::Unicode::Utf16ToUtf8(key, 1);
- // Call onKeyPress event
- auto emitter = std::static_pointer_cast(m_eventEmitter);
- facebook::react::WindowsTextInputEventEmitter::OnKeyPress onKeyPressArgs;
- if (keyString.compare("\r") == 0) {
- onKeyPressArgs.key = "Enter";
- } else if (keyString.compare("\b") == 0) {
- onKeyPressArgs.key = "Backspace";
- } else {
- onKeyPressArgs.key = keyString;
- }
- emitter->onKeyPress(onKeyPressArgs);
+ // Call onKeyPress event using the helper method
+ EmitOnKeyPress(static_cast(args.KeyCode()));
WPARAM wParam = static_cast(args.KeyCode());
@@ -1033,6 +1127,24 @@ void WindowsTextInputComponentView::onLostFocus(
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
m_hasFocus = false;
Super::onLostFocus(args);
+
+ // Reset InputScope on parent HWND when losing focus
+ HWND hwndParent = GetHwndForParenting();
+ if (hwndParent) {
+ if (auto pfnSetInputScopes = GetSetInputScopesProc()) {
+ InputScope defaultScope = IS_DEFAULT;
+ pfnSetInputScopes(hwndParent, &defaultScope, 1, nullptr, 0, nullptr, nullptr);
+ }
+ }
+
+ // Clear proxy EDIT control focus
+ if (s_currentFocusedTextInput == this) {
+ s_currentFocusedTextInput = nullptr;
+ if (s_proxyEditHwnd) {
+ ShowWindow(s_proxyEditHwnd, SW_HIDE);
+ }
+ }
+
if (m_textServices) {
LRESULT lresult;
DrawBlock db(*this);
@@ -1062,6 +1174,40 @@ void WindowsTextInputComponentView::onGotFocus(
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
m_hasFocus = true;
Super::onGotFocus(args);
+
+ // Set InputScope on parent HWND for touch keyboard layout
+ updateKeyboardType(windowsTextInputProps().keyboardType);
+
+ // Use proxy EDIT control for Touch Keyboard InputScope support
+ HWND hwndParent = GetHwndForParenting();
+ if (hwndParent) {
+ EnsureProxyEditControl(hwndParent);
+
+ if (s_proxyEditHwnd) {
+ // Set this as the current focused TextInput
+ s_currentFocusedTextInput = this;
+
+ // Set InputScope on the proxy EDIT control
+ if (auto pfnSetInputScopes = GetSetInputScopesProc()) {
+ pfnSetInputScopes(s_proxyEditHwnd, &m_currentInputScope, 1, nullptr, 0, nullptr, nullptr);
+ }
+
+ // Position the proxy EDIT at our location (even though it's transparent)
+ auto screenPos = LocalToScreen({0, 0});
+ SetWindowPos(
+ s_proxyEditHwnd,
+ HWND_TOP,
+ (int)screenPos.X,
+ (int)screenPos.Y,
+ (int)m_layoutMetrics.frame.size.width,
+ (int)m_layoutMetrics.frame.size.height,
+ SWP_NOACTIVATE);
+
+ // Give it Windows focus so Touch Keyboard sees correct InputScope
+ SetFocus(s_proxyEditHwnd);
+ }
+ }
+
if (m_textServices) {
LRESULT lresult;
DrawBlock db(*this);
@@ -1192,10 +1338,26 @@ void WindowsTextInputComponentView::updateProps(
updateAutoCorrect(newTextInputProps.autoCorrect);
}
+ if (oldTextInputProps.keyboardType != newTextInputProps.keyboardType ||
+ oldTextInputProps.secureTextEntry != newTextInputProps.secureTextEntry) {
+ updateKeyboardType(newTextInputProps.keyboardType);
+ }
+
if (oldTextInputProps.selectionColor != newTextInputProps.selectionColor) {
m_needsRedraw = true;
}
+ // Notify TSF when keyboardType changes so IME can update
+ if (oldTextInputProps.keyboardType != newTextInputProps.keyboardType ||
+ oldTextInputProps.secureTextEntry != newTextInputProps.secureTextEntry) {
+ // Force TSF to re-query ITfInputScope by simulating focus change
+ if (m_textServices && m_hasFocus) {
+ LRESULT lresult;
+ m_textServices->TxSendMessage(WM_KILLFOCUS, 0, 0, &lresult);
+ m_textServices->TxSendMessage(WM_SETFOCUS, 0, 0, &lresult);
+ }
+ }
+
UpdatePropertyBits();
}
@@ -1438,6 +1600,10 @@ void WindowsTextInputComponentView::onMounted() noexcept {
m_propBitsMask |= TXTBIT_CHARFORMATCHANGE;
m_propBits |= TXTBIT_CHARFORMATCHANGE;
}
+
+ // Initialize keyboardType
+ updateKeyboardType(windowsTextInputProps().keyboardType);
+
InternalFinalize();
// Handle autoFocus property - focus the component when mounted if autoFocus is true
@@ -1790,8 +1956,8 @@ WindowsTextInputComponentView::createVisual() noexcept {
LRESULT res;
winrt::check_hresult(m_textServices->TxSendMessage(EM_SETTEXTMODE, TM_PLAINTEXT, 0, &res));
- // Enable TSF support
- winrt::check_hresult(m_textServices->TxSendMessage(EM_SETEDITSTYLE, SES_USECTF, SES_USECTF, nullptr));
+ // Enable TSF (Text Services Framework) for advanced input method support
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETEDITSTYLE, SES_USECTF, SES_USECTF, &res));
m_caretVisual = m_compContext.CreateCaretVisual();
visual.InsertAt(m_caretVisual.InnerVisual(), 0);
@@ -1921,4 +2087,43 @@ void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Founda
DestroyMenu(menu);
}
+void WindowsTextInputComponentView::updateKeyboardType(const std::string &keyboardType) noexcept {
+ // Get the parent/root HWND
+ HWND hwndParent = GetHwndForParenting();
+ if (!hwndParent) {
+ return;
+ }
+
+ // Map keyboard type to InputScope
+ InputScope scope = IS_DEFAULT;
+ bool isSecureTextEntry = windowsTextInputProps().secureTextEntry;
+
+ static const std::unordered_map scopeMap = {
+ {"default", IS_DEFAULT},
+ {"numeric", IS_NUMBER},
+ {"number-pad", IS_DIGITS},
+ {"decimal-pad", IS_NUMBER},
+ {"email-address", IS_EMAIL_SMTPEMAILADDRESS},
+ {"phone-pad", IS_TELEPHONE_FULLTELEPHONENUMBER},
+ {"url", IS_URL},
+ {"web-search", IS_SEARCH}};
+
+ if (isSecureTextEntry) {
+ scope = (keyboardType == "numeric") ? IS_NUMBER : IS_PASSWORD;
+ } else {
+ auto it = scopeMap.find(keyboardType);
+ if (it != scopeMap.end()) {
+ scope = it->second;
+ }
+ }
+
+ // Store the current scope for proxy EDIT control
+ m_currentInputScope = scope;
+
+ // Use SetInputScopes API to set InputScope on the parent HWND
+ if (auto pfnSetInputScopes = GetSetInputScopesProc()) {
+ pfnSetInputScopes(hwndParent, &scope, 1, nullptr, 0, nullptr, nullptr);
+ }
+}
+
} // namespace winrt::Microsoft::ReactNative::Composition::implementation
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h
index 26dc207961c..5c9bc3371b6 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h
@@ -7,6 +7,7 @@
#include "Composition.WindowsTextInputComponentView.g.h"
#include
#include
+#include
#include
#include
#include
@@ -99,6 +100,7 @@ struct WindowsTextInputComponentView
void UpdateParaFormat() noexcept;
void UpdateText(const std::string &str) noexcept;
void OnTextUpdated() noexcept;
+ void EmitOnKeyPress(wchar_t keyChar) noexcept;
void EmitOnScrollEvent() noexcept;
void OnSelectionChanged(LONG start, LONG end) noexcept;
std::pair GetContentSize() const noexcept;
@@ -119,6 +121,7 @@ struct WindowsTextInputComponentView
void updateAutoCorrect(bool value) noexcept;
void updateSpellCheck(bool value) noexcept;
void ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept;
+ void updateKeyboardType(const std::string &keyboardType) noexcept;
winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr};
@@ -148,6 +151,14 @@ struct WindowsTextInputComponentView
HCURSOR m_hcursor{nullptr};
std::chrono::steady_clock::time_point m_lastClickTime{};
std::vector m_submitKeyEvents;
+ InputScope m_currentInputScope{IS_DEFAULT};
+
+ // Hidden proxy EDIT control for InputScope/Touch Keyboard support
+ static HWND s_proxyEditHwnd;
+ static WNDPROC s_originalProxyEditWndProc;
+ static WindowsTextInputComponentView *s_currentFocusedTextInput;
+ static void EnsureProxyEditControl(HWND parentHwnd);
+ static LRESULT CALLBACK ProxyEditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
};
} // namespace winrt::Microsoft::ReactNative::Composition::implementation