diff --git a/change/@uifabric-utilities-2019-11-28-22-34-32-master.json b/change/@uifabric-utilities-2019-11-28-22-34-32-master.json new file mode 100644 index 0000000000000..7eb015f398088 --- /dev/null +++ b/change/@uifabric-utilities-2019-11-28-22-34-32-master.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "scroll.ts: added allowOverscrollOnElement, alternative to allowScrollOnElement that allows body scroll on touch devices", + "packageName": "@uifabric/utilities", + "email": "dmitriy.ravdin@siemens.com", + "commit": "00c16da9f0d9ee3de29c6de6a0aa32d50397cdcc", + "date": "2019-11-28T21:34:32.268Z" +} diff --git a/change/office-ui-fabric-react-2019-11-28-22-34-32-master.json b/change/office-ui-fabric-react-2019-11-28-22-34-32-master.json new file mode 100644 index 0000000000000..7f394a630fef6 --- /dev/null +++ b/change/office-ui-fabric-react-2019-11-28-22-34-32-master.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "Modal, Panel, Overlay: added optional prop allowTouchBodyScroll that allows body scroll on touch devices", + "packageName": "office-ui-fabric-react", + "email": "dmitriy.ravdin@siemens.com", + "commit": "00c16da9f0d9ee3de29c6de6a0aa32d50397cdcc", + "date": "2019-11-28T21:34:30.900Z" +} diff --git a/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md b/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md index b723bb0f21974..fb3a15c243621 100644 --- a/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md +++ b/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.md @@ -5538,6 +5538,7 @@ export interface IModal { // @public (undocumented) export interface IModalProps extends React.ClassAttributes, IWithResponsiveModeState, IAccessiblePopupProps { + allowTouchBodyScroll?: boolean; className?: string; componentRef?: IRefObject; containerClassName?: string; @@ -5746,6 +5747,7 @@ export interface IOverlay { // @public (undocumented) export interface IOverlayProps extends React.HTMLAttributes { + allowTouchBodyScroll?: boolean; className?: string; componentRef?: IRefObject; isDarkThemed?: boolean; @@ -5825,6 +5827,7 @@ export interface IPanelHeaderRenderer extends IRenderFunction { // // @public (undocumented) export interface IPanelProps extends React.HTMLAttributes { + allowTouchBodyScroll?: boolean; className?: string; closeButtonAriaLabel?: string; // @deprecated @@ -8237,6 +8240,7 @@ export const Overlay: React.StatelessComponent; // @public (undocumented) export class OverlayBase extends BaseComponent { + constructor(props: IOverlayProps); // (undocumented) componentDidMount(): void; // (undocumented) diff --git a/packages/office-ui-fabric-react/src/components/Modal/Modal.base.tsx b/packages/office-ui-fabric-react/src/components/Modal/Modal.base.tsx index aab764bea4d55..5b49c0b2bd0b4 100644 --- a/packages/office-ui-fabric-react/src/components/Modal/Modal.base.tsx +++ b/packages/office-ui-fabric-react/src/components/Modal/Modal.base.tsx @@ -1,5 +1,13 @@ import * as React from 'react'; -import { BaseComponent, classNamesFunction, getId, allowScrollOnElement, KeyCodes, elementContains } from '../../Utilities'; +import { + BaseComponent, + classNamesFunction, + getId, + allowScrollOnElement, + allowOverscrollOnElement, + KeyCodes, + elementContains +} from '../../Utilities'; import { FocusTrapZone, IFocusTrapZone } from '../FocusTrapZone/index'; import { animationDuration } from './Modal.styles'; import { IModalProps, IModalStyleProps, IModalStyles, IModal } from './Modal.types'; @@ -47,6 +55,7 @@ export class ModalBase extends BaseComponent implemen private _scrollableContent: HTMLDivElement | null; private _lastSetX: number; private _lastSetY: number; + private _allowTouchBodyScroll: boolean; private _hasRegisteredKeyUp: boolean; constructor(props: IModalProps) { @@ -66,6 +75,9 @@ export class ModalBase extends BaseComponent implemen this._warnDeprecations({ onLayerDidMount: 'layerProps.onLayerDidMount' }); + + const { allowTouchBodyScroll = false } = this.props; + this._allowTouchBodyScroll = allowTouchBodyScroll; } // tslint:disable-next-line function-name @@ -238,7 +250,14 @@ export class ModalBase extends BaseComponent implemen onDismiss={onDismiss} >
- {!isModeless && } + {!isModeless && ( + + )} {dragOptions ? ( 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); + if (this._allowTouchBodyScroll) { + allowOverscrollOnElement(elt, this._events); + } else { + allowScrollOnElement(elt, this._events); + } } else { this._events.off(this._scrollableContent); } diff --git a/packages/office-ui-fabric-react/src/components/Modal/Modal.types.ts b/packages/office-ui-fabric-react/src/components/Modal/Modal.types.ts index 1ed289a4b0201..0aabd6eba50e6 100644 --- a/packages/office-ui-fabric-react/src/components/Modal/Modal.types.ts +++ b/packages/office-ui-fabric-react/src/components/Modal/Modal.types.ts @@ -153,6 +153,12 @@ export interface IModalProps extends React.ClassAttributes, IWithResp * The options to make the modal draggable */ dragOptions?: IDragOptions; + + /** + * Allow body scroll on content and overlay on touch devices. Changing after mounting has no effect. + * @defaultvalue false + */ + allowTouchBodyScroll?: boolean; } /** diff --git a/packages/office-ui-fabric-react/src/components/Overlay/Overlay.base.tsx b/packages/office-ui-fabric-react/src/components/Overlay/Overlay.base.tsx index 5c809c6f321e0..2b6b3841d05db 100644 --- a/packages/office-ui-fabric-react/src/components/Overlay/Overlay.base.tsx +++ b/packages/office-ui-fabric-react/src/components/Overlay/Overlay.base.tsx @@ -5,12 +5,21 @@ import { IOverlayProps, IOverlayStyleProps, IOverlayStyles } from './Overlay.typ const getClassNames = classNamesFunction(); export class OverlayBase extends BaseComponent { + private _allowTouchBodyScroll: boolean; + + constructor(props: IOverlayProps) { + super(props); + + const { allowTouchBodyScroll = false } = this.props; + this._allowTouchBodyScroll = allowTouchBodyScroll; + } + public componentDidMount(): void { - disableBodyScroll(); + !this._allowTouchBodyScroll && disableBodyScroll(); } public componentWillUnmount(): void { - enableBodyScroll(); + !this._allowTouchBodyScroll && enableBodyScroll(); } public render(): JSX.Element { diff --git a/packages/office-ui-fabric-react/src/components/Overlay/Overlay.types.ts b/packages/office-ui-fabric-react/src/components/Overlay/Overlay.types.ts index 1fd50cb6b9876..a018b119152f8 100644 --- a/packages/office-ui-fabric-react/src/components/Overlay/Overlay.types.ts +++ b/packages/office-ui-fabric-react/src/components/Overlay/Overlay.types.ts @@ -39,6 +39,12 @@ export interface IOverlayProps extends React.HTMLAttributes { isDarkThemed?: boolean; onClick?: () => void; + + /** + * Allow body scroll on touch devices. Changing after mounting has no effect. + * @defaultvalue false + */ + allowTouchBodyScroll?: boolean; } /** diff --git a/packages/office-ui-fabric-react/src/components/Panel/Panel.base.tsx b/packages/office-ui-fabric-react/src/components/Panel/Panel.base.tsx index 0b8df439c7c81..3307d7425d7fd 100644 --- a/packages/office-ui-fabric-react/src/components/Panel/Panel.base.tsx +++ b/packages/office-ui-fabric-react/src/components/Panel/Panel.base.tsx @@ -6,6 +6,7 @@ import { Popup } from '../../Popup'; import { getTheme, IconFontSizes, IProcessedStyleSet } from '../../Styling'; import { allowScrollOnElement, + allowOverscrollOnElement, BaseComponent, classNamesFunction, divProperties, @@ -45,6 +46,7 @@ export class PanelBase extends BaseComponent implement private _classNames: IProcessedStyleSet; private _scrollableContent: HTMLDivElement | null; private _animationCallback: number | null = null; + private _allowTouchBodyScroll: boolean; public static getDerivedStateFromProps(nextProps: Readonly, prevState: Readonly): Partial | null { if (nextProps.isOpen === undefined) { @@ -68,6 +70,9 @@ export class PanelBase extends BaseComponent implement constructor(props: IPanelProps) { super(props); + const { allowTouchBodyScroll = false } = this.props; + this._allowTouchBodyScroll = allowTouchBodyScroll; + this._warnDeprecations({ ignoreExternalFocusing: 'focusTrapZoneProps', forceFocusInsideTrap: 'focusTrapZoneProps', @@ -169,7 +174,7 @@ export class PanelBase extends BaseComponent implement type }); - const { _classNames } = this; + const { _classNames, _allowTouchBodyScroll } = this; let overlay; if (isBlocking && isOpen) { @@ -178,6 +183,7 @@ export class PanelBase extends BaseComponent implement className={_classNames.overlay} isDarkThemed={false} onClick={isLightDismiss ? onLightDismissClick : undefined} + allowTouchBodyScroll={_allowTouchBodyScroll} {...overlayProps} /> ); @@ -268,7 +274,11 @@ export class PanelBase extends BaseComponent implement // Allow the user to scroll within the panel but not on the body private _allowScrollOnPanel = (elt: HTMLDivElement | null): void => { if (elt) { - allowScrollOnElement(elt, this._events); + if (this._allowTouchBodyScroll) { + allowOverscrollOnElement(elt, this._events); + } else { + allowScrollOnElement(elt, this._events); + } } else { this._events.off(this._scrollableContent); } diff --git a/packages/office-ui-fabric-react/src/components/Panel/Panel.types.ts b/packages/office-ui-fabric-react/src/components/Panel/Panel.types.ts index fdf46702d8521..3df038be1458c 100644 --- a/packages/office-ui-fabric-react/src/components/Panel/Panel.types.ts +++ b/packages/office-ui-fabric-react/src/components/Panel/Panel.types.ts @@ -225,6 +225,12 @@ export interface IPanelProps extends React.HTMLAttributes { * @deprecated Serves no function. */ componentId?: string; + + /** + * Allow body scroll on content and overlay on touch devices. Changing after mounting has no effect. + * @defaultvalue false + */ + allowTouchBodyScroll?: boolean; } /** diff --git a/packages/utilities/etc/utilities.api.md b/packages/utilities/etc/utilities.api.md index 364b877dec031..fcf2be0d167ff 100644 --- a/packages/utilities/etc/utilities.api.md +++ b/packages/utilities/etc/utilities.api.md @@ -17,6 +17,9 @@ export function addDirectionalKeyCode(which: number): void; // @public export function addElementAtIndex(array: T[], index: number, itemToAdd: T): T[]; +// @public +export const allowOverscrollOnElement: (element: HTMLElement | null, events: EventGroup) => void; + // @public export const allowScrollOnElement: (element: HTMLElement | null, events: EventGroup) => void; diff --git a/packages/utilities/src/scroll.ts b/packages/utilities/src/scroll.ts index a6b30de616cda..83b0983e49727 100644 --- a/packages/utilities/src/scroll.ts +++ b/packages/utilities/src/scroll.ts @@ -84,6 +84,19 @@ const _makeElementScrollAllower = () => { */ export const allowScrollOnElement = _makeElementScrollAllower(); +/** + * Same as allowScrollOnElement but does not prevent overscrolling. + */ +export const allowOverscrollOnElement = (element: HTMLElement | null, events: EventGroup): void => { + if (!element) { + return; + } + const _allowElementScroll = (event: TouchEvent) => { + event.stopPropagation(); + }; + events.on(element, 'touchmove', _allowElementScroll, { passive: false }); +}; + const _disableIosBodyScroll = (event: TouchEvent) => { event.preventDefault(); };