react-native-windows 0.81.37 → 0.81.38

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 (39) 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/CompositionEventHandler.cpp +3 -2
  14. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp +22 -0
  15. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +2 -1
  16. package/Microsoft.ReactNative/Fabric/Composition/TextInput/AbiTextInputState.h +38 -0
  17. package/Microsoft.ReactNative/Fabric/Composition/TextInput/ConcreteTextInputShadowNode.h +272 -0
  18. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentDescriptor.h +21 -14
  19. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +328 -75
  20. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +24 -4
  21. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputProps.h +1 -1
  22. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.cpp +0 -205
  23. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.h +22 -65
  24. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputState.h +2 -1
  25. package/Microsoft.ReactNative/Fabric/WindowsComponentDescriptorRegistry.cpp +0 -1
  26. package/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +5 -0
  27. package/Microsoft.ReactNative/ViewProps.idl +11 -0
  28. package/Microsoft.ReactNative.Cxx/ComponentView.Experimental.interop.h +12 -0
  29. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  30. package/PropertySheets/WinUI.props +2 -1
  31. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/Instance.cpp +386 -0
  32. package/ReactCommon/TEMP_UntilReactCommonUpdate/cxxreact/NativeToJsBridge.h +148 -0
  33. package/Scripts/EnableInternalWinAppSDKFeed.ps1 +12 -5
  34. package/Scripts/NuGetRestoreForceEvaluateAllSolutions.ps1 +2 -2
  35. package/Shared/Shared.vcxitems +3 -3
  36. package/Shared/Shared.vcxitems.filters +1 -1
  37. package/index.windows.js +5 -0
  38. package/package.json +2 -2
  39. 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"
@@ -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;
@@ -1160,6 +1267,12 @@ void WindowsTextInputComponentView::updateCursorColor(
1160
1267
  m_caretVisual.Brush(theme()->Brush(*caretColor));
1161
1268
  }
1162
1269
 
1270
+ const winrt::Microsoft::ReactNative::IComponentProps WindowsTextInputComponentView::userProps(
1271
+ facebook::react::Props::Shared const &props) noexcept {
1272
+ const auto &textInputProps = *std::static_pointer_cast<const ::Microsoft::ReactNative::AbiTextInputProps>(props);
1273
+ return textInputProps.UserProps();
1274
+ }
1275
+
1163
1276
  void WindowsTextInputComponentView::updateProps(
1164
1277
  facebook::react::Props::Shared const &props,
1165
1278
  facebook::react::Props::Shared const &oldProps) noexcept {
@@ -1270,15 +1383,28 @@ void WindowsTextInputComponentView::updateProps(
1270
1383
  void WindowsTextInputComponentView::updateState(
1271
1384
  facebook::react::State::Shared const &state,
1272
1385
  facebook::react::State::Shared const &oldState) noexcept {
1273
- m_state = std::static_pointer_cast<facebook::react::WindowsTextInputShadowNode::ConcreteState const>(state);
1386
+ if (m_builder) {
1387
+ m_abistate = std::static_pointer_cast<::Microsoft::ReactNative::AbiTextInputShadowNode::ConcreteState const>(state);
1388
+ } else {
1389
+ m_state = std::static_pointer_cast<facebook::react::WindowsTextInputShadowNode::ConcreteState const>(state);
1390
+ }
1274
1391
 
1275
- if (!m_state) {
1392
+ // Avoid new-ing up a new AbiComponentState on every state change if we are not a custom component
1393
+ if (m_builder && m_builder->UpdateStateHandler()) {
1394
+ m_builder->UpdateStateHandler()(
1395
+ *this,
1396
+ winrt::make<::Microsoft::ReactNative::AbiComponentState<::Microsoft::ReactNative::AbiTextInputStateData>>(
1397
+ state));
1398
+ }
1399
+
1400
+ if (!m_state && !m_abistate) {
1276
1401
  assert(false && "State is `null` for <TextInput> component.");
1277
1402
  return;
1278
1403
  }
1279
1404
 
1280
1405
  if (!oldState) {
1281
- m_mostRecentEventCount = m_state->getData().mostRecentEventCount;
1406
+ m_mostRecentEventCount =
1407
+ m_builder ? m_abistate->getData().mostRecentEventCount : m_state->getData().mostRecentEventCount;
1282
1408
  }
1283
1409
 
1284
1410
  if (auto root = rootComponentView()) {
@@ -1290,22 +1416,32 @@ void WindowsTextInputComponentView::updateState(
1290
1416
  }
1291
1417
  }
1292
1418
 
1293
- if (m_mostRecentEventCount == m_state->getData().mostRecentEventCount) {
1419
+ // Userdata-only updates must not replay potentially stale native text.
1420
+ const bool textChanged = !m_builder || !oldState ||
1421
+ m_abistate->getData().attributedStringBox !=
1422
+ static_cast<const ::Microsoft::ReactNative::AbiTextInputShadowNode::ConcreteState *>(oldState.get())
1423
+ ->getData()
1424
+ .attributedStringBox;
1425
+ if (textChanged &&
1426
+ (m_builder ? m_mostRecentEventCount == m_abistate->getData().mostRecentEventCount
1427
+ : m_mostRecentEventCount == m_state->getData().mostRecentEventCount)) {
1294
1428
  m_comingFromState = true;
1295
- auto &fragments = m_state->getData().attributedStringBox.getValue().getFragments();
1296
1429
  {
1297
1430
  // DrawBlock defers DrawText() until after UpdateText completes
1298
1431
  DrawBlock db(*this);
1299
- UpdateText(fragments.size() ? fragments[0].string : "");
1432
+ UpdateText(
1433
+ m_builder ? m_abistate->getData().attributedStringBox.getValue()
1434
+ : m_state->getData().attributedStringBox.getValue());
1300
1435
  }
1301
1436
  m_comingFromState = false;
1302
1437
  }
1303
1438
  }
1304
1439
 
1305
- void WindowsTextInputComponentView::UpdateText(const std::string &str) noexcept {
1440
+ void WindowsTextInputComponentView::UpdateText(const facebook::react::AttributedString &attributedString) noexcept {
1441
+ DrawBlock db(*this);
1306
1442
  SETTEXTEX stt;
1307
1443
  memset(&stt, 0, sizeof(stt));
1308
- stt.flags = ST_DEFAULT;
1444
+ stt.flags = 0x20 /*ST_PLAINTEXTONLY*/;
1309
1445
  stt.codepage = CP_UTF8;
1310
1446
  LRESULT res;
1311
1447
 
@@ -1313,8 +1449,51 @@ void WindowsTextInputComponentView::UpdateText(const std::string &str) noexcept
1313
1449
  cr.cpMin = cr.cpMax = 0;
1314
1450
  winrt::check_hresult(m_textServices->TxSendMessage(EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&cr), &res));
1315
1451
 
1452
+ // Formatting uses temporary selections and must not publish intermediate text or selection changes.
1453
+ LRESULT eventMask;
1454
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_GETEVENTMASK, 0, 0, &eventMask));
1455
+ winrt::check_hresult(
1456
+ m_textServices->TxSendMessage(EM_SETEVENTMASK, 0, eventMask & ~(ENM_CHANGE | ENM_SELCHANGE), &res));
1457
+
1458
+ auto text = attributedString.getString();
1316
1459
  winrt::check_hresult(m_textServices->TxSendMessage(
1317
- EM_SETTEXTEX, reinterpret_cast<WPARAM>(&stt), reinterpret_cast<LPARAM>(str.c_str()), &res));
1460
+ EM_SETTEXTEX, reinterpret_cast<WPARAM>(&stt), reinterpret_cast<LPARAM>(text.c_str()), &res));
1461
+
1462
+ auto baseTextAttributes = windowsTextInputProps().textAttributes;
1463
+ baseTextAttributes.apply(attributedString.getBaseTextAttributes());
1464
+ auto charFormat = GetCharFormat(baseTextAttributes);
1465
+ winrt::check_hresult(
1466
+ m_textServices->TxSendMessage(EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast<LPARAM>(&charFormat), &res));
1467
+ winrt::check_bool(res != 0);
1468
+
1469
+ // Richedit in password mode does not support per-fragment formatting, so we only apply it when not in secureTextEntry
1470
+ // mode.
1471
+ if (!windowsTextInputProps().secureTextEntry) {
1472
+ LONG position = 0;
1473
+ bool previousWasCR = false;
1474
+ for (const auto &fragment : attributedString.getFragments()) {
1475
+ auto fragmentText = ::Microsoft::Common::Unicode::Utf8ToUtf16(fragment.string);
1476
+ LONG end = position;
1477
+ for (auto character : fragmentText) {
1478
+ // RichEdit positions count UTF-16 code units, with CRLF normalized to a single paragraph mark.
1479
+ if (character != L'\n' || !previousWasCR) {
1480
+ ++end;
1481
+ }
1482
+ previousWasCR = character == L'\r';
1483
+ }
1484
+
1485
+ if (end != position) {
1486
+ auto textAttributes = baseTextAttributes;
1487
+ textAttributes.apply(fragment.textAttributes);
1488
+ charFormat = GetCharFormat(textAttributes);
1489
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETSEL, position, end, &res));
1490
+ winrt::check_hresult(m_textServices->TxSendMessage(
1491
+ EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(&charFormat), &res));
1492
+ winrt::check_bool(res != 0);
1493
+ }
1494
+ position = end;
1495
+ }
1496
+ }
1318
1497
 
1319
1498
  winrt::check_hresult(
1320
1499
  m_textServices->TxSendMessage(EM_SETSEL, static_cast<WPARAM>(cr.cpMin), static_cast<LPARAM>(cr.cpMax), &res));
@@ -1322,6 +1501,20 @@ void WindowsTextInputComponentView::UpdateText(const std::string &str) noexcept
1322
1501
  // enable colored emojis
1323
1502
  winrt::check_hresult(
1324
1503
  m_textServices->TxSendMessage(EM_SETTYPOGRAPHYOPTIONS, 0x1000 | 0x2000, 0x1000 | 0x2000, nullptr));
1504
+
1505
+ m_recalculateContentVerticalOffset = true;
1506
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETEVENTMASK, 0, eventMask, &res));
1507
+ if ((eventMask & ENM_CHANGE) && !m_comingFromState) {
1508
+ OnTextUpdated();
1509
+ }
1510
+ if (eventMask & ENM_SELCHANGE) {
1511
+ CHARRANGE finalSelection;
1512
+ winrt::check_hresult(
1513
+ m_textServices->TxSendMessage(EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&finalSelection), &res));
1514
+ if (finalSelection.cpMin != cr.cpMin || finalSelection.cpMax != cr.cpMax) {
1515
+ OnSelectionChanged(finalSelection.cpMin, finalSelection.cpMax);
1516
+ }
1517
+ }
1325
1518
  }
1326
1519
 
1327
1520
  void WindowsTextInputComponentView::updateLayoutMetrics(
@@ -1373,7 +1566,10 @@ std::pair<float, float> WindowsTextInputComponentView::GetContentSize() const no
1373
1566
 
1374
1567
  SIZE extentHimetric = {
1375
1568
  static_cast<LONG>(availableWidth * scale * HIMETRIC_PER_INCH / dpi),
1376
- static_cast<LONG>(std::numeric_limits<LONG>::max() * HIMETRIC_PER_INCH / dpi)};
1569
+ // -1 measures without zoom, matching how this host draws (TxGetExtent returns E_NOTIMPL).
1570
+ // A positive extent is taken as a zoom reference; LONG_MAX scaled to HIMETRIC saturated to a
1571
+ // huge positive value on ARM64 and shrank the natural height to about 0.
1572
+ -1};
1377
1573
 
1378
1574
  SIZE naturalSize = {0, 0};
1379
1575
 
@@ -1401,14 +1597,22 @@ std::pair<float, float> WindowsTextInputComponentView::GetContentSize() const no
1401
1597
 
1402
1598
  // When we are notified by RichEdit that the text changed, we need to notify JS
1403
1599
  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));
1600
+ if (m_builder) {
1601
+ auto attributedStringBox = facebook::react::AttributedStringBox(getAttributedString());
1602
+ const auto eventCount = m_nativeEventCount;
1603
+ m_abistate->updateState([attributedStringBox = std::move(attributedStringBox),
1604
+ eventCount](const ::Microsoft::ReactNative::AbiTextInputStateData &oldData) {
1605
+ auto data = oldData;
1606
+ data.attributedStringBox = attributedStringBox;
1607
+ data.mostRecentEventCount = eventCount;
1608
+ return std::make_shared<const ::Microsoft::ReactNative::AbiTextInputStateData>(std::move(data));
1609
+ });
1610
+ } else {
1611
+ auto data = m_state->getData();
1612
+ data.attributedStringBox = facebook::react::AttributedStringBox(getAttributedString());
1613
+ data.mostRecentEventCount = m_nativeEventCount;
1614
+ m_state->updateState(std::move(data));
1615
+ }
1412
1616
 
1413
1617
  if (m_eventEmitter && !m_comingFromJS) {
1414
1618
  // call onChange event
@@ -1537,7 +1741,9 @@ std::optional<std::string> WindowsTextInputComponentView::getAccessiblityValue()
1537
1741
  }
1538
1742
 
1539
1743
  void WindowsTextInputComponentView::setAcccessiblityValue(std::string &&value) noexcept {
1540
- UpdateText(value);
1744
+ facebook::react::AttributedString attributedString;
1745
+ attributedString.appendFragment({std::move(value), windowsTextInputProps().textAttributes, {}});
1746
+ UpdateText(attributedString);
1541
1747
  }
1542
1748
 
1543
1749
  bool WindowsTextInputComponentView::getAcccessiblityIsReadOnly() noexcept {
@@ -1554,6 +1760,11 @@ const facebook::react::WindowsTextInputProps &WindowsTextInputComponentView::win
1554
1760
  }
1555
1761
 
1556
1762
  void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1763
+ m_cf = GetCharFormat(windowsTextInputProps().textAttributes);
1764
+ }
1765
+
1766
+ CHARFORMAT2W WindowsTextInputComponentView::GetCharFormat(
1767
+ const facebook::react::TextAttributes &textAttributes) noexcept {
1557
1768
  CHARFORMAT2W cfNew = {0};
1558
1769
  // Set CHARFORMAT structure
1559
1770
  cfNew.cbSize = sizeof(CHARFORMAT2W);
@@ -1567,15 +1778,18 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1567
1778
  // m_crText = RemoveAlpha(fontDetails.FontColor);
1568
1779
  // }
1569
1780
 
1570
- const auto &props = windowsTextInputProps();
1571
1781
  float fontSize =
1572
- (std::isnan(props.textAttributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
1573
- : props.textAttributes.fontSize);
1782
+ (std::isnan(textAttributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
1783
+ : textAttributes.fontSize);
1574
1784
 
1575
1785
  // Apply maxFontSizeMultiplier if specified
1576
- auto maxFontSizeMultiplier = windowsTextInputProps().textAttributes.maxFontSizeMultiplier;
1577
- fontSize *=
1578
- (maxFontSizeMultiplier >= 1.0f) ? std::min(maxFontSizeMultiplier, m_fontSizeMultiplier) : m_fontSizeMultiplier;
1786
+ if (textAttributes.allowFontScaling.value_or(windowsTextInputProps().allowFontScaling)) {
1787
+ auto fontSizeMultiplier =
1788
+ std::isnan(textAttributes.fontSizeMultiplier) ? m_fontSizeMultiplier : textAttributes.fontSizeMultiplier;
1789
+ auto maxFontSizeMultiplier = textAttributes.maxFontSizeMultiplier;
1790
+ fontSize *=
1791
+ (maxFontSizeMultiplier >= 1.0f) ? std::min(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier;
1792
+ }
1579
1793
 
1580
1794
  cfNew.dwMask |= CFM_SIZE;
1581
1795
  // set font size -- 15 to convert twips to pt
@@ -1583,20 +1797,19 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1583
1797
 
1584
1798
  // set bold
1585
1799
  cfNew.dwMask |= CFM_WEIGHT;
1586
- cfNew.wWeight =
1587
- props.textAttributes.fontWeight ? static_cast<WORD>(*props.textAttributes.fontWeight) : DWRITE_FONT_WEIGHT_NORMAL;
1800
+ cfNew.wWeight = textAttributes.fontWeight ? static_cast<WORD>(*textAttributes.fontWeight) : DWRITE_FONT_WEIGHT_NORMAL;
1588
1801
 
1589
1802
  // set font style (italic)
1590
1803
  cfNew.dwMask |= CFM_ITALIC;
1591
- if (props.textAttributes.fontStyle == facebook::react::FontStyle::Italic ||
1592
- props.textAttributes.fontStyle == facebook::react::FontStyle::Oblique) {
1804
+ if (textAttributes.fontStyle == facebook::react::FontStyle::Italic ||
1805
+ textAttributes.fontStyle == facebook::react::FontStyle::Oblique) {
1593
1806
  cfNew.dwEffects |= CFE_ITALIC;
1594
1807
  }
1595
1808
 
1596
1809
  // set text decoration (underline and strikethrough)
1597
1810
  cfNew.dwMask |= (CFM_UNDERLINE | CFM_STRIKEOUT);
1598
- if (props.textAttributes.textDecorationLineType.has_value()) {
1599
- auto decorationType = *props.textAttributes.textDecorationLineType;
1811
+ if (textAttributes.textDecorationLineType.has_value()) {
1812
+ auto decorationType = *textAttributes.textDecorationLineType;
1600
1813
  if (decorationType == facebook::react::TextDecorationLineType::Underline ||
1601
1814
  decorationType == facebook::react::TextDecorationLineType::UnderlineStrikethrough) {
1602
1815
  cfNew.dwEffects |= CFE_UNDERLINE;
@@ -1608,11 +1821,10 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1608
1821
  }
1609
1822
 
1610
1823
  // set font family
1611
- if (!props.textAttributes.fontFamily.empty()) {
1824
+ if (!textAttributes.fontFamily.empty()) {
1612
1825
  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);
1826
+ auto fontFamily = ::Microsoft::Common::Unicode::Utf8ToUtf16(textAttributes.fontFamily);
1827
+ wcsncpy_s(cfNew.szFaceName, fontFamily.c_str(), _TRUNCATE);
1616
1828
  } else {
1617
1829
  cfNew.dwMask |= CFM_FACE;
1618
1830
  wcsncpy_s(cfNew.szFaceName, L"Segoe UI\0", LF_FACESIZE);
@@ -1624,17 +1836,29 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1624
1836
  cfNew.yOffset = 0;
1625
1837
 
1626
1838
  // 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
1839
+ float letterSpacing = textAttributes.letterSpacing;
1840
+ cfNew.dwMask |= CFM_SPACING;
1841
+ cfNew.sSpacing = std::isnan(letterSpacing) ? 0 : static_cast<SHORT>(letterSpacing * 20); // Convert to TWIPS
1842
+
1843
+ cfNew.dwMask |= CFM_COLOR | CFM_BACKCOLOR;
1844
+ cfNew.dwEffects |= CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
1845
+ if (textAttributes.foregroundColor &&
1846
+ textAttributes.foregroundColor != windowsTextInputProps().textAttributes.foregroundColor) {
1847
+ auto color = theme()->Color(*textAttributes.foregroundColor);
1848
+ cfNew.crTextColor = RGB(color.R, color.G, color.B);
1849
+ cfNew.dwEffects &= ~CFE_AUTOCOLOR;
1850
+ }
1851
+ if (facebook::react::isColorMeaningful(textAttributes.backgroundColor)) {
1852
+ auto color = theme()->Color(*textAttributes.backgroundColor);
1853
+ cfNew.crBackColor = RGB(color.R, color.G, color.B);
1854
+ cfNew.dwEffects &= ~CFE_AUTOBACKCOLOR;
1631
1855
  }
1632
1856
 
1633
1857
  // set charset
1634
1858
  cfNew.dwMask |= CFM_CHARSET;
1635
1859
  cfNew.bCharSet = DEFAULT_CHARSET;
1636
1860
 
1637
- m_cf = cfNew;
1861
+ return cfNew;
1638
1862
  }
1639
1863
 
1640
1864
  void WindowsTextInputComponentView::UpdateParaFormat() noexcept {
@@ -1885,21 +2109,50 @@ void WindowsTextInputComponentView::DrawText() noexcept {
1885
2109
  m_needsRedraw = false;
1886
2110
  }
1887
2111
 
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);
2112
+ void WindowsTextInputComponentView::ensureTextHost() noexcept {
2113
+ if (!m_textHost) {
2114
+ winrt::check_hresult(HrEnsureRichEd20Loaded());
2115
+ m_textHost = winrt::make<CompTextHost>(this);
2116
+ winrt::com_ptr<::IUnknown> spUnk;
2117
+ winrt::check_hresult(g_pfnCreateTextServices(nullptr, m_textHost.get(), spUnk.put()));
2118
+ spUnk.as(m_textServices);
2119
+ winrt::check_bool(m_textServices != nullptr);
2120
+
2121
+ // Enable TSF support
2122
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETEDITSTYLE, SES_USECTF, SES_USECTF, nullptr));
2123
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETTEXTMODE, TM_RICHTEXT, 0, nullptr));
2124
+ }
2125
+ }
1897
2126
 
2127
+ HRESULT WindowsTextInputComponentView::GetTextDocument(ITextDocument2 **textDocument) noexcept {
2128
+ winrt::com_ptr<IUnknown> spUnk;
2129
+ winrt::com_ptr<ITextDocument2> spTextDoc;
2130
+ ensureTextHost();
2131
+
2132
+ // initialize TOM
1898
2133
  LRESULT res;
1899
- winrt::check_hresult(m_textServices->TxSendMessage(EM_SETTEXTMODE, TM_PLAINTEXT, 0, &res));
2134
+ m_textServices->TxSendMessage(EM_GETOLEINTERFACE, 0, reinterpret_cast<LPARAM>(spUnk.put()), &res);
2135
+ HRESULT hr = spUnk->QueryInterface(__uuidof(ITextDocument2), reinterpret_cast<void **>(spTextDoc.put()));
2136
+ spTextDoc.copy_to(textDocument);
2137
+ return hr;
2138
+ }
2139
+
2140
+ HRESULT WindowsTextInputComponentView::GetTextServices(ITextServices2 **textServices) noexcept {
2141
+ ensureTextHost();
2142
+ m_textServices.copy_to(textServices);
2143
+ return S_OK;
2144
+ }
2145
+
2146
+ HRESULT WindowsTextInputComponentView::GetTextHost(ITextHost **textHost) noexcept {
2147
+ ensureTextHost();
2148
+ m_textHost.copy_to(textHost);
2149
+ return S_OK;
2150
+ }
1900
2151
 
1901
- // Enable TSF support
1902
- winrt::check_hresult(m_textServices->TxSendMessage(EM_SETEDITSTYLE, SES_USECTF, SES_USECTF, nullptr));
2152
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
2153
+ WindowsTextInputComponentView::createVisual() noexcept {
2154
+ auto visual = m_compContext.CreateSpriteVisual();
2155
+ ensureTextHost();
1903
2156
 
1904
2157
  m_caretVisual = m_compContext.CreateCaretVisual();
1905
2158
  visual.InsertAt(m_caretVisual.InnerVisual(), 0);
@@ -5,6 +5,8 @@
5
5
  #pragma once
6
6
 
7
7
  #include "Composition.WindowsTextInputComponentView.g.h"
8
+ #include <ComponentView.Experimental.interop.h>
9
+ #include <Fabric/AbiTextInputShadowNode.h>
8
10
  #include <ReactContext.h>
9
11
  #include <Windows.Graphics.DirectX.Direct3D11.interop.h>
10
12
  #include <richedit.h>
@@ -22,10 +24,16 @@ namespace winrt::Microsoft::ReactNative::Composition::implementation {
22
24
  struct CompTextHost;
23
25
 
24
26
  struct WindowsTextInputComponentView
25
- : WindowsTextInputComponentViewT<WindowsTextInputComponentView, ViewComponentView> {
27
+ : public WindowsTextInputComponentViewT<
28
+ WindowsTextInputComponentView,
29
+ ViewComponentView,
30
+ ::Microsoft::ReactNative::Composition::Experimental::ITextInputComponentInterop> {
26
31
  friend CompTextHost;
27
32
 
28
- using Super = WindowsTextInputComponentViewT<WindowsTextInputComponentView, ViewComponentView>;
33
+ using Super = WindowsTextInputComponentViewT<
34
+ WindowsTextInputComponentView,
35
+ ViewComponentView,
36
+ ::Microsoft::ReactNative::Composition::Experimental::ITextInputComponentInterop>;
29
37
  [[nodiscard]] static winrt::Microsoft::ReactNative::ComponentView Create(
30
38
  const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
31
39
  facebook::react::Tag tag,
@@ -71,6 +79,9 @@ struct WindowsTextInputComponentView
71
79
  void OnContextMenuKey(
72
80
  const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept override;
73
81
  void onMounted() noexcept override;
82
+ const winrt::Microsoft::ReactNative::IComponentProps userProps(
83
+ facebook::react::Props::Shared const &props) noexcept override;
84
+ winrt::Microsoft::ReactNative::ViewProps ViewPropsInner() noexcept override;
74
85
 
75
86
  std::optional<std::string> getAccessiblityValue() noexcept override;
76
87
  void setAcccessiblityValue(std::string &&value) noexcept override;
@@ -80,13 +91,18 @@ struct WindowsTextInputComponentView
80
91
  WindowsTextInputComponentView(
81
92
  const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
82
93
  facebook::react::Tag tag,
83
- winrt::Microsoft::ReactNative::ReactContext const &reactContext);
94
+ winrt::Microsoft::ReactNative::ReactContext const &reactContext,
95
+ winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder *builder = nullptr);
84
96
  ~WindowsTextInputComponentView();
85
97
 
86
98
  winrt::Microsoft::ReactNative::Composition::Experimental::IVisual createVisual() noexcept;
87
99
 
88
100
  std::pair<facebook::react::Cursor, HCURSOR> cursor() const noexcept override;
89
101
 
102
+ HRESULT GetTextDocument(ITextDocument2 **) noexcept;
103
+ HRESULT GetTextServices(ITextServices2 **textServices) noexcept;
104
+ HRESULT GetTextHost(ITextHost **textHost) noexcept;
105
+
90
106
  private:
91
107
  struct DrawBlock {
92
108
  DrawBlock(WindowsTextInputComponentView &view);
@@ -100,8 +116,9 @@ struct WindowsTextInputComponentView
100
116
  void ShowCaret(bool show) noexcept;
101
117
  winrt::com_ptr<::IDWriteTextLayout> CreatePlaceholderLayout();
102
118
  void UpdateCharFormat() noexcept;
119
+ CHARFORMAT2W GetCharFormat(const facebook::react::TextAttributes &textAttributes) noexcept;
103
120
  void UpdateParaFormat() noexcept;
104
- void UpdateText(const std::string &str) noexcept;
121
+ void UpdateText(const facebook::react::AttributedString &attributedString) noexcept;
105
122
  void OnTextUpdated() noexcept;
106
123
  void EmitOnScrollEvent() noexcept;
107
124
  void OnSelectionChanged(LONG start, LONG end) noexcept;
@@ -124,6 +141,8 @@ struct WindowsTextInputComponentView
124
141
  void ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept;
125
142
  void calculateContentVerticalOffset() noexcept;
126
143
 
144
+ void ensureTextHost() noexcept;
145
+
127
146
  winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
128
147
  winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr};
129
148
  winrt::Microsoft::ReactNative::Composition::Experimental::IDrawingSurfaceBrush m_drawingSurface{nullptr};
@@ -136,6 +155,7 @@ struct WindowsTextInputComponentView
136
155
  winrt::com_ptr<ITextServices2> m_textServices;
137
156
  unsigned int m_imgWidth{0}, m_imgHeight{0};
138
157
  std::shared_ptr<facebook::react::WindowsTextInputShadowNode::ConcreteState const> m_state;
158
+ std::shared_ptr<::Microsoft::ReactNative::AbiTextInputShadowNode::ConcreteState const> m_abistate;
139
159
  float m_fontSizeMultiplier{1.0};
140
160
  int64_t m_mostRecentEventCount{0};
141
161
  int m_nativeEventCount{0};