react-native-windows 0.76.11 → 0.76.13

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 (53) hide show
  1. package/Libraries/Components/Button.windows.js +3 -0
  2. package/Libraries/Components/ScrollView/ScrollView.windows.js +1959 -0
  3. package/Libraries/Components/View/View.windows.js +107 -56
  4. package/Libraries/Image/Image.windows.js +42 -21
  5. package/Libraries/Text/Text.d.ts +16 -1
  6. package/Microsoft.ReactNative/CompositionComponentView.idl +0 -5
  7. package/Microsoft.ReactNative/CompositionSwitcher.idl +4 -0
  8. package/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp +32 -0
  9. package/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +159 -4
  10. package/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h +11 -4
  11. package/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +0 -4
  12. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +22 -17
  13. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +1 -27
  14. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +0 -2
  15. package/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp +36 -11
  16. package/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h +3 -0
  17. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +50 -125
  18. package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp +9 -6
  19. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +31 -12
  20. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +6 -1
  21. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp +2 -2
  22. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +145 -19
  23. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +13 -0
  24. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentDescriptor.h +0 -2
  25. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +134 -11
  26. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +6 -0
  27. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.cpp +31 -0
  28. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.h +14 -1
  29. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputProps.cpp +6 -2
  30. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputProps.h +4 -1
  31. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.cpp +127 -109
  32. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.h +28 -25
  33. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputState.cpp +8 -18
  34. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputState.h +12 -35
  35. package/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp +53 -11
  36. package/Microsoft.ReactNative/Fabric/FabricUIManagerModule.h +10 -2
  37. package/Microsoft.ReactNative/Fabric/ImageRequestParams.cpp +26 -0
  38. package/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp +38 -8
  39. package/Microsoft.ReactNative/Fabric/WindowsImageManager.h +3 -1
  40. package/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.cpp +206 -41
  41. package/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.h +14 -0
  42. package/Microsoft.ReactNative/Modules/Animated/AnimationDriver.cpp +2 -1
  43. package/Microsoft.ReactNative/ReactNativeIsland.idl +3 -0
  44. package/Microsoft.ReactNative/Utils/ImageUtils.h +1 -0
  45. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  46. package/PropertySheets/NuGet.LockFile.props +1 -1
  47. package/PropertySheets/WebView2.props +1 -1
  48. package/PropertySheets/WinUI.props +1 -1
  49. package/Shared/Networking/WinRTWebSocketResource.cpp +82 -101
  50. package/Shared/Networking/WinRTWebSocketResource.h +91 -7
  51. package/Shared/Shared.vcxitems +3 -1
  52. package/Shared/Shared.vcxitems.filters +1 -0
  53. package/package.json +1 -1
@@ -81,9 +81,12 @@ struct ScrollBarComponent {
81
81
  }
82
82
 
83
83
  void ContentSize(winrt::Windows::Foundation::Size contentSize) noexcept {
84
+ if (m_contentSize == contentSize) {
85
+ return;
86
+ }
84
87
  m_contentSize = contentSize;
85
88
  updateThumb();
86
- updateVisibility();
89
+ updateVisibility(m_visible);
87
90
  }
88
91
 
89
92
  void updateTrack() noexcept {
@@ -108,10 +111,22 @@ struct ScrollBarComponent {
108
111
 
109
112
  updateTrack();
110
113
  updateThumb();
111
- updateVisibility();
114
+ updateVisibility(m_visible);
112
115
  }
113
116
 
114
- void updateVisibility() noexcept {
117
+ void updateVisibility(bool visible) noexcept {
118
+ if ((m_size.Width <= 0.0f && m_size.Height <= 0.0f) ||
119
+ (m_contentSize.Width <= 0.0f && m_contentSize.Height <= 0.0f)) {
120
+ m_rootVisual.IsVisible(false);
121
+ return;
122
+ }
123
+
124
+ if (!visible) {
125
+ m_visible = false;
126
+ m_rootVisual.IsVisible(visible);
127
+ return;
128
+ }
129
+
115
130
  bool newVisibility = false;
116
131
  if (m_vertical) {
117
132
  newVisibility = (m_contentSize.Height > m_size.Height);
@@ -119,10 +134,8 @@ struct ScrollBarComponent {
119
134
  newVisibility = (m_contentSize.Width > m_size.Width);
120
135
  }
121
136
 
122
- if (newVisibility != m_visible) {
123
- m_visible = newVisibility;
124
- m_rootVisual.IsVisible(m_visible);
125
- }
137
+ m_visible = newVisibility;
138
+ m_rootVisual.IsVisible(m_visible);
126
139
  }
127
140
 
128
141
  void updateRootAndArrowVisualOffsets() noexcept {
@@ -561,7 +574,7 @@ struct ScrollBarComponent {
561
574
  winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext m_compContext;
562
575
  winrt::Microsoft::ReactNative::ReactContext m_reactContext;
563
576
  const bool m_vertical;
564
- bool m_visible{false};
577
+ bool m_visible{true};
565
578
  bool m_shy{false};
566
579
  int m_thumbSize{0};
567
580
  float m_arrowSize{0};
@@ -753,6 +766,44 @@ void ScrollViewComponentView::updateProps(
753
766
  if (!oldProps || oldViewProps.horizontal != newViewProps.horizontal) {
754
767
  m_scrollVisual.Horizontal(newViewProps.horizontal);
755
768
  }
769
+
770
+ if (!oldProps || oldViewProps.showsHorizontalScrollIndicator != newViewProps.showsHorizontalScrollIndicator) {
771
+ updateShowsHorizontalScrollIndicator(newViewProps.showsHorizontalScrollIndicator);
772
+ }
773
+
774
+ if (!oldProps || oldViewProps.showsVerticalScrollIndicator != newViewProps.showsVerticalScrollIndicator) {
775
+ updateShowsVerticalScrollIndicator(newViewProps.showsVerticalScrollIndicator);
776
+ }
777
+
778
+ if (!oldProps || oldViewProps.decelerationRate != newViewProps.decelerationRate) {
779
+ updateDecelerationRate(newViewProps.decelerationRate);
780
+ }
781
+
782
+ if (!oldProps || oldViewProps.scrollEventThrottle != newViewProps.scrollEventThrottle) {
783
+ // Zero means "send value only once per significant logical event".
784
+ // Prop value is in milliseconds.
785
+ auto throttleInSeconds = newViewProps.scrollEventThrottle / 1000.0;
786
+ auto msPerFrame = 1.0 / 60.0;
787
+ if (throttleInSeconds < 0) {
788
+ m_scrollEventThrottle = INFINITY;
789
+ } else if (throttleInSeconds <= msPerFrame) {
790
+ m_scrollEventThrottle = 0;
791
+ } else {
792
+ m_scrollEventThrottle = throttleInSeconds;
793
+ }
794
+ }
795
+
796
+ if (oldViewProps.maximumZoomScale != newViewProps.maximumZoomScale) {
797
+ m_scrollVisual.SetMaximumZoomScale(newViewProps.maximumZoomScale);
798
+ }
799
+
800
+ if (oldViewProps.minimumZoomScale != newViewProps.minimumZoomScale) {
801
+ m_scrollVisual.SetMinimumZoomScale(newViewProps.minimumZoomScale);
802
+ }
803
+
804
+ if (oldViewProps.zoomScale != newViewProps.zoomScale) {
805
+ m_scrollVisual.Scale({newViewProps.zoomScale, newViewProps.zoomScale, newViewProps.zoomScale});
806
+ }
756
807
  }
757
808
 
758
809
  void ScrollViewComponentView::updateState(
@@ -784,14 +835,15 @@ void ScrollViewComponentView::updateLayoutMetrics(
784
835
  facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept {
785
836
  // Set Position & Size Properties
786
837
  ensureVisual();
787
-
788
- m_verticalScrollbarComponent->updateLayoutMetrics(layoutMetrics);
789
- m_horizontalScrollbarComponent->updateLayoutMetrics(layoutMetrics);
790
- base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
791
- m_scrollVisual.Size(
792
- {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
793
- layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
794
- updateContentVisualSize();
838
+ if (oldLayoutMetrics != layoutMetrics) {
839
+ m_verticalScrollbarComponent->updateLayoutMetrics(layoutMetrics);
840
+ m_horizontalScrollbarComponent->updateLayoutMetrics(layoutMetrics);
841
+ base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
842
+ m_scrollVisual.Size(
843
+ {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
844
+ layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
845
+ updateContentVisualSize();
846
+ }
795
847
  }
796
848
 
797
849
  void ScrollViewComponentView::updateContentVisualSize() noexcept {
@@ -962,6 +1014,8 @@ bool ScrollViewComponentView::scrollToEnd(bool animate) noexcept {
962
1014
 
963
1015
  auto x = (m_contentSize.width - m_layoutMetrics.frame.size.width) * m_layoutMetrics.pointScaleFactor;
964
1016
  auto y = (m_contentSize.height - m_layoutMetrics.frame.size.height) * m_layoutMetrics.pointScaleFactor;
1017
+ // Ensure at least one scroll event will fire
1018
+ m_allowNextScrollNoMatterWhat = true;
965
1019
  m_scrollVisual.TryUpdatePosition({static_cast<float>(x), static_cast<float>(y), 0.0f}, animate);
966
1020
  return true;
967
1021
  }
@@ -976,10 +1030,16 @@ bool ScrollViewComponentView::scrollToStart(bool animate) noexcept {
976
1030
  }
977
1031
 
978
1032
  bool ScrollViewComponentView::pageUp(bool animate) noexcept {
1033
+ if (std::static_pointer_cast<const facebook::react::ScrollViewProps>(viewProps())->horizontal) {
1034
+ return scrollLeft(m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor, animate);
1035
+ }
979
1036
  return scrollUp(m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor, animate);
980
1037
  }
981
1038
 
982
1039
  bool ScrollViewComponentView::pageDown(bool animate) noexcept {
1040
+ if (std::static_pointer_cast<const facebook::react::ScrollViewProps>(viewProps())->horizontal) {
1041
+ return scrollRight(m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor, animate);
1042
+ }
983
1043
  return scrollDown(m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor, animate);
984
1044
  }
985
1045
 
@@ -1036,7 +1096,7 @@ bool ScrollViewComponentView::scrollLeft(float delta, bool animate) noexcept {
1036
1096
  return false;
1037
1097
  }
1038
1098
 
1039
- m_scrollVisual.ScrollBy({delta, 0, 0}, animate);
1099
+ m_scrollVisual.ScrollBy({-delta, 0, 0}, animate);
1040
1100
  return true;
1041
1101
  }
1042
1102
 
@@ -1196,6 +1256,36 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
1196
1256
  [this](
1197
1257
  winrt::IInspectable const & /*sender*/,
1198
1258
  winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
1259
+ auto now = std::chrono::steady_clock::now();
1260
+ auto elapsed = std::chrono::duration_cast<std::chrono::duration<double>>(now - m_lastScrollEventTime).count();
1261
+
1262
+ if (m_allowNextScrollNoMatterWhat ||
1263
+ (m_scrollEventThrottle < std::max(std::chrono::duration<double>(0.017).count(), elapsed))) {
1264
+ updateStateWithContentOffset();
1265
+ auto eventEmitter = GetEventEmitter();
1266
+ if (eventEmitter) {
1267
+ facebook::react::ScrollViewEventEmitter::Metrics scrollMetrics;
1268
+ scrollMetrics.containerSize.height = m_layoutMetrics.frame.size.height;
1269
+ scrollMetrics.containerSize.width = m_layoutMetrics.frame.size.width;
1270
+ scrollMetrics.contentOffset.x = args.Position().x / m_layoutMetrics.pointScaleFactor;
1271
+ scrollMetrics.contentOffset.y = args.Position().y / m_layoutMetrics.pointScaleFactor;
1272
+ scrollMetrics.zoomScale = 1.0;
1273
+ scrollMetrics.contentSize.height = std::max(m_contentSize.height, m_layoutMetrics.frame.size.height);
1274
+ scrollMetrics.contentSize.width = std::max(m_contentSize.width, m_layoutMetrics.frame.size.width);
1275
+ std::static_pointer_cast<facebook::react::ScrollViewEventEmitter const>(eventEmitter)
1276
+ ->onScroll(scrollMetrics);
1277
+ m_lastScrollEventTime = now;
1278
+ m_allowNextScrollNoMatterWhat = false;
1279
+ }
1280
+ }
1281
+ });
1282
+
1283
+ m_scrollBeginDragRevoker = m_scrollVisual.ScrollBeginDrag(
1284
+ winrt::auto_revoke,
1285
+ [this](
1286
+ winrt::IInspectable const & /*sender*/,
1287
+ winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
1288
+ m_allowNextScrollNoMatterWhat = true; // Ensure next scroll event is recorded, regardless of throttle
1199
1289
  updateStateWithContentOffset();
1200
1290
  auto eventEmitter = GetEventEmitter();
1201
1291
  if (eventEmitter) {
@@ -1208,9 +1298,10 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
1208
1298
  scrollMetrics.contentSize.height = std::max(m_contentSize.height, m_layoutMetrics.frame.size.height);
1209
1299
  scrollMetrics.contentSize.width = std::max(m_contentSize.width, m_layoutMetrics.frame.size.width);
1210
1300
  std::static_pointer_cast<facebook::react::ScrollViewEventEmitter const>(eventEmitter)
1211
- ->onScroll(scrollMetrics);
1301
+ ->onScrollBeginDrag(scrollMetrics);
1212
1302
  }
1213
1303
  });
1304
+
1214
1305
  return visual;
1215
1306
  }
1216
1307
 
@@ -1259,7 +1350,7 @@ facebook::react::Point ScrollViewComponentView::getClientOffset() const noexcept
1259
1350
  }
1260
1351
 
1261
1352
  std::string ScrollViewComponentView::DefaultControlType() const noexcept {
1262
- return "scrollbar";
1353
+ return "pane";
1263
1354
  }
1264
1355
 
1265
1356
  winrt::com_ptr<ComponentView> ScrollViewComponentView::focusVisualRoot(
@@ -1272,4 +1363,39 @@ ScrollViewComponentView::visualToHostFocus() noexcept {
1272
1363
  return m_scrollVisual;
1273
1364
  }
1274
1365
 
1366
+ int ScrollViewComponentView::getScrollPositionX() noexcept {
1367
+ return int((m_scrollVisual.ScrollPosition().x / m_horizontalScrollbarComponent->getScrollRange()) * 100);
1368
+ }
1369
+
1370
+ int ScrollViewComponentView::getScrollPositionY() noexcept {
1371
+ return int((m_scrollVisual.ScrollPosition().y / m_verticalScrollbarComponent->getScrollRange()) * 100);
1372
+ }
1373
+
1374
+ double ScrollViewComponentView::getVerticalSize() noexcept {
1375
+ return std::min((m_layoutMetrics.frame.size.height / m_contentSize.height * 100.0), 100.0);
1376
+ }
1377
+
1378
+ double ScrollViewComponentView::getHorizontalSize() noexcept {
1379
+ return std::min((m_layoutMetrics.frame.size.width / m_contentSize.width * 100.0), 100.0);
1380
+ }
1381
+
1382
+ void ScrollViewComponentView::updateShowsHorizontalScrollIndicator(bool value) noexcept {
1383
+ if (value) {
1384
+ m_horizontalScrollbarComponent->updateVisibility(true);
1385
+ } else {
1386
+ m_horizontalScrollbarComponent->updateVisibility(false);
1387
+ }
1388
+ }
1389
+
1390
+ void ScrollViewComponentView::updateShowsVerticalScrollIndicator(bool value) noexcept {
1391
+ if (value) {
1392
+ m_verticalScrollbarComponent->updateVisibility(true);
1393
+ } else {
1394
+ m_verticalScrollbarComponent->updateVisibility(false);
1395
+ }
1396
+ }
1397
+
1398
+ void ScrollViewComponentView::updateDecelerationRate(float value) noexcept {
1399
+ m_scrollVisual.SetDecelerationRate({value, value, value});
1400
+ }
1275
1401
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation
@@ -113,7 +113,13 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
113
113
  winrt::Microsoft::ReactNative::Composition::Experimental::IVisual visualToHostFocus() noexcept override;
114
114
  winrt::com_ptr<ComponentView> focusVisualRoot(const facebook::react::Rect &focusRect) noexcept override;
115
115
 
116
+ int getScrollPositionX() noexcept;
117
+ int getScrollPositionY() noexcept;
118
+ double getVerticalSize() noexcept;
119
+ double getHorizontalSize() noexcept;
120
+
116
121
  private:
122
+ void updateDecelerationRate(float value) noexcept;
117
123
  void updateContentVisualSize() noexcept;
118
124
  bool scrollToEnd(bool animate) noexcept;
119
125
  bool scrollToStart(bool animate) noexcept;
@@ -123,6 +129,8 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
123
129
  bool scrollRight(float delta, bool animate) noexcept;
124
130
  void updateBackgroundColor(const facebook::react::SharedColor &color) noexcept;
125
131
  void updateStateWithContentOffset() noexcept;
132
+ void updateShowsHorizontalScrollIndicator(bool value) noexcept;
133
+ void updateShowsVerticalScrollIndicator(bool value) noexcept;
126
134
 
127
135
  facebook::react::Size m_contentSize;
128
136
  winrt::Microsoft::ReactNative::Composition::Experimental::IScrollVisual m_scrollVisual{nullptr};
@@ -130,6 +138,8 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
130
138
  std::shared_ptr<ScrollBarComponent> m_verticalScrollbarComponent{nullptr};
131
139
  winrt::Microsoft::ReactNative::Composition::Experimental::IScrollVisual::ScrollPositionChanged_revoker
132
140
  m_scrollPositionChangedRevoker{};
141
+ winrt::Microsoft::ReactNative::Composition::Experimental::IScrollVisual::ScrollBeginDrag_revoker
142
+ m_scrollBeginDragRevoker{};
133
143
 
134
144
  float m_zoomFactor{1.0f};
135
145
  bool m_isScrollingFromInertia = false;
@@ -137,6 +147,9 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
137
147
  bool m_isHorizontal = false;
138
148
  bool m_changeViewAfterLoaded = false;
139
149
  bool m_dismissKeyboardOnDrag = false;
150
+ double m_scrollEventThrottle{0.0};
151
+ bool m_allowNextScrollNoMatterWhat{false};
152
+ std::chrono::steady_clock::time_point m_lastScrollEventTime{};
140
153
  std::shared_ptr<facebook::react::ScrollViewShadowNode::ConcreteState const> m_state;
141
154
  };
142
155
 
@@ -75,8 +75,6 @@ virtual State::Shared createInitialState(
75
75
  // and communicate text rendering metrics to mounting layer.
76
76
  textInputShadowNode.setTextLayoutManager(m_textLayoutManager);
77
77
 
78
- textInputShadowNode.setContextContainer(const_cast<ContextContainer *>(getContextContainer().get()));
79
-
80
78
  /*
81
79
  int surfaceId = textInputShadowNode.getSurfaceId();
82
80
  if (surfaceIdToThemePaddingMap_.find(surfaceId) !=
@@ -116,25 +116,25 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
116
116
 
117
117
  //@cmember Show the scroll bar
118
118
  BOOL TxShowScrollBar(INT fnBar, BOOL fShow) override {
119
- assert(false);
119
+ // assert(false);
120
120
  return {};
121
121
  }
122
122
 
123
123
  //@cmember Enable the scroll bar
124
124
  BOOL TxEnableScrollBar(INT fuSBFlags, INT fuArrowflags) override {
125
- assert(false);
125
+ // assert(false);
126
126
  return {};
127
127
  }
128
128
 
129
129
  //@cmember Set the scroll range
130
130
  BOOL TxSetScrollRange(INT fnBar, LONG nMinPos, INT nMaxPos, BOOL fRedraw) override {
131
- assert(false);
131
+ // assert(false);
132
132
  return {};
133
133
  }
134
134
 
135
135
  //@cmember Set the scroll position
136
136
  BOOL TxSetScrollPos(INT fnBar, INT nPos, BOOL fRedraw) override {
137
- assert(false);
137
+ // assert(false);
138
138
  return {};
139
139
  }
140
140
 
@@ -377,8 +377,11 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
377
377
 
378
378
  //@cmember Get the bits representing requested scroll bars for the window
379
379
  HRESULT TxGetScrollBars(DWORD *pdwScrollBar) override {
380
- // TODO support scrolling
381
- *pdwScrollBar = 0;
380
+ if (m_outer->m_multiline) {
381
+ *pdwScrollBar = WS_VSCROLL | WS_HSCROLL | ES_AUTOVSCROLL | ES_AUTOHSCROLL;
382
+ } else {
383
+ *pdwScrollBar = WS_HSCROLL | ES_AUTOHSCROLL;
384
+ }
382
385
  return S_OK;
383
386
  }
384
387
 
@@ -460,6 +463,13 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
460
463
  WindowsTextInputComponentView *m_outer;
461
464
  };
462
465
 
466
+ int WINAPI
467
+ AutoCorrectOffCallback(LANGID langid, const WCHAR *pszBefore, WCHAR *pszAfter, LONG cchAfter, LONG *pcchReplaced) {
468
+ wcsncpy_s(pszAfter, cchAfter, pszBefore, _TRUNCATE);
469
+ *pcchReplaced = static_cast<LONG>(wcslen(pszAfter));
470
+ return ATP_CHANGE;
471
+ }
472
+
463
473
  facebook::react::AttributedString WindowsTextInputComponentView::getAttributedString() const {
464
474
  // Use BaseTextShadowNode to get attributed string from children
465
475
 
@@ -611,6 +621,19 @@ WPARAM PointerRoutedEventArgsToMouseWParam(
611
621
  return wParam;
612
622
  }
613
623
 
624
+ bool WindowsTextInputComponentView::IsDoubleClick() {
625
+ using namespace std::chrono;
626
+
627
+ auto now = steady_clock::now();
628
+ auto duration = duration_cast<milliseconds>(now - m_lastClickTime).count();
629
+
630
+ const int DOUBLE_CLICK_TIME_MS = ::GetDoubleClickTime();
631
+
632
+ m_lastClickTime = now;
633
+
634
+ return (duration < DOUBLE_CLICK_TIME_MS);
635
+ }
636
+
614
637
  void WindowsTextInputComponentView::OnPointerPressed(
615
638
  const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) noexcept {
616
639
  UINT msg = 0;
@@ -627,7 +650,11 @@ void WindowsTextInputComponentView::OnPointerPressed(
627
650
  if (pp.PointerDeviceType() == winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType::Mouse) {
628
651
  switch (pp.Properties().PointerUpdateKind()) {
629
652
  case winrt::Microsoft::ReactNative::Composition::Input::PointerUpdateKind::LeftButtonPressed:
630
- msg = WM_LBUTTONDOWN;
653
+ if (IsDoubleClick()) {
654
+ msg = WM_LBUTTONDBLCLK;
655
+ } else {
656
+ msg = WM_LBUTTONDOWN;
657
+ }
631
658
  break;
632
659
  case winrt::Microsoft::ReactNative::Composition::Input::PointerUpdateKind::MiddleButtonPressed:
633
660
  msg = WM_MBUTTONDOWN;
@@ -930,6 +957,19 @@ void WindowsTextInputComponentView::onLostFocus(
930
957
  m_textServices->TxSendMessage(WM_KILLFOCUS, 0, 0, &lresult);
931
958
  }
932
959
  m_caretVisual.IsVisible(false);
960
+
961
+ // Call onEndEditing when focus is lost
962
+ if (m_eventEmitter && !m_comingFromJS) {
963
+ auto emitter = std::static_pointer_cast<const facebook::react::WindowsTextInputEventEmitter>(m_eventEmitter);
964
+ facebook::react::WindowsTextInputEventEmitter::OnEndEditing onEndEditingArgs;
965
+
966
+ // Set event arguments
967
+ onEndEditingArgs.eventCount = ++m_nativeEventCount;
968
+ onEndEditingArgs.text = GetTextFromRichEdit();
969
+
970
+ // Emit the event
971
+ emitter->onEndEditing(onEndEditingArgs);
972
+ }
933
973
  }
934
974
 
935
975
  void WindowsTextInputComponentView::onGotFocus(
@@ -983,7 +1023,10 @@ void WindowsTextInputComponentView::updateProps(
983
1023
  if (!facebook::react::floatEquality(
984
1024
  oldTextInputProps.textAttributes.fontSize, newTextInputProps.textAttributes.fontSize) ||
985
1025
  (oldTextInputProps.textAttributes.allowFontScaling != newTextInputProps.textAttributes.allowFontScaling) ||
986
- oldTextInputProps.textAttributes.fontWeight != newTextInputProps.textAttributes.fontWeight) {
1026
+ oldTextInputProps.textAttributes.fontWeight != newTextInputProps.textAttributes.fontWeight ||
1027
+ !facebook::react::floatEquality(
1028
+ oldTextInputProps.textAttributes.letterSpacing, newTextInputProps.textAttributes.letterSpacing) ||
1029
+ oldTextInputProps.textAttributes.fontFamily != newTextInputProps.textAttributes.fontFamily) {
987
1030
  m_propBitsMask |= TXTBIT_CHARFORMATCHANGE;
988
1031
  m_propBits |= TXTBIT_CHARFORMATCHANGE;
989
1032
  }
@@ -1042,6 +1085,26 @@ void WindowsTextInputComponentView::updateProps(
1042
1085
  autoCapitalizeOnUpdateProps(oldTextInputProps.autoCapitalize, newTextInputProps.autoCapitalize);
1043
1086
  }
1044
1087
 
1088
+ if (oldTextInputProps.textAlign != newTextInputProps.textAlign) {
1089
+ // Let UpdateParaFormat() to refresh the text field with the new text alignment.
1090
+ m_propBitsMask |= TXTBIT_PARAFORMATCHANGE;
1091
+ m_propBits |= TXTBIT_PARAFORMATCHANGE;
1092
+ }
1093
+
1094
+ // Please note: spellcheck performs both red lines and autocorrect as per windows behaviour
1095
+ bool shouldUpdateSpellCheck =
1096
+ (!oldProps || (oldTextInputProps.spellCheck != newTextInputProps.spellCheck) ||
1097
+ (oldTextInputProps.autoCorrect != newTextInputProps.autoCorrect));
1098
+
1099
+ if (shouldUpdateSpellCheck) {
1100
+ bool effectiveSpellCheck = newTextInputProps.spellCheck || newTextInputProps.autoCorrect;
1101
+ updateSpellCheck(effectiveSpellCheck);
1102
+ }
1103
+
1104
+ if (!oldProps || oldTextInputProps.autoCorrect != newTextInputProps.autoCorrect) {
1105
+ updateAutoCorrect(newTextInputProps.autoCorrect);
1106
+ }
1107
+
1045
1108
  UpdatePropertyBits();
1046
1109
  }
1047
1110
 
@@ -1070,7 +1133,7 @@ void WindowsTextInputComponentView::updateState(
1070
1133
 
1071
1134
  if (m_mostRecentEventCount == m_state->getData().mostRecentEventCount) {
1072
1135
  m_comingFromState = true;
1073
- auto &fragments = m_state->getData().attributedString.getFragments();
1136
+ auto &fragments = m_state->getData().attributedStringBox.getValue().getFragments();
1074
1137
  UpdateText(fragments.size() ? fragments[0].string : "");
1075
1138
 
1076
1139
  m_comingFromState = false;
@@ -1133,7 +1196,7 @@ void WindowsTextInputComponentView::OnTextUpdated() noexcept {
1133
1196
  // auto newAttributedString = getAttributedString();
1134
1197
  // if (data.attributedString == newAttributedString)
1135
1198
  // return;
1136
- data.attributedString = getAttributedString();
1199
+ data.attributedStringBox = facebook::react::AttributedStringBox(getAttributedString());
1137
1200
  data.mostRecentEventCount = m_nativeEventCount;
1138
1201
 
1139
1202
  m_state->updateState(std::move(data));
@@ -1288,10 +1351,24 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
1288
1351
  // cfNew.dwEffects |= CFE_UNDERLINE;
1289
1352
  // }
1290
1353
 
1354
+ // set font family
1355
+ if (!props.textAttributes.fontFamily.empty()) {
1356
+ cfNew.dwMask |= CFM_FACE;
1357
+ std::wstring fontFamily =
1358
+ std::wstring(props.textAttributes.fontFamily.begin(), props.textAttributes.fontFamily.end());
1359
+ wcsncpy_s(cfNew.szFaceName, fontFamily.c_str(), LF_FACESIZE);
1360
+ }
1361
+
1291
1362
  // set char offset
1292
1363
  cfNew.dwMask |= CFM_OFFSET;
1293
1364
  cfNew.yOffset = 0;
1294
1365
 
1366
+ // set letter spacing
1367
+ float letterSpacing = props.textAttributes.letterSpacing;
1368
+ if (!std::isnan(letterSpacing)) {
1369
+ updateLetterSpacing(letterSpacing);
1370
+ }
1371
+
1295
1372
  // set charset
1296
1373
  cfNew.dwMask |= CFM_CHARSET;
1297
1374
  cfNew.bCharSet = DEFAULT_CHARSET;
@@ -1305,7 +1382,15 @@ void WindowsTextInputComponentView::UpdateParaFormat() noexcept {
1305
1382
  m_pf.cbSize = sizeof(PARAFORMAT2);
1306
1383
  m_pf.dwMask = PFM_ALL;
1307
1384
 
1308
- m_pf.wAlignment = PFA_LEFT;
1385
+ auto &textAlign = windowsTextInputProps().textAlign;
1386
+
1387
+ if (textAlign == facebook::react::TextAlignment::Center) {
1388
+ m_pf.wAlignment = PFA_CENTER;
1389
+ } else if (textAlign == facebook::react::TextAlignment::Right) {
1390
+ m_pf.wAlignment = PFA_RIGHT;
1391
+ } else {
1392
+ m_pf.wAlignment = PFA_LEFT;
1393
+ }
1309
1394
 
1310
1395
  m_pf.cTabCount = 1;
1311
1396
  m_pf.rgxTabs[0] = lDefaultTab;
@@ -1475,6 +1560,9 @@ WindowsTextInputComponentView::createVisual() noexcept {
1475
1560
  winrt::check_hresult(g_pfnCreateTextServices(nullptr, m_textHost.get(), spUnk.put()));
1476
1561
  spUnk.as(m_textServices);
1477
1562
 
1563
+ LRESULT res;
1564
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETTEXTMODE, TM_PLAINTEXT, 0, &res));
1565
+
1478
1566
  m_caretVisual = m_compContext.CreateCaretVisual();
1479
1567
  visual.InsertAt(m_caretVisual.InnerVisual(), 0);
1480
1568
  m_caretVisual.IsVisible(false);
@@ -1525,4 +1613,39 @@ void WindowsTextInputComponentView::autoCapitalizeOnUpdateProps(
1525
1613
  }
1526
1614
  }
1527
1615
 
1616
+ void WindowsTextInputComponentView::updateLetterSpacing(float letterSpacing) noexcept {
1617
+ CHARFORMAT2W cf = {};
1618
+ cf.cbSize = sizeof(CHARFORMAT2W);
1619
+ cf.dwMask = CFM_SPACING;
1620
+ cf.sSpacing = static_cast<SHORT>(letterSpacing * 20); // Convert to TWIPS
1621
+
1622
+ LRESULT res;
1623
+
1624
+ // Apply to all existing text like placeholder
1625
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast<LPARAM>(&cf), &res));
1626
+
1627
+ // Apply to future text input
1628
+ winrt::check_hresult(
1629
+ m_textServices->TxSendMessage(EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(&cf), &res));
1630
+ }
1631
+
1632
+ void WindowsTextInputComponentView::updateAutoCorrect(bool enable) noexcept {
1633
+ LRESULT lresult;
1634
+ winrt::check_hresult(m_textServices->TxSendMessage(
1635
+ EM_SETAUTOCORRECTPROC, enable ? 0 : reinterpret_cast<WPARAM>(AutoCorrectOffCallback), 0, &lresult));
1636
+ }
1637
+
1638
+ void WindowsTextInputComponentView::updateSpellCheck(bool enable) noexcept {
1639
+ LRESULT currentLangOptions;
1640
+ winrt::check_hresult(m_textServices->TxSendMessage(EM_GETLANGOPTIONS, 0, 0, &currentLangOptions));
1641
+
1642
+ DWORD newLangOptions = static_cast<DWORD>(currentLangOptions);
1643
+ if (enable) {
1644
+ newLangOptions |= IMF_SPELLCHECKING;
1645
+ }
1646
+
1647
+ LRESULT lresult;
1648
+ winrt::check_hresult(
1649
+ m_textServices->TxSendMessage(EM_SETLANGOPTIONS, IMF_SPELLCHECKING, enable ? newLangOptions : 0, &lresult));
1650
+ }
1528
1651
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation
@@ -70,6 +70,7 @@ struct WindowsTextInputComponentView
70
70
  std::optional<std::string> getAccessiblityValue() noexcept override;
71
71
  void setAcccessiblityValue(std::string &&value) noexcept override;
72
72
  bool getAcccessiblityIsReadOnly() noexcept override;
73
+ bool IsDoubleClick();
73
74
 
74
75
  WindowsTextInputComponentView(
75
76
  const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
@@ -110,6 +111,10 @@ struct WindowsTextInputComponentView
110
111
  const std::string &previousCapitalizationType,
111
112
  const std::string &newcapitalizationType) noexcept;
112
113
 
114
+ void updateLetterSpacing(float letterSpacing) noexcept;
115
+ void updateAutoCorrect(bool value) noexcept;
116
+ void updateSpellCheck(bool value) noexcept;
117
+
113
118
  winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
114
119
  winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr};
115
120
  winrt::Microsoft::ReactNative::Composition::Experimental::IDrawingSurfaceBrush m_drawingSurface{nullptr};
@@ -136,6 +141,7 @@ struct WindowsTextInputComponentView
136
141
  DWORD m_propBitsMask{0};
137
142
  DWORD m_propBits{0};
138
143
  HCURSOR m_hcursor{nullptr};
144
+ std::chrono::steady_clock::time_point m_lastClickTime{};
139
145
  std::vector<facebook::react::CompWindowsTextInputSubmitKeyEventsStruct> m_submitKeyEvents;
140
146
  };
141
147
 
@@ -5,6 +5,8 @@
5
5
 
6
6
  #include "WindowsTextInputEventEmitter.h"
7
7
 
8
+ #include <react/renderer/core/graphicsConversions.h>
9
+
8
10
  namespace facebook::react {
9
11
 
10
12
  void WindowsTextInputEventEmitter::onChange(OnChange event) const {
@@ -46,4 +48,33 @@ void WindowsTextInputEventEmitter::onKeyPress(OnKeyPress event) const {
46
48
  });
47
49
  }
48
50
 
51
+ static jsi::Value textInputMetricsContentSizePayload(
52
+ jsi::Runtime &runtime,
53
+ const WindowsTextInputEventEmitter::OnContentSizeChange &event) {
54
+ auto payload = jsi::Object(runtime);
55
+ {
56
+ auto contentSize = jsi::Object(runtime);
57
+ contentSize.setProperty(runtime, "width", event.contentSize.width);
58
+ contentSize.setProperty(runtime, "height", event.contentSize.height);
59
+ payload.setProperty(runtime, "contentSize", contentSize);
60
+ }
61
+ return payload;
62
+ };
63
+
64
+ void WindowsTextInputEventEmitter::onContentSizeChange(OnContentSizeChange event) const {
65
+ dispatchEvent("textInputContentSizeChange", [event = std::move(event)](jsi::Runtime &runtime) {
66
+ return textInputMetricsContentSizePayload(runtime, event);
67
+ });
68
+ }
69
+
70
+ void WindowsTextInputEventEmitter::onEndEditing(OnEndEditing event) const {
71
+ dispatchEvent("textInputEndEditing", [event = std::move(event)](jsi::Runtime &runtime) {
72
+ auto payload = jsi::Object(runtime);
73
+ payload.setProperty(runtime, "eventCount", event.eventCount);
74
+ payload.setProperty(runtime, "target", event.target);
75
+ payload.setProperty(runtime, "text", event.text);
76
+ return payload;
77
+ });
78
+ }
79
+
49
80
  } // namespace facebook::react
@@ -36,10 +36,23 @@ class WindowsTextInputEventEmitter : public ViewEventEmitter {
36
36
  std::string key;
37
37
  };
38
38
 
39
+ struct OnContentSizeChange {
40
+ int target;
41
+ facebook::react::Size contentSize;
42
+ };
43
+
44
+ struct OnEndEditing {
45
+ int eventCount;
46
+ int target;
47
+ std::string text;
48
+ };
49
+
39
50
  void onChange(OnChange value) const;
40
51
  void onSelectionChange(const OnSelectionChange &value) const;
41
52
  void onSubmitEditing(OnSubmitEditing value) const;
42
53
  void onKeyPress(OnKeyPress value) const;
54
+ void onContentSizeChange(OnContentSizeChange value) const;
55
+ void onEndEditing(OnEndEditing value) const;
43
56
  };
44
57
 
45
- } // namespace facebook::react
58
+ } // namespace facebook::react