react-native-windows 0.81.18 → 0.81.20

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.
@@ -117,6 +117,10 @@ export interface ModalWindowsProps {
117
117
  /* title for the modal, shown in the title bar */
118
118
  // [Windows
119
119
  title?: string | undefined;
120
+
121
+ hideTitleBar?: boolean | undefined;
122
+
123
+ hideBorder?: boolean | undefined;
120
124
  // Windows]
121
125
  }
122
126
 
@@ -174,6 +174,8 @@ export type ModalPropsWindows = {
174
174
  * [Windows] The `title` prop sets the title of the modal window.
175
175
  */
176
176
  title?: ?string,
177
+ hideTitleBar?: ?boolean,
178
+ hideBorder?: ?boolean,
177
179
  };
178
180
 
179
181
  export type ModalProps = {
@@ -352,6 +354,8 @@ class Modal extends React.Component<ModalProps, ModalState> {
352
354
  onOrientationChange={this.props.onOrientationChange}
353
355
  allowSwipeDismissal={this.props.allowSwipeDismissal}
354
356
  testID={this.props.testID}
357
+ hideTitleBar={this.props.hideTitleBar} // [Windows]
358
+ hideBorder={this.props.hideBorder} // [Windows]
355
359
  title={this.props.title}>
356
360
  <VirtualizedListContextResetter>
357
361
  <ScrollView.Context.Provider value={null}>
@@ -1064,6 +1064,11 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::get_SelectionContainer(I
1064
1064
  *pRetVal = nullptr;
1065
1065
 
1066
1066
  auto selectionContainerView = GetSelectionContainer();
1067
+ // Per UIA spec, returning S_OK with *pRetVal == nullptr is correct when the element
1068
+ // is not contained within a selection container.
1069
+ if (!selectionContainerView)
1070
+ return S_OK;
1071
+
1067
1072
  auto uiaProvider =
1068
1073
  winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(selectionContainerView)
1069
1074
  ->EnsureUiaProvider();
@@ -1122,6 +1122,25 @@ void CompositionEventHandler::onPointerCaptureLost(
1122
1122
 
1123
1123
  m_pointerCapturingComponentTag = -1;
1124
1124
  }
1125
+
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();
1136
+ 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
+ }
1143
+ }
1125
1144
  }
1126
1145
 
1127
1146
  void CompositionEventHandler::onPointerMoved(
@@ -1372,7 +1391,7 @@ void CompositionEventHandler::onPointerPressed(
1372
1391
 
1373
1392
  UpdateActiveTouch(activeTouch, ptScaled, ptLocal);
1374
1393
 
1375
- activeTouch.isPrimary = pointerId == 1;
1394
+ activeTouch.isPrimary = pointerPoint.Properties().IsPrimary();
1376
1395
  // Map the Windows pointer ID to a small identifier (0–19) safe for use as a JS array index.
1377
1396
  // Windows touch IDs can be arbitrarily large (e.g. 2233), which causes React Native to warn
1378
1397
  // and corrupts touch state, leaving Pressables stuck after a scroll.
@@ -1620,16 +1639,6 @@ bool CompositionEventHandler::IsPointerWithinInitialTree(const ActiveTouch &acti
1620
1639
  currentView = currentView.Parent();
1621
1640
  }
1622
1641
 
1623
- // Fallback: if the pointer drifted spatially but the original target
1624
- // is still structurally within the initial tree, honor the tap.
1625
- // This provides touch-device tolerance for finger drift.
1626
- auto targetView = viewRegistry.componentViewDescriptorWithTag(activeTouch.touch.target).view;
1627
- while (targetView) {
1628
- if (targetView.Tag() == initialTag)
1629
- return true;
1630
- targetView = targetView.Parent();
1631
- }
1632
-
1633
1642
  return false;
1634
1643
  }
1635
1644
 
@@ -1691,7 +1700,15 @@ void CompositionEventHandler::DispatchTouchEvent(
1691
1700
 
1692
1701
  facebook::react::TouchEvent event;
1693
1702
 
1694
- size_t index = 0;
1703
+ // First pass: build changedTouches and the set of unique emitters from every active
1704
+ // touch. The per-pointer PointerEvent dispatch (onPointerDown/Move/Up/Cancel/Click) is
1705
+ // fired only for the touch whose state actually changed — non-changed touches contribute
1706
+ // to the W3C TouchEvent's touches/targetTouches sets in the loops below but must not
1707
+ // re-fire pointer events of their own. Previously we dispatched the per-pointer event
1708
+ // for every entry in m_activeTouches, which produced duplicated onPointerMove on
1709
+ // non-moving fingers and replayed onPointerUp/onClick on stale targets after the OS
1710
+ // reclaimed a pointer (e.g. ScrollView manipulation redirect leaving a zombie touch).
1711
+ const ActiveTouch *changedTouch = nullptr;
1695
1712
  for (const auto &pair : m_activeTouches) {
1696
1713
  const auto &activeTouch = pair.second;
1697
1714
 
@@ -1700,14 +1717,17 @@ void CompositionEventHandler::DispatchTouchEvent(
1700
1717
  }
1701
1718
 
1702
1719
  if (pair.first == pointerId) {
1720
+ changedTouch = &activeTouch;
1703
1721
  event.changedTouches.insert(activeTouch.touch);
1704
1722
  }
1705
1723
  uniqueEventEmitters.insert(activeTouch.eventEmitter);
1724
+ }
1706
1725
 
1707
- facebook::react::PointerEvent pointerEvent = CreatePointerEventFromActiveTouch(activeTouch, eventType);
1726
+ if (changedTouch) {
1727
+ facebook::react::PointerEvent pointerEvent = CreatePointerEventFromActiveTouch(*changedTouch, eventType);
1708
1728
 
1709
1729
  winrt::Microsoft::ReactNative::ComponentView targetView{nullptr};
1710
- bool shouldLeave = (eventType == TouchEventType::End && activeTouch.shouldLeaveWhenReleased) ||
1730
+ bool shouldLeave = (eventType == TouchEventType::End && changedTouch->shouldLeaveWhenReleased) ||
1711
1731
  eventType == TouchEventType::Cancel;
1712
1732
  if (!shouldLeave) {
1713
1733
  auto *rootViewForHit = RootComponentView();
@@ -1722,29 +1742,29 @@ void CompositionEventHandler::DispatchTouchEvent(
1722
1742
  }
1723
1743
  }
1724
1744
 
1725
- auto handler = [this, &activeTouch, eventType, &pointerEvent](
1745
+ auto handler = [this, changedTouch, eventType, &pointerEvent](
1726
1746
  std::vector<winrt::Microsoft::ReactNative::ComponentView> &eventPathViews) {
1727
1747
  switch (eventType) {
1728
1748
  case TouchEventType::Start:
1729
- activeTouch.eventEmitter->onPointerDown(pointerEvent);
1749
+ changedTouch->eventEmitter->onPointerDown(pointerEvent);
1730
1750
  break;
1731
1751
  case TouchEventType::Move: {
1732
- activeTouch.eventEmitter->onPointerMove(pointerEvent);
1752
+ changedTouch->eventEmitter->onPointerMove(pointerEvent);
1733
1753
  break;
1734
1754
  }
1735
1755
  case TouchEventType::End:
1736
- activeTouch.eventEmitter->onPointerUp(pointerEvent);
1756
+ changedTouch->eventEmitter->onPointerUp(pointerEvent);
1737
1757
  if (pointerEvent.isPrimary && pointerEvent.button == 0) {
1738
- if (IsPointerWithinInitialTree(activeTouch)) {
1739
- activeTouch.eventEmitter->onClick(pointerEvent);
1758
+ if (IsPointerWithinInitialTree(*changedTouch)) {
1759
+ changedTouch->eventEmitter->onClick(pointerEvent);
1740
1760
  }
1741
- } else if (IsPointerWithinInitialTree(activeTouch)) {
1742
- activeTouch.eventEmitter->onAuxClick(pointerEvent);
1761
+ } else if (IsPointerWithinInitialTree(*changedTouch)) {
1762
+ changedTouch->eventEmitter->onAuxClick(pointerEvent);
1743
1763
  }
1744
1764
  break;
1745
1765
  case TouchEventType::Cancel:
1746
1766
  case TouchEventType::CaptureLost:
1747
- activeTouch.eventEmitter->onPointerCancel(pointerEvent);
1767
+ changedTouch->eventEmitter->onPointerCancel(pointerEvent);
1748
1768
  break;
1749
1769
  }
1750
1770
  };
@@ -131,6 +131,7 @@ void ImageComponentView::didReceiveImage(const std::shared_ptr<ImageResponseImag
131
131
  #endif
132
132
 
133
133
  m_imageResponseImage = imageResponseImage;
134
+ m_requiresImageRedraw = true;
134
135
  ensureDrawingSurface();
135
136
  }
136
137
 
@@ -316,6 +317,9 @@ void ImageComponentView::ensureDrawingSurface() noexcept {
316
317
  } else if (m_imageResponseImage->m_brushFactory) {
317
318
  Visual().as<Experimental::ISpriteVisual>().Brush(
318
319
  m_imageResponseImage->m_brushFactory(m_reactContext.Handle(), m_compContext));
320
+ } else if (m_requiresImageRedraw) {
321
+ m_requiresImageRedraw = false;
322
+ DrawImage();
319
323
  }
320
324
  }
321
325
 
@@ -99,6 +99,7 @@ struct ImageComponentView : ImageComponentViewT<ImageComponentView, ViewComponen
99
99
  winrt::Microsoft::ReactNative::Composition::Experimental::IDrawingSurfaceBrush m_drawingSurface;
100
100
  std::shared_ptr<ImageResponseImage> m_imageResponseImage;
101
101
  std::shared_ptr<WindowsImageResponseObserver> m_imageResponseObserver;
102
+ bool m_requiresImageRedraw{true};
102
103
  facebook::react::ImageShadowNode::ConcreteState::Shared m_state;
103
104
  };
104
105
 
@@ -7,6 +7,7 @@
7
7
 
8
8
  #include "../../../codegen/react/components/rnwcore/ModalHostView.g.h"
9
9
  #include <ComponentView.Experimental.interop.h>
10
+ #include <dwmapi.h>
10
11
  #include <winrt/Microsoft.UI.Content.h>
11
12
  #include <winrt/Microsoft.UI.Input.h>
12
13
  #include <winrt/Microsoft.UI.Windowing.h>
@@ -112,6 +113,12 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
112
113
  m_rnWindow.AppWindow().Title(titleValue);
113
114
  }
114
115
 
116
+ if (m_rnWindow &&
117
+ (newViewProps.hideTitleBar != oldViewProps.hideTitleBar ||
118
+ newViewProps.hideBorder != oldViewProps.hideBorder)) {
119
+ UpdateTitleBarAndBorder();
120
+ }
121
+
115
122
  ::Microsoft::ReactNativeSpecs::BaseModalHostView<ModalHostView>::UpdateProps(view, newProps, oldProps);
116
123
  }
117
124
 
@@ -258,6 +265,61 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
258
265
  }
259
266
  }
260
267
 
268
+ void UpdateTitleBarAndBorder() noexcept {
269
+ if (!m_rnWindow) {
270
+ return;
271
+ }
272
+
273
+ auto overlappedPresenter = winrt::Microsoft::UI::Windowing::OverlappedPresenter::Create();
274
+
275
+ // Configure presenter for modal behavior
276
+ overlappedPresenter.IsModal(true);
277
+
278
+ // modal should only have close button
279
+ overlappedPresenter.IsMinimizable(false);
280
+ overlappedPresenter.IsMaximizable(false);
281
+
282
+ // Apply the presenter to the window
283
+ m_rnWindow.AppWindow().SetPresenter(overlappedPresenter);
284
+
285
+ // We only hide the borders if the titlebar is also hidden.
286
+ const bool hideBorders = m_localProps->hideTitleBar.value_or(false) && m_localProps->hideBorder.value_or(false);
287
+
288
+ auto titleBar = m_rnWindow.AppWindow().TitleBar();
289
+ titleBar.ResetToDefault();
290
+ overlappedPresenter.IsResizable(false);
291
+
292
+ overlappedPresenter.SetBorderAndTitleBar(!hideBorders, !m_localProps->hideTitleBar.value_or(false));
293
+
294
+ auto hwnd = GetWindowFromWindowId(m_rnWindow.AppWindow().Id());
295
+
296
+ if (hideBorders) {
297
+ const DWMNCRENDERINGPOLICY ncPolicy = DWMNCRP_DISABLED;
298
+ ::DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &ncPolicy, sizeof(ncPolicy));
299
+
300
+ const DWM_WINDOW_CORNER_PREFERENCE cornerPref = DWMWCP_DEFAULT;
301
+ ::DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPref, sizeof(cornerPref));
302
+
303
+ const COLORREF zeroColor = 0;
304
+ ::DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &zeroColor, sizeof(zeroColor));
305
+ ::DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, &zeroColor, sizeof(zeroColor));
306
+ ::DwmSetWindowAttribute(hwnd, DWMWA_TEXT_COLOR, &zeroColor, sizeof(zeroColor));
307
+ } else {
308
+ const DWMNCRENDERINGPOLICY ncPolicy = DWMNCRP_USEWINDOWSTYLE;
309
+ ::DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &ncPolicy, sizeof(ncPolicy));
310
+
311
+ const DWM_WINDOW_CORNER_PREFERENCE cornerPref = DWMWCP_DEFAULT;
312
+ ::DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPref, sizeof(cornerPref));
313
+
314
+ const COLORREF zeroColor = DWMWA_COLOR_DEFAULT;
315
+ ::DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &zeroColor, sizeof(zeroColor));
316
+ ::DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, &zeroColor, sizeof(zeroColor));
317
+ ::DwmSetWindowAttribute(hwnd, DWMWA_TEXT_COLOR, &zeroColor, sizeof(zeroColor));
318
+
319
+ titleBar.IconShowOptions(winrt::Microsoft::UI::Windowing::IconShowOptions::HideIconAndSystemMenu);
320
+ }
321
+ }
322
+
261
323
  // creates a new modal window
262
324
  void EnsureModalCreated(const winrt::Microsoft::ReactNative::ComponentView &view) {
263
325
  if (m_popUp) {
@@ -282,22 +344,8 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
282
344
  m_rnWindow = winrt::Microsoft::ReactNative::ReactNativeWindow::CreateFromContentSiteBridgeAndIsland(
283
345
  m_popUp, winrt::Microsoft::ReactNative::ReactNativeIsland::CreatePortal(portal));
284
346
  m_rnWindow.ResizePolicy(winrt::Microsoft::ReactNative::ContentSizePolicy::None);
285
- auto overlappedPresenter = winrt::Microsoft::UI::Windowing::OverlappedPresenter::Create();
286
-
287
- // Configure presenter for modal behavior
288
- overlappedPresenter.IsModal(true);
289
- overlappedPresenter.SetBorderAndTitleBar(true, true);
290
-
291
- // modal should only have close button
292
- overlappedPresenter.IsMinimizable(false);
293
- overlappedPresenter.IsMaximizable(false);
294
-
295
- // Apply the presenter to the window
296
- m_rnWindow.AppWindow().SetPresenter(overlappedPresenter);
297
347
 
298
- // Hide the title bar icon
299
- m_rnWindow.AppWindow().TitleBar().IconShowOptions(
300
- winrt::Microsoft::UI::Windowing::IconShowOptions::HideIconAndSystemMenu);
348
+ UpdateTitleBarAndBorder();
301
349
 
302
350
  // Set initial title using the stored local props
303
351
  if (m_localProps && m_localProps->title.has_value()) {
@@ -10,11 +10,11 @@
10
10
  -->
11
11
  <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
12
12
  <PropertyGroup>
13
- <ReactNativeWindowsVersion>0.81.18</ReactNativeWindowsVersion>
13
+ <ReactNativeWindowsVersion>0.81.20</ReactNativeWindowsVersion>
14
14
  <ReactNativeWindowsMajor>0</ReactNativeWindowsMajor>
15
15
  <ReactNativeWindowsMinor>81</ReactNativeWindowsMinor>
16
- <ReactNativeWindowsPatch>18</ReactNativeWindowsPatch>
16
+ <ReactNativeWindowsPatch>20</ReactNativeWindowsPatch>
17
17
  <ReactNativeWindowsCanary>false</ReactNativeWindowsCanary>
18
- <ReactNativeWindowsCommitId>ffbb7afa1dbc3a60b57a6bca6025d3c0537b569e</ReactNativeWindowsCommitId>
18
+ <ReactNativeWindowsCommitId>29ff929a448c74d7ab0362c6ef8bde4313d31b47</ReactNativeWindowsCommitId>
19
19
  </PropertyGroup>
20
20
  </Project>
@@ -37,6 +37,8 @@ struct ModalHostViewProps : winrt::implements<ModalHostViewProps, winrt::Microso
37
37
  supportedOrientations = cloneFromProps->supportedOrientations;
38
38
  identifier = cloneFromProps->identifier;
39
39
  title = cloneFromProps->title;
40
+ hideTitleBar = cloneFromProps->hideTitleBar;
41
+ hideBorder = cloneFromProps->hideBorder;
40
42
  onRequestClose = cloneFromProps->onRequestClose;
41
43
  onShow = cloneFromProps->onShow;
42
44
  onDismiss = cloneFromProps->onDismiss;
@@ -84,6 +86,12 @@ struct ModalHostViewProps : winrt::implements<ModalHostViewProps, winrt::Microso
84
86
  REACT_FIELD(title)
85
87
  std::optional<std::string> title;
86
88
 
89
+ REACT_FIELD(hideTitleBar)
90
+ std::optional<bool> hideTitleBar{};
91
+
92
+ REACT_FIELD(hideBorder)
93
+ std::optional<bool> hideBorder{};
94
+
87
95
  // These fields can be used to determine if JS has registered for this event
88
96
  REACT_FIELD(onRequestClose)
89
97
  bool onRequestClose{false};
@@ -477,7 +477,9 @@ ModalHostViewProps::ModalHostViewProps(
477
477
  allowSwipeDismissal(convertRawProp(context, rawProps, "allowSwipeDismissal", sourceProps.allowSwipeDismissal, {false})),
478
478
  supportedOrientations(convertRawProp(context, rawProps, "supportedOrientations", ModalHostViewSupportedOrientationsMaskWrapped{ .value = sourceProps.supportedOrientations }, {static_cast<ModalHostViewSupportedOrientationsMask>(ModalHostViewSupportedOrientations::Portrait)}).value),
479
479
  identifier(convertRawProp(context, rawProps, "identifier", sourceProps.identifier, {0})),
480
- title(convertRawProp(context, rawProps, "title", sourceProps.title, {})) {}
480
+ title(convertRawProp(context, rawProps, "title", sourceProps.title, {})),
481
+ hideTitleBar(convertRawProp(context, rawProps, "hideTitleBar", sourceProps.hideTitleBar, {false})),
482
+ hideBorder(convertRawProp(context, rawProps, "hideBorder", sourceProps.hideBorder, {false})) {}
481
483
 
482
484
  #ifdef RN_SERIALIZABLE_STATE
483
485
  ComponentName ModalHostViewProps::getDiffPropsImplementationTarget() const {
@@ -542,6 +544,14 @@ folly::dynamic ModalHostViewProps::getDiffProps(
542
544
  if (title != oldProps->title) {
543
545
  result["title"] = title;
544
546
  }
547
+
548
+ if (hideTitleBar != oldProps->hideTitleBar) {
549
+ result["hideTitleBar"] = hideTitleBar;
550
+ }
551
+
552
+ if (hideBorder != oldProps->hideBorder) {
553
+ result["hideBorder"] = hideBorder;
554
+ }
545
555
  return result;
546
556
  }
547
557
  #endif
@@ -470,6 +470,8 @@ class ModalHostViewProps final : public ViewProps {
470
470
  ModalHostViewSupportedOrientationsMask supportedOrientations{static_cast<ModalHostViewSupportedOrientationsMask>(ModalHostViewSupportedOrientations::Portrait)};
471
471
  int identifier{0};
472
472
  std::string title{};
473
+ bool hideTitleBar{false};
474
+ bool hideBorder{false};
473
475
 
474
476
  #ifdef RN_SERIALIZABLE_STATE
475
477
  ComponentName getDiffPropsImplementationTarget() const override;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-windows",
3
- "version": "0.81.18",
3
+ "version": "0.81.20",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -152,6 +152,10 @@ type RCTModalHostViewNativeProps = $ReadOnly<{|
152
152
  */
153
153
  // [Windows
154
154
  title?: WithDefault<string, null>,
155
+
156
+ hideTitleBar?: WithDefault<boolean, false>,
157
+
158
+ hideBorder?: WithDefault<boolean, false>,
155
159
  // Windows]
156
160
  |}>;
157
161