Skip to content

Commit

Permalink
Ensure tail spacer is not used as a scroll anchor
Browse files Browse the repository at this point in the history
We previously stopped development on the native inverted approach for
Windows that would use anchoring and flexDirection to implement a truly
reversed list (as opposed to a list flipped via transform) due to a bug
where the anchoring would cause the view to "ride" to the top of the
ScrollViewer when you scrolled into the virtualized tail spacer.

This change introduces a new View prop called `overflowAnchor` and uses
this prop to ensure that the tail spacer can never be anchored.
  • Loading branch information
rozele committed Oct 27, 2021
1 parent 85172dc commit 1c507bf
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1047,7 +1047,14 @@ class VirtualizedList extends React.PureComponent<Props, State> {
endFrame.length -
(lastFrame.offset + lastFrame.length);
cells.push(
<View key="$tail_spacer" style={{[spacerKey]: tailSpacerLength}} />,
<View
overflowAnchor={this.props.inverted ? 'none' : undefined}
key="$tail_spacer"
style={{
[spacerKey]: tailSpacerLength,
zIndex: this.props.inverted ? 1e6 : undefined,
}}
/>,
);
}
} else if (ListEmptyComponent) {
Expand Down
11 changes: 7 additions & 4 deletions vnext/Microsoft.ReactNative/Views/Impl/ScrollViewViewChanger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "pch.h"

#include <UI.Xaml.Controls.h>
#include <Views/ViewViewManager.h>
#include "ScrollViewUWPImplementation.h"
#include "ScrollViewViewChanger.h"
#include "SnapPointManagingContentControl.h"
Expand Down Expand Up @@ -79,10 +80,12 @@ void ScrollViewViewChanger::SetContentScrollAnchors(const xaml::Controls::Scroll
auto panel = snapPointManager->Content().as<xaml::Controls::Panel>();
for (auto child : panel.Children()) {
const auto childElement = child.as<xaml::UIElement>();
if (enabled) {
childElement.CanBeScrollAnchor(true);
} else {
childElement.ClearValue(xaml::UIElement::CanBeScrollAnchorProperty());
if (winrt::unbox_value<bool>(childElement.GetValue(ViewViewManager::CanBeScrollAnchorProperty()))) {
if (enabled) {
childElement.CanBeScrollAnchor(true);
} else {
childElement.ClearValue(xaml::UIElement::CanBeScrollAnchorProperty());
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "ScrollContentViewManager.h"

#include <UI.Xaml.Controls.h>
#include <Views/ViewViewManager.h>
#include "Impl/SnapPointManagingContentControl.h"
#include "ViewPanel.h"

Expand Down Expand Up @@ -33,7 +34,9 @@ void ScrollContentViewManager::AddView(const XamlView &parent, const XamlView &c
if (viewParent) {
const auto scrollViewContentControl = viewParent.as<SnapPointManagingContentControl>();
if (scrollViewContentControl->IsInverted() && scrollViewContentControl->IsContentAnchoringEnabled()) {
childElement.CanBeScrollAnchor(true);
if (winrt::unbox_value<bool>(child.GetValue(ViewViewManager::CanBeScrollAnchorProperty()))) {
childElement.CanBeScrollAnchor(true);
}
}
}

Expand Down
25 changes: 25 additions & 0 deletions vnext/Microsoft.ReactNative/Views/ViewViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,20 @@ bool TryUpdateBorderProperties(

ViewViewManager::ViewViewManager(const Mso::React::IReactContext &context) : Super(context) {}

const xaml::Interop::TypeName viewViewManagerTypeName{
winrt::hstring{L"ViewViewManager"},
xaml::Interop::TypeKind::Metadata};

/*static*/ xaml::DependencyProperty ViewViewManager::CanBeScrollAnchorProperty() {
static xaml::DependencyProperty s_canBeScrollAnchorProperty = xaml::DependencyProperty::RegisterAttached(
L"CanBeScrollAnchor",
winrt::xaml_typename<bool>(),
viewViewManagerTypeName,
winrt::PropertyMetadata(winrt::box_value(true)));

return s_canBeScrollAnchorProperty;
}

const wchar_t *ViewViewManager::GetName() const {
return L"RCTView";
}
Expand Down Expand Up @@ -383,6 +397,7 @@ void ViewViewManager::GetNativeProps(const winrt::Microsoft::ReactNative::IJSVal
winrt::Microsoft::ReactNative::WriteProperty(writer, L"focusable", L"boolean");
winrt::Microsoft::ReactNative::WriteProperty(writer, L"enableFocusRing", L"boolean");
winrt::Microsoft::ReactNative::WriteProperty(writer, L"tabIndex", L"number");
winrt::Microsoft::ReactNative::WriteProperty(writer, L"overflowAnchor", L"string");
}

bool ViewViewManager::UpdateProperty(
Expand Down Expand Up @@ -421,6 +436,16 @@ bool ViewViewManager::UpdateProperty(
} else if (propertyValue.IsNull()) {
pViewShadowNode->TabIndex(std::numeric_limits<std::int32_t>::max());
}
} else if (propertyName == "overflowAnchor") {
if (propertyValue.Type() == React::JSValueType::String) {
if (propertyValue.AsString() == "none") {
pViewShadowNode->GetView().SetValue(CanBeScrollAnchorProperty(), winrt::box_value(false));
} else {
pViewShadowNode->GetView().ClearValue(CanBeScrollAnchorProperty());
}
} else if (propertyValue.IsNull()) {
pViewShadowNode->GetView().ClearValue(CanBeScrollAnchorProperty());
}
} else {
ret = Super::UpdateProperty(nodeToUpdate, propertyName, propertyValue);
}
Expand Down
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/Views/ViewViewManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class ViewViewManager : public FrameworkElementViewManager {
using Super = FrameworkElementViewManager;

public:
static xaml::DependencyProperty CanBeScrollAnchorProperty();

ViewViewManager(const Mso::React::IReactContext &context);

const wchar_t *GetName() const override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const ReactNativeViewConfig: ViewConfig = {
onMagicTap: true,
opacity: true,
overflow: true,
overflowAnchor: true,
padding: true,
paddingBottom: true,
paddingEnd: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ type WindowsViewProps = $ReadOnly<{|
onBlur?: ?(event: FocusEvent) => mixed,
onMouseLeave?: ?(event: MouseEvent) => mixed,
onMouseEnter?: ?(event: MouseEvent) => mixed,
overflowAnchor?: 'auto' | 'none',
|}>;
// Windows]

Expand Down
5 changes: 5 additions & 0 deletions vnext/src/Libraries/Components/View/ViewWindowsProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,9 @@ export interface IViewWindowsProps extends IKeyboardProps, ViewProps {
* Event fired when the mouse enters the view
*/
onMouseEnter?: (args: IMouseEvent) => void;

/**
* Indicates that view must not be used as scroll anchor candidate.
*/
overflowAnchor?: "none" | "auto";
}

0 comments on commit 1c507bf

Please sign in to comment.