Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Panel, Modal, Overlay: Optional prop that disables bodyscroll block on touch devices #11339

Merged
merged 8 commits into from Dec 19, 2019

Conversation

laiprorus
Copy link
Contributor

@laiprorus laiprorus commented Nov 28, 2019

Pull request checklist

Description of changes

Adds optional prop "allowTouchBodyScroll" to Panel, Modal and Overlay components. This prop allows body scroll events on touch devices.
This prop disables overscroll blocks in allowScrollOnElement and doesnt call disableBodyScroll on overlay mount.
As this prop is optional, without it being set to true, components work exactly as they were before.
This change is a simple workaround for breaking behaviour described in issue: #10982

Focus areas to test

(optional)

Microsoft Reviewers: Open in CodeFlow

@msftclas
Copy link

msftclas commented Nov 28, 2019

CLA assistant check
All CLA requirements met.

@size-auditor
Copy link

size-auditor bot commented Dec 3, 2019

Asset size changes

Project Bundle Baseline Size New Size Difference
office-ui-fabric-react Dropdown 215.432 kB 215.814 kB ExceedsBaseline     382 bytes
office-ui-fabric-react Panel 184.011 kB 184.393 kB ExceedsBaseline     382 bytes
office-ui-fabric-react Dialog 192.677 kB 193.055 kB ExceedsBaseline     378 bytes
office-ui-fabric-react Modal 92.764 kB 93.142 kB ExceedsBaseline     378 bytes
office-ui-fabric-react Utilities 63.912 kB 64.059 kB ExceedsBaseline     147 bytes
office-ui-fabric-react Overlay 52.816 kB 52.938 kB ExceedsBaseline     122 bytes

ExceedsTolerance Over Tolerance (1024 B) ExceedsBaseline Over Baseline BelowBaseline Below Baseline New New Deleted  Removed 1 kB = 1000 B

Baseline commit: 0a85f594efeaf767da188c80db6bb4ae9bbd4399 (build)

@msft-github-bot
Copy link
Contributor

msft-github-bot commented Dec 4, 2019

Component Perf Analysis

No significant results to display.

All results

Scenario Master Ticks PR Ticks Status
BaseButton 777 759
BaseButton (experiments) 1058 1033
DefaultButton 1076 1053
DefaultButton (experiments) 2058 2115
DetailsRow 3282 3394
DetailsRow (fast icons) 3336 3327
DetailsRow without styles 3081 3086
DocumentCardTitle with truncation 32561 32076
MenuButton 1424 1407
MenuButton (experiments) 3675 3700
PrimaryButton 1228 1243
PrimaryButton (experiments) 2153 2106
SplitButton 2948 2892
SplitButton (experiments) 7351 7422
Stack 503 499
Stack with Intrinsic children 1153 1169
Stack with Text children 4488 4550
Text 400 379
Toggle 907 895
Toggle (experiments) 2430 2351
button 60 62

@laiprorus
Copy link
Contributor Author

Besides my original issue #10982, I found more issues that are related to those 2 functions (allowScrollOnElement and disableBodyScroll). A recent one #11343 and older one #8855.

@@ -5531,6 +5531,7 @@ export interface IModal {

// @public (undocumented)
export interface IModalProps extends React.ClassAttributes<ModalBase>, IWithResponsiveModeState, IAccessiblePopupProps {
allowIosBodyScroll?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this to allowTouchBodyScroll or something non-platform specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is good point, i will change it to allowTouchBodyScroll, i think i took the name from _disableIosBodyScroll

@@ -270,7 +281,7 @@ export class ModalBase extends BaseComponent<IModalProps, IDialogState> implemen
// Allow the user to scroll within the modal but not on the body
private _allowScrollOnModal = (elt: HTMLDivElement | null): void => {
if (elt) {
allowScrollOnElement(elt, this._events);
allowScrollOnElement(elt, this._events, this._allowIosBodyScroll);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's missing from the current allowScrollOnElement that misses the touch capability? Instead of making a separate, new method to handle touch, can we fill in the missing gaps to handle touch in allowScrollOnElement?

Copy link
Contributor Author

@laiprorus laiprorus Dec 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowScrollOnElement allows scrolling of content within the element and when top or botton of content is reached, events are blocked, this way it prevents overscrolling _preventOverscrolling where body would be scrolled. But this creates an issue, because _preventOverscrolling blocks all touch events. Example is my original issue #10982 where if you are zoomed in on mobile device and open panel, you can not pan around using content of panel because of _preventOverscrolling event handler, this way user is stuck (he cant reach close button for example) and all he can do is scroll panel content. We need to somehow allow the user to scroll the content, but also be able to pan around while zoomed in. I am not even sure it is possible to differentiate touch events that will scroll the body and from ones that will pan your zoomed in view.
My easiest and minimal to implement solution was, to just make blocking happening in _preventOverscrolling optional, that what boolean argument allowIosOverscroll is used for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this PR, i could create Panel and Modal components that dont block touch events. This is not perfect solution, as it allows body scrolling behind the overlay, which is a bit irritating, but UI is still intuitively usable. Where in current state, user is stuck in not usable UI.

@@ -18,7 +18,7 @@ export function addDirectionalKeyCode(which: number): void;
export function addElementAtIndex<T>(array: T[], index: number, itemToAdd: T): T[];

// @public
export const allowScrollOnElement: (element: HTMLElement | null, events: EventGroup) => void;
export const allowScrollOnElement: (element: HTMLElement | null, events: EventGroup, allowIosOverscroll?: boolean) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a boolean arg is usually not recommended - can we circumvent this without a boolean arg?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See: https://github.com/OfficeDev/office-ui-fabric-react/wiki/Pull-request-reviewing-guidance

Avoid using boolean params in method definitions. Boolean params usually mean a conditional if statement will be used which means the caller probably knows too much about the implementation of the method it's calling. It also makes it hard to change the method later. Make 2 methods that do different things instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originaly i tought of this, but this would create duplicate code. It would be just 2 slighty different versions of _makeElementScrollAllower. But now i see if you remove the code that is needed for _preventOverscrolling handler, not much is left and duplicate code will be minimal.

@laiprorus
Copy link
Contributor Author

laiprorus commented Dec 5, 2019

i want to investigate allowScrollOnElement more, especially the single vs multiple finger touch event. It is not clear to me, why _preventOverscrolling says that it only responds to single finger touch events, but zooming (2 finger event) is still partially blocked on Panel content.

Copy link
Contributor

@maxwellred maxwellred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@laiprorus I have pulled down this branch and attempted to use the functionality but haven't been able to observe the fix. I have attempted using the allowTouchBodyScroll={true} in chrome dev tools mobile emulator and on my samsung s7 phone using a codepen but panning and pinch zooming are still locked when the Panel is shown. Perhaps my testing environment or tools are insufficient for testing this feature? Can you provide a gif or video showing the functionality in action, thanks!

@laiprorus
Copy link
Contributor Author

laiprorus commented Dec 10, 2019

@maxwellred panel with allowTouchBodyScroll
panelwithallow
i am able to zoom in and out, scroll and pan anywhere. some actions are still a bit funny since you sometimes scroll panel content and sometimes you scroll document body behind overlay. but still, you are not getting stuck like in next 2 examples.

@laiprorus
Copy link
Contributor Author

@maxwellred here 2 examples without allowTouchBodyScroll (how it is current fabric).
in first one, i open panel fully zoomed out, then i manage to zoom in and get stuck, not being able zoom out to reach close button.
panelwithoutallow1

in second, i zoom in into button, open panel, scroll to left until no panel is in view point, and stuck again unable to zoom out or reach close button.
panelwithoutallow2

in both examples, some touch actions (inside textfield for example) could not be blocked by fabric and are executed. you can see errors in console saying fabric tried to block but it was intervented by browser.

@laiprorus
Copy link
Contributor Author

Here is code of component i used in this example:

import * as React from 'react';
import { Component, Fragment } from 'react';
import {
  Fabric,
  Text,
  TextField,
  DefaultButton,
  Panel,
  PanelType
} from 'office-ui-fabric-react';

interface DemoComponentProps { }

interface DemoComponentState {
  isOpenWith: boolean
  isOpenWithout: boolean
}

export class DemoComponent extends Component<DemoComponentProps, DemoComponentState> {
  constructor(props: DemoComponentProps) {
    super(props);
    this.state = {
      isOpenWith: false,
      isOpenWithout: false
    }
  }

  _openPanelWith = () => {
    this.setState({ isOpenWith: true });
  }

  _closePanelWith = () => {
    this.setState({ isOpenWith: false });
  }

  _openPanelWithout = () => {
    this.setState({ isOpenWithout: true });
  }

  _closePanelWithout = () => {
    this.setState({ isOpenWithout: false });
  }

  _panelContent = () => {
    return [...Array(20)].map((_, i) => <Fragment key={i}>
      <Text>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
              </Text>
      <TextField label={`TextField ${i}`} />
    </Fragment>);
  }

  render = () => {
    const { isOpenWith, isOpenWithout } = this.state;
    return <Fragment>
      <Fabric>
        <DefaultButton
          text={"Open Panel with allowTouchBodyScroll"}
          onClick={this._openPanelWith}
        />
        <br />
        <DefaultButton
          styles={{ root: { marginTop: 9 } }}
          text={"Open Panel without allowTouchBodyScroll"}
          onClick={this._openPanelWithout}
        />
        <Panel
          type={PanelType.medium}
          isOpen={isOpenWith}
          allowTouchBodyScroll
          onDismiss={this._closePanelWith}
        >
          {this._panelContent()}
        </Panel>
        <Panel
          type={PanelType.medium}
          isOpen={isOpenWithout}
          onDismiss={this._closePanelWithout}
        >
          {this._panelContent()}
        </Panel >
      </Fabric>
    </Fragment >;
  }
}

@maxwellred
Copy link
Contributor

@laiprorus This is great, thank you so much! Everything looks good to me

@laiprorus
Copy link
Contributor Author

i restricted my solution to only mounting of components. to eliminate possible side effects and bugs, like allowTouchBodyScroll prop being changed during touchmove event. I think respecting prop change after mount can implemented later?
Also i find "allow" doesnt describe well what is happening. By default those 3 components will set event blocks. With allowTouchBodyScroll it just does not set those blocks. But if you open Panel with allowTouchBodyScroll set, and then open for example another Modal without that prop, it will block all events, even panel beneath it does not. Maybe you could help change api documentation to describe the behavior better?

Copy link
Contributor

@aneeshack4 aneeshack4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making this change and being so thorough!! LGTM - @JasonGore might have more input on the Modal side

@mrphannguyen
Copy link

Hi @maxwellred
Can we have the fix in the version 6.5x.x? I have the same issue with that version.

Copy link
Member

@JasonGore JasonGore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do have some hesitation about adding another one-dimensional prop (particularly to 3 different component interfaces that already expose their props to one another.) However, given the existing props, I don't think there's a more meaningful way to implement this without refactoring the mobile approach these components take in general. Additionally, duplicating the prop in 3 components does seem to make them easier to use, as well.

@laiprorus
Copy link
Contributor Author

laiprorus commented Dec 19, 2019

Thank you everyone for your feedback and aprroval. Is there anything i could for this PR or should i just wait?

@JasonGore JasonGore merged commit 10045d1 into microsoft:master Dec 19, 2019
@msft-github-bot
Copy link
Contributor

🎉@uifabric/utilities@v7.8.0 has been released which incorporates this pull request.:tada:

Handy links:

@msft-github-bot
Copy link
Contributor

🎉office-ui-fabric-react@v7.76.0 has been released which incorporates this pull request.:tada:

Handy links:

@soryy708
Copy link

soryy708 commented Mar 2, 2021

Why is it false by default?

@laiprorus
Copy link
Contributor Author

Why is it false by default?

This was the behaviour before this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Panel, allowScrollOnElement on mobile issue.
9 participants