react-native-windows 0.81.4 → 0.81.6

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 (62) hide show
  1. package/Libraries/Core/ReactNativeVersion.js +1 -1
  2. package/Libraries/NativeComponent/ViewConfigIgnore.windows.js +45 -0
  3. package/Libraries/Renderer/implementations/ReactFabric-dev.js +38 -35
  4. package/Libraries/Renderer/implementations/ReactFabric-prod.js +51 -22
  5. package/Libraries/Renderer/implementations/ReactFabric-profiling.js +54 -24
  6. package/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js +36 -33
  7. package/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js +5 -5
  8. package/Libraries/Renderer/implementations/ReactNativeRenderer-profiling.js +5 -5
  9. package/Libraries/Renderer/shims/ReactNativeTypes.js +23 -11
  10. package/Libraries/Renderer/shims/ReactNativeTypes.windows.js +23 -12
  11. package/Microsoft.ReactNative/ComponentView.idl +2 -0
  12. package/Microsoft.ReactNative/Composition.Input.idl +7 -0
  13. package/Microsoft.ReactNative/CompositionComponentView.idl +5 -0
  14. package/Microsoft.ReactNative/CompositionHwndHost.idl +1 -0
  15. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +19 -1
  16. package/Microsoft.ReactNative/Fabric/ComponentView.h +10 -1
  17. package/Microsoft.ReactNative/Fabric/Composition/Composition.Input.cpp +12 -0
  18. package/Microsoft.ReactNative/Fabric/Composition/Composition.Input.h +15 -0
  19. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +75 -0
  20. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +1 -0
  21. package/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp +10 -45
  22. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +86 -98
  23. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +4 -0
  24. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +80 -48
  25. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +11 -3
  26. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +61 -74
  27. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +4 -3
  28. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +2 -1
  29. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeWindow.cpp +245 -0
  30. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeWindow.h +80 -0
  31. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +33 -1
  32. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +17 -0
  33. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +137 -84
  34. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +7 -1
  35. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.cpp +3 -1
  36. package/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/WindowsTextLayoutManager.cpp +7 -2
  37. package/Microsoft.ReactNative/Modules/LogBoxModule.cpp +20 -95
  38. package/Microsoft.ReactNative/Modules/LogBoxModule.h +1 -1
  39. package/Microsoft.ReactNative/ReactNativeAppBuilder.cpp +0 -41
  40. package/Microsoft.ReactNative/ReactNativeAppBuilder.idl +0 -11
  41. package/Microsoft.ReactNative/ReactNativeIsland.idl +2 -3
  42. package/Microsoft.ReactNative/ReactNativeWin32App.cpp +31 -101
  43. package/Microsoft.ReactNative/ReactNativeWin32App.h +2 -13
  44. package/Microsoft.ReactNative/ReactNativeWindow.idl +44 -0
  45. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  46. package/Shared/Shared.vcxitems +7 -0
  47. package/Shared/Shared.vcxitems.filters +6 -0
  48. package/codegen/react/components/rnwcore/ActivityIndicatorView.g.h +2 -1
  49. package/codegen/react/components/rnwcore/AndroidDrawerLayout.g.h +26 -9
  50. package/codegen/react/components/rnwcore/AndroidHorizontalScrollContentView.g.h +2 -1
  51. package/codegen/react/components/rnwcore/AndroidProgressBar.g.h +2 -1
  52. package/codegen/react/components/rnwcore/AndroidSwipeRefreshLayout.g.h +8 -3
  53. package/codegen/react/components/rnwcore/AndroidSwitch.g.h +8 -3
  54. package/codegen/react/components/rnwcore/DebuggingOverlay.g.h +1 -0
  55. package/codegen/react/components/rnwcore/InputAccessory.g.h +2 -1
  56. package/codegen/react/components/rnwcore/ModalHostView.g.h +26 -9
  57. package/codegen/react/components/rnwcore/PullToRefreshView.g.h +8 -3
  58. package/codegen/react/components/rnwcore/SafeAreaView.g.h +1 -0
  59. package/codegen/react/components/rnwcore/Switch.g.h +8 -3
  60. package/codegen/react/components/rnwcore/UnimplementedNativeView.g.h +2 -1
  61. package/codegen/react/components/rnwcore/VirtualView.g.h +8 -3
  62. package/package.json +21 -21
@@ -6,7 +6,7 @@
6
6
 
7
7
  #include "ScrollViewComponentView.h"
8
8
 
9
- #include <UI.Xaml.Controls.h>
9
+ #include <Fabric/ComponentView.h>
10
10
  #include <Utils/ValueUtils.h>
11
11
 
12
12
  #pragma warning(push)
@@ -20,6 +20,8 @@
20
20
  #include <AutoDraw.h>
21
21
  #include <Fabric/DWriteHelpers.h>
22
22
  #include <unicode.h>
23
+ #include <functional>
24
+ #include "ContentIslandComponentView.h"
23
25
  #include "JSValueReader.h"
24
26
  #include "RootComponentView.h"
25
27
 
@@ -885,6 +887,13 @@ void ScrollViewComponentView::updateContentVisualSize() noexcept {
885
887
 
886
888
  void ScrollViewComponentView::prepareForRecycle() noexcept {}
887
889
 
890
+ void ScrollViewComponentView::updateChildrenClippingPath(
891
+ facebook::react::LayoutMetrics const & /*layoutMetrics*/,
892
+ const facebook::react::ViewProps & /*viewProps*/) noexcept {
893
+ // No-op: ScrollView mounts children into m_scrollVisual (not Visual()),
894
+ // and scroll visuals inherently clip their content.
895
+ }
896
+
888
897
  /*
889
898
  ScrollViewComponentView::ScrollInteractionTrackerOwner::ScrollInteractionTrackerOwner(
890
899
  ScrollViewComponentView *outer)
@@ -1326,6 +1335,10 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
1326
1335
  m_allowNextScrollNoMatterWhat = false;
1327
1336
  }
1328
1337
  }
1338
+
1339
+ // Issue #15557: Notify listeners that scroll position has changed,
1340
+ // so ContentIslandComponentView can update LocalToParentTransformMatrix
1341
+ FireViewChanged();
1329
1342
  });
1330
1343
 
1331
1344
  m_scrollBeginDragRevoker = m_scrollVisual.ScrollBeginDrag(
@@ -1333,6 +1346,9 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
1333
1346
  [this](
1334
1347
  winrt::IInspectable const & /*sender*/,
1335
1348
  winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
1349
+ // Issue #15557: Notify listeners that scroll position has changed
1350
+ FireViewChanged();
1351
+
1336
1352
  m_allowNextScrollNoMatterWhat = true; // Ensure next scroll event is recorded, regardless of throttle
1337
1353
  updateStateWithContentOffset();
1338
1354
  auto eventEmitter = GetEventEmitter();
@@ -1479,4 +1495,20 @@ void ScrollViewComponentView::updateShowsVerticalScrollIndicator(bool value) noe
1479
1495
  void ScrollViewComponentView::updateDecelerationRate(float value) noexcept {
1480
1496
  m_scrollVisual.SetDecelerationRate({value, value, value});
1481
1497
  }
1498
+
1499
+ // Issue #15557: Notify listeners that scroll position has changed.
1500
+ // ContentIslandComponentView subscribes to this to update LocalToParentTransformMatrix.
1501
+ void ScrollViewComponentView::FireViewChanged() noexcept {
1502
+ m_viewChangedEvent(*this, nullptr);
1503
+ }
1504
+
1505
+ // Issue #15557: Event accessors for ViewChanged
1506
+ winrt::event_token ScrollViewComponentView::ViewChanged(
1507
+ winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable> const &handler) noexcept {
1508
+ return m_viewChangedEvent.add(handler);
1509
+ }
1510
+
1511
+ void ScrollViewComponentView::ViewChanged(winrt::event_token const &token) noexcept {
1512
+ m_viewChangedEvent.remove(token);
1513
+ }
1482
1514
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation
@@ -118,6 +118,18 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
118
118
  double getVerticalSize() noexcept;
119
119
  double getHorizontalSize() noexcept;
120
120
 
121
+ // Issue #15557: Event accessors for ViewChanged (used by ContentIslandComponentView for transform update)
122
+ winrt::event_token ViewChanged(
123
+ winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable> const &handler) noexcept;
124
+ void ViewChanged(winrt::event_token const &token) noexcept;
125
+
126
+ protected:
127
+ // ScrollView mounts children into m_scrollVisual (not Visual()), and scroll visuals
128
+ // inherently clip their content, so we skip the children container clipping logic.
129
+ void updateChildrenClippingPath(
130
+ facebook::react::LayoutMetrics const &layoutMetrics,
131
+ const facebook::react::ViewProps &viewProps) noexcept override;
132
+
121
133
  private:
122
134
  void updateDecelerationRate(float value) noexcept;
123
135
  void updateContentVisualSize() noexcept;
@@ -129,6 +141,8 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
129
141
  bool scrollRight(float delta, bool animate) noexcept;
130
142
  void updateBackgroundColor(const facebook::react::SharedColor &color) noexcept;
131
143
  void updateStateWithContentOffset() noexcept;
144
+ // Issue #15557: Notify listeners that scroll position has changed
145
+ void FireViewChanged() noexcept;
132
146
  facebook::react::ScrollViewEventEmitter::Metrics getScrollMetrics(
133
147
  facebook::react::SharedViewEventEmitter const &eventEmitter,
134
148
  winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) noexcept;
@@ -160,6 +174,9 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
160
174
  bool m_allowNextScrollNoMatterWhat{false};
161
175
  std::chrono::steady_clock::time_point m_lastScrollEventTime{};
162
176
  std::shared_ptr<facebook::react::ScrollViewShadowNode::ConcreteState const> m_state;
177
+
178
+ // Issue #15557: Event for notifying listeners when scroll position changes
179
+ winrt::event<winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>> m_viewChangedEvent;
163
180
  };
164
181
 
165
182
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation
@@ -186,6 +186,7 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
186
186
 
187
187
  auto pt = m_outer->getClientOffset();
188
188
  m_outer->m_caretVisual.Position({x - pt.x, y - pt.y});
189
+ m_outer->m_caretPosition = {x, y};
189
190
  return true;
190
191
  }
191
192
 
@@ -245,6 +246,8 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
245
246
  //@cmember Converts screen coordinates of a specified point to the client coordinates
246
247
  BOOL TxScreenToClient(LPPOINT lppt) override {
247
248
  winrt::Windows::Foundation::Point pt{static_cast<float>(lppt->x), static_cast<float>(lppt->y)};
249
+ pt.X -= m_outer->m_contentOffsetPx.x;
250
+ pt.Y -= m_outer->m_contentOffsetPx.y;
248
251
  auto localpt = m_outer->ScreenToLocal(pt);
249
252
  lppt->x = static_cast<LONG>(localpt.X);
250
253
  lppt->y = static_cast<LONG>(localpt.Y);
@@ -254,9 +257,14 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
254
257
  //@cmember Converts the client coordinates of a specified point to screen coordinates
255
258
  BOOL TxClientToScreen(LPPOINT lppt) override {
256
259
  winrt::Windows::Foundation::Point pt{static_cast<float>(lppt->x), static_cast<float>(lppt->y)};
260
+
261
+ if (!m_outer->m_parent) {
262
+ return false;
263
+ }
264
+
257
265
  auto screenpt = m_outer->LocalToScreen(pt);
258
- lppt->x = static_cast<LONG>(screenpt.X);
259
- lppt->y = static_cast<LONG>(screenpt.Y);
266
+ lppt->x = static_cast<LONG>(screenpt.X) + m_outer->m_contentOffsetPx.x;
267
+ lppt->y = static_cast<LONG>(screenpt.Y) + m_outer->m_contentOffsetPx.y;
260
268
  return true;
261
269
  }
262
270
 
@@ -275,20 +283,25 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
275
283
  //@cmember Retrieves the coordinates of a window's client area
276
284
  HRESULT TxGetClientRect(LPRECT prc) override {
277
285
  *prc = m_outer->getClientRect();
286
+
287
+ prc->top += m_outer->m_contentOffsetPx.y;
288
+ prc->bottom += m_outer->m_contentOffsetPx.y -
289
+ static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.bottom * m_outer->m_layoutMetrics.pointScaleFactor);
290
+ prc->left += m_outer->m_contentOffsetPx.x;
291
+ prc->right += m_outer->m_contentOffsetPx.x -
292
+ static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.right * m_outer->m_layoutMetrics.pointScaleFactor);
293
+
278
294
  return S_OK;
279
295
  }
280
296
 
281
297
  //@cmember Get the view rectangle relative to the inset
282
298
  HRESULT TxGetViewInset(LPRECT prc) override {
283
299
  // Inset is in HIMETRIC
284
- constexpr float HmPerInchF = 2540.0f;
285
- constexpr float PointsPerInch = 96.0f;
286
- constexpr float dipToHm = HmPerInchF / PointsPerInch;
287
300
 
288
- prc->left = static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.left * dipToHm);
289
- prc->top = static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.top * dipToHm);
290
- prc->bottom = static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.bottom * dipToHm);
291
- prc->right = static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.right * dipToHm);
301
+ prc->left = 0;
302
+ prc->top = 0;
303
+ prc->bottom = 0;
304
+ prc->right = 0;
292
305
 
293
306
  return NOERROR;
294
307
  }
@@ -491,11 +504,6 @@ AutoCorrectOffCallback(LANGID langid, const WCHAR *pszBefore, WCHAR *pszAfter, L
491
504
  facebook::react::AttributedString WindowsTextInputComponentView::getAttributedString() const {
492
505
  // Use BaseTextShadowNode to get attributed string from children
493
506
 
494
- auto childTextAttributes = facebook::react::TextAttributes::defaultTextAttributes();
495
- childTextAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
496
-
497
- childTextAttributes.apply(windowsTextInputProps().textAttributes);
498
-
499
507
  auto attributedString = facebook::react::AttributedString{};
500
508
  // auto attachments = facebook::react::BaseTextShadowNode::Attachments{};
501
509
 
@@ -696,17 +704,10 @@ void WindowsTextInputComponentView::OnPointerPressed(
696
704
  }
697
705
 
698
706
  if (m_textServices && msg) {
699
- if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
700
- ShowContextMenu(position);
701
- args.Handled(true);
702
- } else if (msg == WM_RBUTTONUP && windowsTextInputProps().contextMenuHidden) {
703
- args.Handled(true);
704
- } else {
705
- LRESULT lresult;
706
- DrawBlock db(*this);
707
- auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
708
- args.Handled(hr != S_FALSE);
709
- }
707
+ LRESULT lresult;
708
+ DrawBlock db(*this);
709
+ auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
710
+ args.Handled(hr != S_FALSE);
710
711
  }
711
712
 
712
713
  // Emits the OnPressIn event
@@ -768,10 +769,18 @@ void WindowsTextInputComponentView::OnPointerReleased(
768
769
  }
769
770
 
770
771
  if (m_textServices && msg) {
771
- LRESULT lresult;
772
- DrawBlock db(*this);
773
- auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
774
- args.Handled(hr != S_FALSE);
772
+ // Show context menu on right button release (standard Windows behavior)
773
+ if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
774
+ ShowContextMenu(LocalToScreen(position));
775
+ args.Handled(true);
776
+ } else if (msg == WM_RBUTTONUP) {
777
+ // Context menu is hidden - don't mark as handled, let app add custom behavior
778
+ } else {
779
+ LRESULT lresult;
780
+ DrawBlock db(*this);
781
+ auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
782
+ args.Handled(hr != S_FALSE);
783
+ }
775
784
  }
776
785
 
777
786
  // Emits the OnPressOut event
@@ -1112,6 +1121,9 @@ void WindowsTextInputComponentView::updateProps(
1112
1121
  !facebook::react::floatEquality(
1113
1122
  oldTextInputProps.textAttributes.letterSpacing, newTextInputProps.textAttributes.letterSpacing) ||
1114
1123
  oldTextInputProps.textAttributes.fontFamily != newTextInputProps.textAttributes.fontFamily ||
1124
+ oldTextInputProps.textAttributes.fontStyle != newTextInputProps.textAttributes.fontStyle ||
1125
+ oldTextInputProps.textAttributes.textDecorationLineType !=
1126
+ newTextInputProps.textAttributes.textDecorationLineType ||
1115
1127
  !facebook::react::floatEquality(
1116
1128
  oldTextInputProps.textAttributes.maxFontSizeMultiplier,
1117
1129
  newTextInputProps.textAttributes.maxFontSizeMultiplier)) {
@@ -1127,6 +1139,7 @@ void WindowsTextInputComponentView::updateProps(
1127
1139
  }
1128
1140
 
1129
1141
  if (oldTextInputProps.multiline != newTextInputProps.multiline) {
1142
+ m_recalculateContentVerticalOffset = true;
1130
1143
  m_multiline = newTextInputProps.multiline;
1131
1144
  m_propBitsMask |= TXTBIT_MULTILINE | TXTBIT_WORDWRAP;
1132
1145
  if (newTextInputProps.multiline) {
@@ -1225,8 +1238,11 @@ void WindowsTextInputComponentView::updateState(
1225
1238
  if (m_mostRecentEventCount == m_state->getData().mostRecentEventCount) {
1226
1239
  m_comingFromState = true;
1227
1240
  auto &fragments = m_state->getData().attributedStringBox.getValue().getFragments();
1228
- UpdateText(fragments.size() ? fragments[0].string : "");
1229
-
1241
+ {
1242
+ // DrawBlock defers DrawText() until after UpdateText completes
1243
+ DrawBlock db(*this);
1244
+ UpdateText(fragments.size() ? fragments[0].string : "");
1245
+ }
1230
1246
  m_comingFromState = false;
1231
1247
  }
1232
1248
  }
@@ -1273,6 +1289,10 @@ void WindowsTextInputComponentView::updateLayoutMetrics(
1273
1289
  unsigned int newWidth = static_cast<unsigned int>(layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor);
1274
1290
  unsigned int newHeight = static_cast<unsigned int>(layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor);
1275
1291
 
1292
+ if (newHeight != m_imgHeight || oldLayoutMetrics.pointScaleFactor != layoutMetrics.pointScaleFactor) {
1293
+ m_recalculateContentVerticalOffset = true;
1294
+ }
1295
+
1276
1296
  if (newWidth != m_imgWidth || newHeight != m_imgHeight) {
1277
1297
  m_drawingSurface = nullptr; // Invalidate surface if we get a size change
1278
1298
  }
@@ -1375,7 +1395,7 @@ void WindowsTextInputComponentView::EmitOnScrollEvent() noexcept {
1375
1395
  }
1376
1396
 
1377
1397
  void WindowsTextInputComponentView::OnSelectionChanged(LONG start, LONG end) noexcept {
1378
- if (m_eventEmitter && !m_comingFromState /* && !m_comingFromJS ?? */) {
1398
+ if (m_eventEmitter && !m_comingFromState && !m_comingFromJS) {
1379
1399
  auto emitter = std::static_pointer_cast<const facebook::react::WindowsTextInputEventEmitter>(m_eventEmitter);
1380
1400
  facebook::react::WindowsTextInputEventEmitter::OnSelectionChange onSelectionChangeArgs;
1381
1401
  onSelectionChangeArgs.selection.start = start;
@@ -1411,6 +1431,8 @@ void WindowsTextInputComponentView::FinalizeUpdates(
1411
1431
 
1412
1432
  void WindowsTextInputComponentView::UpdatePropertyBits() noexcept {
1413
1433
  if (m_propBitsMask != 0) {
1434
+ if ((m_propBits & TXTBIT_CHARFORMATCHANGE) == TXTBIT_CHARFORMATCHANGE)
1435
+ m_recalculateContentVerticalOffset = true;
1414
1436
  DrawBlock db(*this);
1415
1437
  winrt::check_hresult(m_textServices->OnTxPropertyBitsChange(m_propBitsMask, m_propBits));
1416
1438
  m_propBitsMask = 0;
@@ -1422,6 +1444,10 @@ void WindowsTextInputComponentView::InternalFinalize() noexcept {
1422
1444
  if (m_mounted) {
1423
1445
  UpdatePropertyBits();
1424
1446
 
1447
+ if (m_recalculateContentVerticalOffset) {
1448
+ calculateContentVerticalOffset();
1449
+ }
1450
+
1425
1451
  ensureDrawingSurface();
1426
1452
  if (m_needsRedraw) {
1427
1453
  DrawText();
@@ -1483,12 +1509,6 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1483
1509
  // m_crText = RemoveAlpha(fontDetails.FontColor);
1484
1510
  // }
1485
1511
 
1486
- // set font face
1487
- // cfNew.dwMask |= CFM_FACE;
1488
- // NetUIWzCchCopy(cfNew.szFaceName, _countof(cfNew.szFaceName), fontDetails.FontName.c_str());
1489
- // cfNew.bPitchAndFamily = FF_DONTCARE;
1490
-
1491
- // set font size -- 15 to convert twips to pt
1492
1512
  const auto &props = windowsTextInputProps();
1493
1513
  float fontSize =
1494
1514
  (std::isnan(props.textAttributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
@@ -1499,8 +1519,8 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1499
1519
  fontSize *=
1500
1520
  (maxFontSizeMultiplier >= 1.0f) ? std::min(maxFontSizeMultiplier, m_fontSizeMultiplier) : m_fontSizeMultiplier;
1501
1521
 
1502
- // TODO get fontSize from props.textAttributes, or defaultTextAttributes, or fragment?
1503
1522
  cfNew.dwMask |= CFM_SIZE;
1523
+ // set font size -- 15 to convert twips to pt
1504
1524
  cfNew.yHeight = static_cast<LONG>(fontSize * 15);
1505
1525
 
1506
1526
  // set bold
@@ -1508,18 +1528,26 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1508
1528
  cfNew.wWeight =
1509
1529
  props.textAttributes.fontWeight ? static_cast<WORD>(*props.textAttributes.fontWeight) : DWRITE_FONT_WEIGHT_NORMAL;
1510
1530
 
1511
- // set font style
1512
- // cfNew.dwMask |= (CFM_ITALIC | CFM_STRIKEOUT | CFM_UNDERLINE);
1513
- // int dFontStyle = fontDetails.FontStyle;
1514
- // if (dFontStyle & FS_Italic) {
1515
- // cfNew.dwEffects |= CFE_ITALIC;
1516
- // }
1517
- // if (dFontStyle & FS_StrikeOut) {
1518
- // cfNew.dwEffects |= CFE_STRIKEOUT;
1519
- //}
1520
- // if (dFontStyle & FS_Underline) {
1521
- // cfNew.dwEffects |= CFE_UNDERLINE;
1522
- // }
1531
+ // set font style (italic)
1532
+ cfNew.dwMask |= CFM_ITALIC;
1533
+ if (props.textAttributes.fontStyle == facebook::react::FontStyle::Italic ||
1534
+ props.textAttributes.fontStyle == facebook::react::FontStyle::Oblique) {
1535
+ cfNew.dwEffects |= CFE_ITALIC;
1536
+ }
1537
+
1538
+ // set text decoration (underline and strikethrough)
1539
+ cfNew.dwMask |= (CFM_UNDERLINE | CFM_STRIKEOUT);
1540
+ if (props.textAttributes.textDecorationLineType.has_value()) {
1541
+ auto decorationType = *props.textAttributes.textDecorationLineType;
1542
+ if (decorationType == facebook::react::TextDecorationLineType::Underline ||
1543
+ decorationType == facebook::react::TextDecorationLineType::UnderlineStrikethrough) {
1544
+ cfNew.dwEffects |= CFE_UNDERLINE;
1545
+ }
1546
+ if (decorationType == facebook::react::TextDecorationLineType::Strikethrough ||
1547
+ decorationType == facebook::react::TextDecorationLineType::UnderlineStrikethrough) {
1548
+ cfNew.dwEffects |= CFE_STRIKEOUT;
1549
+ }
1550
+ }
1523
1551
 
1524
1552
  // set font family
1525
1553
  if (!props.textAttributes.fontFamily.empty()) {
@@ -1527,7 +1555,11 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1527
1555
  std::wstring fontFamily =
1528
1556
  std::wstring(props.textAttributes.fontFamily.begin(), props.textAttributes.fontFamily.end());
1529
1557
  wcsncpy_s(cfNew.szFaceName, fontFamily.c_str(), LF_FACESIZE);
1558
+ } else {
1559
+ cfNew.dwMask |= CFM_FACE;
1560
+ wcsncpy_s(cfNew.szFaceName, L"Segoe UI\0", LF_FACESIZE);
1530
1561
  }
1562
+ cfNew.bPitchAndFamily = FF_DONTCARE;
1531
1563
 
1532
1564
  // set char offset
1533
1565
  cfNew.dwMask |= CFM_OFFSET;
@@ -1536,7 +1568,8 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1536
1568
  // set letter spacing
1537
1569
  float letterSpacing = props.textAttributes.letterSpacing;
1538
1570
  if (!std::isnan(letterSpacing)) {
1539
- updateLetterSpacing(letterSpacing);
1571
+ cfNew.dwMask |= CFM_SPACING;
1572
+ cfNew.sSpacing = static_cast<SHORT>(letterSpacing * 20); // Convert to TWIPS
1540
1573
  }
1541
1574
 
1542
1575
  // set charset
@@ -1652,7 +1685,7 @@ winrt::com_ptr<::IDWriteTextLayout> WindowsTextInputComponentView::CreatePlaceho
1652
1685
  const auto &props = windowsTextInputProps();
1653
1686
  facebook::react::TextAttributes textAttributes = props.textAttributes;
1654
1687
  if (std::isnan(props.textAttributes.fontSize)) {
1655
- textAttributes.fontSize = 12.0f;
1688
+ facebook::react::TextAttributes::defaultTextAttributes().fontSize;
1656
1689
  }
1657
1690
  textAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
1658
1691
  fragment1.string = props.placeholder;
@@ -1669,6 +1702,26 @@ winrt::com_ptr<::IDWriteTextLayout> WindowsTextInputComponentView::CreatePlaceho
1669
1702
  return textLayout;
1670
1703
  }
1671
1704
 
1705
+ void WindowsTextInputComponentView::calculateContentVerticalOffset() noexcept {
1706
+ m_recalculateContentVerticalOffset = false;
1707
+
1708
+ const auto &props = windowsTextInputProps();
1709
+
1710
+ m_contentOffsetPx = {
1711
+ static_cast<LONG>(m_layoutMetrics.contentInsets.left * m_layoutMetrics.pointScaleFactor),
1712
+ static_cast<LONG>(m_layoutMetrics.contentInsets.top * m_layoutMetrics.pointScaleFactor)};
1713
+
1714
+ if (props.multiline) {
1715
+ // Align to the top for multiline
1716
+ return;
1717
+ }
1718
+
1719
+ auto [contentWidth, contentHeight] = GetContentSize();
1720
+
1721
+ m_contentOffsetPx.y += static_cast<LONG>(std::round(
1722
+ ((m_layoutMetrics.getContentFrame().size.height - contentHeight) / 2) * m_layoutMetrics.pointScaleFactor));
1723
+ }
1724
+
1672
1725
  void WindowsTextInputComponentView::DrawText() noexcept {
1673
1726
  m_needsRedraw = true;
1674
1727
  if (m_cDrawBlock || theme()->IsEmpty() || !m_textServices) {
@@ -1676,7 +1729,8 @@ void WindowsTextInputComponentView::DrawText() noexcept {
1676
1729
  }
1677
1730
 
1678
1731
  bool isZeroSized =
1679
- m_layoutMetrics.frame.size.width <= (m_layoutMetrics.contentInsets.left + m_layoutMetrics.contentInsets.right);
1732
+ m_layoutMetrics.frame.size.width <= (m_layoutMetrics.contentInsets.left + m_layoutMetrics.contentInsets.right) ||
1733
+ m_layoutMetrics.frame.size.height <= (m_layoutMetrics.contentInsets.top + m_layoutMetrics.contentInsets.bottom);
1680
1734
  if (!m_drawingSurface || isZeroSized)
1681
1735
  return;
1682
1736
 
@@ -1693,16 +1747,13 @@ void WindowsTextInputComponentView::DrawText() noexcept {
1693
1747
  assert(d2dDeviceContext->GetUnitMode() == D2D1_UNIT_MODE_DIPS);
1694
1748
 
1695
1749
  RECTL rc{
1696
- static_cast<LONG>(offset.x),
1697
- static_cast<LONG>(offset.y),
1698
- static_cast<LONG>(offset.x) + static_cast<LONG>(m_imgWidth),
1699
- static_cast<LONG>(offset.y) + static_cast<LONG>(m_imgHeight)};
1750
+ offset.x + m_contentOffsetPx.x,
1751
+ offset.y + m_contentOffsetPx.y,
1752
+ offset.x + m_contentOffsetPx.x + static_cast<LONG>(m_imgWidth),
1753
+ offset.y + m_contentOffsetPx.y + static_cast<LONG>(m_imgHeight)};
1700
1754
 
1701
1755
  RECT rcClient{
1702
- static_cast<LONG>(offset.x),
1703
- static_cast<LONG>(offset.y),
1704
- static_cast<LONG>(offset.x) + static_cast<LONG>(m_imgWidth),
1705
- static_cast<LONG>(offset.y) + static_cast<LONG>(m_imgHeight)};
1756
+ offset.x, offset.y, offset.x + static_cast<LONG>(m_imgWidth), offset.y + static_cast<LONG>(m_imgHeight)};
1706
1757
 
1707
1758
  {
1708
1759
  m_cDrawBlock++; // Dont use AutoDrawBlock as we are already in draw, and dont need to draw again.
@@ -1757,8 +1808,8 @@ void WindowsTextInputComponentView::DrawText() noexcept {
1757
1808
  // draw text
1758
1809
  d2dDeviceContext->DrawTextLayout(
1759
1810
  D2D1::Point2F(
1760
- static_cast<FLOAT>((offset.x + m_layoutMetrics.contentInsets.left) / m_layoutMetrics.pointScaleFactor),
1761
- static_cast<FLOAT>((offset.y + m_layoutMetrics.contentInsets.top) / m_layoutMetrics.pointScaleFactor)),
1811
+ static_cast<FLOAT>(offset.x + m_contentOffsetPx.x) / m_layoutMetrics.pointScaleFactor,
1812
+ static_cast<FLOAT>(offset.y + m_contentOffsetPx.y) / m_layoutMetrics.pointScaleFactor),
1762
1813
  textLayout.get(),
1763
1814
  brush.get(),
1764
1815
  D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
@@ -1834,22 +1885,6 @@ void WindowsTextInputComponentView::autoCapitalizeOnUpdateProps(
1834
1885
  }
1835
1886
  }
1836
1887
 
1837
- void WindowsTextInputComponentView::updateLetterSpacing(float letterSpacing) noexcept {
1838
- CHARFORMAT2W cf = {};
1839
- cf.cbSize = sizeof(CHARFORMAT2W);
1840
- cf.dwMask = CFM_SPACING;
1841
- cf.sSpacing = static_cast<SHORT>(letterSpacing * 20); // Convert to TWIPS
1842
-
1843
- LRESULT res;
1844
-
1845
- // Apply to all existing text like placeholder
1846
- winrt::check_hresult(m_textServices->TxSendMessage(EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast<LPARAM>(&cf), &res));
1847
-
1848
- // Apply to future text input
1849
- winrt::check_hresult(
1850
- m_textServices->TxSendMessage(EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(&cf), &res));
1851
- }
1852
-
1853
1888
  void WindowsTextInputComponentView::updateAutoCorrect(bool enable) noexcept {
1854
1889
  LRESULT lresult;
1855
1890
  winrt::check_hresult(m_textServices->TxSendMessage(
@@ -1870,6 +1905,21 @@ void WindowsTextInputComponentView::updateSpellCheck(bool enable) noexcept {
1870
1905
  m_textServices->TxSendMessage(EM_SETLANGOPTIONS, IMF_SPELLCHECKING, enable ? newLangOptions : 0, &lresult));
1871
1906
  }
1872
1907
 
1908
+ void WindowsTextInputComponentView::OnContextMenuKey(
1909
+ const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept {
1910
+ // Handle context menu key event (SHIFT+F10 or Context Menu key)
1911
+ if (!windowsTextInputProps().contextMenuHidden) {
1912
+ // m_caretPosition is stored from TxSetCaretPos in RichEdit client rect space (physical pixels).
1913
+ // LocalToScreen expects logical (DIP) coordinates, so divide by pointScaleFactor.
1914
+ auto screenPt = LocalToScreen(winrt::Windows::Foundation::Point{
1915
+ static_cast<float>(m_caretPosition.x) / m_layoutMetrics.pointScaleFactor,
1916
+ static_cast<float>(m_caretPosition.y) / m_layoutMetrics.pointScaleFactor});
1917
+ ShowContextMenu(screenPt);
1918
+ args.Handled(true);
1919
+ }
1920
+ // If contextMenuHidden, don't mark as handled - let app handle it
1921
+ }
1922
+
1873
1923
  void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept {
1874
1924
  HMENU menu = CreatePopupMenu();
1875
1925
  if (!menu)
@@ -1889,13 +1939,16 @@ void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Founda
1889
1939
  AppendMenuW(menu, MF_STRING | (canPaste ? 0 : MF_GRAYED), 3, L"Paste");
1890
1940
  AppendMenuW(menu, MF_STRING | (!isEmpty && !isReadOnly ? 0 : MF_GRAYED), 4, L"Select All");
1891
1941
 
1892
- POINT cursorPos;
1893
- GetCursorPos(&cursorPos);
1894
-
1895
1942
  HWND hwnd = GetActiveWindow();
1896
1943
 
1897
1944
  int cmd = TrackPopupMenu(
1898
- menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY, cursorPos.x, cursorPos.y, 0, hwnd, NULL);
1945
+ menu,
1946
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY,
1947
+ static_cast<int>(position.X),
1948
+ static_cast<int>(position.Y),
1949
+ 0,
1950
+ hwnd,
1951
+ NULL);
1899
1952
 
1900
1953
  if (cmd == 1) { // Cut
1901
1954
  m_textServices->TxSendMessage(WM_CUT, 0, 0, &res);
@@ -67,6 +67,8 @@ struct WindowsTextInputComponentView
67
67
  void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept override;
68
68
  void OnCharacterReceived(const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs
69
69
  &args) noexcept override;
70
+ void OnContextMenuKey(
71
+ const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept override;
70
72
  void onMounted() noexcept override;
71
73
 
72
74
  std::optional<std::string> getAccessiblityValue() noexcept override;
@@ -115,10 +117,10 @@ struct WindowsTextInputComponentView
115
117
  const std::string &previousCapitalizationType,
116
118
  const std::string &newcapitalizationType) noexcept;
117
119
 
118
- void updateLetterSpacing(float letterSpacing) noexcept;
119
120
  void updateAutoCorrect(bool value) noexcept;
120
121
  void updateSpellCheck(bool value) noexcept;
121
122
  void ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept;
123
+ void calculateContentVerticalOffset() noexcept;
122
124
 
123
125
  winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
124
126
  winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr};
@@ -143,9 +145,13 @@ struct WindowsTextInputComponentView
143
145
  bool m_hasFocus{false};
144
146
  bool m_clearTextOnSubmit{false};
145
147
  bool m_multiline{false};
148
+ LONG m_contentVerticalOffsetPx{0}; // Used to center single line text within the client rect
149
+ bool m_recalculateContentVerticalOffset{true};
150
+ POINT m_contentOffsetPx{0, 0};
146
151
  DWORD m_propBitsMask{0};
147
152
  DWORD m_propBits{0};
148
153
  HCURSOR m_hcursor{nullptr};
154
+ POINT m_caretPosition{0, 0};
149
155
  std::chrono::steady_clock::time_point m_lastClickTime{};
150
156
  std::vector<facebook::react::CompWindowsTextInputSubmitKeyEventsStruct> m_submitKeyEvents;
151
157
  };
@@ -5,6 +5,7 @@
5
5
 
6
6
  #include <react/featureflags/ReactNativeFeatureFlags.h>
7
7
  #include <react/renderer/attributedstring/AttributedStringBox.h>
8
+ #include <react/renderer/attributedstring/PlaceholderAttributedString.h>
8
9
  #include <react/renderer/attributedstring/TextAttributes.h>
9
10
  #include <react/renderer/components/text/BaseTextShadowNode.h>
10
11
  #include <react/renderer/core/LayoutConstraints.h>
@@ -185,6 +186,7 @@ AttributedString WindowsTextInputShadowNode::getPlaceholderAttributedString(cons
185
186
  const auto &props = getConcreteProps();
186
187
 
187
188
  AttributedString attributedString;
189
+ attributedString.setBaseTextAttributes(props.textAttributes);
188
190
  //[windows
189
191
  if (!props.placeholder.empty()) {
190
192
  auto textAttributes = TextAttributes::defaultTextAttributes();
@@ -203,7 +205,7 @@ AttributedString WindowsTextInputShadowNode::getPlaceholderAttributedString(cons
203
205
  }
204
206
  // windows]
205
207
 
206
- return attributedString;
208
+ return ensurePlaceholderIfEmpty_DO_NOT_USE(attributedString);
207
209
  }
208
210
 
209
211
  #pragma mark - LayoutableShadowNode
@@ -96,7 +96,10 @@ void WindowsTextLayoutManager::GetTextLayout(
96
96
 
97
97
  winrt::com_ptr<IDWriteTextFormat> spTextFormat;
98
98
 
99
- float fontSizeText = outerFragment.textAttributes.fontSize;
99
+ float fontSizeText =
100
+ (std::isnan(outerFragment.textAttributes.fontSize)
101
+ ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
102
+ : outerFragment.textAttributes.fontSize);
100
103
  if (outerFragment.textAttributes.allowFontScaling.value_or(true) &&
101
104
  !std::isnan(outerFragment.textAttributes.fontSizeMultiplier)) {
102
105
  float maxFontSizeMultiplierText = cDefaultMaxFontSizeMultiplier;
@@ -287,7 +290,9 @@ void WindowsTextLayoutManager::GetTextLayout(
287
290
  maxFontSizeMultiplier =
288
291
  (!std::isnan(attributes.maxFontSizeMultiplier) ? attributes.maxFontSizeMultiplier
289
292
  : cDefaultMaxFontSizeMultiplier);
290
- float fontSize = attributes.fontSize;
293
+ float fontSize =
294
+ (std::isnan(attributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
295
+ : attributes.fontSize);
291
296
  if (attributes.allowFontScaling.value_or(true) && (!std::isnan(attributes.fontSizeMultiplier))) {
292
297
  fontSize *= (maxFontSizeMultiplier >= 1.0f) ? std::min(maxFontSizeMultiplier, attributes.fontSizeMultiplier)
293
298
  : attributes.fontSizeMultiplier;