react-native-windows 0.81.21 → 0.81.24

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.
@@ -19,13 +19,23 @@ CallInvoker::CallInvoker(
19
19
  : m_callInvoker(callInvoker), m_context(&reactContext) {}
20
20
 
21
21
  void CallInvoker::InvokeAsync(CallFunc func) noexcept {
22
- m_callInvoker->invokeAsync(
23
- [reactContext = m_context, func](facebook::jsi::Runtime & /*runtime*/) { func(reactContext->JsiRuntime()); });
22
+ m_callInvoker->invokeAsync([reactContext = m_context, func](facebook::jsi::Runtime & /*runtime*/) {
23
+ auto runtime = reactContext->JsiRuntime();
24
+ if (!runtime)
25
+ return;
26
+
27
+ func(runtime);
28
+ });
24
29
  }
25
30
 
26
31
  void CallInvoker::InvokeSync(CallFunc func) noexcept {
27
- m_callInvoker->invokeSync(
28
- [reactContext = m_context, func](facebook::jsi::Runtime & /*runtime*/) { func(reactContext->JsiRuntime()); });
32
+ m_callInvoker->invokeSync([reactContext = m_context, func](facebook::jsi::Runtime & /*runtime*/) {
33
+ auto runtime = reactContext->JsiRuntime();
34
+ if (!runtime)
35
+ return;
36
+
37
+ func(runtime);
38
+ });
29
39
  }
30
40
 
31
41
  winrt::Microsoft::ReactNative::CallInvoker CallInvoker::FromProperties(
@@ -227,6 +227,28 @@ void CompositionEventHandler::Initialize() noexcept {
227
227
  }
228
228
  });
229
229
 
230
+ // Issue #16047: when ScrollView calls VisualInteractionSource::TryRedirectForManipulation
231
+ // and the OS hands the pointer over to the InteractionTracker, WinAppSDK
232
+ // does not fire PointerCaptureLost on this source — but it does fire
233
+ // PointerRoutedAway. Treat it the same way as captureloss: cancel any
234
+ // active touch RN is tracking for this pointer so Pressables don't get
235
+ // stuck in their pressed state.
236
+ m_pointerRoutedAwayToken =
237
+ pointerSource.PointerRoutedAway([wkThis = weak_from_this()](
238
+ winrt::Microsoft::UI::Input::InputPointerSource const &,
239
+ winrt::Microsoft::UI::Input::PointerEventArgs const &args) {
240
+ if (auto strongThis = wkThis.lock()) {
241
+ if (auto strongRootView = strongThis->m_wkRootView.get()) {
242
+ if (strongThis->SurfaceId() == -1)
243
+ return;
244
+
245
+ auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
246
+ args.CurrentPoint(), strongRootView.ScaleFactor());
247
+ strongThis->onPointerRoutedAway(pp, args.KeyModifiers());
248
+ }
249
+ }
250
+ });
251
+
230
252
  m_pointerWheelChangedToken =
231
253
  pointerSource.PointerWheelChanged([wkThis = weak_from_this()](
232
254
  winrt::Microsoft::UI::Input::InputPointerSource const &,
@@ -374,6 +396,7 @@ CompositionEventHandler::~CompositionEventHandler() {
374
396
  pointerSource.PointerReleased(m_pointerReleasedToken);
375
397
  pointerSource.PointerMoved(m_pointerMovedToken);
376
398
  pointerSource.PointerCaptureLost(m_pointerCaptureLostToken);
399
+ pointerSource.PointerRoutedAway(m_pointerRoutedAwayToken);
377
400
  pointerSource.PointerWheelChanged(m_pointerWheelChangedToken);
378
401
  pointerSource.PointerExited(m_pointerExitedToken);
379
402
  auto keyboardSource = winrt::Microsoft::UI::Input::InputKeyboardSource::GetForIsland(island);
@@ -1123,24 +1146,49 @@ void CompositionEventHandler::onPointerCaptureLost(
1123
1146
  m_pointerCapturingComponentTag = -1;
1124
1147
  }
1125
1148
 
1126
- // Also cancel any active touch for the specific pointer that lost capture, even
1127
- // when no JS-level CapturePointer was ever issued. This handles ScrollView (and
1128
- // any other VisualInteractionSource) calling TryRedirectForManipulation: the OS
1129
- // reassigns the pointer to the InteractionTracker, fires PointerCaptureLost, and
1130
- // then stops delivering PointerMoved/PointerReleased to us. Without this cleanup
1131
- // m_activeTouches keeps a zombie entry whose target is the originally-pressed
1132
- // Pressable, leaving it visually pressed and causing later taps to be attributed
1133
- // to that original target. If the entry was already cleared above (for a JS-level
1134
- // capture) or by onPointerReleased running first, the find() is a no-op.
1135
- PointerId pointerId = pointerPoint.PointerId();
1149
+ // Defense-in-depth cleanup for the specific pointer that lost capture, even
1150
+ // when no JS-level CapturePointer was ever issued. The ScrollView
1151
+ // TryRedirectForManipulation path comes in via PointerRoutedAway, not
1152
+ // PointerCaptureLost (see onPointerRoutedAway and issue #16047), so this
1153
+ // path covers the remaining system-driven losses (focus change, another
1154
+ // window stealing input, system back gesture, etc.).
1155
+ CancelActiveTouchForPointerInternal(pointerPoint.PointerId(), pointerPoint, keyModifiers);
1156
+ }
1157
+
1158
+ void CompositionEventHandler::onPointerRoutedAway(
1159
+ const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
1160
+ winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept {
1161
+ if (SurfaceId() == -1)
1162
+ return;
1163
+
1164
+ // Issue #16047: WinAppSDK fires PointerRoutedAway when the OS hands the
1165
+ // pointer to another InputPointerSource — most importantly for us, when
1166
+ // ScrollView calls VisualInteractionSource::TryRedirectForManipulation and
1167
+ // the InteractionTracker takes the gesture for scrolling. We never get
1168
+ // PointerMoved / PointerReleased / PointerCaptureLost for that pointer
1169
+ // afterwards, so without this cleanup m_activeTouches keeps a zombie entry
1170
+ // and the originally-pressed Pressable stays stuck in its pressed state.
1171
+ CancelActiveTouchForPointerInternal(pointerPoint.PointerId(), pointerPoint, keyModifiers);
1172
+ }
1173
+
1174
+ bool CompositionEventHandler::CancelActiveTouchForPointerInternal(
1175
+ PointerId pointerId,
1176
+ const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
1177
+ winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept {
1136
1178
  auto activeTouch = m_activeTouches.find(pointerId);
1137
- if (activeTouch != m_activeTouches.end()) {
1138
- ActiveTouch cancelledTouchCopy = std::move(activeTouch->second);
1139
- m_activeTouches.erase(activeTouch);
1140
- if (cancelledTouchCopy.eventEmitter) {
1141
- DispatchSynthesizedTouchCancelForActiveTouch(cancelledTouchCopy, pointerPoint, keyModifiers);
1142
- }
1179
+ if (activeTouch == m_activeTouches.end()) {
1180
+ return false;
1181
+ }
1182
+
1183
+ ActiveTouch cancelledTouchCopy = std::move(activeTouch->second);
1184
+ m_activeTouches.erase(activeTouch);
1185
+
1186
+ if (!cancelledTouchCopy.eventEmitter) {
1187
+ return false;
1143
1188
  }
1189
+
1190
+ DispatchSynthesizedTouchCancelForActiveTouch(cancelledTouchCopy, pointerPoint, keyModifiers);
1191
+ return true;
1144
1192
  }
1145
1193
 
1146
1194
  void CompositionEventHandler::onPointerMoved(
@@ -66,6 +66,9 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
66
66
  void onPointerCaptureLost(
67
67
  const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
68
68
  winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
69
+ void onPointerRoutedAway(
70
+ const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
71
+ winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
69
72
  void onKeyDown(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept;
70
73
  void onKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept;
71
74
  void onCharacterReceived(
@@ -156,6 +159,13 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
156
159
  const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
157
160
  winrt::Windows::System::VirtualKeyModifiers keyModifiers);
158
161
 
162
+ // Look up the active touch for pointerId, erase it, and dispatch cancel events.
163
+ // Returns true iff a touch was found and cancel events were dispatched.
164
+ bool CancelActiveTouchForPointerInternal(
165
+ PointerId pointerId,
166
+ const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
167
+ winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
168
+
159
169
  std::vector<winrt::Microsoft::ReactNative::ComponentView> GetTouchableViewsInPathToRoot(
160
170
  const winrt::Microsoft::ReactNative::ComponentView &componentView);
161
171
 
@@ -187,6 +197,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
187
197
  winrt::event_token m_pointerMovedToken;
188
198
  winrt::event_token m_pointerWheelChangedToken;
189
199
  winrt::event_token m_pointerCaptureLostToken;
200
+ winrt::event_token m_pointerRoutedAwayToken;
190
201
  winrt::event_token m_pointerExitedToken;
191
202
  winrt::event_token m_keyDownToken;
192
203
  winrt::event_token m_keyUpToken;
@@ -149,11 +149,13 @@ facebook::react::SharedViewEventEmitter ParagraphComponentView::eventEmitterAtPo
149
149
  uint32_t textPosition = metrics.textPosition;
150
150
 
151
151
  for (auto fragment : m_attributedStringBox.getValue().getFragments()) {
152
- if (textPosition < fragment.string.length()) {
152
+ uint32_t utf16Length =
153
+ static_cast<uint32_t>(::Microsoft::Common::Unicode::Utf8ToUtf16(fragment.string).length());
154
+ if (textPosition < utf16Length) {
153
155
  return std::static_pointer_cast<const facebook::react::ViewEventEmitter>(
154
156
  fragment.parentShadowView.eventEmitter);
155
157
  }
156
- textPosition -= static_cast<uint32_t>(fragment.string.length());
158
+ textPosition -= utf16Length;
157
159
  }
158
160
  }
159
161
  }
@@ -206,10 +208,12 @@ bool ParagraphComponentView::IsTextSelectableAtPoint(facebook::react::Point pt)
206
208
 
207
209
  // Finds which fragment contains this text position
208
210
  for (auto fragment : m_attributedStringBox.getValue().getFragments()) {
209
- if (textPosition < fragment.string.length()) {
211
+ uint32_t utf16Length =
212
+ static_cast<uint32_t>(::Microsoft::Common::Unicode::Utf8ToUtf16(fragment.string).length());
213
+ if (textPosition < utf16Length) {
210
214
  return true;
211
215
  }
212
- textPosition -= static_cast<uint32_t>(fragment.string.length());
216
+ textPosition -= utf16Length;
213
217
  }
214
218
  }
215
219
  }
@@ -23,6 +23,7 @@
23
23
  #include <functional>
24
24
  #include "ContentIslandComponentView.h"
25
25
  #include "JSValueReader.h"
26
+ #include "ReactNativeIsland.h"
26
27
  #include "RootComponentView.h"
27
28
 
28
29
  namespace winrt::Microsoft::ReactNative::Composition::implementation {
@@ -849,13 +850,27 @@ void ScrollViewComponentView::updateStateWithContentOffset() noexcept {
849
850
  return;
850
851
  }
851
852
 
852
- auto scrollPosition = m_scrollVisual.ScrollPosition();
853
- m_verticalScrollbarComponent->ContentOffset(scrollPosition);
854
- m_horizontalScrollbarComponent->ContentOffset(scrollPosition);
855
-
856
- m_state->updateState([scrollPosition](const facebook::react::ScrollViewShadowNode::ConcreteState::Data &data) {
853
+ // Issue #16047: m_scrollVisual.ScrollPosition() returns the InteractionTracker
854
+ // position in PHYSICAL pixels (the visual is sized as
855
+ // layoutMetrics.frame.size.* * pointScaleFactor — see updateLayoutMetrics /
856
+ // updateContentVisualSize) but ScrollViewShadowNode state's contentOffset is
857
+ // in DIPs. Without the conversion, JS UIManager.measure() over-subtracts by
858
+ // pointScaleFactor on non-100% display scales, leaving Pressables inside a
859
+ // scrolled ScrollView with stale page-space bounds that don't contain the
860
+ // touch — Pressability fires LEAVE_PRESS_RECT inside pressIn and suppresses
861
+ // press. The JS-event-emitter paths in this file (see lines using
862
+ // args.Position() / pointScaleFactor) already do this division.
863
+ auto rawScrollPosition = m_scrollVisual.ScrollPosition();
864
+ const float pointScaleFactor = m_layoutMetrics.pointScaleFactor > 0.0f ? m_layoutMetrics.pointScaleFactor : 1.0f;
865
+ facebook::react::Point contentOffsetDips{
866
+ rawScrollPosition.x / pointScaleFactor, rawScrollPosition.y / pointScaleFactor};
867
+
868
+ m_verticalScrollbarComponent->ContentOffset(rawScrollPosition);
869
+ m_horizontalScrollbarComponent->ContentOffset(rawScrollPosition);
870
+
871
+ m_state->updateState([contentOffsetDips](const facebook::react::ScrollViewShadowNode::ConcreteState::Data &data) {
857
872
  auto newData = data;
858
- newData.contentOffset = {scrollPosition.x, scrollPosition.y};
873
+ newData.contentOffset = contentOffsetDips;
859
874
  return std::make_shared<facebook::react::ScrollViewShadowNode::ConcreteState::Data const>(newData);
860
875
  });
861
876
  }
@@ -1389,6 +1404,13 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
1389
1404
  [this](
1390
1405
  winrt::IInspectable const & /*sender*/,
1391
1406
  winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
1407
+ // Issue #16047: push the FINAL settled scroll position into Fabric's
1408
+ // shadow tree before notifying JS. The per-frame ScrollPositionChanged
1409
+ // updates can drop the last inertia delta, leaving contentOffset stale
1410
+ // and JS UIManager.measure() returning pre-settle-relative bounds.
1411
+ // ScrollEndDrag / ScrollBeginDrag already call this; momentum-end was
1412
+ // the missing completion path.
1413
+ updateStateWithContentOffset();
1392
1414
  auto eventEmitter = GetEventEmitter();
1393
1415
  if (eventEmitter) {
1394
1416
  auto scrollMetrics = getScrollMetrics(eventEmitter, args);
@@ -66,7 +66,7 @@ void RenderText(
66
66
  unsigned int position = 0;
67
67
  unsigned int length = 0;
68
68
  for (auto fragment : attributedString.getFragments()) {
69
- length = static_cast<UINT32>(fragment.string.length());
69
+ length = static_cast<UINT32>(::Microsoft::Common::Unicode::Utf8ToUtf16(fragment.string).length());
70
70
  DWRITE_TEXT_RANGE range = {position, length};
71
71
  if (fragment.textAttributes.foregroundColor &&
72
72
  (fragment.textAttributes.foregroundColor != textAttributes.foregroundColor) ||
@@ -262,7 +262,7 @@ void WindowsTextLayoutManager::GetTextLayout(
262
262
  attachments.push_back(attachment);
263
263
  position += 1;
264
264
  } else {
265
- unsigned int length = static_cast<UINT32>(fragment.string.length());
265
+ unsigned int length = static_cast<UINT32>(Microsoft::Common::Unicode::Utf8ToUtf16(fragment.string).length());
266
266
  DWRITE_TEXT_RANGE range = {position, length};
267
267
  TextAttributes attributes = fragment.textAttributes;
268
268
  DWRITE_FONT_STYLE fragmentStyle = DWRITE_FONT_STYLE_NORMAL;
@@ -10,11 +10,11 @@
10
10
  -->
11
11
  <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
12
12
  <PropertyGroup>
13
- <ReactNativeWindowsVersion>0.81.21</ReactNativeWindowsVersion>
13
+ <ReactNativeWindowsVersion>0.81.24</ReactNativeWindowsVersion>
14
14
  <ReactNativeWindowsMajor>0</ReactNativeWindowsMajor>
15
15
  <ReactNativeWindowsMinor>81</ReactNativeWindowsMinor>
16
- <ReactNativeWindowsPatch>21</ReactNativeWindowsPatch>
16
+ <ReactNativeWindowsPatch>24</ReactNativeWindowsPatch>
17
17
  <ReactNativeWindowsCanary>false</ReactNativeWindowsCanary>
18
- <ReactNativeWindowsCommitId>8620c45845172a7428e8d0e27037515b6382e5dd</ReactNativeWindowsCommitId>
18
+ <ReactNativeWindowsCommitId>4e9017a484c3cba57081dfef12c4d96edae2ebe4</ReactNativeWindowsCommitId>
19
19
  </PropertyGroup>
20
20
  </Project>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-windows",
3
- "version": "0.81.21",
3
+ "version": "0.81.24",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,6 +15,7 @@
15
15
  <ApplicationType>Windows Store</ApplicationType>
16
16
  <ApplicationTypeRevision>10.0</ApplicationTypeRevision>
17
17
  </PropertyGroup>
18
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
18
19
  <PropertyGroup Label="ReactNativeWindowsProps">
19
20
  <ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
20
21
  </PropertyGroup>
@@ -23,7 +24,6 @@
23
24
  <WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.22621.0</WindowsTargetPlatformVersion>
24
25
  <WindowsTargetPlatformMinVersion Condition=" '$(WindowsTargetPlatformMinVersion)' == '' ">10.0.17763.0</WindowsTargetPlatformMinVersion>
25
26
  </PropertyGroup>
26
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
27
27
  <ItemGroup Label="ProjectConfigurations">
28
28
  <ProjectConfiguration Include="Debug|ARM64">
29
29
  <Configuration>Debug</Configuration>
@@ -40,18 +40,21 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE, PSTR
40
40
  // Register any native modules defined within this app project
41
41
  settings.PackageProviders().Append(winrt::make<CompReactPackageProvider>());
42
42
 
43
- #if BUNDLE
44
- // Load the JS bundle from a file (not Metro):
43
+ // When loading the JS bundle from a file (not Metro):
45
44
  // Set the path (on disk) where the .bundle file is located
46
45
  settings.BundleRootPath(std::wstring(L"file://").append(appDirectory).append(L"\\Bundle\\").c_str());
46
+
47
47
  // Set the name of the bundle file (without the .bundle extension)
48
48
  settings.JavaScriptBundleFile(L"index.windows");
49
- // Disable hot reload
49
+
50
+ // JS Entry file to use when loading from Metro:
51
+ settings.DebugBundlePath(L"index");
52
+
53
+ #if BUNDLE
54
+ // Disable hot reload - bundle will be loaded from prebuilt bundle file.
50
55
  settings.UseFastRefresh(false);
51
56
  #else
52
- // Load the JS bundle from Metro
53
- settings.JavaScriptBundleFile(L"index");
54
- // Enable hot reload
57
+ // Enable hot reload - load the JS bundle from Metro
55
58
  settings.UseFastRefresh(true);
56
59
  #endif
57
60
  #if _DEBUG
@@ -16,11 +16,11 @@
16
16
  <MinimumVisualStudioVersion>17.0</MinimumVisualStudioVersion>
17
17
  <AppxPackage>false</AppxPackage>
18
18
  </PropertyGroup>
19
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
19
20
  <PropertyGroup Label="ReactNativeWindowsProps">
20
21
  <ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
21
22
  </PropertyGroup>
22
23
  <Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.WindowsSdk.Default.props" />
23
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
24
24
  <ItemGroup Label="ProjectConfigurations">
25
25
  <ProjectConfiguration Include="Debug|Win32">
26
26
  <Configuration>Debug</Configuration>
@@ -14,12 +14,12 @@
14
14
  <MinimumVisualStudioVersion>17.0</MinimumVisualStudioVersion>
15
15
  <AppxPackage>false</AppxPackage>
16
16
  </PropertyGroup>
17
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
17
18
  <PropertyGroup Label="ReactNativeWindowsProps">
18
19
  <ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
19
20
  <RunAutolinkCheck>false</RunAutolinkCheck>
20
21
  </PropertyGroup>
21
22
  <Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.WindowsSdk.Default.props" />
22
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
23
23
  <ItemGroup Label="ProjectConfigurations">
24
24
  <ProjectConfiguration Include="Debug|Win32">
25
25
  <Configuration>Debug</Configuration>