react-native-windows 0.81.0-preview.1 → 0.81.0-preview.11

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 (36) hide show
  1. package/.flowconfig +3 -0
  2. package/Chakra/ChakraUtils.cpp +0 -2
  3. package/Directory.Build.props +5 -2
  4. package/Folly/Folly.vcxproj +6 -7
  5. package/Folly/cgmanifest.json +1 -1
  6. package/Libraries/Components/Switch/Switch.js +1 -1
  7. package/Libraries/Components/Switch/Switch.windows.js +1 -1
  8. package/Libraries/Components/TextInput/TextInput.js +6 -1
  9. package/Libraries/Components/TextInput/TextInput.windows.js +5 -0
  10. package/Libraries/Components/View/View.js +5 -1
  11. package/Libraries/Components/View/View.windows.js +6 -1
  12. package/Libraries/Core/ReactNativeVersion.js +2 -2
  13. package/Microsoft.ReactNative/CompositionSwitcher.idl +163 -162
  14. package/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp +104 -4
  15. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +60 -33
  16. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +19 -0
  17. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +102 -18
  18. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +1 -0
  19. package/Microsoft.ReactNative/Fabric/Composition/UriImageManager.cpp +5 -3
  20. package/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp +14 -1
  21. package/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/WindowsTextLayoutManager.cpp +21 -2
  22. package/Microsoft.ReactNative/ReactHost/ReactHost.cpp +39 -10
  23. package/PropertySheets/Generated/PackageVersion.g.props +2 -2
  24. package/PropertySheets/React.Cpp.props +1 -0
  25. package/Shared/MemoryMappedBuffer.cpp +0 -2
  26. package/codegen/NativeReactNativeFeatureFlagsSpec.g.h +39 -21
  27. package/codegen/rnwcoreJSI-generated.cpp +18 -0
  28. package/codegen/rnwcoreJSI.h +27 -0
  29. package/package.json +15 -15
  30. package/src/private/featureflags/ReactNativeFeatureFlags.js +16 -1
  31. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +4 -1
  32. package/src/private/specs_DEPRECATED/components/SwitchNativeComponent.js +1 -0
  33. package/template/metro.config.js +2 -4
  34. package/templates/cpp-app/metro.config.js +2 -4
  35. package/templates/cpp-lib/example/metro.config.js +2 -3
  36. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/css/CSSTokenizer.h +0 -232
@@ -910,6 +910,31 @@ struct CompScrollerVisual : winrt::implements<
910
910
  m_snapToOffsets.push_back(offset);
911
911
  }
912
912
  }
913
+ // Match Paper behavior: snapToOffsets disables pagingEnabled and snapToInterval
914
+ if (!m_snapToOffsets.empty()) {
915
+ m_pagingEnabled = false;
916
+ m_snapToInterval = 0.0f;
917
+ }
918
+ ConfigureSnapInertiaModifiers();
919
+ }
920
+
921
+ void PagingEnabled(bool pagingEnabled) noexcept {
922
+ m_pagingEnabled = pagingEnabled;
923
+ ConfigureSnapInertiaModifiers();
924
+ }
925
+
926
+ void SnapToInterval(float interval) noexcept {
927
+ m_snapToInterval = interval;
928
+ // Match Paper behavior: snapToOffsets disables snapToInterval
929
+ if (!m_snapToOffsets.empty()) {
930
+ m_snapToInterval = 0.0f;
931
+ }
932
+ ConfigureSnapInertiaModifiers();
933
+ }
934
+
935
+ void SnapToAlignment(
936
+ winrt::Microsoft::ReactNative::Composition::Experimental::SnapPointsAlignment alignment) noexcept {
937
+ m_snapToAlignment = alignment;
913
938
  ConfigureSnapInertiaModifiers();
914
939
  }
915
940
 
@@ -934,9 +959,13 @@ struct CompScrollerVisual : winrt::implements<
934
959
  }
935
960
 
936
961
  void Size(winrt::Windows::Foundation::Numerics::float2 const &size) noexcept {
962
+ bool sizeChanged = (m_visualSize.x != size.x || m_visualSize.y != size.y);
937
963
  m_visualSize = size;
938
964
  m_visual.Size(size);
939
965
  UpdateMaxPosition();
966
+ if (sizeChanged && m_pagingEnabled) {
967
+ ConfigureSnapInertiaModifiers();
968
+ }
940
969
  }
941
970
 
942
971
  void Offset(winrt::Windows::Foundation::Numerics::float3 const &offset) noexcept {
@@ -1046,9 +1075,13 @@ struct CompScrollerVisual : winrt::implements<
1046
1075
  }
1047
1076
 
1048
1077
  void ContentSize(winrt::Windows::Foundation::Numerics::float2 const &size) noexcept {
1078
+ bool sizeChanged = (m_contentSize.x != size.x || m_contentSize.y != size.y);
1049
1079
  m_contentSize = size;
1050
1080
  m_contentVisual.Size(size);
1051
1081
  UpdateMaxPosition();
1082
+ if (sizeChanged && m_pagingEnabled) {
1083
+ ConfigureSnapInertiaModifiers();
1084
+ }
1052
1085
  }
1053
1086
 
1054
1087
  winrt::Windows::Foundation::Numerics::float3 ScrollPosition() const noexcept {
@@ -1155,11 +1188,73 @@ struct CompScrollerVisual : winrt::implements<
1155
1188
  // Collect and deduplicate all snap positions
1156
1189
  std::vector<float> snapPositions;
1157
1190
 
1158
- if (m_snapToStart) {
1159
- snapPositions.push_back(0.0f);
1191
+ // Priority: snapToOffsets > snapToInterval > pagingEnabled
1192
+ if (!m_snapToOffsets.empty()) {
1193
+ // Use explicit snap points when snapToOffsets is set (highest priority)
1194
+ if (m_snapToStart) {
1195
+ snapPositions.push_back(0.0f);
1196
+ }
1197
+ snapPositions.insert(snapPositions.end(), m_snapToOffsets.begin(), m_snapToOffsets.end());
1198
+ } else if (m_snapToInterval > 0.0f) {
1199
+ // Generate snap points at interval positions
1200
+ float viewportSize = m_horizontal ? visualSize.x : visualSize.y;
1201
+ float maxScroll =
1202
+ m_horizontal ? std::max(contentSize.x - visualSize.x, 0.0f) : std::max(contentSize.y - visualSize.y, 0.0f);
1203
+
1204
+ // Calculate alignment offset based on snapToAlignment
1205
+ float alignmentOffset = 0.0f;
1206
+ using SnapPointsAlignment = winrt::Microsoft::ReactNative::Composition::Experimental::SnapPointsAlignment;
1207
+ if (m_snapToAlignment == SnapPointsAlignment::Center) {
1208
+ alignmentOffset = -viewportSize / 2.0f;
1209
+ } else if (m_snapToAlignment == SnapPointsAlignment::Far) {
1210
+ alignmentOffset = -viewportSize;
1211
+ }
1212
+ // Near (start) alignment has no offset (alignmentOffset = 0)
1213
+
1214
+ // Generate snap points at interval positions with alignment offset
1215
+ for (float position = alignmentOffset; position <= maxScroll; position += m_snapToInterval) {
1216
+ if (position >= 0.0f) { // Only include positions >= 0
1217
+ snapPositions.push_back(position);
1218
+ }
1219
+ }
1220
+
1221
+ // Ensure we have at least the start position
1222
+ if (snapPositions.empty() || snapPositions.front() > 0.0f) {
1223
+ snapPositions.insert(snapPositions.begin(), 0.0f);
1224
+ }
1225
+
1226
+ // Ensure the end position is included if not already
1227
+ if (!snapPositions.empty() && snapPositions.back() < maxScroll) {
1228
+ snapPositions.push_back(maxScroll);
1229
+ }
1230
+ } else if (m_pagingEnabled) {
1231
+ // Generate snap points at viewport intervals (paging)
1232
+ float viewportSize = m_horizontal ? visualSize.x : visualSize.y;
1233
+ float maxScroll =
1234
+ m_horizontal ? std::max(contentSize.x - visualSize.x, 0.0f) : std::max(contentSize.y - visualSize.y, 0.0f);
1235
+
1236
+ // Only generate paging snap points if viewport size is valid
1237
+ if (viewportSize > 0 && maxScroll > 0) {
1238
+ // Add snap points at each page (viewport size) interval
1239
+ for (float position = 0.0f; position <= maxScroll; position += viewportSize) {
1240
+ snapPositions.push_back(position);
1241
+ }
1242
+
1243
+ // Ensure the end position is included if not already
1244
+ if (!snapPositions.empty() && snapPositions.back() < maxScroll) {
1245
+ snapPositions.push_back(maxScroll);
1246
+ }
1247
+ } else {
1248
+ // If content fits in viewport or invalid size, just snap to start
1249
+ snapPositions.push_back(0.0f);
1250
+ }
1251
+ } else {
1252
+ // No interval or paging - use explicit snap points only
1253
+ if (m_snapToStart) {
1254
+ snapPositions.push_back(0.0f);
1255
+ }
1160
1256
  }
1161
1257
 
1162
- snapPositions.insert(snapPositions.end(), m_snapToOffsets.begin(), m_snapToOffsets.end());
1163
1258
  std::sort(snapPositions.begin(), snapPositions.end());
1164
1259
  snapPositions.erase(std::unique(snapPositions.begin(), snapPositions.end()), snapPositions.end());
1165
1260
 
@@ -1222,7 +1317,8 @@ struct CompScrollerVisual : winrt::implements<
1222
1317
  restingValues.push_back(restingValue);
1223
1318
  }
1224
1319
 
1225
- if (m_snapToEnd) {
1320
+ // Only add snapToEnd handling when NOT using pagingEnabled or snapToInterval (they already include end position)
1321
+ if (m_snapToEnd && !m_pagingEnabled && m_snapToInterval <= 0.0f) {
1226
1322
  auto endRestingValue = TTypeRedirects::InteractionTrackerInertiaRestingValue::Create(compositor);
1227
1323
 
1228
1324
  // Create property sets to dynamically compute content - visual size
@@ -1286,6 +1382,10 @@ struct CompScrollerVisual : winrt::implements<
1286
1382
  bool m_horizontal{false};
1287
1383
  bool m_snapToStart{true};
1288
1384
  bool m_snapToEnd{true};
1385
+ bool m_pagingEnabled{false};
1386
+ float m_snapToInterval{0.0f};
1387
+ winrt::Microsoft::ReactNative::Composition::Experimental::SnapPointsAlignment m_snapToAlignment{
1388
+ winrt::Microsoft::ReactNative::Composition::Experimental::SnapPointsAlignment::Near};
1289
1389
  std::vector<float> m_snapToOffsets;
1290
1390
  bool m_inertia{false};
1291
1391
  bool m_custom{false};
@@ -35,26 +35,44 @@ struct ModalHostState
35
35
  struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::Foundation::IInspectable>,
36
36
  ::Microsoft::ReactNativeSpecs::BaseModalHostView<ModalHostView> {
37
37
  ~ModalHostView() {
38
- if (m_reactNativeIsland) {
39
- m_reactNativeIsland.Island().Close();
40
- }
38
+ if (m_popUp) {
39
+ // Unregister closing event handler
40
+ if (m_appWindow && m_appWindowClosingToken) {
41
+ m_appWindow.Closing(m_appWindowClosingToken);
42
+ m_appWindowClosingToken.value = 0;
43
+ }
41
44
 
42
- // Add AppWindow closing token cleanup
43
- if (m_appWindow && m_appWindowClosingToken) {
44
- m_appWindow.Closing(m_appWindowClosingToken);
45
- m_appWindowClosingToken.value = 0;
46
- }
45
+ // Reset topWindowID before destroying
46
+ if (m_prevWindowID) {
47
+ winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId(
48
+ m_reactContext.Properties().Handle(), m_prevWindowID);
49
+ m_prevWindowID = 0;
50
+ }
47
51
 
48
- if (m_popUp) {
49
- if (m_departFocusToken && !m_popUp.IsClosed()) {
50
- // WASDK BUG: InputFocusNavigationHost::GetForSiteBridge fails on a DesktopPopupSiteBridge
51
- // https://github.com/microsoft/react-native-windows/issues/14604
52
- /*
53
- auto navHost =
54
- winrt::Microsoft::UI::Input::InputFocusNavigationHost::GetForSiteBridge(m_popUp.as<winrt::Microsoft::UI::Content::IContentSiteBridge>());
55
- navHost.DepartFocusRequested(m_departFocusToken);
56
- */
52
+ // Close island
53
+ if (m_reactNativeIsland) {
54
+ m_reactNativeIsland.Island().Close();
55
+ m_reactNativeIsland = nullptr;
56
+ }
57
+
58
+ // Hide popup
59
+ if (m_popUp.IsVisible()) {
60
+ m_popUp.Hide();
61
+ }
62
+
63
+ // Destroy AppWindow this automatically resumes parent window to receive inputs
64
+ if (m_appWindow) {
65
+ m_appWindow.Destroy();
66
+ m_appWindow = nullptr;
67
+ }
68
+
69
+ // Bring parent window to foreground
70
+ if (m_parentHwnd) {
71
+ SetForegroundWindow(m_parentHwnd);
72
+ SetFocus(m_parentHwnd);
57
73
  }
74
+
75
+ // Close bridge
58
76
  m_popUp.Close();
59
77
  m_popUp = nullptr;
60
78
  }
@@ -88,7 +106,7 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
88
106
  QueueShow(view);
89
107
  } else {
90
108
  m_visible = false;
91
- CloseWindow();
109
+ HideWindow();
92
110
  }
93
111
  }
94
112
 
@@ -219,31 +237,33 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
219
237
  }
220
238
  }
221
239
 
222
- void CloseWindow() noexcept {
223
- // enable input to parent before closing the modal window, so focus can return back to the parent window
224
- EnableWindow(m_parentHwnd, true);
240
+ /*
241
+ HideWindow called on visible=false
242
+ unmounts the modal window using onDismiss event
243
+ */
244
+ void HideWindow() noexcept {
245
+ // Hide popup
225
246
  if (m_popUp) {
226
247
  m_popUp.Hide();
227
248
  }
228
249
 
229
- // Unregister closing event handler
230
- if (m_appWindow && m_appWindowClosingToken) {
231
- m_appWindow.Closing(m_appWindowClosingToken);
232
- m_appWindowClosingToken.value = 0;
250
+ // Restore message routing to parent
251
+ if (m_prevWindowID) {
252
+ winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId(
253
+ m_reactContext.Properties().Handle(), m_prevWindowID);
233
254
  }
234
255
 
235
- // dispatch onDismiss event
256
+ // Bring parent window to foreground
257
+ if (m_parentHwnd) {
258
+ SetForegroundWindow(m_parentHwnd);
259
+ SetFocus(m_parentHwnd);
260
+ }
261
+
262
+ // Dispatch onDismiss event
236
263
  if (auto eventEmitter = EventEmitter()) {
237
264
  ::Microsoft::ReactNativeSpecs::ModalHostViewEventEmitter::OnDismiss eventArgs;
238
265
  eventEmitter->onDismiss(eventArgs);
239
266
  }
240
-
241
- // reset the topWindowID
242
- if (m_prevWindowID) {
243
- winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId(
244
- m_reactContext.Properties().Handle(), m_prevWindowID);
245
- m_prevWindowID = 0;
246
- }
247
267
  }
248
268
 
249
269
  // creates a new modal window
@@ -279,9 +299,16 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
279
299
  overlappedPresenter.IsModal(true);
280
300
  overlappedPresenter.SetBorderAndTitleBar(true, true);
281
301
 
302
+ // modal should only have close button
303
+ overlappedPresenter.IsMinimizable(false);
304
+ overlappedPresenter.IsMaximizable(false);
305
+
282
306
  // Apply the presenter to the window
283
307
  m_appWindow.SetPresenter(overlappedPresenter);
284
308
 
309
+ // Hide the title bar icon
310
+ m_appWindow.TitleBar().IconShowOptions(winrt::Microsoft::UI::Windowing::IconShowOptions::HideIconAndSystemMenu);
311
+
285
312
  // Set initial title using the stored local props
286
313
  if (m_localProps && m_localProps->title.has_value()) {
287
314
  winrt::hstring titleValue = winrt::to_hstring(m_localProps->title.value());
@@ -814,6 +814,25 @@ void ScrollViewComponentView::updateProps(
814
814
  }
815
815
  m_scrollVisual.SetSnapPoints(newViewProps.snapToStart, newViewProps.snapToEnd, snapToOffsets.GetView());
816
816
  }
817
+
818
+ if (!oldProps || oldViewProps.pagingEnabled != newViewProps.pagingEnabled) {
819
+ m_scrollVisual.PagingEnabled(newViewProps.pagingEnabled);
820
+ }
821
+
822
+ if (!oldProps || oldViewProps.snapToInterval != newViewProps.snapToInterval) {
823
+ m_scrollVisual.SnapToInterval(static_cast<float>(newViewProps.snapToInterval));
824
+ }
825
+
826
+ if (!oldProps || oldViewProps.snapToAlignment != newViewProps.snapToAlignment) {
827
+ using SnapPointsAlignment = winrt::Microsoft::ReactNative::Composition::Experimental::SnapPointsAlignment;
828
+ SnapPointsAlignment alignment = SnapPointsAlignment::Near; // default is "start"
829
+ if (newViewProps.snapToAlignment == facebook::react::ScrollViewSnapToAlignment::Center) {
830
+ alignment = SnapPointsAlignment::Center;
831
+ } else if (newViewProps.snapToAlignment == facebook::react::ScrollViewSnapToAlignment::End) {
832
+ alignment = SnapPointsAlignment::Far;
833
+ }
834
+ m_scrollVisual.SnapToAlignment(alignment);
835
+ }
817
836
  }
818
837
 
819
838
  void ScrollViewComponentView::updateState(
@@ -697,10 +697,17 @@ void WindowsTextInputComponentView::OnPointerPressed(
697
697
  }
698
698
 
699
699
  if (m_textServices && msg) {
700
- LRESULT lresult;
701
- DrawBlock db(*this);
702
- auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
703
- args.Handled(hr != S_FALSE);
700
+ if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
701
+ ShowContextMenu(position);
702
+ args.Handled(true);
703
+ } else if (msg == WM_RBUTTONUP && windowsTextInputProps().contextMenuHidden) {
704
+ args.Handled(true);
705
+ } else {
706
+ LRESULT lresult;
707
+ DrawBlock db(*this);
708
+ auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
709
+ args.Handled(hr != S_FALSE);
710
+ }
704
711
  }
705
712
 
706
713
  // Emits the OnPressIn event
@@ -844,8 +851,8 @@ void WindowsTextInputComponentView::OnPointerWheelChanged(
844
851
  }
845
852
  void WindowsTextInputComponentView::OnKeyDown(
846
853
  const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
847
- // Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with WinUI
848
- // behavior We do forward Ctrl+Tab to the textinput.
854
+ // Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
855
+ // WinUI behavior We do forward Ctrl+Tab to the textinput.
849
856
  if (args.Key() != winrt::Windows::System::VirtualKey::Tab ||
850
857
  (args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Control) &
851
858
  winrt::Microsoft::UI::Input::VirtualKeyStates::Down) == winrt::Microsoft::UI::Input::VirtualKeyStates::Down) {
@@ -872,8 +879,8 @@ void WindowsTextInputComponentView::OnKeyDown(
872
879
 
873
880
  void WindowsTextInputComponentView::OnKeyUp(
874
881
  const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
875
- // Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with WinUI
876
- // behavior We do forward Ctrl+Tab to the textinput.
882
+ // Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
883
+ // WinUI behavior We do forward Ctrl+Tab to the textinput.
877
884
  if (args.Key() != winrt::Windows::System::VirtualKey::Tab ||
878
885
  (args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Control) &
879
886
  winrt::Microsoft::UI::Input::VirtualKeyStates::Down) == winrt::Microsoft::UI::Input::VirtualKeyStates::Down) {
@@ -943,8 +950,8 @@ bool WindowsTextInputComponentView::ShouldSubmit(
943
950
 
944
951
  void WindowsTextInputComponentView::OnCharacterReceived(
945
952
  const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept {
946
- // Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with WinUI
947
- // behavior We do forward Ctrl+Tab to the textinput.
953
+ // Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
954
+ // WinUI behavior We do forward Ctrl+Tab to the textinput.
948
955
  if ((args.KeyCode() == '\t') &&
949
956
  ((args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Control) &
950
957
  winrt::Microsoft::UI::Input::VirtualKeyStates::Down) != winrt::Microsoft::UI::Input::VirtualKeyStates::Down)) {
@@ -1547,25 +1554,59 @@ void WindowsTextInputComponentView::UpdateParaFormat() noexcept {
1547
1554
  m_pf.dwMask = PFM_ALL;
1548
1555
 
1549
1556
  auto &textAlign = windowsTextInputProps().textAlign;
1557
+ auto &baseWritingDirection = windowsTextInputProps().textAttributes.baseWritingDirection;
1558
+
1559
+ // Handle writingDirection (baseWritingDirection)
1560
+ // For WritingDirection::Natural, use the computed layout direction from the layout tree
1561
+ // since direction can be overridden at any point in the tree
1562
+ bool isRTL = false;
1563
+ if (baseWritingDirection.has_value()) {
1564
+ if (*baseWritingDirection == facebook::react::WritingDirection::RightToLeft) {
1565
+ isRTL = true;
1566
+ m_pf.dwMask |= PFM_RTLPARA;
1567
+ m_pf.wEffects |= PFE_RTLPARA;
1568
+ } else if (*baseWritingDirection == facebook::react::WritingDirection::LeftToRight) {
1569
+ isRTL = false;
1570
+ // Ensure RTL flag is not set
1571
+ m_pf.wEffects &= ~PFE_RTLPARA;
1572
+ } else if (*baseWritingDirection == facebook::react::WritingDirection::Natural) {
1573
+ // Natural uses the layout direction computed from the tree
1574
+ isRTL = (layoutMetrics().layoutDirection == facebook::react::LayoutDirection::RightToLeft);
1575
+ if (isRTL) {
1576
+ m_pf.dwMask |= PFM_RTLPARA;
1577
+ m_pf.wEffects |= PFE_RTLPARA;
1578
+ } else {
1579
+ m_pf.wEffects &= ~PFE_RTLPARA;
1580
+ }
1581
+ }
1582
+ } else {
1583
+ // No explicit writing direction set - use layout direction from tree
1584
+ isRTL = (layoutMetrics().layoutDirection == facebook::react::LayoutDirection::RightToLeft);
1585
+ if (isRTL) {
1586
+ m_pf.dwMask |= PFM_RTLPARA;
1587
+ m_pf.wEffects |= PFE_RTLPARA;
1588
+ } else {
1589
+ m_pf.wEffects &= ~PFE_RTLPARA;
1590
+ }
1591
+ }
1550
1592
 
1593
+ // Handle textAlign
1551
1594
  if (textAlign == facebook::react::TextAlignment::Center) {
1552
1595
  m_pf.wAlignment = PFA_CENTER;
1553
1596
  } else if (textAlign == facebook::react::TextAlignment::Right) {
1554
1597
  m_pf.wAlignment = PFA_RIGHT;
1598
+ } else if (textAlign == facebook::react::TextAlignment::Justified) {
1599
+ m_pf.wAlignment = PFA_JUSTIFY;
1600
+ } else if (textAlign == facebook::react::TextAlignment::Natural) {
1601
+ // Natural alignment respects writing direction
1602
+ m_pf.wAlignment = isRTL ? PFA_RIGHT : PFA_LEFT;
1555
1603
  } else {
1604
+ // Default to left alignment
1556
1605
  m_pf.wAlignment = PFA_LEFT;
1557
1606
  }
1558
1607
 
1559
1608
  m_pf.cTabCount = 1;
1560
1609
  m_pf.rgxTabs[0] = lDefaultTab;
1561
-
1562
- /*
1563
- if (m_spcontroller->IsCurrentReadingOrderRTL())
1564
- {
1565
- m_pf.dwMask |= PFM_RTLPARA;
1566
- m_pf.wEffects |= PFE_RTLPARA;
1567
- }
1568
- */
1569
1610
  }
1570
1611
 
1571
1612
  void WindowsTextInputComponentView::OnRenderingDeviceLost() noexcept {
@@ -1826,4 +1867,47 @@ void WindowsTextInputComponentView::updateSpellCheck(bool enable) noexcept {
1826
1867
  winrt::check_hresult(
1827
1868
  m_textServices->TxSendMessage(EM_SETLANGOPTIONS, IMF_SPELLCHECKING, enable ? newLangOptions : 0, &lresult));
1828
1869
  }
1870
+
1871
+ void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept {
1872
+ HMENU menu = CreatePopupMenu();
1873
+ if (!menu)
1874
+ return;
1875
+
1876
+ CHARRANGE selection;
1877
+ LRESULT res;
1878
+ m_textServices->TxSendMessage(EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&selection), &res);
1879
+
1880
+ bool hasSelection = selection.cpMin != selection.cpMax;
1881
+ bool isEmpty = GetTextFromRichEdit().empty();
1882
+ bool isReadOnly = windowsTextInputProps().editable == false;
1883
+ bool canPaste = !isReadOnly && IsClipboardFormatAvailable(CF_UNICODETEXT);
1884
+
1885
+ AppendMenuW(menu, MF_STRING | (hasSelection && !isReadOnly ? 0 : MF_GRAYED), 1, L"Cut");
1886
+ AppendMenuW(menu, MF_STRING | (hasSelection ? 0 : MF_GRAYED), 2, L"Copy");
1887
+ AppendMenuW(menu, MF_STRING | (canPaste ? 0 : MF_GRAYED), 3, L"Paste");
1888
+ AppendMenuW(menu, MF_STRING | (!isEmpty && !isReadOnly ? 0 : MF_GRAYED), 4, L"Select All");
1889
+
1890
+ POINT cursorPos;
1891
+ GetCursorPos(&cursorPos);
1892
+
1893
+ HWND hwnd = GetActiveWindow();
1894
+
1895
+ int cmd = TrackPopupMenu(
1896
+ menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY, cursorPos.x, cursorPos.y, 0, hwnd, NULL);
1897
+
1898
+ if (cmd == 1) { // Cut
1899
+ m_textServices->TxSendMessage(WM_CUT, 0, 0, &res);
1900
+ OnTextUpdated();
1901
+ } else if (cmd == 2) { // Copy
1902
+ m_textServices->TxSendMessage(WM_COPY, 0, 0, &res);
1903
+ } else if (cmd == 3) { // Paste
1904
+ m_textServices->TxSendMessage(WM_PASTE, 0, 0, &res);
1905
+ OnTextUpdated();
1906
+ } else if (cmd == 4) { // Select All
1907
+ m_textServices->TxSendMessage(EM_SETSEL, 0, -1, &res);
1908
+ }
1909
+
1910
+ DestroyMenu(menu);
1911
+ }
1912
+
1829
1913
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation
@@ -118,6 +118,7 @@ struct WindowsTextInputComponentView
118
118
  void updateLetterSpacing(float letterSpacing) noexcept;
119
119
  void updateAutoCorrect(bool value) noexcept;
120
120
  void updateSpellCheck(bool value) noexcept;
121
+ void ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept;
121
122
 
122
123
  winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
123
124
  winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr};
@@ -291,9 +291,11 @@ ImageResponseOrImageErrorInfo ImageFailedResponse::ResolveImage() {
291
291
  if (imageOrError.errorInfo->error.empty()) {
292
292
  imageOrError.errorInfo->error = "Failed to load image.";
293
293
  }
294
- for (auto &&[header, value] : m_responseHeaders) {
295
- imageOrError.errorInfo->httpResponseHeaders.push_back(
296
- std::make_pair<std::string, std::string>(winrt::to_string(header), winrt::to_string(value)));
294
+ if (m_responseHeaders) {
295
+ for (auto &&[header, value] : m_responseHeaders) {
296
+ imageOrError.errorInfo->httpResponseHeaders.push_back(
297
+ std::make_pair<std::string, std::string>(winrt::to_string(header), winrt::to_string(value)));
298
+ }
297
299
  }
298
300
  return imageOrError;
299
301
  }
@@ -10,6 +10,7 @@
10
10
  #include <Fabric/Composition/ImageResponseImage.h>
11
11
  #include <Fabric/Composition/UriImageManager.h>
12
12
  #include <Networking/NetworkPropertyIds.h>
13
+ #include <Utils/CppWinrtLessExceptions.h>
13
14
  #include <Utils/ImageUtils.h>
14
15
  #include <fmt/format.h>
15
16
  #include <functional/functor.h>
@@ -131,7 +132,19 @@ WindowsImageManager::GetImageRandomAccessStreamAsync(
131
132
  request.Content(bodyContent);
132
133
  }
133
134
 
134
- winrt::Windows::Web::Http::HttpResponseMessage response(co_await m_httpClient.SendRequestAsync(request));
135
+ auto asyncOp = m_httpClient.SendRequestAsync(request);
136
+ co_await lessthrow_await_adapter<winrt::Windows::Foundation::IAsyncOperationWithProgress<
137
+ winrt::Windows::Web::Http::HttpResponseMessage,
138
+ winrt::Windows::Web::Http::HttpProgress>>{asyncOp};
139
+
140
+ if (asyncOp.Status() == winrt::Windows::Foundation::AsyncStatus::Error ||
141
+ asyncOp.Status() == winrt::Windows::Foundation::AsyncStatus::Canceled) {
142
+ auto errorMessage = FormatHResultError(winrt::hresult_error(asyncOp.ErrorCode()));
143
+ co_return winrt::Microsoft::ReactNative::Composition::ImageFailedResponse(
144
+ winrt::to_hstring("Network request failed: " + errorMessage));
145
+ }
146
+
147
+ winrt::Windows::Web::Http::HttpResponseMessage response = asyncOp.GetResults();
135
148
 
136
149
  if (!response.IsSuccessStatusCode()) {
137
150
  co_return winrt::Microsoft::ReactNative::Composition::ImageFailedResponse(
@@ -136,6 +136,25 @@ void WindowsTextLayoutManager::GetTextLayout(
136
136
  outerFragment.textAttributes.lineHeight * 0.8f));
137
137
  }
138
138
 
139
+ // Set reading direction (RTL/LTR) based on baseWritingDirection
140
+ // Only set reading direction if explicitly specified to avoid breaking existing layouts
141
+ bool isRTL = false;
142
+ if (outerFragment.textAttributes.baseWritingDirection.has_value()) {
143
+ DWRITE_READING_DIRECTION readingDirection = DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
144
+ if (*outerFragment.textAttributes.baseWritingDirection == facebook::react::WritingDirection::RightToLeft) {
145
+ readingDirection = DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
146
+ isRTL = true;
147
+ } else if (*outerFragment.textAttributes.baseWritingDirection == facebook::react::WritingDirection::LeftToRight) {
148
+ readingDirection = DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
149
+ isRTL = false;
150
+ } else if (*outerFragment.textAttributes.baseWritingDirection == facebook::react::WritingDirection::Natural) {
151
+ // Natural uses the layout direction from textAttributes
152
+ isRTL = (outerFragment.textAttributes.layoutDirection == facebook::react::LayoutDirection::RightToLeft);
153
+ readingDirection = isRTL ? DWRITE_READING_DIRECTION_RIGHT_TO_LEFT : DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
154
+ }
155
+ winrt::check_hresult(spTextFormat->SetReadingDirection(readingDirection));
156
+ }
157
+
139
158
  // Set text alignment
140
159
  DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT_LEADING;
141
160
  if (outerFragment.textAttributes.alignment) {
@@ -152,9 +171,9 @@ void WindowsTextLayoutManager::GetTextLayout(
152
171
  case facebook::react::TextAlignment::Right:
153
172
  alignment = DWRITE_TEXT_ALIGNMENT_TRAILING;
154
173
  break;
155
- // TODO use LTR values
156
174
  case facebook::react::TextAlignment::Natural:
157
- alignment = DWRITE_TEXT_ALIGNMENT_LEADING;
175
+ // Natural alignment respects reading direction if baseWritingDirection was set
176
+ alignment = isRTL ? DWRITE_TEXT_ALIGNMENT_TRAILING : DWRITE_TEXT_ALIGNMENT_LEADING;
158
177
  break;
159
178
  default:
160
179
  assert(false);
@@ -310,10 +310,6 @@ class ReactNativeWindowsFeatureFlags : public facebook::react::ReactNativeFeatur
310
310
  bool fuseboxEnabledRelease() override {
311
311
  return true; // Enable Fusebox (modern CDP backend) by default for React Native Windows
312
312
  }
313
-
314
- bool fuseboxNetworkInspectionEnabled() override {
315
- return true; // Enable network inspection support in Fusebox
316
- }
317
313
  };
318
314
 
319
315
  //=============================================================================================
@@ -326,10 +322,32 @@ class ReactInspectorHostTargetDelegate : public jsinspector_modern::HostTargetDe
326
322
  ReactInspectorHostTargetDelegate(Mso::WeakPtr<ReactHost> &&reactHost) noexcept : m_reactHost(std::move(reactHost)) {}
327
323
 
328
324
  jsinspector_modern::HostTargetMetadata getMetadata() override {
329
- // TODO: (vmoroz) provide more info
330
- return {
331
- .integrationName = "React Native Windows (Host)",
332
- };
325
+ jsinspector_modern::HostTargetMetadata metadata{};
326
+ metadata.integrationName = "React Native Windows (Host)";
327
+ metadata.platform = "windows";
328
+
329
+ if (Mso::CntPtr<ReactHost> reactHost = m_reactHost.GetStrongPtr()) {
330
+ const ReactOptions &options = reactHost->Options();
331
+ if (!options.Identity.empty()) {
332
+ std::string identity = options.Identity;
333
+ // Replace illegal characters with underscore
334
+ for (char &c : identity) {
335
+ if (c == '\\' || c == '/' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' ||
336
+ c == '|') {
337
+ c = '_';
338
+ }
339
+ }
340
+ metadata.appDisplayName = identity;
341
+ }
342
+ }
343
+
344
+ wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1];
345
+ DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
346
+ if (GetComputerNameW(computerName, &size)) {
347
+ metadata.deviceName = winrt::to_string(computerName);
348
+ }
349
+
350
+ return metadata;
333
351
  }
334
352
 
335
353
  void onReload(jsinspector_modern::HostTargetDelegate::PageReloadRequest const &request) override {
@@ -630,9 +648,20 @@ void ReactHost::AddInspectorPage() noexcept {
630
648
  jsinspector_modern::InspectorTargetCapabilities capabilities;
631
649
  capabilities.nativePageReloads = true;
632
650
  capabilities.prefersFuseboxFrontend = true;
633
- // TODO: (vmoroz) improve the page name
651
+
652
+ auto metadata = m_inspectorHostTargetDelegate->getMetadata();
653
+ std::string pageName;
654
+ if (metadata.appDisplayName.has_value() && !metadata.appDisplayName.value().empty()) {
655
+ pageName = metadata.appDisplayName.value();
656
+ } else {
657
+ pageName = "React Native Windows (Experimental)";
658
+ }
659
+ if (metadata.deviceName.has_value() && !metadata.deviceName.value().empty()) {
660
+ pageName += " (" + metadata.deviceName.value() + ")";
661
+ }
662
+
634
663
  inspectorPageId = jsinspector_modern::getInspectorInstance().addPage(
635
- "React Native Windows (Experimental)",
664
+ pageName,
636
665
  "Hermes",
637
666
  [weakInspectorHostTarget =
638
667
  std::weak_ptr(m_inspectorHostTarget)](std::unique_ptr<jsinspector_modern::IRemoteConnection> remote)
@@ -10,11 +10,11 @@
10
10
  -->
11
11
  <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
12
12
  <PropertyGroup>
13
- <ReactNativeWindowsVersion>0.81.0-preview.1</ReactNativeWindowsVersion>
13
+ <ReactNativeWindowsVersion>0.81.0-preview.11</ReactNativeWindowsVersion>
14
14
  <ReactNativeWindowsMajor>0</ReactNativeWindowsMajor>
15
15
  <ReactNativeWindowsMinor>81</ReactNativeWindowsMinor>
16
16
  <ReactNativeWindowsPatch>0</ReactNativeWindowsPatch>
17
17
  <ReactNativeWindowsCanary>false</ReactNativeWindowsCanary>
18
- <ReactNativeWindowsCommitId>c90943314678ce91d624ceba523e50f8d35a5ea5</ReactNativeWindowsCommitId>
18
+ <ReactNativeWindowsCommitId>eba0afc668e103f80fbcc4a5408636dde1e0bbd3</ReactNativeWindowsCommitId>
19
19
  </PropertyGroup>
20
20
  </Project>