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