react-native-windows 0.84.0-preview.9 → 0.84.0
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.
- package/Common/unicode.cpp +36 -0
- package/Common/unicode.h +8 -0
- package/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +5 -0
- package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +91 -23
- package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +11 -0
- package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp +6 -4
- package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +28 -6
- package/Microsoft.ReactNative/Fabric/Composition/TextDrawing.cpp +1 -1
- package/Microsoft.ReactNative/Fabric/Composition/TooltipService.cpp +80 -0
- package/Microsoft.ReactNative/Fabric/Composition/TooltipService.h +17 -0
- package/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/WindowsTextLayoutManager.cpp +1 -1
- package/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +3 -2
- package/Microsoft.ReactNative/Utils/LocalBundleReader.cpp +33 -25
- package/Microsoft.ReactNative/Utils/LocalBundleReader.h +5 -3
- package/Microsoft.ReactNative/Utils/UwpScriptStore.cpp +2 -2
- package/Microsoft.ReactNative/Utils/UwpScriptStore.h +3 -2
- package/Microsoft.ReactNative.Managed/Microsoft.ReactNative.Managed.csproj +3 -3
- package/Microsoft.ReactNative.Managed.CodeGen/Microsoft.ReactNative.Managed.CodeGen.csproj +10 -10
- package/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Debug.pubxml +1 -1
- package/Microsoft.ReactNative.Managed.CodeGen/Properties/PublishProfiles/DeployAsTool-Release.pubxml +1 -1
- package/PropertySheets/Generated/PackageVersion.g.props +2 -2
- package/PropertySheets/JSEngine.props +1 -1
- package/PropertySheets/React.Cpp.props +4 -3
- package/PropertySheets/WinUI.props +2 -2
- package/Scripts/JustMyXaml.ps1 +8 -8
- package/Scripts/UnitTest.ps1 +3 -1
- package/Scripts/rnw-dependencies.ps1 +15 -16
- package/Shared/DevSupportManager.cpp +55 -48
- package/Shared/DevSupportManager.h +0 -1
- package/Shared/Modules/IWebSocketModuleContentHandler.h +15 -0
- package/Shared/Modules/WebSocketModule.cpp +8 -3
- package/Shared/Networking/DefaultBlobResource.cpp +37 -0
- package/Shared/Networking/DefaultBlobResource.h +12 -0
- package/Shared/Networking/WinRTWebSocketResource.h +5 -1
- package/just-task.js +13 -2
- package/package.json +6 -6
- package/templates/cpp-app/windows/MyApp/MyApp.vcxproj +1 -1
- package/templates/cpp-lib/windows/MyLib/MyLib.vcxproj +1 -1
package/Common/unicode.cpp
CHANGED
|
@@ -93,6 +93,42 @@ std::wstring Utf8ToUtf16(const std::string &utf8) {
|
|
|
93
93
|
return Utf8ToUtf16(utf8.c_str(), utf8.length());
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
size_t Utf8ToUtf16Length(const char *utf8, size_t utf8Len) {
|
|
97
|
+
if (utf8Len == 0) {
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (utf8Len > static_cast<size_t>((std::numeric_limits<int>::max)())) {
|
|
102
|
+
throw std::overflow_error("Length of input string to Utf8ToUtf16Length() must fit into an int.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const int utf8Length = static_cast<int>(utf8Len);
|
|
106
|
+
|
|
107
|
+
constexpr DWORD flags = 0;
|
|
108
|
+
|
|
109
|
+
const int utf16Length = ::MultiByteToWideChar(
|
|
110
|
+
CP_UTF8, // Source string is in UTF-8.
|
|
111
|
+
flags, // Conversion flags.
|
|
112
|
+
utf8, // Source UTF-8 string pointer.
|
|
113
|
+
utf8Length, // Length of the source UTF-8 string, in chars.
|
|
114
|
+
nullptr, // Do not convert, just request the size.
|
|
115
|
+
0 // Request size of destination buffer, in wchar_ts.
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (utf16Length == 0) {
|
|
119
|
+
throw UnicodeConversionException(
|
|
120
|
+
"Cannot get result string length when converting from UTF-8 to UTF-16 "
|
|
121
|
+
"(MultiByteToWideChar failed).",
|
|
122
|
+
GetLastError());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return static_cast<size_t>(utf16Length);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
size_t Utf8ToUtf16Length(const std::string &utf8) {
|
|
129
|
+
return Utf8ToUtf16Length(utf8.c_str(), utf8.length());
|
|
130
|
+
}
|
|
131
|
+
|
|
96
132
|
#if _HAS_CXX17
|
|
97
133
|
std::wstring Utf8ToUtf16(const std::string_view &utf8) {
|
|
98
134
|
return Utf8ToUtf16(utf8.data(), utf8.length());
|
package/Common/unicode.h
CHANGED
|
@@ -55,6 +55,14 @@ class UnicodeConversionException : public std::runtime_error {
|
|
|
55
55
|
/* (4) */ std::wstring Utf8ToUtf16(const std::string_view &utf8);
|
|
56
56
|
#endif
|
|
57
57
|
|
|
58
|
+
// The following functions return the length of the UTF-16 string that would
|
|
59
|
+
// result from converting the input UTF-8 string, without actually performing
|
|
60
|
+
// the conversion or allocating a temporary std::wstring. This is useful in
|
|
61
|
+
// hot paths where only the length is needed (e.g. DirectWrite text ranges).
|
|
62
|
+
//
|
|
63
|
+
size_t Utf8ToUtf16Length(const char *utf8, size_t utf8Len);
|
|
64
|
+
size_t Utf8ToUtf16Length(const std::string &utf8);
|
|
65
|
+
|
|
58
66
|
// The following functions convert UTF-16BE strings to UTF-8 strings. Their
|
|
59
67
|
// behaviors mirror those of the above Utf8ToUtf16 functions.
|
|
60
68
|
//
|
|
@@ -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();
|
|
@@ -224,6 +224,28 @@ void CompositionEventHandler::Initialize() noexcept {
|
|
|
224
224
|
}
|
|
225
225
|
});
|
|
226
226
|
|
|
227
|
+
// Issue #16047: when ScrollView calls VisualInteractionSource::TryRedirectForManipulation
|
|
228
|
+
// and the OS hands the pointer over to the InteractionTracker, WinAppSDK
|
|
229
|
+
// does not fire PointerCaptureLost on this source — but it does fire
|
|
230
|
+
// PointerRoutedAway. Treat it the same way as captureloss: cancel any
|
|
231
|
+
// active touch RN is tracking for this pointer so Pressables don't get
|
|
232
|
+
// stuck in their pressed state.
|
|
233
|
+
m_pointerRoutedAwayToken =
|
|
234
|
+
pointerSource.PointerRoutedAway([wkThis = weak_from_this()](
|
|
235
|
+
winrt::Microsoft::UI::Input::InputPointerSource const &,
|
|
236
|
+
winrt::Microsoft::UI::Input::PointerEventArgs const &args) {
|
|
237
|
+
if (auto strongThis = wkThis.lock()) {
|
|
238
|
+
if (auto strongRootView = strongThis->m_wkRootView.get()) {
|
|
239
|
+
if (strongThis->SurfaceId() == -1)
|
|
240
|
+
return;
|
|
241
|
+
|
|
242
|
+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
|
|
243
|
+
args.CurrentPoint(), strongRootView.ScaleFactor());
|
|
244
|
+
strongThis->onPointerRoutedAway(pp, args.KeyModifiers());
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
227
249
|
m_pointerWheelChangedToken =
|
|
228
250
|
pointerSource.PointerWheelChanged([wkThis = weak_from_this()](
|
|
229
251
|
winrt::Microsoft::UI::Input::InputPointerSource const &,
|
|
@@ -369,6 +391,7 @@ CompositionEventHandler::~CompositionEventHandler() {
|
|
|
369
391
|
pointerSource.PointerReleased(m_pointerReleasedToken);
|
|
370
392
|
pointerSource.PointerMoved(m_pointerMovedToken);
|
|
371
393
|
pointerSource.PointerCaptureLost(m_pointerCaptureLostToken);
|
|
394
|
+
pointerSource.PointerRoutedAway(m_pointerRoutedAwayToken);
|
|
372
395
|
pointerSource.PointerWheelChanged(m_pointerWheelChangedToken);
|
|
373
396
|
pointerSource.PointerExited(m_pointerExitedToken);
|
|
374
397
|
auto keyboardSource = winrt::Microsoft::UI::Input::InputKeyboardSource::GetForIsland(island);
|
|
@@ -1116,6 +1139,50 @@ void CompositionEventHandler::onPointerCaptureLost(
|
|
|
1116
1139
|
|
|
1117
1140
|
m_pointerCapturingComponentTag = -1;
|
|
1118
1141
|
}
|
|
1142
|
+
|
|
1143
|
+
// Defense-in-depth cleanup for the specific pointer that lost capture, even
|
|
1144
|
+
// when no JS-level CapturePointer was ever issued. The ScrollView
|
|
1145
|
+
// TryRedirectForManipulation path comes in via PointerRoutedAway, not
|
|
1146
|
+
// PointerCaptureLost (see onPointerRoutedAway and issue #16047), so this
|
|
1147
|
+
// path covers the remaining system-driven losses (focus change, another
|
|
1148
|
+
// window stealing input, system back gesture, etc.).
|
|
1149
|
+
CancelActiveTouchForPointerInternal(pointerPoint.PointerId(), pointerPoint, keyModifiers);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
void CompositionEventHandler::onPointerRoutedAway(
|
|
1153
|
+
const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
|
|
1154
|
+
winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept {
|
|
1155
|
+
if (SurfaceId() == -1)
|
|
1156
|
+
return;
|
|
1157
|
+
|
|
1158
|
+
// Issue #16047: WinAppSDK fires PointerRoutedAway when the OS hands the
|
|
1159
|
+
// pointer to another InputPointerSource — most importantly for us, when
|
|
1160
|
+
// ScrollView calls VisualInteractionSource::TryRedirectForManipulation and
|
|
1161
|
+
// the InteractionTracker takes the gesture for scrolling. We never get
|
|
1162
|
+
// PointerMoved / PointerReleased / PointerCaptureLost for that pointer
|
|
1163
|
+
// afterwards, so without this cleanup m_activeTouches keeps a zombie entry
|
|
1164
|
+
// and the originally-pressed Pressable stays stuck in its pressed state.
|
|
1165
|
+
CancelActiveTouchForPointerInternal(pointerPoint.PointerId(), pointerPoint, keyModifiers);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
bool CompositionEventHandler::CancelActiveTouchForPointerInternal(
|
|
1169
|
+
PointerId pointerId,
|
|
1170
|
+
const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
|
|
1171
|
+
winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept {
|
|
1172
|
+
auto activeTouch = m_activeTouches.find(pointerId);
|
|
1173
|
+
if (activeTouch == m_activeTouches.end()) {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
ActiveTouch cancelledTouchCopy = std::move(activeTouch->second);
|
|
1178
|
+
m_activeTouches.erase(activeTouch);
|
|
1179
|
+
|
|
1180
|
+
if (!cancelledTouchCopy.eventEmitter) {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
DispatchSynthesizedTouchCancelForActiveTouch(cancelledTouchCopy, pointerPoint, keyModifiers);
|
|
1185
|
+
return true;
|
|
1119
1186
|
}
|
|
1120
1187
|
|
|
1121
1188
|
void CompositionEventHandler::onPointerMoved(
|
|
@@ -1366,7 +1433,7 @@ void CompositionEventHandler::onPointerPressed(
|
|
|
1366
1433
|
|
|
1367
1434
|
UpdateActiveTouch(activeTouch, ptScaled, ptLocal);
|
|
1368
1435
|
|
|
1369
|
-
activeTouch.isPrimary =
|
|
1436
|
+
activeTouch.isPrimary = pointerPoint.Properties().IsPrimary();
|
|
1370
1437
|
// Map the Windows pointer ID to a small identifier (0–19) safe for use as a JS array index.
|
|
1371
1438
|
// Windows touch IDs can be arbitrarily large (e.g. 2233), which causes React Native to warn
|
|
1372
1439
|
// and corrupts touch state, leaving Pressables stuck after a scroll.
|
|
@@ -1614,16 +1681,6 @@ bool CompositionEventHandler::IsPointerWithinInitialTree(const ActiveTouch &acti
|
|
|
1614
1681
|
currentView = currentView.Parent();
|
|
1615
1682
|
}
|
|
1616
1683
|
|
|
1617
|
-
// Fallback: if the pointer drifted spatially but the original target
|
|
1618
|
-
// is still structurally within the initial tree, honor the tap.
|
|
1619
|
-
// This provides touch-device tolerance for finger drift.
|
|
1620
|
-
auto targetView = viewRegistry.componentViewDescriptorWithTag(activeTouch.touch.target).view;
|
|
1621
|
-
while (targetView) {
|
|
1622
|
-
if (targetView.Tag() == initialTag)
|
|
1623
|
-
return true;
|
|
1624
|
-
targetView = targetView.Parent();
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
1684
|
return false;
|
|
1628
1685
|
}
|
|
1629
1686
|
|
|
@@ -1685,7 +1742,15 @@ void CompositionEventHandler::DispatchTouchEvent(
|
|
|
1685
1742
|
|
|
1686
1743
|
facebook::react::TouchEvent event;
|
|
1687
1744
|
|
|
1688
|
-
|
|
1745
|
+
// First pass: build changedTouches and the set of unique emitters from every active
|
|
1746
|
+
// touch. The per-pointer PointerEvent dispatch (onPointerDown/Move/Up/Cancel/Click) is
|
|
1747
|
+
// fired only for the touch whose state actually changed — non-changed touches contribute
|
|
1748
|
+
// to the W3C TouchEvent's touches/targetTouches sets in the loops below but must not
|
|
1749
|
+
// re-fire pointer events of their own. Previously we dispatched the per-pointer event
|
|
1750
|
+
// for every entry in m_activeTouches, which produced duplicated onPointerMove on
|
|
1751
|
+
// non-moving fingers and replayed onPointerUp/onClick on stale targets after the OS
|
|
1752
|
+
// reclaimed a pointer (e.g. ScrollView manipulation redirect leaving a zombie touch).
|
|
1753
|
+
const ActiveTouch *changedTouch = nullptr;
|
|
1689
1754
|
for (const auto &pair : m_activeTouches) {
|
|
1690
1755
|
const auto &activeTouch = pair.second;
|
|
1691
1756
|
|
|
@@ -1694,14 +1759,17 @@ void CompositionEventHandler::DispatchTouchEvent(
|
|
|
1694
1759
|
}
|
|
1695
1760
|
|
|
1696
1761
|
if (pair.first == pointerId) {
|
|
1762
|
+
changedTouch = &activeTouch;
|
|
1697
1763
|
event.changedTouches.insert(activeTouch.touch);
|
|
1698
1764
|
}
|
|
1699
1765
|
uniqueEventEmitters.insert(activeTouch.eventEmitter);
|
|
1766
|
+
}
|
|
1700
1767
|
|
|
1701
|
-
|
|
1768
|
+
if (changedTouch) {
|
|
1769
|
+
facebook::react::PointerEvent pointerEvent = CreatePointerEventFromActiveTouch(*changedTouch, eventType);
|
|
1702
1770
|
|
|
1703
1771
|
winrt::Microsoft::ReactNative::ComponentView targetView{nullptr};
|
|
1704
|
-
bool shouldLeave = (eventType == TouchEventType::End &&
|
|
1772
|
+
bool shouldLeave = (eventType == TouchEventType::End && changedTouch->shouldLeaveWhenReleased) ||
|
|
1705
1773
|
eventType == TouchEventType::Cancel;
|
|
1706
1774
|
if (!shouldLeave) {
|
|
1707
1775
|
auto *rootViewForHit = RootComponentView();
|
|
@@ -1716,29 +1784,29 @@ void CompositionEventHandler::DispatchTouchEvent(
|
|
|
1716
1784
|
}
|
|
1717
1785
|
}
|
|
1718
1786
|
|
|
1719
|
-
auto handler = [this,
|
|
1787
|
+
auto handler = [this, changedTouch, eventType, &pointerEvent](
|
|
1720
1788
|
std::vector<winrt::Microsoft::ReactNative::ComponentView> &eventPathViews) {
|
|
1721
1789
|
switch (eventType) {
|
|
1722
1790
|
case TouchEventType::Start:
|
|
1723
|
-
|
|
1791
|
+
changedTouch->eventEmitter->onPointerDown(pointerEvent);
|
|
1724
1792
|
break;
|
|
1725
1793
|
case TouchEventType::Move: {
|
|
1726
|
-
|
|
1794
|
+
changedTouch->eventEmitter->onPointerMove(pointerEvent);
|
|
1727
1795
|
break;
|
|
1728
1796
|
}
|
|
1729
1797
|
case TouchEventType::End:
|
|
1730
|
-
|
|
1798
|
+
changedTouch->eventEmitter->onPointerUp(pointerEvent);
|
|
1731
1799
|
if (pointerEvent.isPrimary && pointerEvent.button == 0) {
|
|
1732
|
-
if (IsPointerWithinInitialTree(
|
|
1733
|
-
|
|
1800
|
+
if (IsPointerWithinInitialTree(*changedTouch)) {
|
|
1801
|
+
changedTouch->eventEmitter->onClick(pointerEvent);
|
|
1734
1802
|
}
|
|
1735
|
-
} /* else if (IsPointerWithinInitialTree(
|
|
1736
|
-
|
|
1803
|
+
} /* else if (IsPointerWithinInitialTree(*changedTouch)) {
|
|
1804
|
+
changedTouch->eventEmitter->onAuxClick(pointerEvent);
|
|
1737
1805
|
} */
|
|
1738
1806
|
break;
|
|
1739
1807
|
case TouchEventType::Cancel:
|
|
1740
1808
|
case TouchEventType::CaptureLost:
|
|
1741
|
-
|
|
1809
|
+
changedTouch->eventEmitter->onPointerCancel(pointerEvent);
|
|
1742
1810
|
break;
|
|
1743
1811
|
}
|
|
1744
1812
|
};
|
|
@@ -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
|
|
|
@@ -186,6 +196,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
|
|
|
186
196
|
winrt::event_token m_pointerMovedToken;
|
|
187
197
|
winrt::event_token m_pointerWheelChangedToken;
|
|
188
198
|
winrt::event_token m_pointerCaptureLostToken;
|
|
199
|
+
winrt::event_token m_pointerRoutedAwayToken;
|
|
189
200
|
winrt::event_token m_pointerExitedToken;
|
|
190
201
|
winrt::event_token m_keyDownToken;
|
|
191
202
|
winrt::event_token m_keyUpToken;
|
|
@@ -153,11 +153,12 @@ facebook::react::SharedViewEventEmitter ParagraphComponentView::eventEmitterAtPo
|
|
|
153
153
|
uint32_t textPosition = metrics.textPosition;
|
|
154
154
|
|
|
155
155
|
for (auto fragment : m_attributedStringBox.getValue().getFragments()) {
|
|
156
|
-
|
|
156
|
+
uint32_t utf16Length = static_cast<uint32_t>(::Microsoft::Common::Unicode::Utf8ToUtf16Length(fragment.string));
|
|
157
|
+
if (textPosition < utf16Length) {
|
|
157
158
|
return std::static_pointer_cast<const facebook::react::ViewEventEmitter>(
|
|
158
159
|
fragment.parentShadowView.eventEmitter);
|
|
159
160
|
}
|
|
160
|
-
textPosition -=
|
|
161
|
+
textPosition -= utf16Length;
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
}
|
|
@@ -210,10 +211,11 @@ bool ParagraphComponentView::IsTextSelectableAtPoint(facebook::react::Point pt)
|
|
|
210
211
|
|
|
211
212
|
// Finds which fragment contains this text position
|
|
212
213
|
for (auto fragment : m_attributedStringBox.getValue().getFragments()) {
|
|
213
|
-
|
|
214
|
+
uint32_t utf16Length = static_cast<uint32_t>(::Microsoft::Common::Unicode::Utf8ToUtf16Length(fragment.string));
|
|
215
|
+
if (textPosition < utf16Length) {
|
|
214
216
|
return true;
|
|
215
217
|
}
|
|
216
|
-
textPosition -=
|
|
218
|
+
textPosition -= utf16Length;
|
|
217
219
|
}
|
|
218
220
|
}
|
|
219
221
|
}
|
|
@@ -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
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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 =
|
|
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
|
|
69
|
+
length = static_cast<UINT32>(::Microsoft::Common::Unicode::Utf8ToUtf16Length(fragment.string));
|
|
70
70
|
DWRITE_TEXT_RANGE range = {position, length};
|
|
71
71
|
if (fragment.textAttributes.foregroundColor &&
|
|
72
72
|
(fragment.textAttributes.foregroundColor != textAttributes.foregroundColor) ||
|
|
@@ -169,12 +169,22 @@ TooltipTracker::TooltipTracker(
|
|
|
169
169
|
view.PointerEntered({this, &TooltipTracker::OnPointerEntered});
|
|
170
170
|
view.PointerExited({this, &TooltipTracker::OnPointerExited});
|
|
171
171
|
view.PointerMoved({this, &TooltipTracker::OnPointerMoved});
|
|
172
|
+
view.GotFocus({this, &TooltipTracker::OnGotFocus});
|
|
173
|
+
view.LostFocus({this, &TooltipTracker::OnLostFocus});
|
|
172
174
|
view.Unmounted({this, &TooltipTracker::OnUnmounted});
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
TooltipTracker::~TooltipTracker() {
|
|
176
178
|
DestroyTimer();
|
|
177
179
|
DestroyTooltip();
|
|
180
|
+
m_outer->NotifyDismiss(this);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
void TooltipTracker::DismissForExternalRequest() noexcept {
|
|
184
|
+
// Service is already updating its active slot; do not call back into it.
|
|
185
|
+
m_focusTooltip = false;
|
|
186
|
+
DestroyTimer();
|
|
187
|
+
DestroyTooltip();
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
facebook::react::Tag TooltipTracker::Tag() const noexcept {
|
|
@@ -192,6 +202,9 @@ void TooltipTracker::OnPointerEntered(
|
|
|
192
202
|
auto pp = args.GetCurrentPoint(-1);
|
|
193
203
|
m_pos = pp.Position();
|
|
194
204
|
|
|
205
|
+
// Claim the single tooltip slot, dismissing any other tracker's pending or visible tooltip.
|
|
206
|
+
m_outer->NotifyShow(this);
|
|
207
|
+
|
|
195
208
|
m_timer = winrt::Microsoft::ReactNative::Timer::Create(m_properties.Handle());
|
|
196
209
|
m_timer.Interval(std::chrono::milliseconds(toolTipTimeToShowMs));
|
|
197
210
|
m_timer.Tick({this, &TooltipTracker::OnTick});
|
|
@@ -225,6 +238,56 @@ void TooltipTracker::OnPointerExited(
|
|
|
225
238
|
return;
|
|
226
239
|
DestroyTimer();
|
|
227
240
|
DestroyTooltip();
|
|
241
|
+
m_outer->NotifyDismiss(this);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
void TooltipTracker::OnGotFocus(
|
|
245
|
+
const winrt::Windows::Foundation::IInspectable & /*sender*/,
|
|
246
|
+
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs & /*args*/) noexcept {
|
|
247
|
+
// Skip if a mouse-driven tooltip or its dwell timer is already in flight on this view.
|
|
248
|
+
if (m_hwndTip || m_timer) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
auto view = m_view.view();
|
|
253
|
+
if (!view) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
auto viewCompView = view.try_as<winrt::Microsoft::ReactNative::Composition::ViewComponentView>();
|
|
258
|
+
if (!viewCompView) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
auto selfView =
|
|
262
|
+
winrt::get_self<winrt::Microsoft::ReactNative::Composition::implementation::ViewComponentView>(viewCompView);
|
|
263
|
+
RECT rc = selfView->getClientRect();
|
|
264
|
+
auto scaleFactor = view.LayoutMetrics().PointScaleFactor;
|
|
265
|
+
if (scaleFactor <= 0) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Anchor in DIPs at the horizontal center of the view's top edge; ShowTooltip re-applies scaleFactor.
|
|
270
|
+
m_pos = {static_cast<float>(rc.left + rc.right) / 2.0f / scaleFactor, static_cast<float>(rc.top) / scaleFactor};
|
|
271
|
+
|
|
272
|
+
m_focusTooltip = true;
|
|
273
|
+
// Claim the single tooltip slot, dismissing any other tracker's pending or visible tooltip.
|
|
274
|
+
m_outer->NotifyShow(this);
|
|
275
|
+
m_timer = winrt::Microsoft::ReactNative::Timer::Create(m_properties.Handle());
|
|
276
|
+
m_timer.Interval(std::chrono::milliseconds(toolTipTimeToShowMs));
|
|
277
|
+
m_timer.Tick({this, &TooltipTracker::OnTick});
|
|
278
|
+
m_timer.Start();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
void TooltipTracker::OnLostFocus(
|
|
282
|
+
const winrt::Windows::Foundation::IInspectable & /*sender*/,
|
|
283
|
+
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs & /*args*/) noexcept {
|
|
284
|
+
if (!m_focusTooltip) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
m_focusTooltip = false;
|
|
288
|
+
DestroyTimer();
|
|
289
|
+
DestroyTooltip();
|
|
290
|
+
m_outer->NotifyDismiss(this);
|
|
228
291
|
}
|
|
229
292
|
|
|
230
293
|
void TooltipTracker::OnUnmounted(
|
|
@@ -232,6 +295,7 @@ void TooltipTracker::OnUnmounted(
|
|
|
232
295
|
const winrt::Microsoft::ReactNative::ComponentView &) noexcept {
|
|
233
296
|
DestroyTimer();
|
|
234
297
|
DestroyTooltip();
|
|
298
|
+
m_outer->NotifyDismiss(this);
|
|
235
299
|
}
|
|
236
300
|
|
|
237
301
|
void TooltipTracker::ShowTooltip(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
|
|
@@ -326,6 +390,22 @@ void TooltipService::StopTracking(const winrt::Microsoft::ReactNative::Component
|
|
|
326
390
|
}
|
|
327
391
|
}
|
|
328
392
|
|
|
393
|
+
void TooltipService::NotifyShow(TooltipTracker *tracker) noexcept {
|
|
394
|
+
if (m_activeTracker == tracker) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (m_activeTracker) {
|
|
398
|
+
m_activeTracker->DismissForExternalRequest();
|
|
399
|
+
}
|
|
400
|
+
m_activeTracker = tracker;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
void TooltipService::NotifyDismiss(TooltipTracker *tracker) noexcept {
|
|
404
|
+
if (m_activeTracker == tracker) {
|
|
405
|
+
m_activeTracker = nullptr;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
329
409
|
static const ReactPropertyId<winrt::Microsoft::ReactNative::ReactNonAbiValue<std::shared_ptr<TooltipService>>>
|
|
330
410
|
&TooltipServicePropertyId() noexcept {
|
|
331
411
|
static const ReactPropertyId<winrt::Microsoft::ReactNative::ReactNonAbiValue<std::shared_ptr<TooltipService>>> prop{
|
|
@@ -27,6 +27,12 @@ struct TooltipTracker {
|
|
|
27
27
|
void OnPointerExited(
|
|
28
28
|
const winrt::Windows::Foundation::IInspectable &sender,
|
|
29
29
|
const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) noexcept;
|
|
30
|
+
void OnGotFocus(
|
|
31
|
+
const winrt::Windows::Foundation::IInspectable &sender,
|
|
32
|
+
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept;
|
|
33
|
+
void OnLostFocus(
|
|
34
|
+
const winrt::Windows::Foundation::IInspectable &sender,
|
|
35
|
+
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept;
|
|
30
36
|
void OnTick(
|
|
31
37
|
const winrt::Windows::Foundation::IInspectable &,
|
|
32
38
|
const winrt::Windows::Foundation::IInspectable &) noexcept;
|
|
@@ -36,6 +42,10 @@ struct TooltipTracker {
|
|
|
36
42
|
|
|
37
43
|
facebook::react::Tag Tag() const noexcept;
|
|
38
44
|
|
|
45
|
+
// Cancel pending dwell timer and close any visible tooltip popup; used by the service when another tracker takes
|
|
46
|
+
// over.
|
|
47
|
+
void DismissForExternalRequest() noexcept;
|
|
48
|
+
|
|
39
49
|
private:
|
|
40
50
|
void ShowTooltip(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept;
|
|
41
51
|
void DestroyTimer() noexcept;
|
|
@@ -46,6 +56,7 @@ struct TooltipTracker {
|
|
|
46
56
|
::Microsoft::ReactNative::ReactTaggedView m_view;
|
|
47
57
|
winrt::Microsoft::ReactNative::ITimer m_timer;
|
|
48
58
|
HWND m_hwndTip{nullptr};
|
|
59
|
+
bool m_focusTooltip{false};
|
|
49
60
|
winrt::Microsoft::ReactNative::ReactPropertyBag m_properties;
|
|
50
61
|
};
|
|
51
62
|
|
|
@@ -54,12 +65,18 @@ struct TooltipService {
|
|
|
54
65
|
void StartTracking(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept;
|
|
55
66
|
void StopTracking(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept;
|
|
56
67
|
|
|
68
|
+
// Enforce "only one tooltip visible at a time": dismisses the previously active tracker, if any.
|
|
69
|
+
void NotifyShow(TooltipTracker *tracker) noexcept;
|
|
70
|
+
// Clears the active-tracker slot if it still points at `tracker`.
|
|
71
|
+
void NotifyDismiss(TooltipTracker *tracker) noexcept;
|
|
72
|
+
|
|
57
73
|
static std::shared_ptr<TooltipService> GetCurrent(
|
|
58
74
|
const winrt::Microsoft::ReactNative::ReactPropertyBag &properties) noexcept;
|
|
59
75
|
|
|
60
76
|
private:
|
|
61
77
|
std::vector<std::shared_ptr<TooltipTracker>> m_enteredTrackers;
|
|
62
78
|
std::vector<std::shared_ptr<TooltipTracker>> m_trackers;
|
|
79
|
+
TooltipTracker *m_activeTracker{nullptr};
|
|
63
80
|
winrt::Microsoft::ReactNative::ReactPropertyBag m_properties;
|
|
64
81
|
};
|
|
65
82
|
|
|
@@ -267,7 +267,7 @@ void WindowsTextLayoutManager::GetTextLayout(
|
|
|
267
267
|
attachments.push_back(attachment);
|
|
268
268
|
position += 1;
|
|
269
269
|
} else {
|
|
270
|
-
unsigned int length = static_cast<UINT32>(fragment.string
|
|
270
|
+
unsigned int length = static_cast<UINT32>(Microsoft::Common::Unicode::Utf8ToUtf16Length(fragment.string));
|
|
271
271
|
DWRITE_TEXT_RANGE range = {position, length};
|
|
272
272
|
TextAttributes attributes = fragment.textAttributes;
|
|
273
273
|
DWRITE_FONT_STYLE fragmentStyle = DWRITE_FONT_STYLE_NORMAL;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<ProjectName>Microsoft.ReactNative</ProjectName>
|
|
11
11
|
<RootNamespace>Microsoft.ReactNative</RootNamespace>
|
|
12
12
|
<DefaultLanguage>en-US</DefaultLanguage>
|
|
13
|
-
<MinimumVisualStudioVersion>
|
|
13
|
+
<MinimumVisualStudioVersion>18.0</MinimumVisualStudioVersion>
|
|
14
14
|
<AppContainerApplication>true</AppContainerApplication>
|
|
15
15
|
<ApplicationType>Windows Store</ApplicationType>
|
|
16
16
|
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
|
97
97
|
<ForcedIncludeFiles>pch.h</ForcedIncludeFiles>
|
|
98
98
|
<WarningLevel>Level4</WarningLevel>
|
|
99
|
-
<AdditionalOptions
|
|
99
|
+
<AdditionalOptions>%(AdditionalOptions) /bigobj /ZH:SHA_256</AdditionalOptions>
|
|
100
|
+
<AdditionalOptions Condition="$(PlatformToolsetVersion)<145">%(AdditionalOptions) /await</AdditionalOptions>
|
|
100
101
|
<AdditionalIncludeDirectories>
|
|
101
102
|
$(FmtDir)\include;
|
|
102
103
|
$(ReactNativeWindowsDir)Microsoft.ReactNative;
|