react-native-windows 0.81.37 → 0.81.40

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 (52) hide show
  1. package/Microsoft.ReactNative/Fabric/AbiComponentDescriptor.cpp +6 -2
  2. package/Microsoft.ReactNative/Fabric/AbiShadowNode.cpp +0 -47
  3. package/Microsoft.ReactNative/Fabric/AbiShadowNode.h +40 -8
  4. package/Microsoft.ReactNative/Fabric/AbiState.h +26 -8
  5. package/Microsoft.ReactNative/Fabric/AbiTextInputProps.h +51 -0
  6. package/Microsoft.ReactNative/Fabric/AbiTextInputShadowNode.cpp +88 -0
  7. package/Microsoft.ReactNative/Fabric/AbiTextInputShadowNode.h +65 -0
  8. package/Microsoft.ReactNative/Fabric/AbiViewComponentDescriptor.h +52 -18
  9. package/Microsoft.ReactNative/Fabric/AbiViewShadowNode.cpp +0 -18
  10. package/Microsoft.ReactNative/Fabric/AbiViewShadowNode.h +16 -4
  11. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +2 -1
  12. package/Microsoft.ReactNative/Fabric/Composition/ComponentViewRegistry.cpp +1 -2
  13. package/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp +1 -1
  14. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +3 -2
  15. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +6 -4
  16. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +6 -4
  17. package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp +2 -1
  18. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp +22 -0
  19. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +2 -1
  20. package/Microsoft.ReactNative/Fabric/Composition/TextInput/AbiTextInputState.h +38 -0
  21. package/Microsoft.ReactNative/Fabric/Composition/TextInput/ConcreteTextInputShadowNode.h +272 -0
  22. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentDescriptor.h +21 -14
  23. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +345 -90
  24. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +24 -4
  25. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputProps.h +1 -1
  26. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.cpp +0 -205
  27. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.h +22 -65
  28. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputState.h +2 -1
  29. package/Microsoft.ReactNative/Fabric/WindowsComponentDescriptorRegistry.cpp +0 -1
  30. package/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +5 -0
  31. package/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +0 -1
  32. package/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +60 -5
  33. package/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h +3 -0
  34. package/Microsoft.ReactNative/ViewProps.idl +11 -0
  35. package/Microsoft.ReactNative/Views/ViewViewManager.cpp +2 -2
  36. package/Microsoft.ReactNative.Cxx/ComponentView.Experimental.interop.h +12 -0
  37. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  38. package/PropertySheets/JSEngine.props +1 -1
  39. package/PropertySheets/React.Cpp.props +0 -1
  40. package/PropertySheets/WinUI.props +2 -1
  41. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/Instance.cpp +386 -0
  42. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/NativeToJsBridge.h +148 -0
  43. package/Scripts/EnableInternalWinAppSDKFeed.ps1 +12 -5
  44. package/Scripts/NuGetRestoreForceEvaluateAllSolutions.ps1 +2 -2
  45. package/Shared/Networking/WinRTWebSocketResource.h +1 -5
  46. package/Shared/OInstance.cpp +13 -8
  47. package/Shared/Shared.vcxitems +3 -3
  48. package/Shared/Shared.vcxitems.filters +1 -1
  49. package/external/folly/folly.vcxitems +10 -0
  50. package/index.windows.js +5 -0
  51. package/package.json +4 -4
  52. package/Microsoft.ReactNative/Fabric/AbiState.cpp +0 -29
@@ -6,6 +6,7 @@
6
6
  #include "WindowsTextInputComponentView.h"
7
7
 
8
8
  #include <AutoDraw.h>
9
+ #include <Fabric/AbiTextInputProps.h>
9
10
  #include <Fabric/Composition/UiaHelpers.h>
10
11
  #include <Fabric/platform/react/renderer/graphics/PlatformColorUtils.h>
11
12
  #include <Utils/ThemeUtils.h>
@@ -19,6 +20,7 @@
19
20
  #include <winrt/Windows.System.h>
20
21
  #include <winrt/Windows.UI.h>
21
22
  #include <mutex>
23
+ #include <string_view>
22
24
  #include "../Composition.Input.h"
23
25
  #include "../CompositionHelpers.h"
24
26
  #include "../RootComponentView.h"
@@ -83,7 +85,7 @@ static PCreateTextServices g_pfnCreateTextServices;
83
85
  HRESULT HrEnsureRichEd20Loaded() noexcept {
84
86
  HRESULT hr = S_OK;
85
87
  std::call_once(g_richEditLoadedFlag, [&hr]() {
86
- g_hInstRichEdit = LoadLibrary(L"Msftedit.dll");
88
+ g_hInstRichEdit = LoadLibraryExW(L"Msftedit.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
87
89
  if (!g_hInstRichEdit) {
88
90
  hr = E_FAIL;
89
91
  return;
@@ -543,28 +545,118 @@ AutoCorrectOffCallback(LANGID langid, const WCHAR *pszBefore, WCHAR *pszAfter, L
543
545
  }
544
546
 
545
547
  facebook::react::AttributedString WindowsTextInputComponentView::getAttributedString() const {
546
- // Use BaseTextShadowNode to get attributed string from children
547
-
548
548
  auto attributedString = facebook::react::AttributedString{};
549
- // auto attachments = facebook::react::BaseTextShadowNode::Attachments{};
550
-
551
- // BaseTextShadowNode only gets children. We must detect and prepend text
552
- // value attributes manually.
553
- auto text = GetTextFromRichEdit();
554
- if (!text.empty()) {
555
- auto textAttributes = facebook::react::TextAttributes::defaultTextAttributes();
556
- textAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
557
- textAttributes.apply(windowsTextInputProps().textAttributes);
549
+ const auto &props = windowsTextInputProps();
550
+ auto baseTextAttributes = facebook::react::TextAttributes::defaultTextAttributes();
551
+ baseTextAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
552
+ baseTextAttributes.apply(props.textAttributes);
553
+ baseTextAttributes.allowFontScaling = baseTextAttributes.allowFontScaling.value_or(props.allowFontScaling);
554
+ baseTextAttributes.foregroundColor = props.textAttributes.foregroundColor;
555
+ baseTextAttributes.backgroundColor = facebook::react::clearColor();
556
+ attributedString.setBaseTextAttributes(baseTextAttributes);
557
+
558
+ if (!m_textServices) {
559
+ return attributedString;
560
+ }
561
+
562
+ winrt::com_ptr<::IUnknown> richEditOle;
563
+ LRESULT result;
564
+ winrt::check_hresult(
565
+ m_textServices->TxSendMessage(EM_GETOLEINTERFACE, 0, reinterpret_cast<LPARAM>(richEditOle.put()), &result));
566
+ winrt::check_bool(richEditOle != nullptr);
567
+ auto document = richEditOle.as<ITextDocument>();
568
+ winrt::com_ptr<ITextRange> range;
569
+ winrt::check_hresult(document->Range(0, 0, range.put()));
570
+ LONG storyLength;
571
+ winrt::check_hresult(range->GetStoryLength(&storyLength));
572
+ LRESULT textMode{};
573
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_GETTEXTMODE, 0, 0, &textMode));
574
+ // Only rich-text stories include a final paragraph mark outside the TextInput's value.
575
+ const LONG textLength = storyLength - ((textMode & TM_RICHTEXT) ? 1 : 0);
576
+
577
+ float fontSizeMultiplier = 1.0f;
578
+ if (*baseTextAttributes.allowFontScaling) {
579
+ auto maxFontSizeMultiplier = baseTextAttributes.maxFontSizeMultiplier;
580
+ fontSizeMultiplier = (maxFontSizeMultiplier >= 1.0f)
581
+ ? std::min(maxFontSizeMultiplier, baseTextAttributes.fontSizeMultiplier)
582
+ : baseTextAttributes.fontSizeMultiplier;
583
+ }
584
+ winrt::check_bool(fontSizeMultiplier > 0);
585
+
586
+ for (LONG position = 0; position < textLength;) {
587
+ winrt::check_hresult(range->SetRange(position, position));
588
+ LONG delta;
589
+ winrt::check_hresult(range->EndOf(tomCharFormat, tomExtend, &delta));
590
+ LONG end;
591
+ winrt::check_hresult(range->GetEnd(&end));
592
+ end = std::min(end, textLength);
593
+ winrt::check_bool(end > position);
594
+ winrt::check_hresult(range->SetRange(position, end));
595
+
558
596
  auto fragment = facebook::react::AttributedString::Fragment{};
559
- fragment.string = text;
560
- // fragment.string = m_props->text;
561
- fragment.textAttributes = textAttributes;
562
- // If the TextInput opacity is 0 < n < 1, the opacity of the TextInput and
563
- // text value's background will stack. This is a hack/workaround to prevent
564
- // that effect.
565
- fragment.textAttributes.backgroundColor = facebook::react::clearColor();
566
- // fragment.parentShadowView = facebook::react::ShadowView(*this);
567
- attributedString.prependFragment(std::move(fragment));
597
+ BSTR text = nullptr;
598
+ winrt::check_hresult(range->GetText(&text));
599
+ auto bstrToString = [](BSTR value) {
600
+ auto converted = value ? winrt::to_string(std::wstring_view{value, SysStringLen(value)}) : std::string{};
601
+ SysFreeString(value);
602
+ return converted;
603
+ };
604
+ fragment.string = bstrToString(text);
605
+ auto &textAttributes = fragment.textAttributes;
606
+ textAttributes = baseTextAttributes;
607
+
608
+ winrt::com_ptr<ITextFont> font;
609
+ winrt::check_hresult(range->GetFont(font.put()));
610
+ BSTR fontName = nullptr;
611
+ winrt::check_hresult(font->GetName(&fontName));
612
+ textAttributes.fontFamily = bstrToString(fontName);
613
+ float fontSize;
614
+ winrt::check_hresult(font->GetSize(&fontSize));
615
+ // TOM reports rendered points; store unscaled DIPs so UpdateText does not scale them twice.
616
+ textAttributes.fontSize = fontSize * (4.0f / 3.0f) / fontSizeMultiplier;
617
+ LONG weight;
618
+ winrt::check_hresult(font->GetWeight(&weight));
619
+ textAttributes.fontWeight = static_cast<facebook::react::FontWeight>(weight);
620
+ LONG italic;
621
+ winrt::check_hresult(font->GetItalic(&italic));
622
+ textAttributes.fontStyle =
623
+ italic == tomTrue ? facebook::react::FontStyle::Italic : facebook::react::FontStyle::Normal;
624
+ float spacing;
625
+ winrt::check_hresult(font->GetSpacing(&spacing));
626
+ textAttributes.letterSpacing = spacing;
627
+
628
+ LONG underline;
629
+ LONG strikethrough;
630
+ winrt::check_hresult(font->GetUnderline(&underline));
631
+ winrt::check_hresult(font->GetStrikeThrough(&strikethrough));
632
+ const bool hasUnderline = underline != tomNone;
633
+ const bool hasStrikethrough = strikethrough == tomTrue;
634
+ textAttributes.textDecorationLineType = hasUnderline
635
+ ? (hasStrikethrough ? facebook::react::TextDecorationLineType::UnderlineStrikethrough
636
+ : facebook::react::TextDecorationLineType::Underline)
637
+ : (hasStrikethrough ? facebook::react::TextDecorationLineType::Strikethrough
638
+ : facebook::react::TextDecorationLineType::None);
639
+
640
+ LONG foreground;
641
+ LONG background;
642
+ winrt::check_hresult(font->GetForeColor(&foreground));
643
+ winrt::check_hresult(font->GetBackColor(&background));
644
+ if (foreground != tomAutoColor) {
645
+ textAttributes.foregroundColor =
646
+ facebook::react::colorFromRGBA(GetRValue(foreground), GetGValue(foreground), GetBValue(foreground), 255);
647
+ }
648
+ if (background != tomAutoColor) {
649
+ textAttributes.backgroundColor =
650
+ facebook::react::colorFromRGBA(GetRValue(background), GetGValue(background), GetBValue(background), 255);
651
+ }
652
+
653
+ auto &fragments = attributedString.getFragments();
654
+ if (!fragments.empty() && fragments.back().textAttributes == textAttributes) {
655
+ fragments.back().string += fragment.string;
656
+ } else {
657
+ attributedString.appendFragment(std::move(fragment));
658
+ }
659
+ position = end;
568
660
  }
569
661
 
570
662
  return attributedString;
@@ -573,13 +665,15 @@ facebook::react::AttributedString WindowsTextInputComponentView::getAttributedSt
573
665
  WindowsTextInputComponentView::WindowsTextInputComponentView(
574
666
  const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
575
667
  facebook::react::Tag tag,
576
- winrt::Microsoft::ReactNative::ReactContext const &reactContext)
668
+ winrt::Microsoft::ReactNative::ReactContext const &reactContext,
669
+ winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder *builder)
577
670
  : Super(
578
671
  WindowsTextInputComponentView::defaultProps(),
579
672
  compContext,
580
673
  tag,
581
674
  reactContext,
582
- ComponentViewFeatures::Default & ~ComponentViewFeatures::Background) {}
675
+ ComponentViewFeatures::Default & ~ComponentViewFeatures::Background,
676
+ builder) {}
583
677
 
584
678
  WindowsTextInputComponentView::~WindowsTextInputComponentView() {
585
679
  // Release text services first to prevent further callbacks into CompTextHost.
@@ -591,6 +685,17 @@ WindowsTextInputComponentView::~WindowsTextInputComponentView() {
591
685
  }
592
686
  }
593
687
 
688
+ winrt::Microsoft::ReactNative::ViewProps WindowsTextInputComponentView::ViewPropsInner() noexcept {
689
+ // During initialization, even custom text inputs still have native default props.
690
+ if (auto abiProps = dynamic_cast<const ::Microsoft::ReactNative::AbiTextInputProps *>(viewProps().get())) {
691
+ if (auto props = abiProps->ViewProps()) {
692
+ return props;
693
+ }
694
+ }
695
+
696
+ return winrt::make<winrt::Microsoft::ReactNative::implementation::TextInputProps>(viewProps());
697
+ }
698
+
594
699
  void WindowsTextInputComponentView::HandleCommand(
595
700
  const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept {
596
701
  Super::HandleCommand(args);
@@ -608,7 +713,9 @@ void WindowsTextInputComponentView::HandleCommand(
608
713
  {
609
714
  if (text.has_value()) {
610
715
  DrawBlock db(*this);
611
- UpdateText(winrt::to_string(text.value()));
716
+ facebook::react::AttributedString attributedString;
717
+ attributedString.appendFragment({winrt::to_string(text.value()), windowsTextInputProps().textAttributes, {}});
718
+ UpdateText(attributedString);
612
719
  }
613
720
 
614
721
  SELCHANGE sc;
@@ -1039,21 +1146,23 @@ void WindowsTextInputComponentView::OnCharacterReceived(
1039
1146
  return;
1040
1147
  }
1041
1148
 
1042
- // convert keyCode to std::string
1043
- wchar_t key[2] = L" ";
1044
- key[0] = static_cast<wchar_t>(args.KeyCode());
1045
- std::string keyString = ::Microsoft::Common::Unicode::Utf16ToUtf8(key, 1);
1046
- // Call onKeyPress event
1047
- auto emitter = std::static_pointer_cast<const facebook::react::WindowsTextInputEventEmitter>(m_eventEmitter);
1048
- facebook::react::WindowsTextInputEventEmitter::OnKeyPress onKeyPressArgs;
1049
- if (keyString.compare("\r") == 0) {
1050
- onKeyPressArgs.key = "Enter";
1051
- } else if (keyString.compare("\b") == 0) {
1052
- onKeyPressArgs.key = "Backspace";
1053
- } else {
1054
- onKeyPressArgs.key = keyString;
1149
+ if (m_eventEmitter) {
1150
+ // convert keyCode to std::string
1151
+ wchar_t key[2] = L" ";
1152
+ key[0] = static_cast<wchar_t>(args.KeyCode());
1153
+ std::string keyString = ::Microsoft::Common::Unicode::Utf16ToUtf8(key, 1);
1154
+ // Call onKeyPress event
1155
+ auto emitter = std::static_pointer_cast<const facebook::react::WindowsTextInputEventEmitter>(m_eventEmitter);
1156
+ facebook::react::WindowsTextInputEventEmitter::OnKeyPress onKeyPressArgs;
1157
+ if (keyString.compare("\r") == 0) {
1158
+ onKeyPressArgs.key = "Enter";
1159
+ } else if (keyString.compare("\b") == 0) {
1160
+ onKeyPressArgs.key = "Backspace";
1161
+ } else {
1162
+ onKeyPressArgs.key = keyString;
1163
+ }
1164
+ emitter->onKeyPress(onKeyPressArgs);
1055
1165
  }
1056
- emitter->onKeyPress(onKeyPressArgs);
1057
1166
 
1058
1167
  WPARAM wParam = static_cast<WPARAM>(args.KeyCode());
1059
1168
 
@@ -1160,6 +1269,12 @@ void WindowsTextInputComponentView::updateCursorColor(
1160
1269
  m_caretVisual.Brush(theme()->Brush(*caretColor));
1161
1270
  }
1162
1271
 
1272
+ const winrt::Microsoft::ReactNative::IComponentProps WindowsTextInputComponentView::userProps(
1273
+ facebook::react::Props::Shared const &props) noexcept {
1274
+ const auto &textInputProps = *std::static_pointer_cast<const ::Microsoft::ReactNative::AbiTextInputProps>(props);
1275
+ return textInputProps.UserProps();
1276
+ }
1277
+
1163
1278
  void WindowsTextInputComponentView::updateProps(
1164
1279
  facebook::react::Props::Shared const &props,
1165
1280
  facebook::react::Props::Shared const &oldProps) noexcept {
@@ -1270,15 +1385,28 @@ void WindowsTextInputComponentView::updateProps(
1270
1385
  void WindowsTextInputComponentView::updateState(
1271
1386
  facebook::react::State::Shared const &state,
1272
1387
  facebook::react::State::Shared const &oldState) noexcept {
1273
- m_state = std::static_pointer_cast<facebook::react::WindowsTextInputShadowNode::ConcreteState const>(state);
1388
+ if (m_builder) {
1389
+ m_abistate = std::static_pointer_cast<::Microsoft::ReactNative::AbiTextInputShadowNode::ConcreteState const>(state);
1390
+ } else {
1391
+ m_state = std::static_pointer_cast<facebook::react::WindowsTextInputShadowNode::ConcreteState const>(state);
1392
+ }
1393
+
1394
+ // Avoid new-ing up a new AbiComponentState on every state change if we are not a custom component
1395
+ if (m_builder && m_builder->UpdateStateHandler()) {
1396
+ m_builder->UpdateStateHandler()(
1397
+ *this,
1398
+ winrt::make<::Microsoft::ReactNative::AbiComponentState<::Microsoft::ReactNative::AbiTextInputStateData>>(
1399
+ state));
1400
+ }
1274
1401
 
1275
- if (!m_state) {
1402
+ if (!m_state && !m_abistate) {
1276
1403
  assert(false && "State is `null` for <TextInput> component.");
1277
1404
  return;
1278
1405
  }
1279
1406
 
1280
1407
  if (!oldState) {
1281
- m_mostRecentEventCount = m_state->getData().mostRecentEventCount;
1408
+ m_mostRecentEventCount =
1409
+ m_builder ? m_abistate->getData().mostRecentEventCount : m_state->getData().mostRecentEventCount;
1282
1410
  }
1283
1411
 
1284
1412
  if (auto root = rootComponentView()) {
@@ -1290,22 +1418,32 @@ void WindowsTextInputComponentView::updateState(
1290
1418
  }
1291
1419
  }
1292
1420
 
1293
- if (m_mostRecentEventCount == m_state->getData().mostRecentEventCount) {
1421
+ // Userdata-only updates must not replay potentially stale native text.
1422
+ const bool textChanged = !m_builder || !oldState ||
1423
+ m_abistate->getData().attributedStringBox !=
1424
+ static_cast<const ::Microsoft::ReactNative::AbiTextInputShadowNode::ConcreteState *>(oldState.get())
1425
+ ->getData()
1426
+ .attributedStringBox;
1427
+ if (textChanged &&
1428
+ (m_builder ? m_mostRecentEventCount == m_abistate->getData().mostRecentEventCount
1429
+ : m_mostRecentEventCount == m_state->getData().mostRecentEventCount)) {
1294
1430
  m_comingFromState = true;
1295
- auto &fragments = m_state->getData().attributedStringBox.getValue().getFragments();
1296
1431
  {
1297
1432
  // DrawBlock defers DrawText() until after UpdateText completes
1298
1433
  DrawBlock db(*this);
1299
- UpdateText(fragments.size() ? fragments[0].string : "");
1434
+ UpdateText(
1435
+ m_builder ? m_abistate->getData().attributedStringBox.getValue()
1436
+ : m_state->getData().attributedStringBox.getValue());
1300
1437
  }
1301
1438
  m_comingFromState = false;
1302
1439
  }
1303
1440
  }
1304
1441
 
1305
- void WindowsTextInputComponentView::UpdateText(const std::string &str) noexcept {
1442
+ void WindowsTextInputComponentView::UpdateText(const facebook::react::AttributedString &attributedString) noexcept {
1443
+ DrawBlock db(*this);
1306
1444
  SETTEXTEX stt;
1307
1445
  memset(&stt, 0, sizeof(stt));
1308
- stt.flags = ST_DEFAULT;
1446
+ stt.flags = 0x20 /*ST_PLAINTEXTONLY*/;
1309
1447
  stt.codepage = CP_UTF8;
1310
1448
  LRESULT res;
1311
1449
 
@@ -1313,8 +1451,51 @@ void WindowsTextInputComponentView::UpdateText(const std::string &str) noexcept
1313
1451
  cr.cpMin = cr.cpMax = 0;
1314
1452
  winrt::check_hresult(m_textServices->TxSendMessage(EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&cr), &res));
1315
1453
 
1454
+ // Formatting uses temporary selections and must not publish intermediate text or selection changes.
1455
+ LRESULT eventMask;
1456
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_GETEVENTMASK, 0, 0, &eventMask));
1457
+ winrt::check_hresult(
1458
+ m_textServices->TxSendMessage(EM_SETEVENTMASK, 0, eventMask & ~(ENM_CHANGE | ENM_SELCHANGE), &res));
1459
+
1460
+ auto text = attributedString.getString();
1316
1461
  winrt::check_hresult(m_textServices->TxSendMessage(
1317
- EM_SETTEXTEX, reinterpret_cast<WPARAM>(&stt), reinterpret_cast<LPARAM>(str.c_str()), &res));
1462
+ EM_SETTEXTEX, reinterpret_cast<WPARAM>(&stt), reinterpret_cast<LPARAM>(text.c_str()), &res));
1463
+
1464
+ auto baseTextAttributes = windowsTextInputProps().textAttributes;
1465
+ baseTextAttributes.apply(attributedString.getBaseTextAttributes());
1466
+ auto charFormat = GetCharFormat(baseTextAttributes);
1467
+ winrt::check_hresult(
1468
+ m_textServices->TxSendMessage(EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast<LPARAM>(&charFormat), &res));
1469
+ winrt::check_bool(res != 0);
1470
+
1471
+ // Richedit in password mode does not support per-fragment formatting, so we only apply it when not in secureTextEntry
1472
+ // mode.
1473
+ if (!windowsTextInputProps().secureTextEntry) {
1474
+ LONG position = 0;
1475
+ bool previousWasCR = false;
1476
+ for (const auto &fragment : attributedString.getFragments()) {
1477
+ auto fragmentText = ::Microsoft::Common::Unicode::Utf8ToUtf16(fragment.string);
1478
+ LONG end = position;
1479
+ for (auto character : fragmentText) {
1480
+ // RichEdit positions count UTF-16 code units, with CRLF normalized to a single paragraph mark.
1481
+ if (character != L'\n' || !previousWasCR) {
1482
+ ++end;
1483
+ }
1484
+ previousWasCR = character == L'\r';
1485
+ }
1486
+
1487
+ if (end != position) {
1488
+ auto textAttributes = baseTextAttributes;
1489
+ textAttributes.apply(fragment.textAttributes);
1490
+ charFormat = GetCharFormat(textAttributes);
1491
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETSEL, position, end, &res));
1492
+ winrt::check_hresult(m_textServices->TxSendMessage(
1493
+ EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(&charFormat), &res));
1494
+ winrt::check_bool(res != 0);
1495
+ }
1496
+ position = end;
1497
+ }
1498
+ }
1318
1499
 
1319
1500
  winrt::check_hresult(
1320
1501
  m_textServices->TxSendMessage(EM_SETSEL, static_cast<WPARAM>(cr.cpMin), static_cast<LPARAM>(cr.cpMax), &res));
@@ -1322,6 +1503,20 @@ void WindowsTextInputComponentView::UpdateText(const std::string &str) noexcept
1322
1503
  // enable colored emojis
1323
1504
  winrt::check_hresult(
1324
1505
  m_textServices->TxSendMessage(EM_SETTYPOGRAPHYOPTIONS, 0x1000 | 0x2000, 0x1000 | 0x2000, nullptr));
1506
+
1507
+ m_recalculateContentVerticalOffset = true;
1508
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETEVENTMASK, 0, eventMask, &res));
1509
+ if ((eventMask & ENM_CHANGE) && !m_comingFromState) {
1510
+ OnTextUpdated();
1511
+ }
1512
+ if (eventMask & ENM_SELCHANGE) {
1513
+ CHARRANGE finalSelection;
1514
+ winrt::check_hresult(
1515
+ m_textServices->TxSendMessage(EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&finalSelection), &res));
1516
+ if (finalSelection.cpMin != cr.cpMin || finalSelection.cpMax != cr.cpMax) {
1517
+ OnSelectionChanged(finalSelection.cpMin, finalSelection.cpMax);
1518
+ }
1519
+ }
1325
1520
  }
1326
1521
 
1327
1522
  void WindowsTextInputComponentView::updateLayoutMetrics(
@@ -1373,7 +1568,10 @@ std::pair<float, float> WindowsTextInputComponentView::GetContentSize() const no
1373
1568
 
1374
1569
  SIZE extentHimetric = {
1375
1570
  static_cast<LONG>(availableWidth * scale * HIMETRIC_PER_INCH / dpi),
1376
- static_cast<LONG>(std::numeric_limits<LONG>::max() * HIMETRIC_PER_INCH / dpi)};
1571
+ // -1 measures without zoom, matching how this host draws (TxGetExtent returns E_NOTIMPL).
1572
+ // A positive extent is taken as a zoom reference; LONG_MAX scaled to HIMETRIC saturated to a
1573
+ // huge positive value on ARM64 and shrank the natural height to about 0.
1574
+ -1};
1377
1575
 
1378
1576
  SIZE naturalSize = {0, 0};
1379
1577
 
@@ -1401,14 +1599,22 @@ std::pair<float, float> WindowsTextInputComponentView::GetContentSize() const no
1401
1599
 
1402
1600
  // When we are notified by RichEdit that the text changed, we need to notify JS
1403
1601
  void WindowsTextInputComponentView::OnTextUpdated() noexcept {
1404
- auto data = m_state->getData();
1405
- // auto newAttributedString = getAttributedString();
1406
- // if (data.attributedString == newAttributedString)
1407
- // return;
1408
- data.attributedStringBox = facebook::react::AttributedStringBox(getAttributedString());
1409
- data.mostRecentEventCount = m_nativeEventCount;
1410
-
1411
- m_state->updateState(std::move(data));
1602
+ if (m_builder) {
1603
+ auto attributedStringBox = facebook::react::AttributedStringBox(getAttributedString());
1604
+ const auto eventCount = m_nativeEventCount;
1605
+ m_abistate->updateState([attributedStringBox = std::move(attributedStringBox),
1606
+ eventCount](const ::Microsoft::ReactNative::AbiTextInputStateData &oldData) {
1607
+ auto data = oldData;
1608
+ data.attributedStringBox = attributedStringBox;
1609
+ data.mostRecentEventCount = eventCount;
1610
+ return std::make_shared<const ::Microsoft::ReactNative::AbiTextInputStateData>(std::move(data));
1611
+ });
1612
+ } else {
1613
+ auto data = m_state->getData();
1614
+ data.attributedStringBox = facebook::react::AttributedStringBox(getAttributedString());
1615
+ data.mostRecentEventCount = m_nativeEventCount;
1616
+ m_state->updateState(std::move(data));
1617
+ }
1412
1618
 
1413
1619
  if (m_eventEmitter && !m_comingFromJS) {
1414
1620
  // call onChange event
@@ -1537,7 +1743,9 @@ std::optional<std::string> WindowsTextInputComponentView::getAccessiblityValue()
1537
1743
  }
1538
1744
 
1539
1745
  void WindowsTextInputComponentView::setAcccessiblityValue(std::string &&value) noexcept {
1540
- UpdateText(value);
1746
+ facebook::react::AttributedString attributedString;
1747
+ attributedString.appendFragment({std::move(value), windowsTextInputProps().textAttributes, {}});
1748
+ UpdateText(attributedString);
1541
1749
  }
1542
1750
 
1543
1751
  bool WindowsTextInputComponentView::getAcccessiblityIsReadOnly() noexcept {
@@ -1554,6 +1762,11 @@ const facebook::react::WindowsTextInputProps &WindowsTextInputComponentView::win
1554
1762
  }
1555
1763
 
1556
1764
  void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1765
+ m_cf = GetCharFormat(windowsTextInputProps().textAttributes);
1766
+ }
1767
+
1768
+ CHARFORMAT2W WindowsTextInputComponentView::GetCharFormat(
1769
+ const facebook::react::TextAttributes &textAttributes) noexcept {
1557
1770
  CHARFORMAT2W cfNew = {0};
1558
1771
  // Set CHARFORMAT structure
1559
1772
  cfNew.cbSize = sizeof(CHARFORMAT2W);
@@ -1567,15 +1780,18 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1567
1780
  // m_crText = RemoveAlpha(fontDetails.FontColor);
1568
1781
  // }
1569
1782
 
1570
- const auto &props = windowsTextInputProps();
1571
1783
  float fontSize =
1572
- (std::isnan(props.textAttributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
1573
- : props.textAttributes.fontSize);
1784
+ (std::isnan(textAttributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
1785
+ : textAttributes.fontSize);
1574
1786
 
1575
1787
  // Apply maxFontSizeMultiplier if specified
1576
- auto maxFontSizeMultiplier = windowsTextInputProps().textAttributes.maxFontSizeMultiplier;
1577
- fontSize *=
1578
- (maxFontSizeMultiplier >= 1.0f) ? std::min(maxFontSizeMultiplier, m_fontSizeMultiplier) : m_fontSizeMultiplier;
1788
+ if (textAttributes.allowFontScaling.value_or(windowsTextInputProps().allowFontScaling)) {
1789
+ auto fontSizeMultiplier =
1790
+ std::isnan(textAttributes.fontSizeMultiplier) ? m_fontSizeMultiplier : textAttributes.fontSizeMultiplier;
1791
+ auto maxFontSizeMultiplier = textAttributes.maxFontSizeMultiplier;
1792
+ fontSize *=
1793
+ (maxFontSizeMultiplier >= 1.0f) ? std::min(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier;
1794
+ }
1579
1795
 
1580
1796
  cfNew.dwMask |= CFM_SIZE;
1581
1797
  // set font size -- 15 to convert twips to pt
@@ -1583,20 +1799,19 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1583
1799
 
1584
1800
  // set bold
1585
1801
  cfNew.dwMask |= CFM_WEIGHT;
1586
- cfNew.wWeight =
1587
- props.textAttributes.fontWeight ? static_cast<WORD>(*props.textAttributes.fontWeight) : DWRITE_FONT_WEIGHT_NORMAL;
1802
+ cfNew.wWeight = textAttributes.fontWeight ? static_cast<WORD>(*textAttributes.fontWeight) : DWRITE_FONT_WEIGHT_NORMAL;
1588
1803
 
1589
1804
  // set font style (italic)
1590
1805
  cfNew.dwMask |= CFM_ITALIC;
1591
- if (props.textAttributes.fontStyle == facebook::react::FontStyle::Italic ||
1592
- props.textAttributes.fontStyle == facebook::react::FontStyle::Oblique) {
1806
+ if (textAttributes.fontStyle == facebook::react::FontStyle::Italic ||
1807
+ textAttributes.fontStyle == facebook::react::FontStyle::Oblique) {
1593
1808
  cfNew.dwEffects |= CFE_ITALIC;
1594
1809
  }
1595
1810
 
1596
1811
  // set text decoration (underline and strikethrough)
1597
1812
  cfNew.dwMask |= (CFM_UNDERLINE | CFM_STRIKEOUT);
1598
- if (props.textAttributes.textDecorationLineType.has_value()) {
1599
- auto decorationType = *props.textAttributes.textDecorationLineType;
1813
+ if (textAttributes.textDecorationLineType.has_value()) {
1814
+ auto decorationType = *textAttributes.textDecorationLineType;
1600
1815
  if (decorationType == facebook::react::TextDecorationLineType::Underline ||
1601
1816
  decorationType == facebook::react::TextDecorationLineType::UnderlineStrikethrough) {
1602
1817
  cfNew.dwEffects |= CFE_UNDERLINE;
@@ -1608,11 +1823,10 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1608
1823
  }
1609
1824
 
1610
1825
  // set font family
1611
- if (!props.textAttributes.fontFamily.empty()) {
1826
+ if (!textAttributes.fontFamily.empty()) {
1612
1827
  cfNew.dwMask |= CFM_FACE;
1613
- std::wstring fontFamily =
1614
- std::wstring(props.textAttributes.fontFamily.begin(), props.textAttributes.fontFamily.end());
1615
- wcsncpy_s(cfNew.szFaceName, fontFamily.c_str(), LF_FACESIZE);
1828
+ auto fontFamily = ::Microsoft::Common::Unicode::Utf8ToUtf16(textAttributes.fontFamily);
1829
+ wcsncpy_s(cfNew.szFaceName, fontFamily.c_str(), _TRUNCATE);
1616
1830
  } else {
1617
1831
  cfNew.dwMask |= CFM_FACE;
1618
1832
  wcsncpy_s(cfNew.szFaceName, L"Segoe UI\0", LF_FACESIZE);
@@ -1624,17 +1838,29 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1624
1838
  cfNew.yOffset = 0;
1625
1839
 
1626
1840
  // set letter spacing
1627
- float letterSpacing = props.textAttributes.letterSpacing;
1628
- if (!std::isnan(letterSpacing)) {
1629
- cfNew.dwMask |= CFM_SPACING;
1630
- cfNew.sSpacing = static_cast<SHORT>(letterSpacing * 20); // Convert to TWIPS
1841
+ float letterSpacing = textAttributes.letterSpacing;
1842
+ cfNew.dwMask |= CFM_SPACING;
1843
+ cfNew.sSpacing = std::isnan(letterSpacing) ? 0 : static_cast<SHORT>(letterSpacing * 20); // Convert to TWIPS
1844
+
1845
+ cfNew.dwMask |= CFM_COLOR | CFM_BACKCOLOR;
1846
+ cfNew.dwEffects |= CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
1847
+ if (textAttributes.foregroundColor &&
1848
+ textAttributes.foregroundColor != windowsTextInputProps().textAttributes.foregroundColor) {
1849
+ auto color = theme()->Color(*textAttributes.foregroundColor);
1850
+ cfNew.crTextColor = RGB(color.R, color.G, color.B);
1851
+ cfNew.dwEffects &= ~CFE_AUTOCOLOR;
1852
+ }
1853
+ if (facebook::react::isColorMeaningful(textAttributes.backgroundColor)) {
1854
+ auto color = theme()->Color(*textAttributes.backgroundColor);
1855
+ cfNew.crBackColor = RGB(color.R, color.G, color.B);
1856
+ cfNew.dwEffects &= ~CFE_AUTOBACKCOLOR;
1631
1857
  }
1632
1858
 
1633
1859
  // set charset
1634
1860
  cfNew.dwMask |= CFM_CHARSET;
1635
1861
  cfNew.bCharSet = DEFAULT_CHARSET;
1636
1862
 
1637
- m_cf = cfNew;
1863
+ return cfNew;
1638
1864
  }
1639
1865
 
1640
1866
  void WindowsTextInputComponentView::UpdateParaFormat() noexcept {
@@ -1885,21 +2111,50 @@ void WindowsTextInputComponentView::DrawText() noexcept {
1885
2111
  m_needsRedraw = false;
1886
2112
  }
1887
2113
 
1888
- winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
1889
- WindowsTextInputComponentView::createVisual() noexcept {
1890
- HrEnsureRichEd20Loaded();
1891
- auto visual = m_compContext.CreateSpriteVisual();
1892
- m_textHost = winrt::make<CompTextHost>(this);
1893
- winrt::com_ptr<::IUnknown> spUnk;
1894
- winrt::check_hresult(g_pfnCreateTextServices(nullptr, m_textHost.get(), spUnk.put()));
1895
- spUnk.as(m_textServices);
1896
- winrt::check_bool(m_textServices != nullptr);
2114
+ void WindowsTextInputComponentView::ensureTextHost() noexcept {
2115
+ if (!m_textHost) {
2116
+ winrt::check_hresult(HrEnsureRichEd20Loaded());
2117
+ m_textHost = winrt::make<CompTextHost>(this);
2118
+ winrt::com_ptr<::IUnknown> spUnk;
2119
+ winrt::check_hresult(g_pfnCreateTextServices(nullptr, m_textHost.get(), spUnk.put()));
2120
+ spUnk.as(m_textServices);
2121
+ winrt::check_bool(m_textServices != nullptr);
1897
2122
 
2123
+ // Enable TSF support
2124
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETEDITSTYLE, SES_USECTF, SES_USECTF, nullptr));
2125
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETTEXTMODE, TM_RICHTEXT, 0, nullptr));
2126
+ }
2127
+ }
2128
+
2129
+ HRESULT WindowsTextInputComponentView::GetTextDocument(ITextDocument2 **textDocument) noexcept {
2130
+ winrt::com_ptr<IUnknown> spUnk;
2131
+ winrt::com_ptr<ITextDocument2> spTextDoc;
2132
+ ensureTextHost();
2133
+
2134
+ // initialize TOM
1898
2135
  LRESULT res;
1899
- winrt::check_hresult(m_textServices->TxSendMessage(EM_SETTEXTMODE, TM_PLAINTEXT, 0, &res));
2136
+ m_textServices->TxSendMessage(EM_GETOLEINTERFACE, 0, reinterpret_cast<LPARAM>(spUnk.put()), &res);
2137
+ HRESULT hr = spUnk->QueryInterface(__uuidof(ITextDocument2), reinterpret_cast<void **>(spTextDoc.put()));
2138
+ spTextDoc.copy_to(textDocument);
2139
+ return hr;
2140
+ }
2141
+
2142
+ HRESULT WindowsTextInputComponentView::GetTextServices(ITextServices2 **textServices) noexcept {
2143
+ ensureTextHost();
2144
+ m_textServices.copy_to(textServices);
2145
+ return S_OK;
2146
+ }
2147
+
2148
+ HRESULT WindowsTextInputComponentView::GetTextHost(ITextHost **textHost) noexcept {
2149
+ ensureTextHost();
2150
+ m_textHost.copy_to(textHost);
2151
+ return S_OK;
2152
+ }
1900
2153
 
1901
- // Enable TSF support
1902
- winrt::check_hresult(m_textServices->TxSendMessage(EM_SETEDITSTYLE, SES_USECTF, SES_USECTF, nullptr));
2154
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
2155
+ WindowsTextInputComponentView::createVisual() noexcept {
2156
+ auto visual = m_compContext.CreateSpriteVisual();
2157
+ ensureTextHost();
1903
2158
 
1904
2159
  m_caretVisual = m_compContext.CreateCaretVisual();
1905
2160
  visual.InsertAt(m_caretVisual.InnerVisual(), 0);