Skip to content

Commit

Permalink
Move react tag to an attached property (#9658)
Browse files Browse the repository at this point in the history
## Description
This PR defines a new "ReactTag" attached property to store react tag values on XAML Dependency Objects, rather than using FrameworkElement's general-purpose Tag property.

Some downstream users (i.e. custom controls) expect to be able to use FE's Tag property for their own purposes, so overriding it ourselves can limit, if not break, integration with RN and XAML.

This PR updates the internal SetTag and GetTag helper methods to make this transition as seamless as possible. SetTag sets both the new property and the old FrameworkElement Tag, to improve compatibility with older ViewManagers who expect that behavior. GetTag always uses the new property, so we aren't broken if a ViewManager overwrites  FE's Tag.

This PR also exposes the new property under XamlHelpers::ReactTagProperty() for external modules who want to get the definitive tag value of a XAML element, and defines various `RNW_REACTTAG_API` macros/constants to easily detect if this change is present

Closes #4187

### Type of Change
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
- This change requires a documentation update
  • Loading branch information
jonthysell committed Aug 31, 2022
1 parent d9f801a commit 6363177
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Move react tag to an attached property",
"packageName": "react-native-windows",
"email": "jthysell@microsoft.com",
"dependentChangeType": "patch"
}
4 changes: 2 additions & 2 deletions vnext/Microsoft.ReactNative/Views/TouchEventHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ winrt::IPropertyValue TestHit(
return resTag;

if (isHit) {
tag = el.GetValue(xaml::FrameworkElement::TagProperty()).try_as<winrt::IPropertyValue>();
tag = el.GetValue(ReactTagProperty()).try_as<winrt::IPropertyValue>();
if (tag) {
return tag;
}
Expand Down Expand Up @@ -500,7 +500,7 @@ bool TagFromOriginalSource(const winrt::PointerRoutedEventArgs &args, int64_t *p
winrt::IPropertyValue tag(nullptr);

while (sourceElement) {
auto tagValue = sourceElement.ReadLocalValue(xaml::FrameworkElement::TagProperty());
auto tagValue = sourceElement.ReadLocalValue(ReactTagProperty());
if (tagValue != xaml::DependencyProperty::UnsetValue()) {
tag = tagValue.try_as<winrt::IPropertyValue>();
// If a TextBlock was the UIElement event source, perform a more accurate hit test,
Expand Down
9 changes: 9 additions & 0 deletions vnext/Microsoft.ReactNative/XamlUIService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
#include "DynamicWriter.h"
#include "ShadowNodeBase.h"
#include "Views/ShadowNodeBase.h"
#include "XamlView.h"

namespace winrt::Microsoft::ReactNative::implementation {

XamlUIService::XamlUIService(Mso::CntPtr<Mso::React::IReactContext> &&context) noexcept : m_context(context) {}

xaml::DependencyProperty XamlUIService::ReactTagProperty() noexcept {
return ::Microsoft::ReactNative::ReactTagProperty();
}

xaml::DependencyObject XamlUIService::ElementFromReactTag(int64_t reactTag) noexcept {
if (auto uiManager = ::Microsoft::ReactNative::GetNativeUIManager(*m_context).lock()) {
auto shadowNode = uiManager->getHost()->FindShadowNodeForTag(reactTag);
Expand All @@ -25,6 +30,10 @@ xaml::DependencyObject XamlUIService::ElementFromReactTag(int64_t reactTag) noex
return nullptr;
}

int64_t XamlUIService::ReactTagFromElement(xaml::DependencyObject dependencyObject) noexcept {
return ::Microsoft::ReactNative::GetTag(dependencyObject);
}

/*static*/ winrt::Microsoft::ReactNative::XamlUIService XamlUIService::FromContext(IReactContext context) {
return context.Properties()
.Get(XamlUIService::XamlUIServiceProperty().Handle())
Expand Down
4 changes: 4 additions & 0 deletions vnext/Microsoft.ReactNative/XamlUIService.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ struct XamlUIService : XamlUIServiceT<XamlUIService> {
XamlUIService(Mso::CntPtr<Mso::React::IReactContext> &&context) noexcept;
static ReactPropertyId<XamlUIService> XamlUIServiceProperty() noexcept;

static xaml::DependencyProperty ReactTagProperty() noexcept;

int64_t ReactTagFromElement(xaml::DependencyObject dependencyObject) noexcept;

xaml::DependencyObject ElementFromReactTag(int64_t reactTag) noexcept;
static winrt::Microsoft::ReactNative::XamlUIService FromContext(IReactContext context);
void DispatchEvent(
Expand Down
6 changes: 6 additions & 0 deletions vnext/Microsoft.ReactNative/XamlUIService.idl
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ namespace Microsoft.ReactNative
DOC_STRING("Use this method to get access to the @XamlUIService associated with the @IReactContext.")
static XamlUIService FromContext(IReactContext context);

DOC_STRING("Returns the attached property where the react tag is stored on a XAML Dependency Object.")
static XAML_NAMESPACE.DependencyProperty ReactTagProperty { get; };

DOC_STRING("Gets the backing XAML element from a react tag.")
XAML_NAMESPACE.DependencyObject ElementFromReactTag(Int64 reactTag);

DOC_STRING("Gets the react tag from a backing XAML element.")
Int64 ReactTagFromElement(XAML_NAMESPACE.DependencyObject dependencyObject);

DOC_STRING("Dispatches an event to a JS component.")
void DispatchEvent(XAML_NAMESPACE.FrameworkElement view, String eventName, JSValueArgWriter eventDataArgWriter);

Expand Down
10 changes: 10 additions & 0 deletions vnext/Microsoft.ReactNative/XamlView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@

namespace Microsoft::ReactNative {

xaml::DependencyProperty ReactTagProperty() noexcept {
static xaml::DependencyProperty s_tagProperty = xaml::DependencyProperty::RegisterAttached(
L"ReactTag",
winrt::xaml_typename<int64_t>(),
winrt::xaml_typename<XamlView>(),
xaml::PropertyMetadata(winrt::box_value(InvalidTag)));

return s_tagProperty;
}

xaml::XamlRoot TryGetXamlRoot(const XamlView &view) {
xaml::XamlRoot root{nullptr};
if (auto uielement = view.try_as<xaml::UIElement>()) {
Expand Down
9 changes: 7 additions & 2 deletions vnext/Microsoft.ReactNative/XamlView.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@

#pragma once

#include <winrt/Windows.UI.Xaml.Interop.h>
#include "CppWinRTIncludes.h"

namespace Microsoft::ReactNative {

using XamlView = xaml::DependencyObject;
constexpr int64_t InvalidTag = -1;

xaml::DependencyProperty ReactTagProperty() noexcept;

inline int64_t GetTag(XamlView view) {
auto tagValue = view.ReadLocalValue(xaml::FrameworkElement::TagProperty());
auto tagValue = view.ReadLocalValue(ReactTagProperty());
if (tagValue != xaml::DependencyProperty::UnsetValue()) {
if (auto tagValueInt = tagValue.try_as<winrt::IPropertyValue>()) {
if (tagValueInt.Type() == winrt::PropertyType::Int64) {
Expand All @@ -24,6 +27,8 @@ inline int64_t GetTag(XamlView view) {
}

inline void SetTag(XamlView view, int64_t tag) {
view.SetValue(ReactTagProperty(), winrt::PropertyValue::CreateInt64(tag));
// Kept here to maintain compatibility with external ViewManagers who expect FE::Tag to contain the react tag
view.SetValue(xaml::FrameworkElement::TagProperty(), winrt::PropertyValue::CreateInt64(tag));
}

Expand All @@ -43,7 +48,7 @@ inline int64_t GetTag(winrt::IPropertyValue value) {

inline winrt::IPropertyValue GetTagAsPropertyValue(XamlView view) {
assert(view);
return view.GetValue(xaml::FrameworkElement::TagProperty()).try_as<winrt::IPropertyValue>();
return view.GetValue(ReactTagProperty()).try_as<winrt::IPropertyValue>();
}

xaml::XamlRoot TryGetXamlRoot(const XamlView &view);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
<ResolveNuGetPackages>false</ResolveNuGetPackages>
</PropertyGroup>

<Import Project="$(ReactNativeWindowsDir)\PropertySheets\NewAPIDeclarations.props" />

<Import Project="$(ReactNativeWindowsDir)\PropertySheets\Generated\PackageVersion.g.props" />
</Project>
25 changes: 25 additions & 0 deletions vnext/PropertySheets/NewAPIDeclarations.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
Defines properties/flags for customers to easily detect new APIS.
-->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<RnwReactTagAPI>true</RnwReactTagAPI>
</PropertyGroup>

<PropertyGroup>
<DefineConstants>RNW_REACTTAG_API;$(DefineConstants)</DefineConstants>
</PropertyGroup>

<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>RNW_REACTTAG_API;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Midl>
<PreprocessorDefinitions>RNW_REACTTAG_API;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</Midl>
</ItemDefinitionGroup>
</Project>

0 comments on commit 6363177

Please sign in to comment.