From 9b0ff4bcdf940ec870fd4b0d2300003b4809b646 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 19 Jun 2019 20:28:42 +0530 Subject: [PATCH 01/48] prop to opt for placeholder vs. actual element --- .../ScrollablePane/ScrollablePane.base.tsx | 111 +++++++++++++----- .../ScrollablePane/ScrollablePane.styles.ts | 2 +- .../ScrollablePane/ScrollablePane.types.ts | 30 +++++ .../src/components/Sticky/Sticky.tsx | 101 ++++++++++++---- 4 files changed, 192 insertions(+), 52 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 3e3fdda80f52a..176971e5633c1 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -6,7 +6,8 @@ import { IScrollablePaneProps, IScrollablePaneStyleProps, IScrollablePaneStyles, - ScrollablePaneContext + ScrollablePaneContext, + PlaceholderPosition } from './ScrollablePane.types'; import { Sticky } from '../../Sticky'; @@ -87,10 +88,12 @@ export class ScrollablePaneBase extends BaseComponent { + const { stickyPosition } = sticky.props; + const { stickyAboveContainerBehavior, stickyBelowContainerBehavior } = this.props; + if ( + !stickyPosition && + stickyAboveContainerBehavior && + stickyBelowContainerBehavior && + stickyAboveContainerBehavior.notUsePlaceHolder !== stickyBelowContainerBehavior.notUsePlaceHolder + ) { + throw `If Sticky component has stickyPosition 'Both', stickyAboveContainerBehavior & stickyBelowContainerBehavior must be same`; + } + this._stickies.add(sticky); // If ScrollablePane is mounted, then sort sticky in correct place @@ -241,24 +255,28 @@ export class ScrollablePaneBase extends BaseComponent { - const { isStickyTop, isStickyBottom } = sticky.state; - if (sticky.nonStickyContent) { - if (isStickyTop) { - stickyTopHeight += sticky.nonStickyContent.offsetHeight; - } - if (isStickyBottom) { - stickyBottomHeight += sticky.nonStickyContent.offsetHeight; + const placeholderUsedForStickyContentTop = this.usePlaceholderForSticky('top'); + const placeholderUsedForStickyContentBottom = this.usePlaceholderForSticky('bottom'); + + if (placeholderUsedForStickyContentBottom || placeholderUsedForStickyContentTop) { + stickyItems.forEach((sticky: Sticky) => { + const { isStickyTop, isStickyBottom } = sticky.state; + if (sticky.nonStickyContent) { + if (isStickyTop && placeholderUsedForStickyContentTop) { + stickyTopHeight += sticky.nonStickyContent.offsetHeight; + } + if (isStickyBottom && placeholderUsedForStickyContentBottom) { + stickyBottomHeight += sticky.nonStickyContent.offsetHeight; + } + this._checkStickyStatus(sticky); } - this._checkStickyStatus(sticky); - } - }); + }); - this.setState({ - stickyTopHeight: stickyTopHeight, - stickyBottomHeight: stickyBottomHeight - }); + this.setState({ + stickyTopHeight: stickyTopHeight, + stickyBottomHeight: stickyBottomHeight + }); + } }; public notifySubscribers = (): void => { @@ -270,9 +288,9 @@ export class ScrollablePaneBase extends BaseComponent { + public getScrollPosition = (horizontal?: boolean): number => { if (this.contentContainer) { - return this.contentContainer.scrollTop; + return horizontal ? this.contentContainer.scrollLeft : this.contentContainer.scrollTop; } return 0; @@ -284,6 +302,15 @@ export class ScrollablePaneBase extends BaseComponent { + const { stickyBelowContainerBehavior, stickyAboveContainerBehavior } = this.props; + // if stickyContainerBehavior is not defined, use placeholder (default behavior) + const usePlaceholderForStickyTop: boolean = !stickyAboveContainerBehavior || !stickyAboveContainerBehavior.notUsePlaceHolder; + const usePlaceholderForStickyBottom: boolean = !stickyBelowContainerBehavior || !stickyBelowContainerBehavior.notUsePlaceHolder; + + return placeholderPosition === 'top' ? usePlaceholderForStickyTop : usePlaceholderForStickyBottom; + }; + private _getScrollablePaneContext = (): IScrollablePaneContext => { return { scrollablePane: { @@ -294,23 +321,42 @@ export class ScrollablePaneBase extends BaseComponent { + const stickyContainerHeight = this._setStickyContainerHeight(isTop) ? height : undefined; return { - height: height, + ...(stickyContainerHeight !== undefined ? { height: height } : {}), ...(getRTL() ? { right: '0', @@ -413,6 +460,10 @@ export class ScrollablePaneBase extends BaseComponent void; notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; + usePlaceholderForSticky: (placeholderPosition: PlaceholderPosition) => boolean; + getScrollPosition: (horizontal?: boolean) => number; }; } diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index 386c63c0b3036..7eb6b59400e1e 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -1,7 +1,7 @@ import * as PropTypes from 'prop-types'; import * as React from 'react'; import { BaseComponent } from '../../Utilities'; -import { IScrollablePaneContext, ScrollablePaneContext } from '../ScrollablePane/ScrollablePane.types'; +import { IScrollablePaneContext, ScrollablePaneContext, PlaceholderPosition } from '../ScrollablePane/ScrollablePane.types'; import { IStickyProps, StickyPositionType } from './Sticky.types'; export interface IStickyState { @@ -69,9 +69,19 @@ export class Sticky extends BaseComponent { public syncScroll = (container: HTMLElement): void => { const { nonStickyContent } = this; - - if (nonStickyContent && this.props.isScrollSynced) { - nonStickyContent.scrollLeft = container.scrollLeft; + const { isStickyBottom, isStickyTop } = this.state; + const { scrollablePane } = this.context; + // scroll sync is needed only if current state is sticky + if (!scrollablePane || !this.props.isScrollSynced || !(isStickyBottom || isStickyTop)) { + return; + } + const containerScrollLeft = scrollablePane.getScrollPosition(true /** horizontal */); + if (isStickyTop && !scrollablePane.usePlaceholderForSticky('top' /** StickyContentTop */) && this.stickyContentTop) { + this.stickyContentTop.children[0].scrollLeft = containerScrollLeft; + } else if (isStickyBottom && !scrollablePane.usePlaceholderForSticky('bottom' /** StickyContentBottom */) && this.stickyContentBottom) { + this.stickyContentBottom.children[0].scrollLeft = containerScrollLeft; + } else if (nonStickyContent) { + nonStickyContent.scrollLeft = containerScrollLeft; } }; @@ -135,9 +145,7 @@ export class Sticky extends BaseComponent { this.props.stickyPosition !== nextProps.stickyPosition || this.props.children !== nextProps.children || distanceFromTop !== nextState.distanceFromTop || - _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentTop) || - _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentBottom) || - _isOffsetHeightDifferent(this._nonStickyContent, this._placeHolder)) as boolean; + this._isOffsetHeightDifferent()) as boolean; } public render(): JSX.Element { @@ -150,16 +158,8 @@ export class Sticky extends BaseComponent { return (
- {this.canStickyTop && ( -
-
-
- )} - {this.canStickyBottom && ( -
-
-
- )} + {this.canStickyTop && this._getStickyContent('top', isStickyTop)} + {this.canStickyBottom && this._getStickyContent('bottom', isStickyBottom)}
{ ); } - public addSticky(stickyContent: HTMLDivElement): void { - if (this.nonStickyContent) { + public addSticky(stickyContent: HTMLDivElement, placeholderPosition: PlaceholderPosition): void { + const placeholderUsedForStickyContent = this.context.scrollablePane.usePlaceholderForSticky(placeholderPosition); + if (placeholderUsedForStickyContent && this.nonStickyContent) { stickyContent.appendChild(this.nonStickyContent); } } public resetSticky(): void { - if (this.nonStickyContent && this.placeholder) { + const placeholderUsedForContent = this.context.scrollablePane.usePlaceholderForSticky(this.canStickyTop ? 'top' : 'bottom'); + if (placeholderUsedForContent && this.nonStickyContent && this.placeholder) { this.placeholder.appendChild(this.nonStickyContent); } } @@ -193,9 +195,49 @@ export class Sticky extends BaseComponent { private _getContext = (): IScrollablePaneContext => this.context; private _getContentStyles(isSticky: boolean): React.CSSProperties { + const { scrollablePane } = this.context; + if (!scrollablePane) { + return {}; + } + const isVisible = isSticky + ? (this.canStickyTop && scrollablePane.usePlaceholderForSticky('top')) || + (this.canStickyBottom && scrollablePane.usePlaceholderForSticky('bottom')) + : true; return { backgroundColor: this.props.stickyBackgroundColor || this._getBackground(), - overflow: isSticky ? 'hidden' : '' + overflow: isSticky ? 'hidden' : '', + visibility: isVisible ? 'visible' : 'hidden' + }; + } + + private _getStickyContent(placeholderPosition: PlaceholderPosition, isSticky: boolean): JSX.Element { + const { stickyClassName, children } = this.props; + const { scrollablePane } = this.context; + // decide if actual element is to be replicated or placeholder is be to used. + const usePlaceholderForStickyContent = scrollablePane.usePlaceholderForSticky(placeholderPosition); + return ( +
+ {usePlaceholderForStickyContent ? ( +
+ ) : ( +
+ {children} +
+ )} +
+ ); + } + + private _getStickyContentStyles(isSticky: boolean): React.CSSProperties { + return { + visibility: isSticky ? 'visible' : 'hidden', + pointerEvents: isSticky ? 'auto' : 'none', + overflow: 'hidden', + backgroundColor: this.props.stickyBackgroundColor || this._getBackground() }; } @@ -207,6 +249,23 @@ export class Sticky extends BaseComponent { }; } + private _isOffsetHeightDifferent(): boolean { + const { scrollablePane } = this.context; + if (!scrollablePane) { + return false; + } + + const usePlaceholderForStickyContentTop = this.canStickyTop && scrollablePane.usePlaceholderForSticky('top'); + const usePlaceholderForStickyContentBottom = this.canStickyBottom && scrollablePane.usePlaceholderForSticky('bottom'); + + return ( + (usePlaceholderForStickyContentTop && _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentTop)) || + (usePlaceholderForStickyContentBottom && _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentBottom)) || + ((usePlaceholderForStickyContentBottom || usePlaceholderForStickyContentTop) && + _isOffsetHeightDifferent(this._nonStickyContent, this._placeHolder)) + ); + } + private _getNonStickyPlaceholderHeightAndWidth(): React.CSSProperties { const { isStickyTop, isStickyBottom } = this.state; if (isStickyTop || isStickyBottom) { From d1ee0704fc7d51e2a263a7c84bdeb3044e8f7fc9 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 19 Jun 2019 22:51:36 +0530 Subject: [PATCH 02/48] ScrollablePane doesn't listen to events or observe mutations if no Sticky --- .../ScrollablePane/ScrollablePane.base.tsx | 133 ++++++++++-------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 176971e5633c1..e1d41b7aac8c7 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -21,6 +21,8 @@ export interface IScrollablePaneState { const getClassNames = classNamesFunction(); export class ScrollablePaneBase extends BaseComponent implements IScrollablePane { + private _isMounted: boolean; + private _listeningToEvents: boolean; private _root = React.createRef(); private _stickyAboveRef = React.createRef(); private _stickyBelowRef = React.createRef(); @@ -63,8 +65,11 @@ export class ScrollablePaneBase extends BaseComponent { - // Function to check if mutation is occuring in stickyAbove or stickyBelow - function checkIfMutationIsSticky(mutationRecord: MutationRecord): boolean { - if (this.stickyAbove !== null && this.stickyBelow !== null) { - return this.stickyAbove.contains(mutationRecord.target) || this.stickyBelow.contains(mutationRecord.target); - } - return false; - } - - // Compute the scrollbar height which might have changed due to change in width of the content which might cause overflow - const scrollbarHeight = this._getScrollbarHeight(); - const scrollbarWidth = this._getScrollbarWidth(); - // check if the scroll bar height has changed and update the state so that it's postioned correctly below sticky footer - if (scrollbarHeight !== this.state.scrollbarHeight || scrollbarWidth !== this.state.scrollbarWidth) { - this.setState({ - scrollbarHeight: scrollbarHeight, - scrollbarWidth: scrollbarWidth - }); - } - - // Notify subscribers again to re-check whether Sticky should be Sticky'd or not - this.notifySubscribers(); - - // If mutation occurs in sticky header or footer, then update sticky top/bottom heights - if (mutation.some(checkIfMutationIsSticky.bind(this))) { - this.updateStickyRefHeights(); - } else { - // If mutation occurs in scrollable region, then find Sticky it belongs to and force update - const stickyList: Sticky[] = []; - this._stickies.forEach(sticky => { - if (sticky.root && sticky.root.contains(mutation[0].target)) { - stickyList.push(sticky); - } - }); - if (stickyList.length) { - stickyList.forEach(sticky => { - sticky.forceUpdate(); - }); - } - } - }); - - if (this.root) { - this._mutationObserver.observe(this.root, { - childList: true, - attributes: true, - subtree: true, - characterData: true - }); - } + if (this._stickies.size) { + this._listenToEventsAndObserveMutations(); } + this._isMounted = true; } public componentWillUnmount() { + this._isMounted = false; + this._listeningToEvents = false; this._events.off(this.contentContainer); this._events.off(window); @@ -162,8 +120,9 @@ export class ScrollablePaneBase extends BaseComponent { + if (this._isMounted) { + this._listenToEventsAndObserveMutations(); + } const { stickyPosition } = sticky.props; const { stickyAboveContainerBehavior, stickyBelowContainerBehavior } = this.props; if ( @@ -485,4 +447,63 @@ export class ScrollablePaneBase extends BaseComponent { + // Function to check if mutation is occuring in stickyAbove or stickyBelow + function checkIfMutationIsSticky(mutationRecord: MutationRecord): boolean { + if (this.stickyAbove !== null && this.stickyBelow !== null) { + return this.stickyAbove.contains(mutationRecord.target) || this.stickyBelow.contains(mutationRecord.target); + } + return false; + } + // Compute the scrollbar height which might have changed due to change in width of the content which might cause overflow + const scrollbarHeight = this._getScrollbarHeight(); + const scrollbarWidth = this._getScrollbarWidth(); + // check if the scroll bar height has changed and update the state so that it's postioned correctly below sticky footer + if (scrollbarHeight !== this.state.scrollbarHeight || scrollbarWidth !== this.state.scrollbarWidth) { + this.setState({ + scrollbarHeight: scrollbarHeight, + scrollbarWidth: scrollbarWidth + }); + } + + // Notify subscribers again to re-check whether Sticky should be Sticky'd or not + this.notifySubscribers(); + + // If mutation occurs in sticky header or footer, then update sticky top/bottom heights + if (mutation.some(checkIfMutationIsSticky.bind(this))) { + this.updateStickyRefHeights(); + } else { + // If mutation occurs in scrollable region, then find Sticky it belongs to and force update + const stickyList: Sticky[] = []; + this._stickies.forEach(sticky => { + if (sticky.root && sticky.root.contains(mutation[0].target)) { + stickyList.push(sticky); + } + }); + if (stickyList.length) { + stickyList.forEach(sticky => { + sticky.forceUpdate(); + }); + } + } + }); + + if (this.root) { + this._mutationObserver.observe(this.root, { + childList: true, + attributes: true, + subtree: true, + characterData: true + }); + } + } + } } From c243fdb381d17d1362752e2d29e855b55657dd1c Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 19 Jun 2019 23:40:38 +0530 Subject: [PATCH 03/48] caching scroll values; no scroll sync needed if component is non-sticky --- .../ScrollablePane/ScrollablePane.base.tsx | 22 +++++++++++++------ .../src/components/Sticky/Sticky.tsx | 6 ++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index e1d41b7aac8c7..6073278a6864d 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -21,6 +21,8 @@ export interface IScrollablePaneState { const getClassNames = classNamesFunction(); export class ScrollablePaneBase extends BaseComponent implements IScrollablePane { + private _scrollLeft: number; + private _scrollTop: number; private _isMounted: boolean; private _listeningToEvents: boolean; private _root = React.createRef(); @@ -43,7 +45,7 @@ export class ScrollablePaneBase extends BaseComponent { if (this.contentContainer) { - return horizontal ? this.contentContainer.scrollLeft : this.contentContainer.scrollTop; + return horizontal ? this._scrollLeft : this._scrollTop; } return 0; @@ -440,12 +442,18 @@ export class ScrollablePaneBase extends BaseComponent { - sticky.syncScroll(contentContainer); - }); + // sync Sticky scroll if contentContainer has scrolled horizontally + if (this._scrollLeft !== contentContainer.scrollLeft) { + this._scrollLeft = contentContainer.scrollLeft; + this._stickies.forEach((sticky: Sticky) => { + sticky.syncScroll(contentContainer); + }); + } + if (this._scrollTop !== contentContainer.scrollTop) { + this._scrollTop = contentContainer.scrollTop; + this._notifyThrottled(); + } } - - this._notifyThrottled(); }; private _listenToEventsAndObserveMutations() { diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index 7eb6b59400e1e..5681936e6365f 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -115,19 +115,17 @@ export class Sticky extends BaseComponent { } const { isStickyBottom, isStickyTop, distanceFromTop } = this.state; - let syncScroll: boolean = false; if (prevState.distanceFromTop !== distanceFromTop) { scrollablePane.sortSticky(this, true /*sortAgain*/); - syncScroll = true; } if (prevState.isStickyTop !== isStickyTop || prevState.isStickyBottom !== isStickyBottom) { if (this._activeElement) { this._activeElement.focus(); } scrollablePane.updateStickyRefHeights(); - syncScroll = true; } - if (syncScroll) { + // horizontal scroll has to be synced only if component is sticky + if ((isStickyBottom || isStickyTop) && scrollablePane.getScrollPosition(true /** horizontal */)) { // Sync Sticky scroll position with content container on each update scrollablePane.syncScrollSticky(this); } From b4c4979a3f9b877ae06ee3ea23e3b05cb15927bb Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 00:12:15 +0530 Subject: [PATCH 04/48] fix non sticky placeholder logic --- .../office-ui-fabric-react/src/components/Sticky/Sticky.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index 5681936e6365f..c1c8aaa720811 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -266,7 +266,9 @@ export class Sticky extends BaseComponent { private _getNonStickyPlaceholderHeightAndWidth(): React.CSSProperties { const { isStickyTop, isStickyBottom } = this.state; - if (isStickyTop || isStickyBottom) { + const usePlaceholderForStickyTop = this.context.scrollablePane.usePlaceholderForSticky('top'); + const usePlaceholderForStickyBottom = this.context.scrollablePane.usePlaceholderForSticky('bottom'); + if ((isStickyTop && usePlaceholderForStickyTop) || (isStickyBottom && usePlaceholderForStickyBottom)) { let height = 0, width = 0; // Why is placeHolder width needed? From c63cbf808b06e181ee681b1986468dbb9017f2a3 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 00:21:50 +0530 Subject: [PATCH 05/48] minimize reflow due to scrollbar height/width calculation if scrollbar visibility is set to 'always' --- .../ScrollablePane/ScrollablePane.base.tsx | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 6073278a6864d..5e4019708201d 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -7,7 +7,8 @@ import { IScrollablePaneStyleProps, IScrollablePaneStyles, ScrollablePaneContext, - PlaceholderPosition + PlaceholderPosition, + ScrollbarVisibility } from './ScrollablePane.types'; import { Sticky } from '../../Sticky'; @@ -66,7 +67,15 @@ export class ScrollablePaneBase extends BaseComponent { - const scrollbarWidth = this._getScrollbarWidth(); - const scrollbarHeight = this._getScrollbarHeight(); - + const { scrollbarHeight, scrollbarWidth } = this.state; + let newScrollbarWidth: number = scrollbarHeight; + let newScrollbarHeight: number = scrollbarWidth; + if (this.props.scrollbarVisibility !== ScrollbarVisibility.always) { + newScrollbarHeight = this._getScrollbarHeight(); + newScrollbarWidth = this._getScrollbarWidth(); + } this.setState({ - scrollbarWidth, - scrollbarHeight + scrollbarHeight: newScrollbarHeight, + scrollbarWidth: newScrollbarWidth }); this.notifySubscribers(); @@ -471,15 +484,17 @@ export class ScrollablePaneBase extends BaseComponent Date: Thu, 20 Jun 2019 00:24:29 +0530 Subject: [PATCH 06/48] horizontal & vertical scrollbars are visible independent of content overflow if scrollbar visibility is set to 'always' --- .../src/components/ScrollablePane/ScrollablePane.styles.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.styles.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.styles.ts index c4b878c83a5a6..27e50a421f015 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.styles.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.styles.ts @@ -30,7 +30,8 @@ export const getStyles = (props: IScrollablePaneStyleProps): IScrollablePaneStyl contentContainer: [ classNames.contentContainer, { - overflowY: props.scrollbarVisibility === 'always' ? 'scroll' : 'auto' + overflowY: props.scrollbarVisibility === 'always' ? 'scroll' : 'auto', + overflowX: props.scrollbarVisibility === 'always' ? 'scroll' : 'auto' }, positioningStyle ], From 62383a6e01734d396fa5f11c8f0e35198ee216b5 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 01:49:49 +0530 Subject: [PATCH 07/48] adding prop 'order' for Sticky to arrange stickies (top to bottom) --- .../ScrollablePane/ScrollablePane.base.tsx | 95 ++++++++++++++----- .../ScrollablePane/ScrollablePane.types.ts | 7 ++ .../src/components/Sticky/Sticky.types.ts | 7 ++ 3 files changed, 85 insertions(+), 24 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 5e4019708201d..598a23d0fe0bd 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -8,9 +8,10 @@ import { IScrollablePaneStyles, ScrollablePaneContext, PlaceholderPosition, - ScrollbarVisibility + ScrollbarVisibility, + StickyContainerPosition } from './ScrollablePane.types'; -import { Sticky } from '../../Sticky'; +import { Sticky, StickyPositionType } from '../../Sticky'; export interface IScrollablePaneState { stickyTopHeight: number; @@ -161,7 +162,9 @@ export class ScrollablePaneBase extends BaseComponent { - sticky.setDistanceFromTop(this.contentContainer as HTMLDivElement); + if (!this._sortBasedOnOrder(sticky.canStickyTop ? 'above' : 'below')) { + sticky.setDistanceFromTop(this.contentContainer as HTMLDivElement); + } }); } } @@ -182,15 +185,16 @@ export class ScrollablePaneBase extends BaseComponent { if (this.stickyAbove && this.stickyBelow) { + // When is sorting needed? + // 1. not a part of stickyContainer (or to be added first time) + // 2. part of stickyContainer and not sorted based on order + const isPartOfStickyAboveContainer = sticky.canStickyTop && this._stickyContainerContainsStickyContent(sticky, 'top'); + const isPartOfStickyBelowContainer = sticky.canStickyBottom && this._stickyContainerContainsStickyContent(sticky, 'bottom'); + const isPartOfStickyContainer = sticky.props.stickyPosition + ? isPartOfStickyAboveContainer !== isPartOfStickyBelowContainer + : isPartOfStickyAboveContainer && isPartOfStickyBelowContainer; + + if (isPartOfStickyContainer) { + const alreadySortedBasedOnOrder = this._sortBasedOnOrder(sticky.canStickyTop ? 'above' : 'below'); + if (alreadySortedBasedOnOrder) { + return; + } + } if (sortAgain) { this._removeStickyFromContainers(sticky); } @@ -346,6 +365,7 @@ export class ScrollablePaneBase extends BaseComponent { @@ -356,21 +376,18 @@ export class ScrollablePaneBase extends BaseComponent { - return (a.state.distanceFromTop || 0) - (b.state.distanceFromTop || 0); - }) - .filter(item => { - const stickyContent = stickyContainer === this.stickyAbove ? item.stickyContentTop : item.stickyContentBottom; - if (stickyContent) { - return stickyChildrenElements.indexOf(stickyContent) > -1; - } - }); + const sortBasedOnOrder: boolean = this._sortBasedOnOrder(isStickyAboveContainer ? 'above' : 'below'); + const stickyListSorted = this._sortStickyList(stickyList, sortBasedOnOrder).filter(item => { + const stickyContent = isStickyAboveContainer ? item.stickyContentTop : item.stickyContentBottom; + if (stickyContent) { + return stickyChildrenElements.indexOf(stickyContent) > -1; + } + }); // Get first element that has a distance from top that is further than our sticky that is being added let targetStickyToAppendBefore: Sticky | undefined = undefined; for (const i in stickyListSorted) { - if ((stickyListSorted[i].state.distanceFromTop || 0) >= (sticky.state.distanceFromTop || 0)) { + if (this._isTargetContainer(stickyListSorted[i], sticky, sortBasedOnOrder)) { targetStickyToAppendBefore = stickyListSorted[i]; break; } @@ -390,11 +407,11 @@ export class ScrollablePaneBase extends BaseComponent { - if (this.stickyAbove && sticky.stickyContentTop && this.stickyAbove.contains(sticky.stickyContentTop)) { - this.stickyAbove.removeChild(sticky.stickyContentTop); + if (this._stickyContainerContainsStickyContent(sticky, 'top')) { + this.stickyAbove!.removeChild(sticky.stickyContentTop!); } - if (this.stickyBelow && sticky.stickyContentBottom && this.stickyBelow.contains(sticky.stickyContentBottom)) { - this.stickyBelow.removeChild(sticky.stickyContentBottom); + if (this._stickyContainerContainsStickyContent(sticky, 'bottom')) { + this.stickyBelow!.removeChild(sticky.stickyContentBottom!); } }; @@ -469,6 +486,36 @@ export class ScrollablePaneBase extends BaseComponent { + return (a.props.order || 0) - (b.props.order || 0); + }); + } else { + return stickyList.sort((a, b) => { + return (a.state.distanceFromTop || 0) - (b.state.distanceFromTop || 0); + }); + } + } + + private _isTargetContainer(a: Sticky, b: Sticky, sortBasedOnOrder: boolean): boolean { + return sortBasedOnOrder + ? (a.props.order || 0) > (b.props.order || 0) + : (a.state.distanceFromTop || 0) >= (b.state.distanceFromTop || 0); + } + private _listenToEventsAndObserveMutations() { if (!this._listeningToEvents) { this._listeningToEvents = true; diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index eb0bf67e37afd..fa0f75198bb55 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -118,10 +118,17 @@ export interface IStickyContainerBehavior { * It's a trade off- cost of replicating the element vs. cost of calculating placeholder height & width. */ notUsePlaceHolder: boolean; + + /** + * If true, arranges Sticky component(s) based on Sticky's 'order' prop in ascending order. + */ + arrangeStickiesBasedOnOrder: boolean; } export type PlaceholderPosition = 'top' | 'bottom'; +export type StickyContainerPosition = 'above' | 'below'; + /** * {@docCategory ScrollablePane} */ diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.types.ts b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.types.ts index 7389525902d0d..80f67f014db20 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.types.ts +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.types.ts @@ -29,6 +29,13 @@ export interface IStickyProps extends React.Props { * @defaultvalue true */ isScrollSynced?: boolean; + + /** + * It decides the order (top to bottom) in which a Sticky element will be arranged when it is sticky, + * compared to other Sticky elements when they are sticky. When Sticky elements become sticky, + * they are sorted in ascending order based on this prop. + */ + order?: number; } export enum StickyPositionType { From 53028e40368fc752da9bfd1810fd55c18967602c Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 16:19:36 +0530 Subject: [PATCH 08/48] adding containerBehavior prop --- .../ScrollablePane/ScrollablePane.base.tsx | 89 +++++++++++++++---- .../ScrollablePane/ScrollablePane.types.ts | 35 ++++++++ .../src/components/Sticky/Sticky.tsx | 38 +++++++- 3 files changed, 143 insertions(+), 19 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 598a23d0fe0bd..1a9624ae75d14 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -9,7 +9,8 @@ import { ScrollablePaneContext, PlaceholderPosition, ScrollbarVisibility, - StickyContainerPosition + StickyContainerPosition, + StickyContainerBehaviorType } from './ScrollablePane.types'; import { Sticky, StickyPositionType } from '../../Sticky'; @@ -27,6 +28,7 @@ export class ScrollablePaneBase extends BaseComponent(); private _stickyAboveRef = React.createRef(); private _stickyBelowRef = React.createRef(); @@ -189,21 +191,43 @@ export class ScrollablePaneBase extends BaseComponent { @@ -254,10 +278,18 @@ export class ScrollablePaneBase extends BaseComponent { const { isStickyTop, isStickyBottom } = sticky.state; if (sticky.nonStickyContent) { - if (isStickyTop && placeholderUsedForStickyContentTop) { + if ( + isStickyTop && + placeholderUsedForStickyContentTop && + !this.verifyStickyContainerBehavior('above', StickyContainerBehaviorType.StickyAlways) + ) { stickyTopHeight += sticky.nonStickyContent.offsetHeight; } - if (isStickyBottom && placeholderUsedForStickyContentBottom) { + if ( + isStickyBottom && + placeholderUsedForStickyContentBottom && + !this.verifyStickyContainerBehavior('below', StickyContainerBehaviorType.StickyAlways) + ) { stickyBottomHeight += sticky.nonStickyContent.offsetHeight; } this._checkStickyStatus(sticky); @@ -303,6 +335,26 @@ export class ScrollablePaneBase extends BaseComponent { + return stickyContainerPosition === 'above' + ? (this._getStickyConatinerBehavior('above') || StickyContainerBehaviorType.Default) === stickyContainerBehavior + : (this._getStickyConatinerBehavior('below') || StickyContainerBehaviorType.Default) === stickyContainerBehavior; + }; + + public getUserInteractionStatus = (): boolean => { + return this._userInteractionStarted; + }; + + private _getStickyConatinerBehavior(stickyContainerPosition: StickyContainerPosition): StickyContainerBehaviorType | undefined { + const { stickyBelowContainerBehavior, stickyAboveContainerBehavior } = this.props; + return stickyContainerPosition === 'above' + ? stickyAboveContainerBehavior && stickyAboveContainerBehavior.containerBehavior + : stickyBelowContainerBehavior && stickyBelowContainerBehavior.containerBehavior; + } + private _getScrollablePaneContext = (): IScrollablePaneContext => { return { scrollablePane: { @@ -315,7 +367,9 @@ export class ScrollablePaneBase extends BaseComponent { - if (stickyContainer === this.stickyAbove && sticky.canStickyTop) { + if (isStickyAboveContainer && sticky.canStickyTop) { stickyList.push(stickyItem); } else if (sticky.canStickyBottom) { stickyList.push(stickyItem); @@ -396,10 +450,9 @@ export class ScrollablePaneBase extends BaseComponent void; usePlaceholderForSticky: (placeholderPosition: PlaceholderPosition) => boolean; getScrollPosition: (horizontal?: boolean) => number; + verifyStickyContainerBehavior: ( + stickyContainerPosition: StickyContainerPosition, + stickyContainerBehavior: StickyContainerBehaviorType + ) => boolean; + getUserInteractionStatus: () => boolean; }; } diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index c1c8aaa720811..b0cfc396a03df 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -1,7 +1,12 @@ import * as PropTypes from 'prop-types'; import * as React from 'react'; import { BaseComponent } from '../../Utilities'; -import { IScrollablePaneContext, ScrollablePaneContext, PlaceholderPosition } from '../ScrollablePane/ScrollablePane.types'; +import { + IScrollablePaneContext, + ScrollablePaneContext, + PlaceholderPosition, + StickyContainerBehaviorType +} from '../ScrollablePane/ScrollablePane.types'; import { IStickyProps, StickyPositionType } from './Sticky.types'; export interface IStickyState { @@ -266,8 +271,8 @@ export class Sticky extends BaseComponent { private _getNonStickyPlaceholderHeightAndWidth(): React.CSSProperties { const { isStickyTop, isStickyBottom } = this.state; - const usePlaceholderForStickyTop = this.context.scrollablePane.usePlaceholderForSticky('top'); - const usePlaceholderForStickyBottom = this.context.scrollablePane.usePlaceholderForSticky('bottom'); + const usePlaceholderForStickyTop = this.canStickyTop && this.context.scrollablePane.usePlaceholderForSticky('top'); + const usePlaceholderForStickyBottom = this.canStickyBottom && this.context.scrollablePane.usePlaceholderForSticky('bottom'); if ((isStickyTop && usePlaceholderForStickyTop) || (isStickyBottom && usePlaceholderForStickyBottom)) { let height = 0, width = 0; @@ -304,7 +309,32 @@ export class Sticky extends BaseComponent { } private _onScrollEvent = (container: HTMLElement, footerStickyContainer: HTMLElement): void => { - if (this.root && this.nonStickyContent) { + const { scrollablePane } = this.context; + if (!this.root || !this.nonStickyContent || !scrollablePane) { + return; + } + if (scrollablePane.verifyStickyContainerBehavior(this.canStickyTop ? 'above' : 'below', StickyContainerBehaviorType.StickyAlways)) { + // 1. ScrollablePane is mounted and has called notifySubscriber + // 2. stickyAlways has to re-render if mutation could 've affected it's offsetHeight. + this.setState({ + isStickyTop: this.canStickyTop, + isStickyBottom: !this.canStickyTop, + distanceFromTop: 0 // must so that sorting happens. + }); + } else if ( + !scrollablePane.getUserInteractionStatus() && + scrollablePane.verifyStickyContainerBehavior(this.canStickyTop ? 'above' : 'below', StickyContainerBehaviorType.StickyOnScroll) + ) { + // user interaction has not started + // 1. ScrollablePane is mounted and has called notifySubscriber, sort is required. + // 2. StickyOnScroll has to re-render if mutation could 've affected it's offsetHeight. + const { isStickyBottom, isStickyTop } = this.state; + scrollablePane.sortSticky(this, false); + this.setState({ + isStickyBottom: isStickyBottom, + isStickyTop: isStickyTop + }); + } else { const distanceFromTop = this._getNonStickyDistanceFromTop(container); let isStickyTop = false; let isStickyBottom = false; From 0b3a8f16f4047fdb100f3468c5fc8632864d7a86 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 16:20:11 +0530 Subject: [PATCH 09/48] adding example with new sticky/scrollablePane props --- .../ScrollablePane/ScrollablePane.doc.tsx | 10 + ...e.Sticky.Optimized.DetailsList.Example.tsx | 184 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx index ee0a7513ebe1a..c8dd709d8efe4 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx @@ -3,12 +3,15 @@ import { ScrollablePaneDefaultExample } from './examples/ScrollablePane.Default. import { IDocPageProps } from '../../common/DocPage.types'; import { ScrollablePaneDetailsListExample } from './examples/ScrollablePane.DetailsList.Example'; +import { ScrollablePaneStickyOptimizedDetailsListExample } from './examples/ScrollablePane.Sticky.Optimized.DetailsList.Example'; const ScrollablePaneDefaultExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Default.Example.tsx') as string; const ScrollablePaneDetailsListExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx') as string; +const ScrollablePaneStickyOptimizedDetailsListExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx') as string; const ScrollablePaneDefaultExampleCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Default.Example.tsx') as string; const ScrollablePaneDetailsListExampleCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx') as string; +const ScrollablePaneStickyOptimizedDetailsListExampleCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx') as string; export const ScrollablePanePageProps: IDocPageProps = { title: 'ScrollablePane', @@ -29,6 +32,13 @@ export const ScrollablePanePageProps: IDocPageProps = { codepenJS: ScrollablePaneDetailsListExampleCodepen, view: , isScrollable: false + }, + { + title: 'DetailsList Locked Header & Footer (optimized using props)', + code: ScrollablePaneStickyOptimizedDetailsListExampleCode, + view: , + codepenJS: ScrollablePaneStickyOptimizedDetailsListExampleCodepen, + isScrollable: false } ], overview: require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/docs/ScrollablePaneOverview.md'), diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx new file mode 100644 index 0000000000000..4fb791b8d08e4 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -0,0 +1,184 @@ +import * as React from 'react'; +import { TextField } from 'office-ui-fabric-react/lib/TextField'; +import { + DetailsList, + DetailsListLayoutMode, + IDetailsHeaderProps, + Selection, + IColumn, + ConstrainMode, + IDetailsFooterProps, + DetailsRow +} from 'office-ui-fabric-react/lib/DetailsList'; +import { IRenderFunction } from 'office-ui-fabric-react/lib/Utilities'; +import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Tooltip'; +import { + ScrollablePane, + ScrollbarVisibility, + IStickyContainerBehavior, + StickyContainerBehaviorType +} from 'office-ui-fabric-react/lib/ScrollablePane'; +import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; +import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'; +import { SelectionMode } from 'office-ui-fabric-react/lib/utilities/selection/index'; +import { mergeStyleSets, getTheme } from 'office-ui-fabric-react/lib/Styling'; +import { IScrollablePaneDetailsListExampleItem, IScrollablePaneDetailsListExampleState } from './ScrollablePane.DetailsList.Example'; +const classNames = mergeStyleSets({ + wrapper: { + height: '80vh', + position: 'relative' + }, + filter: { + paddingBottom: 20, + maxWidth: 300 + }, + header: { + margin: 0 + }, + row: { + display: 'inline-block' + } +}); + +const _footerItem: IScrollablePaneDetailsListExampleItem = { + key: 'footer', + test1: 'Footer 1', + test2: 'Footer 2', + test3: 'Footer 3', + test4: 'Footer 4', + test5: 'Footer 5', + test6: 'Footer 6' +}; + +export class ScrollablePaneStickyOptimizedDetailsListExample extends React.Component<{}, IScrollablePaneDetailsListExampleState> { + private _selection: Selection; + private _allItems: IScrollablePaneDetailsListExampleItem[]; + private _columns: IColumn[]; + + constructor(props: {}) { + super(props); + + this._selection = new Selection(); + + this._allItems = []; + let rowData = ''; + for (let i = 0; i < 200; i++) { + rowData = 'row ' + (i + 1).toString() + ', column '; + this._allItems.push({ + key: i, + test1: rowData + '1', + test2: rowData + '2', + test3: rowData + '3', + test4: rowData + '4', + test5: rowData + '5', + test6: rowData + '6' + }); + } + + this._columns = []; + for (let i = 1; i < 7; i++) { + this._columns.push({ + key: 'column' + i, + name: 'Test ' + i, + fieldName: 'test' + i, + minWidth: 100, + maxWidth: 200, + isResizable: true + }); + } + + this.state = { + items: this._allItems + }; + } + + public render(): JSX.Element { + const { items } = this.state; + const stickyAboveContainer: IStickyContainerBehavior = { + arrangeStickiesBasedOnOrder: true, + // use 'StickyOnScroll', 'StickyAlways' or 'Default' as per need. + containerBehavior: StickyContainerBehaviorType.StickyOnScroll, + notUsePlaceHolder: true + }; + const stickyBelowContainer: IStickyContainerBehavior = { + arrangeStickiesBasedOnOrder: true, + containerBehavior: StickyContainerBehaviorType.StickyAlways, + notUsePlaceHolder: true + }; + const stickyBackgroundColor = getTheme().palette.white; + return ( +
+ + + + + +

Item list

+
+ + + +
+
+ ); + } + + private _onFilterChange = (ev: React.FormEvent, text: string) => { + this.setState({ + items: text ? this._allItems.filter((item: IScrollablePaneDetailsListExampleItem) => hasText(item, text)) : this._allItems + }); + }; +} + +function _onItemInvoked(item: IScrollablePaneDetailsListExampleItem): void { + alert('Item invoked: ' + item.test1); +} + +function onRenderDetailsHeader(props: IDetailsHeaderProps, defaultRender?: IRenderFunction): JSX.Element { + return ( + + {defaultRender!({ + ...props, + onRenderColumnHeaderTooltip: (tooltipHostProps: ITooltipHostProps) => + })} + + ); +} + +function onRenderDetailsFooter(props: IDetailsFooterProps, defaultRender?: IRenderFunction): JSX.Element { + return ( + +
+ +
+
+ ); +} + +function hasText(item: IScrollablePaneDetailsListExampleItem, text: string): boolean { + return `${item.test1}|${item.test2}|${item.test3}|${item.test4}|${item.test5}|${item.test6}`.indexOf(text) > -1; +} From 3a99a030839efd2966d2d7b0c58fe77852526bd5 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 16:27:20 +0530 Subject: [PATCH 10/48] updating api --- .../etc/office-ui-fabric-react.api.md | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) 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 5245fbaa5bca0..d2b5a27deb9d9 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 @@ -6314,6 +6314,10 @@ export interface IScrollablePaneContext { sortSticky: (sticky: Sticky, sortAgain?: boolean) => void; notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; + usePlaceholderForSticky: (placeholderPosition: PlaceholderPosition) => boolean; + getScrollPosition: (horizontal?: boolean) => number; + verifyStickyContainerBehavior: (stickyContainerPosition: StickyContainerPosition, stickyContainerBehavior: StickyContainerBehaviorType) => boolean; + getUserInteractionStatus: () => boolean; }; } @@ -6322,8 +6326,9 @@ export interface IScrollablePaneProps extends React.HTMLAttributes; initialScrollPosition?: number; - // (undocumented) scrollbarVisibility?: ScrollbarVisibility; + stickyAboveContainerBehavior?: IStickyContainerBehavior; + stickyBelowContainerBehavior?: IStickyContainerBehavior; styles?: IStyleFunctionOrObject; theme?: ITheme; } @@ -7007,6 +7012,13 @@ export interface IStackTokens { padding?: number | string; } +// @public (undocumented) +export interface IStickyContainerBehavior { + arrangeStickiesBasedOnOrder: boolean; + containerBehavior: StickyContainerBehaviorType; + notUsePlaceHolder: boolean; +} + // @public (undocumented) export interface IStickyContext { // (undocumented) @@ -7017,6 +7029,7 @@ export interface IStickyContext { export interface IStickyProps extends React.Props { componentRef?: IRefObject; isScrollSynced?: boolean; + order?: number; stickyBackgroundColor?: string; stickyClassName?: string; stickyPosition?: StickyPositionType; @@ -8305,6 +8318,9 @@ export enum PivotLinkSize { normal = 0 } +// @public (undocumented) +export type PlaceholderPosition = 'top' | 'bottom'; + // @public (undocumented) export const PlainCard: React.StatelessComponent; @@ -8484,7 +8500,9 @@ export class ScrollablePaneBase extends BaseComponent number; + getScrollPosition: (horizontal?: boolean | undefined) => number; + // (undocumented) + getUserInteractionStatus: () => boolean; // (undocumented) notifySubscribers: () => void; // (undocumented) @@ -8511,6 +8529,10 @@ export class ScrollablePaneBase extends BaseComponent void; // (undocumented) updateStickyRefHeights: () => void; + // (undocumented) + usePlaceholderForSticky: (placeholderPosition: PlaceholderPosition) => boolean; + // (undocumented) + verifyStickyContainerBehavior: (stickyContainerPosition: StickyContainerPosition, stickyContainerBehavior: StickyContainerBehaviorType) => boolean; } // @public (undocumented) @@ -8817,7 +8839,7 @@ export const StackItem: React.StatelessComponent; export class Sticky extends BaseComponent { constructor(props: IStickyProps); // (undocumented) - addSticky(stickyContent: HTMLDivElement): void; + addSticky(stickyContent: HTMLDivElement, placeholderPosition: PlaceholderPosition): void; // (undocumented) readonly canStickyBottom: boolean; // (undocumented) @@ -8854,6 +8876,16 @@ export class Sticky extends BaseComponent { syncScroll: (container: HTMLElement) => void; } +// @public (undocumented) +export enum StickyContainerBehaviorType { + Default = 0, + StickyAlways = 2, + StickyOnScroll = 1 +} + +// @public (undocumented) +export type StickyContainerPosition = 'above' | 'below'; + // @public (undocumented) export enum StickyPositionType { // (undocumented) From 3a992f0b358b17985af12616dce801965c831ae5 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 19:35:35 +0530 Subject: [PATCH 11/48] updating snapshot for ScrollablePane and removing example --- .../ScrollablePane/ScrollablePane.doc.tsx | 11 -- .../ScrollablePane.test.tsx.snap | 7 +- ...e.Sticky.Optimized.DetailsList.Example.tsx | 184 ------------------ 3 files changed, 4 insertions(+), 198 deletions(-) delete mode 100644 packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx index c8dd709d8efe4..a086b7e7ddb62 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx @@ -3,16 +3,12 @@ import { ScrollablePaneDefaultExample } from './examples/ScrollablePane.Default. import { IDocPageProps } from '../../common/DocPage.types'; import { ScrollablePaneDetailsListExample } from './examples/ScrollablePane.DetailsList.Example'; -import { ScrollablePaneStickyOptimizedDetailsListExample } from './examples/ScrollablePane.Sticky.Optimized.DetailsList.Example'; const ScrollablePaneDefaultExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Default.Example.tsx') as string; const ScrollablePaneDetailsListExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx') as string; -const ScrollablePaneStickyOptimizedDetailsListExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx') as string; const ScrollablePaneDefaultExampleCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Default.Example.tsx') as string; const ScrollablePaneDetailsListExampleCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx') as string; -const ScrollablePaneStickyOptimizedDetailsListExampleCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx') as string; - export const ScrollablePanePageProps: IDocPageProps = { title: 'ScrollablePane', componentName: 'ScrollablePane', @@ -32,13 +28,6 @@ export const ScrollablePanePageProps: IDocPageProps = { codepenJS: ScrollablePaneDetailsListExampleCodepen, view: , isScrollable: false - }, - { - title: 'DetailsList Locked Header & Footer (optimized using props)', - code: ScrollablePaneStickyOptimizedDetailsListExampleCode, - view: , - codepenJS: ScrollablePaneStickyOptimizedDetailsListExampleCodepen, - isScrollable: false } ], overview: require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/docs/ScrollablePaneOverview.md'), diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/__snapshots__/ScrollablePane.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ScrollablePane/__snapshots__/ScrollablePane.test.tsx.snap index e2db3b0095ff4..36570cc9edfdd 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/__snapshots__/ScrollablePane.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/__snapshots__/ScrollablePane.test.tsx.snap @@ -25,6 +25,7 @@ exports[`ScrollablePane renders correctly 1`] = ` -webkit-overflow-scrolling: touch; bottom: 0px; left: 0px; + overflow-x: auto; overflow-y: auto; position: absolute; right: 0px; @@ -36,7 +37,7 @@ exports[`ScrollablePane renders correctly 1`] = ` className= { - pointer-events: auto; + pointer-events: none; position: absolute; top: 0px; } @@ -57,7 +58,7 @@ exports[`ScrollablePane renders correctly 1`] = ` { bottom: 0px; - pointer-events: auto; + pointer-events: none; position: absolute; } @media screen and (-ms-high-contrast: active){& { @@ -77,7 +78,7 @@ exports[`ScrollablePane renders correctly 1`] = ` { bottom: 0px; - pointer-events: auto; + pointer-events: none; position: absolute; width: 100%; } diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx deleted file mode 100644 index 4fb791b8d08e4..0000000000000 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import * as React from 'react'; -import { TextField } from 'office-ui-fabric-react/lib/TextField'; -import { - DetailsList, - DetailsListLayoutMode, - IDetailsHeaderProps, - Selection, - IColumn, - ConstrainMode, - IDetailsFooterProps, - DetailsRow -} from 'office-ui-fabric-react/lib/DetailsList'; -import { IRenderFunction } from 'office-ui-fabric-react/lib/Utilities'; -import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Tooltip'; -import { - ScrollablePane, - ScrollbarVisibility, - IStickyContainerBehavior, - StickyContainerBehaviorType -} from 'office-ui-fabric-react/lib/ScrollablePane'; -import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; -import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'; -import { SelectionMode } from 'office-ui-fabric-react/lib/utilities/selection/index'; -import { mergeStyleSets, getTheme } from 'office-ui-fabric-react/lib/Styling'; -import { IScrollablePaneDetailsListExampleItem, IScrollablePaneDetailsListExampleState } from './ScrollablePane.DetailsList.Example'; -const classNames = mergeStyleSets({ - wrapper: { - height: '80vh', - position: 'relative' - }, - filter: { - paddingBottom: 20, - maxWidth: 300 - }, - header: { - margin: 0 - }, - row: { - display: 'inline-block' - } -}); - -const _footerItem: IScrollablePaneDetailsListExampleItem = { - key: 'footer', - test1: 'Footer 1', - test2: 'Footer 2', - test3: 'Footer 3', - test4: 'Footer 4', - test5: 'Footer 5', - test6: 'Footer 6' -}; - -export class ScrollablePaneStickyOptimizedDetailsListExample extends React.Component<{}, IScrollablePaneDetailsListExampleState> { - private _selection: Selection; - private _allItems: IScrollablePaneDetailsListExampleItem[]; - private _columns: IColumn[]; - - constructor(props: {}) { - super(props); - - this._selection = new Selection(); - - this._allItems = []; - let rowData = ''; - for (let i = 0; i < 200; i++) { - rowData = 'row ' + (i + 1).toString() + ', column '; - this._allItems.push({ - key: i, - test1: rowData + '1', - test2: rowData + '2', - test3: rowData + '3', - test4: rowData + '4', - test5: rowData + '5', - test6: rowData + '6' - }); - } - - this._columns = []; - for (let i = 1; i < 7; i++) { - this._columns.push({ - key: 'column' + i, - name: 'Test ' + i, - fieldName: 'test' + i, - minWidth: 100, - maxWidth: 200, - isResizable: true - }); - } - - this.state = { - items: this._allItems - }; - } - - public render(): JSX.Element { - const { items } = this.state; - const stickyAboveContainer: IStickyContainerBehavior = { - arrangeStickiesBasedOnOrder: true, - // use 'StickyOnScroll', 'StickyAlways' or 'Default' as per need. - containerBehavior: StickyContainerBehaviorType.StickyOnScroll, - notUsePlaceHolder: true - }; - const stickyBelowContainer: IStickyContainerBehavior = { - arrangeStickiesBasedOnOrder: true, - containerBehavior: StickyContainerBehaviorType.StickyAlways, - notUsePlaceHolder: true - }; - const stickyBackgroundColor = getTheme().palette.white; - return ( -
- - - - - -

Item list

-
- - - -
-
- ); - } - - private _onFilterChange = (ev: React.FormEvent, text: string) => { - this.setState({ - items: text ? this._allItems.filter((item: IScrollablePaneDetailsListExampleItem) => hasText(item, text)) : this._allItems - }); - }; -} - -function _onItemInvoked(item: IScrollablePaneDetailsListExampleItem): void { - alert('Item invoked: ' + item.test1); -} - -function onRenderDetailsHeader(props: IDetailsHeaderProps, defaultRender?: IRenderFunction): JSX.Element { - return ( - - {defaultRender!({ - ...props, - onRenderColumnHeaderTooltip: (tooltipHostProps: ITooltipHostProps) => - })} - - ); -} - -function onRenderDetailsFooter(props: IDetailsFooterProps, defaultRender?: IRenderFunction): JSX.Element { - return ( - -
- -
-
- ); -} - -function hasText(item: IScrollablePaneDetailsListExampleItem, text: string): boolean { - return `${item.test1}|${item.test2}|${item.test3}|${item.test4}|${item.test5}|${item.test6}`.indexOf(text) > -1; -} From 0482462e04e6d3961d7e91a98f8f9389e3d36044 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 19:35:53 +0530 Subject: [PATCH 12/48] adding screener test --- ...ollablePane.Sticky.DetailsList.stories.tsx | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx new file mode 100644 index 0000000000000..6781ebf5a0266 --- /dev/null +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -0,0 +1,267 @@ +/*! Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. */ +import * as React from 'react'; +import Screener from 'screener-storybook/src/screener'; +import { storiesOf } from '@storybook/react'; +import { FabricDecorator } from '../utilities'; +import { TextField } from 'office-ui-fabric-react/lib/TextField'; +import { + DetailsList, + DetailsListLayoutMode, + IDetailsHeaderProps, + Selection, + IColumn, + ConstrainMode, + IDetailsFooterProps, + DetailsRow +} from 'office-ui-fabric-react/lib/DetailsList'; +import { IRenderFunction } from 'office-ui-fabric-react/lib/Utilities'; +import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Tooltip'; +import { + ScrollablePane, + ScrollbarVisibility, + IStickyContainerBehavior, + StickyContainerBehaviorType +} from 'office-ui-fabric-react/lib/ScrollablePane'; +import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; +import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'; +import { SelectionMode } from 'office-ui-fabric-react/lib/utilities/selection/index'; +import { Fabric } from 'office-ui-fabric-react/lib/Fabric'; +import { getTheme, mergeStyleSets } from 'office-ui-fabric-react/lib/Styling'; + +const classNames = mergeStyleSets({ + wrapper: { + height: '80vh', + position: 'relative' + }, + filter: { + paddingBottom: 20, + maxWidth: 300 + }, + header: { + margin: 0 + }, + row: { + display: 'inline-block' + } +}); + +const _footerItem: IScrollablePaneDetailsListExampleItem = { + key: 'footer', + test1: 'Footer 1', + test2: 'Footer 2', + test3: 'Footer 3' +}; + +interface IScrollablePaneDetailsListExampleItem { + key: number | string; + test1: string; + test2: string; + test3: string; +} + +interface IScrollablePaneDetailsListExampleState { + items: IScrollablePaneDetailsListExampleItem[]; +} + +export class ScrollablePaneDetailsListExample extends React.Component< + {}, + IScrollablePaneDetailsListExampleState +> { + private _selection: Selection; + private _allItems: IScrollablePaneDetailsListExampleItem[]; + private _columns: IColumn[]; + + constructor(props: {}) { + super(props); + + this._selection = new Selection(); + + this._allItems = []; + let rowData = ''; + for (let i = 0; i < 200; i++) { + rowData = 'row ' + (i + 1).toString() + ', column '; + this._allItems.push({ + key: i, + test1: rowData + '1', + test2: rowData + '2', + test3: rowData + '3' + }); + } + + this._columns = []; + for (let i = 1; i < 3; i++) { + this._columns.push({ + key: 'column' + i, + name: 'Test ' + i, + fieldName: 'test' + i, + minWidth: 100, + maxWidth: 200, + isResizable: true + }); + } + + this.state = { + items: this._allItems + }; + } + + public render(): JSX.Element { + const { items } = this.state; + const stickyAboveContainer: IStickyContainerBehavior = { + arrangeStickiesBasedOnOrder: true, + containerBehavior: StickyContainerBehaviorType.StickyOnScroll, + notUsePlaceHolder: true + }; + const stickyBelowContainer: IStickyContainerBehavior = { + arrangeStickiesBasedOnOrder: true, + containerBehavior: StickyContainerBehaviorType.StickyAlways, + notUsePlaceHolder: true + }; + const stickyBackgroundColor = getTheme().palette.white; + return ( +
+ +
+ + + + + +

Item list

+
+ + + +
+
+
+
+ ); + } + + private _onFilterChange = (ev: React.FormEvent, text: string) => { + this.setState({ + items: text + ? this._allItems.filter((item: IScrollablePaneDetailsListExampleItem) => + hasText(item, text) + ) + : this._allItems + }); + }; +} + +function _onItemInvoked(item: IScrollablePaneDetailsListExampleItem): void { + alert('Item invoked: ' + item.test1); +} + +function onRenderDetailsHeader( + props: IDetailsHeaderProps, + defaultRender?: IRenderFunction +): JSX.Element { + return ( + + {defaultRender!({ + ...props, + onRenderColumnHeaderTooltip: (tooltipHostProps: ITooltipHostProps) => ( + + ) + })} + + ); +} + +function onRenderDetailsFooter( + props: IDetailsFooterProps, + defaultRender?: IRenderFunction +): JSX.Element { + return ( + +
+ +
+
+ ); +} + +function hasText(item: IScrollablePaneDetailsListExampleItem, text: string): boolean { + return `${item.test1}|${item.test2}|${item.test3}`.indexOf(text) > -1; +} +const getElement = "document.getElementsByClassName('ms-ScrollablePane--contentContainer')[0]"; +const cropTo = { cropTo: '.testWrapper' }; + +storiesOf('ScrollablePane-Sticky Details List', module) + .addDecorator(FabricDecorator) + .addDecorator(story => ( + + {story()} + + )) + .addStory('ScrollablePane Details List with sticky header & footer (1)', () => ( + + )); From 8b82da30eba90baa19c8075ba2b3f730987840c7 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 19:36:51 +0530 Subject: [PATCH 13/48] change file --- ...-scrollablePane-newProps-7.x_2019-06-20-14-06.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/office-ui-fabric-react/sticky-scrollablePane-newProps-7.x_2019-06-20-14-06.json diff --git a/common/changes/office-ui-fabric-react/sticky-scrollablePane-newProps-7.x_2019-06-20-14-06.json b/common/changes/office-ui-fabric-react/sticky-scrollablePane-newProps-7.x_2019-06-20-14-06.json new file mode 100644 index 0000000000000..298b6da2ee9b8 --- /dev/null +++ b/common/changes/office-ui-fabric-react/sticky-scrollablePane-newProps-7.x_2019-06-20-14-06.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Adding new props for Sticky and ScrollablePane", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "hatrived@microsoft.com" +} \ No newline at end of file From de5093cb3c436c9b24fc61f7c32a5561059988e9 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 19:45:20 +0530 Subject: [PATCH 14/48] updating test --- .../src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 6781ebf5a0266..f693843fe5108 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -89,7 +89,7 @@ export class ScrollablePaneDetailsListExample extends React.Component< } this._columns = []; - for (let i = 1; i < 3; i++) { + for (let i = 1; i < 4; i++) { this._columns.push({ key: 'column' + i, name: 'Test ' + i, From e63a6dc75913ad7517bb614cf4e558cd609bb268 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 20 Jun 2019 22:50:54 +0530 Subject: [PATCH 15/48] updating screener --- .../src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index f693843fe5108..afb544891cde5 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -124,13 +124,13 @@ export class ScrollablePaneDetailsListExample extends React.Component< height: '80vh', position: 'relative', maxHeight: 'inherit', - width: '700px' + width: '900px' }} >
Date: Fri, 21 Jun 2019 18:26:41 +0530 Subject: [PATCH 16/48] read scrollbar height/width from local storage --- .../ScrollablePane/ScrollablePane.base.tsx | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 1a9624ae75d14..672b8fc7e3882 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -24,6 +24,7 @@ export interface IScrollablePaneState { const getClassNames = classNamesFunction(); export class ScrollablePaneBase extends BaseComponent implements IScrollablePane { + private static readonly _scrollbarHeightKey = 'ScrollablePaneBase_ScrollbarHeight'; private _scrollLeft: number; private _scrollTop: number; private _isMounted: boolean; @@ -74,9 +75,10 @@ export class ScrollablePaneBase extends BaseComponent { const { contentContainer } = this; From 1b70cd3062b55a6535387f8eba379746db4b83b7 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Fri, 21 Jun 2019 20:20:39 +0530 Subject: [PATCH 17/48] read scrollbar height/width if available before mounting --- .../ScrollablePane/ScrollablePane.base.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 672b8fc7e3882..13b0f61530211 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -43,12 +43,12 @@ export class ScrollablePaneBase extends BaseComponent(); this._stickies = new Set(); - + const { scrollbarVisibility } = this.props; this.state = { stickyTopHeight: 0, stickyBottomHeight: 0, - scrollbarWidth: 0, - scrollbarHeight: 0 + scrollbarWidth: scrollbarVisibility === ScrollbarVisibility.always ? this._getScrollbarHeightFromLocalStorage(true) : 0, + scrollbarHeight: scrollbarVisibility === ScrollbarVisibility.always ? this._getScrollbarHeightFromLocalStorage(true) : 0 }; this._scrollLeft = this._scrollTop = 0; this._notifyThrottled = this._async.throttle(this.notifySubscribers, 50); @@ -75,7 +75,7 @@ export class ScrollablePaneBase extends BaseComponent Date: Mon, 24 Jun 2019 21:21:37 +0530 Subject: [PATCH 18/48] adding example --- .../src/components/ComponentExamples.test.tsx | 3 +- .../ScrollablePane/ScrollablePane.doc.tsx | 11 + ...e.Sticky.Optimized.DetailsList.Example.tsx | 198 ++++++++++++++++++ 3 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx diff --git a/packages/office-ui-fabric-react/src/components/ComponentExamples.test.tsx b/packages/office-ui-fabric-react/src/components/ComponentExamples.test.tsx index f27f295187219..9f26f0b16bc67 100644 --- a/packages/office-ui-fabric-react/src/components/ComponentExamples.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComponentExamples.test.tsx @@ -79,7 +79,8 @@ const excludedExampleFiles: string[] = [ 'Picker.CustomResult.Example.tsx', 'ScrollablePane.Default.Example.tsx', 'ScrollablePane.DetailsList.Example.tsx', - 'SelectedPeopleList.Basic.Example.tsx' + 'SelectedPeopleList.Basic.Example.tsx', + 'ScrollablePane.Sticky.Optimized.DetailsList' ]; declare const global: any; diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx index a086b7e7ddb62..4c9976f06ffaf 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.doc.tsx @@ -3,12 +3,16 @@ import { ScrollablePaneDefaultExample } from './examples/ScrollablePane.Default. import { IDocPageProps } from '../../common/DocPage.types'; import { ScrollablePaneDetailsListExample } from './examples/ScrollablePane.DetailsList.Example'; +import { ScrollablePaneStickyOptimizedDetailsList } from './examples/ScrollablePane.Sticky.Optimized.DetailsList.Example'; const ScrollablePaneDefaultExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Default.Example.tsx') as string; const ScrollablePaneDetailsListExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx') as string; +const ScrollablePaneStickyOptimizedDetailsListCode = require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx') as string; const ScrollablePaneDefaultExampleCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Default.Example.tsx') as string; const ScrollablePaneDetailsListExampleCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx') as string; +const ScrollablePaneStickyOptimizedDetailsListCodepen = require('!@uifabric/codepen-loader!office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx') as string; + export const ScrollablePanePageProps: IDocPageProps = { title: 'ScrollablePane', componentName: 'ScrollablePane', @@ -28,6 +32,13 @@ export const ScrollablePanePageProps: IDocPageProps = { codepenJS: ScrollablePaneDetailsListExampleCodepen, view: , isScrollable: false + }, + { + title: 'Details List Locked Header & Footer (Optimized for PLT)', + code: ScrollablePaneStickyOptimizedDetailsListCode, + codepenJS: ScrollablePaneStickyOptimizedDetailsListCodepen, + view: , + isScrollable: false } ], overview: require('!raw-loader!office-ui-fabric-react/src/components/ScrollablePane/docs/ScrollablePaneOverview.md'), diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx new file mode 100644 index 0000000000000..651a2b4bb01e4 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -0,0 +1,198 @@ +import * as React from 'react'; +import { TextField } from 'office-ui-fabric-react/lib/TextField'; +import { + DetailsList, + DetailsListLayoutMode, + IDetailsHeaderProps, + Selection, + IColumn, + ConstrainMode, + IDetailsFooterProps, + DetailsRow +} from 'office-ui-fabric-react/lib/DetailsList'; +import { IRenderFunction } from 'office-ui-fabric-react/lib/Utilities'; +import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Tooltip'; +import { + ScrollablePane, + ScrollbarVisibility, + IStickyContainerBehavior, + StickyContainerBehaviorType +} from 'office-ui-fabric-react/lib/ScrollablePane'; +import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; +import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'; +import { SelectionMode } from 'office-ui-fabric-react/lib/utilities/selection/index'; +import { mergeStyleSets, getTheme } from 'office-ui-fabric-react/lib/Styling'; + +const classNames = mergeStyleSets({ + wrapper: { + height: '80vh', + position: 'relative' + }, + filter: { + paddingBottom: 20, + maxWidth: 300 + }, + header: { + margin: 0 + }, + row: { + display: 'inline-block' + } +}); + +const _footerItem: IScrollablePaneDetailsListExampleItem = { + key: 'footer', + test1: 'Footer 1', + test2: 'Footer 2', + test3: 'Footer 3', + test4: 'Footer 4', + test5: 'Footer 5', + test6: 'Footer 6' +}; + +export interface IScrollablePaneDetailsListExampleItem { + key: number | string; + test1: string; + test2: string; + test3: string; + test4: string; + test5: string; + test6: string; +} + +export interface IScrollablePaneDetailsListExampleState { + items: IScrollablePaneDetailsListExampleItem[]; +} + +export class ScrollablePaneStickyOptimizedDetailsList extends React.Component<{}, IScrollablePaneDetailsListExampleState> { + private _selection: Selection; + private _allItems: IScrollablePaneDetailsListExampleItem[]; + private _columns: IColumn[]; + + constructor(props: {}) { + super(props); + + this._selection = new Selection(); + + this._allItems = []; + let rowData = ''; + for (let i = 0; i < 200; i++) { + rowData = 'row ' + (i + 1).toString() + ', column '; + this._allItems.push({ + key: i, + test1: rowData + '1', + test2: rowData + '2', + test3: rowData + '3', + test4: rowData + '4', + test5: rowData + '5', + test6: rowData + '6' + }); + } + + this._columns = []; + for (let i = 1; i < 7; i++) { + this._columns.push({ + key: 'column' + i, + name: 'Test ' + i, + fieldName: 'test' + i, + minWidth: 100, + maxWidth: 200, + isResizable: true + }); + } + + this.state = { + items: this._allItems + }; + } + + public render(): JSX.Element { + const { items } = this.state; + const stickyAboveContainer: IStickyContainerBehavior = { + arrangeStickiesBasedOnOrder: true, + // use 'StickyOnScroll', 'StickyAlways' or 'Default' as per need. + containerBehavior: StickyContainerBehaviorType.StickyOnScroll, + notUsePlaceHolder: true + }; + const stickyBelowContainer: IStickyContainerBehavior = { + arrangeStickiesBasedOnOrder: true, + containerBehavior: StickyContainerBehaviorType.StickyAlways, + notUsePlaceHolder: true + }; + const stickyBackgroundColor = getTheme().palette.white; + return ( +
+ + + + + +

Item list

+
+ + + +
+
+ ); + } + + private _onFilterChange = (ev: React.FormEvent, text: string) => { + this.setState({ + items: text ? this._allItems.filter((item: IScrollablePaneDetailsListExampleItem) => hasText(item, text)) : this._allItems + }); + }; +} + +function _onItemInvoked(item: IScrollablePaneDetailsListExampleItem): void { + alert('Item invoked: ' + item.test1); +} + +function onRenderDetailsHeader(props: IDetailsHeaderProps, defaultRender?: IRenderFunction): JSX.Element { + return ( + + {defaultRender!({ + ...props, + onRenderColumnHeaderTooltip: (tooltipHostProps: ITooltipHostProps) => + })} + + ); +} + +function onRenderDetailsFooter(props: IDetailsFooterProps, defaultRender?: IRenderFunction): JSX.Element { + return ( + +
+ +
+
+ ); +} + +function hasText(item: IScrollablePaneDetailsListExampleItem, text: string): boolean { + return `${item.test1}|${item.test2}|${item.test3}|${item.test4}|${item.test5}|${item.test6}`.indexOf(text) > -1; +} From 303b2d1f3064e4fba1841c05f2ab6e78e14da963 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 3 Jul 2019 19:53:43 +0530 Subject: [PATCH 19/48] updating change files --- .../office-ui-fabric-react-2019-06-20-19-36-43.json | 8 ++++++++ .../buttonSemanticSlots_2019-07-01-23-45.json | 11 ----------- .../react-cards/cardImports_2019-07-02-00-22.json | 11 ----------- .../styling/buttonSemanticSlots_2019-07-01-23-45.json | 11 ----------- .../buttonSemanticSlots_2019-07-01-23-45.json | 11 ----------- .../panel-remove-windowheight_2019-07-02-01-32.json | 11 ----------- ...-scrollablePane-newProps-7.x_2019-06-20-14-06.json | 11 ----------- 7 files changed, 8 insertions(+), 66 deletions(-) create mode 100644 change/office-ui-fabric-react-2019-06-20-19-36-43.json delete mode 100644 common/changes/@uifabric/experiments/buttonSemanticSlots_2019-07-01-23-45.json delete mode 100644 common/changes/@uifabric/react-cards/cardImports_2019-07-02-00-22.json delete mode 100644 common/changes/@uifabric/styling/buttonSemanticSlots_2019-07-01-23-45.json delete mode 100644 common/changes/office-ui-fabric-react/buttonSemanticSlots_2019-07-01-23-45.json delete mode 100644 common/changes/office-ui-fabric-react/panel-remove-windowheight_2019-07-02-01-32.json delete mode 100644 common/changes/office-ui-fabric-react/sticky-scrollablePane-newProps-7.x_2019-06-20-14-06.json diff --git a/change/office-ui-fabric-react-2019-06-20-19-36-43.json b/change/office-ui-fabric-react-2019-06-20-19-36-43.json new file mode 100644 index 0000000000000..50183cbb0eaf3 --- /dev/null +++ b/change/office-ui-fabric-react-2019-06-20-19-36-43.json @@ -0,0 +1,8 @@ +{ + "comment": "Adding new props for Sticky and ScrollablePane", + "type": "minor", + "packageName": "office-ui-fabric-react", + "email": "hatrived@microsoft.com", + "commit": "8b82da30eba90baa19c8075ba2b3f730987840c7", + "date": "2019-06-20T14:06:43.573Z" +} diff --git a/common/changes/@uifabric/experiments/buttonSemanticSlots_2019-07-01-23-45.json b/common/changes/@uifabric/experiments/buttonSemanticSlots_2019-07-01-23-45.json deleted file mode 100644 index e6327cb6a4c3c..0000000000000 --- a/common/changes/@uifabric/experiments/buttonSemanticSlots_2019-07-01-23-45.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "@uifabric/experiments", - "comment": "Button: Updating Button styles to use corresponding semanticColors instead of palette.", - "type": "patch" - } - ], - "packageName": "@uifabric/experiments", - "email": "Humberto.Morimoto@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/@uifabric/react-cards/cardImports_2019-07-02-00-22.json b/common/changes/@uifabric/react-cards/cardImports_2019-07-02-00-22.json deleted file mode 100644 index 78c14dfd64043..0000000000000 --- a/common/changes/@uifabric/react-cards/cardImports_2019-07-02-00-22.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "@uifabric/react-cards", - "comment": "Card: Importing from office-ui-fabric-react directly instead of from office-ui-fabric-react/lib/...", - "type": "patch" - } - ], - "packageName": "@uifabric/react-cards", - "email": "Humberto.Morimoto@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/@uifabric/styling/buttonSemanticSlots_2019-07-01-23-45.json b/common/changes/@uifabric/styling/buttonSemanticSlots_2019-07-01-23-45.json deleted file mode 100644 index f8bd67328171a..0000000000000 --- a/common/changes/@uifabric/styling/buttonSemanticSlots_2019-07-01-23-45.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "@uifabric/styling", - "comment": "Updating button-related semanticColors to match Fluent styles.", - "type": "patch" - } - ], - "packageName": "@uifabric/styling", - "email": "Humberto.Morimoto@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/buttonSemanticSlots_2019-07-01-23-45.json b/common/changes/office-ui-fabric-react/buttonSemanticSlots_2019-07-01-23-45.json deleted file mode 100644 index d5769783f6101..0000000000000 --- a/common/changes/office-ui-fabric-react/buttonSemanticSlots_2019-07-01-23-45.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "office-ui-fabric-react", - "comment": "Button: Updating Button styles to use corresponding semanticColors instead of palette.", - "type": "patch" - } - ], - "packageName": "office-ui-fabric-react", - "email": "Humberto.Morimoto@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/panel-remove-windowheight_2019-07-02-01-32.json b/common/changes/office-ui-fabric-react/panel-remove-windowheight_2019-07-02-01-32.json deleted file mode 100644 index 3cff133205d2e..0000000000000 --- a/common/changes/office-ui-fabric-react/panel-remove-windowheight_2019-07-02-01-32.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "office-ui-fabric-react", - "comment": "Panel: remove window.innerHeight from styles", - "type": "patch" - } - ], - "packageName": "office-ui-fabric-react", - "email": "kakje@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/sticky-scrollablePane-newProps-7.x_2019-06-20-14-06.json b/common/changes/office-ui-fabric-react/sticky-scrollablePane-newProps-7.x_2019-06-20-14-06.json deleted file mode 100644 index 298b6da2ee9b8..0000000000000 --- a/common/changes/office-ui-fabric-react/sticky-scrollablePane-newProps-7.x_2019-06-20-14-06.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "office-ui-fabric-react", - "comment": "Adding new props for Sticky and ScrollablePane", - "type": "minor" - } - ], - "packageName": "office-ui-fabric-react", - "email": "hatrived@microsoft.com" -} \ No newline at end of file From 59b19f0b5be21a11d99b86ec0893292d050c8a49 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 3 Jul 2019 19:53:59 +0530 Subject: [PATCH 20/48] fix ssr test for ScrollablePane --- .../src/components/ScrollablePane/ScrollablePane.base.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 13b0f61530211..53da01e6879b2 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -530,7 +530,7 @@ export class ScrollablePaneBase extends BaseComponent Date: Wed, 3 Jul 2019 20:07:57 +0530 Subject: [PATCH 21/48] changing prop names --- .../ScrollablePane/ScrollablePane.base.tsx | 14 ++++++------ .../ScrollablePane/ScrollablePane.doc.tsx | 2 +- .../ScrollablePane/ScrollablePane.types.ts | 4 ++-- ...e.Sticky.Optimized.DetailsList.Example.tsx | 4 ++-- .../src/components/Sticky/Sticky.tsx | 22 ++++++++++++++----- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 53da01e6879b2..a1465e5714c59 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -329,10 +329,10 @@ export class ScrollablePaneBase extends BaseComponent { - const { stickyBelowContainerBehavior, stickyAboveContainerBehavior } = this.props; + const { stickyHeaderContainerBehavior, stickyFooterContainerBehavior } = this.props; // if stickyContainerBehavior is not defined, use placeholder (default behavior) - const usePlaceholderForStickyTop: boolean = !stickyAboveContainerBehavior || !stickyAboveContainerBehavior.notUsePlaceHolder; - const usePlaceholderForStickyBottom: boolean = !stickyBelowContainerBehavior || !stickyBelowContainerBehavior.notUsePlaceHolder; + const usePlaceholderForStickyTop: boolean = !stickyHeaderContainerBehavior || !stickyHeaderContainerBehavior.notUsePlaceHolder; + const usePlaceholderForStickyBottom: boolean = !stickyFooterContainerBehavior || !stickyFooterContainerBehavior.notUsePlaceHolder; return placeholderPosition === 'top' ? usePlaceholderForStickyTop : usePlaceholderForStickyBottom; }; @@ -351,10 +351,10 @@ export class ScrollablePaneBase extends BaseComponent { @@ -563,7 +563,7 @@ export class ScrollablePaneBase extends BaseComponent, diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index 5b7bb34418c43..3021095c152ec 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -55,12 +55,12 @@ export interface IScrollablePaneProps extends React.HTMLAttributes diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index b0cfc396a03df..38a6a379fff22 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -73,7 +73,7 @@ export class Sticky extends BaseComponent { } public syncScroll = (container: HTMLElement): void => { - const { nonStickyContent } = this; + const { nonStickyContent, stickyContentTop, stickyContentBottom } = this; const { isStickyBottom, isStickyTop } = this.state; const { scrollablePane } = this.context; // scroll sync is needed only if current state is sticky @@ -81,10 +81,22 @@ export class Sticky extends BaseComponent { return; } const containerScrollLeft = scrollablePane.getScrollPosition(true /** horizontal */); - if (isStickyTop && !scrollablePane.usePlaceholderForSticky('top' /** StickyContentTop */) && this.stickyContentTop) { - this.stickyContentTop.children[0].scrollLeft = containerScrollLeft; - } else if (isStickyBottom && !scrollablePane.usePlaceholderForSticky('bottom' /** StickyContentBottom */) && this.stickyContentBottom) { - this.stickyContentBottom.children[0].scrollLeft = containerScrollLeft; + if ( + isStickyTop && + !scrollablePane.usePlaceholderForSticky('top' /** StickyContentTop */) && + stickyContentTop && + stickyContentTop.children && + stickyContentTop.children.length > 0 + ) { + stickyContentTop.children[0].scrollLeft = containerScrollLeft; + } else if ( + isStickyBottom && + !scrollablePane.usePlaceholderForSticky('bottom' /** StickyContentBottom */) && + stickyContentBottom && + stickyContentBottom.children && + stickyContentBottom.children.length > 0 + ) { + stickyContentBottom.children[0].scrollLeft = containerScrollLeft; } else if (nonStickyContent) { nonStickyContent.scrollLeft = containerScrollLeft; } From 2e920148cba1ce4dd82b205af9903ad2fe325d58 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 3 Jul 2019 20:16:05 +0530 Subject: [PATCH 22/48] updating api --- .../office-ui-fabric-react/etc/office-ui-fabric-react.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 89657adcf4d13..7c69c5e3cf496 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 @@ -6340,8 +6340,8 @@ export interface IScrollablePaneProps extends React.HTMLAttributes; initialScrollPosition?: number; scrollbarVisibility?: ScrollbarVisibility; - stickyAboveContainerBehavior?: IStickyContainerBehavior; - stickyBelowContainerBehavior?: IStickyContainerBehavior; + stickyFooterContainerBehavior?: IStickyContainerBehavior; + stickyHeaderContainerBehavior?: IStickyContainerBehavior; styles?: IStyleFunctionOrObject; theme?: ITheme; } From a88c38c7b583306016a8ac35dca0f6517d3751b7 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 3 Jul 2019 20:29:38 +0530 Subject: [PATCH 23/48] updating snapshots --- .../src/tests/__snapshots__/ComponentExamples.test.tsx.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/a11y-tests/src/tests/__snapshots__/ComponentExamples.test.tsx.snap b/apps/a11y-tests/src/tests/__snapshots__/ComponentExamples.test.tsx.snap index e3ed0ea8acd8a..313109488e9ac 100644 --- a/apps/a11y-tests/src/tests/__snapshots__/ComponentExamples.test.tsx.snap +++ b/apps/a11y-tests/src/tests/__snapshots__/ComponentExamples.test.tsx.snap @@ -7315,6 +7315,8 @@ exports[`a11y test checks accessibility of ScrollablePane (ScrollablePane.Defaul exports[`a11y test checks accessibility of ScrollablePane (ScrollablePane.DetailsList.Example) 1`] = `Array []`; +exports[`a11y test checks accessibility of ScrollablePane (ScrollablePane.Sticky.Optimized.DetailsList.Example) 1`] = `Array []`; + exports[`a11y test checks accessibility of SearchBox (SearchBox.CustomIcon.Example) 1`] = `Array []`; exports[`a11y test checks accessibility of SearchBox (SearchBox.Disabled.Example) 1`] = `Array []`; From d7a1753b167c3602c698ee26560995f6deb76c3c Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 3 Jul 2019 21:55:05 +0530 Subject: [PATCH 24/48] updating props name --- .../stories/ScrollablePane.Sticky.DetailsList.stories.tsx | 4 ++-- .../src/components/ScrollablePane/ScrollablePane.base.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index afb544891cde5..4e15a650d59b8 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -132,8 +132,8 @@ export class ScrollablePaneDetailsListExample extends React.Component< Date: Thu, 4 Jul 2019 01:55:21 +0530 Subject: [PATCH 25/48] fixing screeners --- .../stories/ScrollablePane.Sticky.DetailsList.stories.tsx | 6 +++--- .../src/components/ScrollablePane/ScrollablePane.base.tsx | 8 ++++---- .../examples/ScrollablePane.DetailsList.Example.tsx | 2 +- ...crollablePane.Sticky.Optimized.DetailsList.Example.tsx | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 4e15a650d59b8..f5eb0bc149015 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -52,14 +52,14 @@ const _footerItem: IScrollablePaneDetailsListExampleItem = { test3: 'Footer 3' }; -interface IScrollablePaneDetailsListExampleItem { +export interface IScrollablePaneDetailsListExampleItem { key: number | string; test1: string; test2: string; test3: string; } -interface IScrollablePaneDetailsListExampleState { +export interface IScrollablePaneDetailsListExampleState { items: IScrollablePaneDetailsListExampleItem[]; } @@ -158,7 +158,7 @@ export class ScrollablePaneDetailsListExample extends React.Component< items={items} columns={this._columns} setKey="set" - layoutMode={DetailsListLayoutMode.fixedColumns} + layoutMode={DetailsListLayoutMode.justified} constrainMode={ConstrainMode.unconstrained} onRenderDetailsHeader={onRenderDetailsHeader} onRenderDetailsFooter={onRenderDetailsFooter} diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index dd831d8b712dd..3bfaea95c0560 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -194,7 +194,7 @@ export class ScrollablePaneBase extends BaseComponent { return stickyContainerPosition === 'above' - ? (this._getStickyConatinerBehavior('above') || StickyContainerBehaviorType.Default) === stickyContainerBehavior - : (this._getStickyConatinerBehavior('below') || StickyContainerBehaviorType.Default) === stickyContainerBehavior; + ? (this._getStickyContainerBehavior('above') || StickyContainerBehaviorType.Default) === stickyContainerBehavior + : (this._getStickyContainerBehavior('below') || StickyContainerBehaviorType.Default) === stickyContainerBehavior; }; public getUserInteractionStatus = (): boolean => { return this._userInteractionStarted; }; - private _getStickyConatinerBehavior(stickyContainerPosition: StickyContainerPosition): StickyContainerBehaviorType | undefined { + private _getStickyContainerBehavior(stickyContainerPosition: StickyContainerPosition): StickyContainerBehaviorType | undefined { const { stickyFooterContainerBehavior, stickyHeaderContainerBehavior } = this.props; return stickyContainerPosition === 'above' ? stickyHeaderContainerBehavior && stickyHeaderContainerBehavior.containerBehavior diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx index 8a120440a948f..946912d3effed 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx @@ -116,7 +116,7 @@ export class ScrollablePaneDetailsListExample extends React.Component<{}, IScrol items={items} columns={this._columns} setKey="set" - layoutMode={DetailsListLayoutMode.fixedColumns} + layoutMode={DetailsListLayoutMode.justified} constrainMode={ConstrainMode.unconstrained} onRenderDetailsHeader={onRenderDetailsHeader} onRenderDetailsFooter={onRenderDetailsFooter} diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx index 9bcafcb383823..30973ff596aa8 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -138,7 +138,7 @@ export class ScrollablePaneStickyOptimizedDetailsList extends React.Component<{} items={items} columns={this._columns} setKey="set" - layoutMode={DetailsListLayoutMode.fixedColumns} + layoutMode={DetailsListLayoutMode.justified} constrainMode={ConstrainMode.unconstrained} onRenderDetailsHeader={onRenderDetailsHeader} onRenderDetailsFooter={onRenderDetailsFooter} From 424f814c2fafd329e5ad88a7c4b89e52323e51f0 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Tue, 9 Jul 2019 17:46:00 +0530 Subject: [PATCH 26/48] updating prop name --- ...ollablePane.Sticky.DetailsList.stories.tsx | 4 +-- .../etc/office-ui-fabric-react.api.md | 2 +- .../ScrollablePane/ScrollablePane.base.tsx | 31 ++++++++++++------- .../ScrollablePane/ScrollablePane.types.ts | 2 +- ...e.Sticky.Optimized.DetailsList.Example.tsx | 4 +-- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index f5eb0bc149015..97ea2b7da0e18 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -110,12 +110,12 @@ export class ScrollablePaneDetailsListExample extends React.Component< const stickyAboveContainer: IStickyContainerBehavior = { arrangeStickiesBasedOnOrder: true, containerBehavior: StickyContainerBehaviorType.StickyOnScroll, - notUsePlaceHolder: true + disablePlaceHolder: true }; const stickyBelowContainer: IStickyContainerBehavior = { arrangeStickiesBasedOnOrder: true, containerBehavior: StickyContainerBehaviorType.StickyAlways, - notUsePlaceHolder: true + disablePlaceHolder: true }; const stickyBackgroundColor = getTheme().palette.white; return ( 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 7c69c5e3cf496..5f2dd0e825952 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 @@ -7029,7 +7029,7 @@ export interface IStackTokens { export interface IStickyContainerBehavior { arrangeStickiesBasedOnOrder: boolean; containerBehavior: StickyContainerBehaviorType; - notUsePlaceHolder: boolean; + disablePlaceHolder: boolean; } // @public (undocumented) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 3bfaea95c0560..1729097ecc338 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -47,8 +47,8 @@ export class ScrollablePaneBase extends BaseComponent { const { stickyHeaderContainerBehavior, stickyFooterContainerBehavior } = this.props; // if stickyContainerBehavior is not defined, use placeholder (default behavior) - const usePlaceholderForStickyTop: boolean = !stickyHeaderContainerBehavior || !stickyHeaderContainerBehavior.notUsePlaceHolder; - const usePlaceholderForStickyBottom: boolean = !stickyFooterContainerBehavior || !stickyFooterContainerBehavior.notUsePlaceHolder; + const usePlaceholderForStickyTop: boolean = !stickyHeaderContainerBehavior || !stickyHeaderContainerBehavior.disablePlaceHolder; + const usePlaceholderForStickyBottom: boolean = !stickyFooterContainerBehavior || !stickyFooterContainerBehavior.disablePlaceHolder; return placeholderPosition === 'top' ? usePlaceholderForStickyTop : usePlaceholderForStickyBottom; }; @@ -528,18 +533,20 @@ export class ScrollablePaneBase extends BaseComponent { diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index 3021095c152ec..09074ae28b071 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -117,7 +117,7 @@ export interface IStickyContainerBehavior { * Calculating placeholder height & width could be an expensive operation. * It's a trade off- cost of replicating the element vs. cost of calculating placeholder height & width. */ - notUsePlaceHolder: boolean; + disablePlaceHolder: boolean; /** * If true, arranges Sticky component(s) based on Sticky's 'order' prop in ascending order. diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx index 30973ff596aa8..311869d4236e5 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -112,12 +112,12 @@ export class ScrollablePaneStickyOptimizedDetailsList extends React.Component<{} arrangeStickiesBasedOnOrder: true, // use 'StickyOnScroll', 'StickyAlways' or 'Default' as per need. containerBehavior: StickyContainerBehaviorType.StickyOnScroll, - notUsePlaceHolder: true + disablePlaceHolder: true }; const stickyBelowContainer: IStickyContainerBehavior = { arrangeStickiesBasedOnOrder: true, containerBehavior: StickyContainerBehaviorType.StickyAlways, - notUsePlaceHolder: true + disablePlaceHolder: true }; const stickyBackgroundColor = getTheme().palette.white; return ( From 0d5a4aa04b68c4b7c085edb87f16eaf2a478c5cd Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 18 Jul 2019 19:41:18 +0530 Subject: [PATCH 27/48] changing props for scrollablePane --- ...ollablePane.Sticky.DetailsList.stories.tsx | 16 +-- .../etc/office-ui-fabric-react.api.md | 29 ++--- .../ScrollablePane/ScrollablePane.base.tsx | 118 ++++++++---------- .../ScrollablePane/ScrollablePane.types.ts | 42 ++----- ...e.Sticky.Optimized.DetailsList.Example.tsx | 23 +--- .../src/components/Sticky/Sticky.tsx | 73 +++++------ 6 files changed, 106 insertions(+), 195 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 97ea2b7da0e18..05f576760dc1c 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -19,7 +19,6 @@ import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Toolt import { ScrollablePane, ScrollbarVisibility, - IStickyContainerBehavior, StickyContainerBehaviorType } from 'office-ui-fabric-react/lib/ScrollablePane'; import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; @@ -107,16 +106,6 @@ export class ScrollablePaneDetailsListExample extends React.Component< public render(): JSX.Element { const { items } = this.state; - const stickyAboveContainer: IStickyContainerBehavior = { - arrangeStickiesBasedOnOrder: true, - containerBehavior: StickyContainerBehaviorType.StickyOnScroll, - disablePlaceHolder: true - }; - const stickyBelowContainer: IStickyContainerBehavior = { - arrangeStickiesBasedOnOrder: true, - containerBehavior: StickyContainerBehaviorType.StickyAlways, - disablePlaceHolder: true - }; const stickyBackgroundColor = getTheme().palette.white; return (
void; notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; - usePlaceholderForSticky: (placeholderPosition: PlaceholderPosition) => boolean; + usePlaceholderForSticky: () => boolean; getScrollPosition: (horizontal?: boolean) => number; - verifyStickyContainerBehavior: (stickyContainerPosition: StickyContainerPosition, stickyContainerBehavior: StickyContainerBehaviorType) => boolean; + verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: StickyContainerBehaviorType) => boolean; getUserInteractionStatus: () => boolean; }; } @@ -6346,9 +6346,11 @@ export interface IScrollablePaneProps extends React.HTMLAttributes; initialScrollPosition?: number; + // (undocumented) + optimizeForPerformace?: boolean; scrollbarVisibility?: ScrollbarVisibility; - stickyFooterContainerBehavior?: IStickyContainerBehavior; - stickyHeaderContainerBehavior?: IStickyContainerBehavior; + stickyFooterContainerBehavior?: StickyContainerBehaviorType; + stickyHeaderContainerBehavior?: StickyContainerBehaviorType; styles?: IStyleFunctionOrObject; theme?: ITheme; } @@ -7030,13 +7032,6 @@ export interface IStackTokens { padding?: number | string; } -// @public (undocumented) -export interface IStickyContainerBehavior { - arrangeStickiesBasedOnOrder: boolean; - containerBehavior: StickyContainerBehaviorType; - disablePlaceHolder: boolean; -} - // @public (undocumented) export interface IStickyContext { // (undocumented) @@ -8340,9 +8335,6 @@ export enum PivotLinkSize { normal = 0 } -// @public (undocumented) -export type PlaceholderPosition = 'top' | 'bottom'; - // @public (undocumented) export const PlainCard: React.StatelessComponent; @@ -8552,9 +8544,9 @@ export class ScrollablePaneBase extends BaseComponent void; // (undocumented) - usePlaceholderForSticky: (placeholderPosition: PlaceholderPosition) => boolean; + usePlaceholderForSticky: () => boolean; // (undocumented) - verifyStickyContainerBehavior: (stickyContainerPosition: StickyContainerPosition, stickyContainerBehavior: StickyContainerBehaviorType) => boolean; + verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: StickyContainerBehaviorType) => boolean; } // @public (undocumented) @@ -8863,7 +8855,7 @@ export const StackItem: React.StatelessComponent; export class Sticky extends BaseComponent { constructor(props: IStickyProps); // (undocumented) - addSticky(stickyContent: HTMLDivElement, placeholderPosition: PlaceholderPosition): void; + addSticky(stickyContent: HTMLDivElement): void; // (undocumented) readonly canStickyBottom: boolean; // (undocumented) @@ -8907,9 +8899,6 @@ export enum StickyContainerBehaviorType { StickyOnScroll = 1 } -// @public (undocumented) -export type StickyContainerPosition = 'above' | 'below'; - // @public (undocumented) export enum StickyPositionType { // (undocumented) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 1729097ecc338..3c7960388d407 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -7,9 +7,7 @@ import { IScrollablePaneStyleProps, IScrollablePaneStyles, ScrollablePaneContext, - PlaceholderPosition, ScrollbarVisibility, - StickyContainerPosition, StickyContainerBehaviorType } from './ScrollablePane.types'; import { Sticky, StickyPositionType } from '../../Sticky'; @@ -171,7 +169,7 @@ export class ScrollablePaneBase extends BaseComponent { - if (!this._sortBasedOnOrder(sticky.canStickyTop ? 'above' : 'below')) { + if (!this._sortBasedOnOrder()) { sticky.setDistanceFromTop(this.contentContainer as HTMLDivElement); } }); @@ -194,31 +192,27 @@ export class ScrollablePaneBase extends BaseComponent { const { isStickyTop, isStickyBottom } = sticky.state; if (sticky.nonStickyContent) { - if ( - isStickyTop && - placeholderUsedForStickyContentTop && - !this.verifyStickyContainerBehavior('above', StickyContainerBehaviorType.StickyAlways) - ) { + if (isStickyTop && !this.verifyStickyContainerBehavior(StickyPositionType.Header, StickyContainerBehaviorType.StickyAlways)) { stickyTopHeight += sticky.nonStickyContent.offsetHeight; } - if ( - isStickyBottom && - placeholderUsedForStickyContentBottom && - !this.verifyStickyContainerBehavior('below', StickyContainerBehaviorType.StickyAlways) - ) { + if (isStickyBottom && !this.verifyStickyContainerBehavior(StickyPositionType.Footer, StickyContainerBehaviorType.StickyAlways)) { stickyBottomHeight += sticky.nonStickyContent.offsetHeight; } this._checkStickyStatus(sticky); @@ -333,33 +320,27 @@ export class ScrollablePaneBase extends BaseComponent { - const { stickyHeaderContainerBehavior, stickyFooterContainerBehavior } = this.props; - // if stickyContainerBehavior is not defined, use placeholder (default behavior) - const usePlaceholderForStickyTop: boolean = !stickyHeaderContainerBehavior || !stickyHeaderContainerBehavior.disablePlaceHolder; - const usePlaceholderForStickyBottom: boolean = !stickyFooterContainerBehavior || !stickyFooterContainerBehavior.disablePlaceHolder; - - return placeholderPosition === 'top' ? usePlaceholderForStickyTop : usePlaceholderForStickyBottom; + public usePlaceholderForSticky = (): boolean => { + return !this.props.optimizeForPerformace; }; public verifyStickyContainerBehavior = ( - stickyContainerPosition: StickyContainerPosition, + stickyContainerPosition: StickyPositionType, stickyContainerBehavior: StickyContainerBehaviorType ): boolean => { - return stickyContainerPosition === 'above' - ? (this._getStickyContainerBehavior('above') || StickyContainerBehaviorType.Default) === stickyContainerBehavior - : (this._getStickyContainerBehavior('below') || StickyContainerBehaviorType.Default) === stickyContainerBehavior; + return stickyContainerPosition === StickyPositionType.Header + ? (this._getStickyContainerBehavior(StickyPositionType.Header) || StickyContainerBehaviorType.Default) === stickyContainerBehavior + : (this._getStickyContainerBehavior(StickyPositionType.Footer) || StickyContainerBehaviorType.Default) === stickyContainerBehavior; }; public getUserInteractionStatus = (): boolean => { return this._userInteractionStarted; }; - private _getStickyContainerBehavior(stickyContainerPosition: StickyContainerPosition): StickyContainerBehaviorType | undefined { - const { stickyFooterContainerBehavior, stickyHeaderContainerBehavior } = this.props; - return stickyContainerPosition === 'above' - ? stickyHeaderContainerBehavior && stickyHeaderContainerBehavior.containerBehavior - : stickyFooterContainerBehavior && stickyFooterContainerBehavior.containerBehavior; + private _getStickyContainerBehavior(stickyContainerPosition: StickyPositionType): StickyContainerBehaviorType | undefined { + return stickyContainerPosition === StickyPositionType.Header + ? this.props.stickyHeaderContainerBehavior + : this.props.stickyFooterContainerBehavior; } private _getScrollablePaneContext = (): IScrollablePaneContext => { @@ -382,8 +363,9 @@ export class ScrollablePaneBase extends BaseComponent { const stickyContent = isStickyAboveContainer ? item.stickyContentTop : item.stickyContentBottom; if (stickyContent) { @@ -467,10 +449,10 @@ export class ScrollablePaneBase extends BaseComponent { - if (this._stickyContainerContainsStickyContent(sticky, 'top')) { + if (this._stickyContainerContainsStickyContent(sticky, StickyPositionType.Header)) { this.stickyAbove!.removeChild(sticky.stickyContentTop!); } - if (this._stickyContainerContainsStickyContent(sticky, 'bottom')) { + if (this._stickyContainerContainsStickyContent(sticky, StickyPositionType.Footer)) { this.stickyBelow!.removeChild(sticky.stickyContentBottom!); } }; @@ -516,10 +498,10 @@ export class ScrollablePaneBase extends BaseComponent void; notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; - usePlaceholderForSticky: (placeholderPosition: PlaceholderPosition) => boolean; + usePlaceholderForSticky: () => boolean; getScrollPosition: (horizontal?: boolean) => number; verifyStickyContainerBehavior: ( - stickyContainerPosition: StickyContainerPosition, + stickyContainerPosition: StickyPositionType, stickyContainerBehavior: StickyContainerBehaviorType ) => boolean; getUserInteractionStatus: () => boolean; diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx index 311869d4236e5..52c4f0f57a00b 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -12,12 +12,7 @@ import { } from 'office-ui-fabric-react/lib/DetailsList'; import { IRenderFunction } from 'office-ui-fabric-react/lib/Utilities'; import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Tooltip'; -import { - ScrollablePane, - ScrollbarVisibility, - IStickyContainerBehavior, - StickyContainerBehaviorType -} from 'office-ui-fabric-react/lib/ScrollablePane'; +import { ScrollablePane, ScrollbarVisibility, StickyContainerBehaviorType } from 'office-ui-fabric-react/lib/ScrollablePane'; import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'; import { SelectionMode } from 'office-ui-fabric-react/lib/utilities/selection/index'; @@ -108,24 +103,14 @@ export class ScrollablePaneStickyOptimizedDetailsList extends React.Component<{} public render(): JSX.Element { const { items } = this.state; - const stickyAboveContainer: IStickyContainerBehavior = { - arrangeStickiesBasedOnOrder: true, - // use 'StickyOnScroll', 'StickyAlways' or 'Default' as per need. - containerBehavior: StickyContainerBehaviorType.StickyOnScroll, - disablePlaceHolder: true - }; - const stickyBelowContainer: IStickyContainerBehavior = { - arrangeStickiesBasedOnOrder: true, - containerBehavior: StickyContainerBehaviorType.StickyAlways, - disablePlaceHolder: true - }; const stickyBackgroundColor = getTheme().palette.white; return (
diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index 38a6a379fff22..64ef1b3a69e3c 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -1,12 +1,7 @@ import * as PropTypes from 'prop-types'; import * as React from 'react'; import { BaseComponent } from '../../Utilities'; -import { - IScrollablePaneContext, - ScrollablePaneContext, - PlaceholderPosition, - StickyContainerBehaviorType -} from '../ScrollablePane/ScrollablePane.types'; +import { IScrollablePaneContext, ScrollablePaneContext, StickyContainerBehaviorType } from '../ScrollablePane/ScrollablePane.types'; import { IStickyProps, StickyPositionType } from './Sticky.types'; export interface IStickyState { @@ -81,22 +76,10 @@ export class Sticky extends BaseComponent { return; } const containerScrollLeft = scrollablePane.getScrollPosition(true /** horizontal */); - if ( - isStickyTop && - !scrollablePane.usePlaceholderForSticky('top' /** StickyContentTop */) && - stickyContentTop && - stickyContentTop.children && - stickyContentTop.children.length > 0 - ) { - stickyContentTop.children[0].scrollLeft = containerScrollLeft; - } else if ( - isStickyBottom && - !scrollablePane.usePlaceholderForSticky('bottom' /** StickyContentBottom */) && - stickyContentBottom && - stickyContentBottom.children && - stickyContentBottom.children.length > 0 - ) { - stickyContentBottom.children[0].scrollLeft = containerScrollLeft; + const usePlaceholderForSticky = scrollablePane.usePlaceholderForSticky(); + const stickyContent = isStickyTop ? stickyContentTop : stickyContentBottom; + if (!usePlaceholderForSticky && stickyContent && stickyContent.children && stickyContent.children.length > 0) { + stickyContent.children[0].scrollLeft = containerScrollLeft; } else if (nonStickyContent) { nonStickyContent.scrollLeft = containerScrollLeft; } @@ -173,8 +156,8 @@ export class Sticky extends BaseComponent { return (
- {this.canStickyTop && this._getStickyContent('top', isStickyTop)} - {this.canStickyBottom && this._getStickyContent('bottom', isStickyBottom)} + {this.canStickyTop && this._getStickyContent(StickyPositionType.Header, isStickyTop)} + {this.canStickyBottom && this._getStickyContent(StickyPositionType.Footer, isStickyBottom)}
{ ); } - public addSticky(stickyContent: HTMLDivElement, placeholderPosition: PlaceholderPosition): void { - const placeholderUsedForStickyContent = this.context.scrollablePane.usePlaceholderForSticky(placeholderPosition); + public addSticky(stickyContent: HTMLDivElement): void { + const placeholderUsedForStickyContent = this.context.scrollablePane.usePlaceholderForSticky(); if (placeholderUsedForStickyContent && this.nonStickyContent) { stickyContent.appendChild(this.nonStickyContent); } } public resetSticky(): void { - const placeholderUsedForContent = this.context.scrollablePane.usePlaceholderForSticky(this.canStickyTop ? 'top' : 'bottom'); + const placeholderUsedForContent = this.context.scrollablePane.usePlaceholderForSticky(); if (placeholderUsedForContent && this.nonStickyContent && this.placeholder) { this.placeholder.appendChild(this.nonStickyContent); } @@ -214,10 +197,8 @@ export class Sticky extends BaseComponent { if (!scrollablePane) { return {}; } - const isVisible = isSticky - ? (this.canStickyTop && scrollablePane.usePlaceholderForSticky('top')) || - (this.canStickyBottom && scrollablePane.usePlaceholderForSticky('bottom')) - : true; + const usePlaceholderForSticky = scrollablePane.usePlaceholderForSticky(); + const isVisible = isSticky ? (this.canStickyTop && usePlaceholderForSticky) || (this.canStickyBottom && usePlaceholderForSticky) : true; return { backgroundColor: this.props.stickyBackgroundColor || this._getBackground(), overflow: isSticky ? 'hidden' : '', @@ -225,14 +206,14 @@ export class Sticky extends BaseComponent { }; } - private _getStickyContent(placeholderPosition: PlaceholderPosition, isSticky: boolean): JSX.Element { + private _getStickyContent(stickyPositionType: StickyPositionType, isSticky: boolean): JSX.Element { const { stickyClassName, children } = this.props; const { scrollablePane } = this.context; // decide if actual element is to be replicated or placeholder is be to used. - const usePlaceholderForStickyContent = scrollablePane.usePlaceholderForSticky(placeholderPosition); + const usePlaceholderForStickyContent = scrollablePane.usePlaceholderForSticky(); return (
@@ -269,10 +250,9 @@ export class Sticky extends BaseComponent { if (!scrollablePane) { return false; } - - const usePlaceholderForStickyContentTop = this.canStickyTop && scrollablePane.usePlaceholderForSticky('top'); - const usePlaceholderForStickyContentBottom = this.canStickyBottom && scrollablePane.usePlaceholderForSticky('bottom'); - + const usePlaceholderForSticky = scrollablePane.usePlaceholderForSticky(); + const usePlaceholderForStickyContentTop = this.canStickyTop && usePlaceholderForSticky; + const usePlaceholderForStickyContentBottom = this.canStickyBottom && usePlaceholderForSticky; return ( (usePlaceholderForStickyContentTop && _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentTop)) || (usePlaceholderForStickyContentBottom && _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentBottom)) || @@ -283,8 +263,9 @@ export class Sticky extends BaseComponent { private _getNonStickyPlaceholderHeightAndWidth(): React.CSSProperties { const { isStickyTop, isStickyBottom } = this.state; - const usePlaceholderForStickyTop = this.canStickyTop && this.context.scrollablePane.usePlaceholderForSticky('top'); - const usePlaceholderForStickyBottom = this.canStickyBottom && this.context.scrollablePane.usePlaceholderForSticky('bottom'); + const usePlaceholderForSticky = this.context.scrollablePane.usePlaceholderForSticky(); + const usePlaceholderForStickyTop = this.canStickyTop && usePlaceholderForSticky; + const usePlaceholderForStickyBottom = this.canStickyBottom && usePlaceholderForSticky; if ((isStickyTop && usePlaceholderForStickyTop) || (isStickyBottom && usePlaceholderForStickyBottom)) { let height = 0, width = 0; @@ -325,7 +306,12 @@ export class Sticky extends BaseComponent { if (!this.root || !this.nonStickyContent || !scrollablePane) { return; } - if (scrollablePane.verifyStickyContainerBehavior(this.canStickyTop ? 'above' : 'below', StickyContainerBehaviorType.StickyAlways)) { + if ( + scrollablePane.verifyStickyContainerBehavior( + this.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, + StickyContainerBehaviorType.StickyAlways + ) + ) { // 1. ScrollablePane is mounted and has called notifySubscriber // 2. stickyAlways has to re-render if mutation could 've affected it's offsetHeight. this.setState({ @@ -335,7 +321,10 @@ export class Sticky extends BaseComponent { }); } else if ( !scrollablePane.getUserInteractionStatus() && - scrollablePane.verifyStickyContainerBehavior(this.canStickyTop ? 'above' : 'below', StickyContainerBehaviorType.StickyOnScroll) + scrollablePane.verifyStickyContainerBehavior( + this.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, + StickyContainerBehaviorType.StickyOnScroll + ) ) { // user interaction has not started // 1. ScrollablePane is mounted and has called notifySubscriber, sort is required. From 565f13810b92b5f4bc1a6d02e4d1ab8d79eca04b Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Fri, 19 Jul 2019 18:37:20 +0530 Subject: [PATCH 28/48] fix scrollablePane example --- .../examples/ScrollablePane.DetailsList.Example.tsx | 9 +++++++++ ...rollablePane.Sticky.Optimized.DetailsList.Example.tsx | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx index 946912d3effed..7deed000a546f 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.DetailsList.Example.tsx @@ -23,6 +23,13 @@ const classNames = mergeStyleSets({ height: '80vh', position: 'relative' }, + detailsListRows: { + selectors: { + '.ms-DetailsRow': { + animation: 'none' + } + } + }, filter: { paddingBottom: 20, maxWidth: 300 @@ -113,6 +120,7 @@ export class ScrollablePaneDetailsListExample extends React.Component<{}, IScrol
Date: Tue, 23 Jul 2019 15:45:04 +0530 Subject: [PATCH 29/48] updating screener --- ...ollablePane.Sticky.DetailsList.stories.tsx | 81 +++++++++++++++---- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 05f576760dc1c..7815d61b257f3 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -19,7 +19,8 @@ import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Toolt import { ScrollablePane, ScrollbarVisibility, - StickyContainerBehaviorType + StickyContainerBehaviorType, + IScrollablePaneProps } from 'office-ui-fabric-react/lib/ScrollablePane'; import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'; @@ -48,7 +49,10 @@ const _footerItem: IScrollablePaneDetailsListExampleItem = { key: 'footer', test1: 'Footer 1', test2: 'Footer 2', - test3: 'Footer 3' + test3: 'Footer 3', + test4: 'Footer 4', + test5: 'Footer 5', + test6: 'Footer 6' }; export interface IScrollablePaneDetailsListExampleItem { @@ -56,44 +60,55 @@ export interface IScrollablePaneDetailsListExampleItem { test1: string; test2: string; test3: string; + test4: string; + test5: string; + test6: string; } export interface IScrollablePaneDetailsListExampleState { items: IScrollablePaneDetailsListExampleItem[]; } - +export interface IScrollablePaneDetailsListExampleProps { + scrollablePaneProps: IScrollablePaneProps; + itemCount: number; + numberOfColumns: number; +} export class ScrollablePaneDetailsListExample extends React.Component< - {}, + IScrollablePaneDetailsListExampleProps, IScrollablePaneDetailsListExampleState > { private _selection: Selection; private _allItems: IScrollablePaneDetailsListExampleItem[]; private _columns: IColumn[]; - constructor(props: {}) { + constructor(props: IScrollablePaneDetailsListExampleProps) { super(props); this._selection = new Selection(); this._allItems = []; + const { numberOfColumns, itemCount } = this.props; let rowData = ''; - for (let i = 0; i < 200; i++) { + for (let i = 0; i < itemCount; i++) { rowData = 'row ' + (i + 1).toString() + ', column '; this._allItems.push({ key: i, test1: rowData + '1', test2: rowData + '2', - test3: rowData + '3' + test3: rowData + '3', + test4: rowData + '4', + test5: rowData + '5', + test6: rowData + '6' }); } this._columns = []; - for (let i = 1; i < 4; i++) { + for (let i = 1; i <= numberOfColumns; i++) { this._columns.push({ key: 'column' + i, name: 'Test ' + i, fieldName: 'test' + i, - minWidth: 100, + minWidth: 150, maxWidth: 200, isResizable: true }); @@ -120,10 +135,7 @@ export class ScrollablePaneDetailsListExample extends React.Component<
)) .addStory('ScrollablePane Details List with sticky header & footer (1)', () => ( - + + )) + .addStory('ScrollablePane Details List with sticky header & footer (2)', () => ( + + )) + .addStory('ScrollablePane Details List with sticky header & footer (2)', () => ( + )); From 7ede1f0f38ea291346be15260e0226ca93d2c0ab Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Tue, 23 Jul 2019 16:10:08 +0530 Subject: [PATCH 30/48] Change files --- ...07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 change/office-ui-fabric-react-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json diff --git a/change/office-ui-fabric-react-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json b/change/office-ui-fabric-react-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json new file mode 100644 index 0000000000000..f0358454c1015 --- /dev/null +++ b/change/office-ui-fabric-react-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json @@ -0,0 +1,8 @@ +{ + "comment": "Merge branch 'master' of https://github.com/OfficeDev/office-ui-fabric-react into sticky-scrollablePane-newProps-7.x", + "type": "patch", + "packageName": "office-ui-fabric-react", + "email": "hatrived@microsoft.com", + "commit": "f37e9d70f3d9eaa5ba7f643236766c36b0c3fb4c", + "date": "2019-07-23T10:40:08.114Z" +} From 176921300264a19ea34350478a2ff6c63feb91fd Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Tue, 23 Jul 2019 16:12:06 +0530 Subject: [PATCH 31/48] updating change file --- ...-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change/office-ui-fabric-react-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json b/change/office-ui-fabric-react-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json index f0358454c1015..460133d56a182 100644 --- a/change/office-ui-fabric-react-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json +++ b/change/office-ui-fabric-react-2019-07-23-16-10-08-sticky-scrollablePane-newProps-7.x.json @@ -1,5 +1,5 @@ { - "comment": "Merge branch 'master' of https://github.com/OfficeDev/office-ui-fabric-react into sticky-scrollablePane-newProps-7.x", + "comment": "fixing ScrollablePane example", "type": "patch", "packageName": "office-ui-fabric-react", "email": "hatrived@microsoft.com", From 904b2ea0ad77d9c700e81e736a9cdee8ebec6aec Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Mon, 5 Aug 2019 18:14:43 +0530 Subject: [PATCH 32/48] adding documentation, removing enum for StickyContainerBehaviorType, updating code owner and updating screener test --- .github/CODEOWNERS | 2 + ...ollablePane.Sticky.DetailsList.stories.tsx | 72 +++++++++++++------ .../etc/office-ui-fabric-react.api.md | 18 ++--- .../ScrollablePane/ScrollablePane.base.tsx | 26 +++---- .../ScrollablePane/ScrollablePane.types.ts | 20 ++++-- ...e.Sticky.Optimized.DetailsList.Example.tsx | 70 ++++++++++++------ .../src/components/Sticky/Sticky.tsx | 21 +++--- 7 files changed, 146 insertions(+), 83 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fd2d230ab2be6..9974431224422 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -125,6 +125,7 @@ Popup/ @JasonGore ProgressIndicator/ @aneeshack4 Rating/ @jdhuntington ResizeGroup/ @micahgodbolt +ScrollablePane/ @tharshada SearchBox/ @ecraig12345 Separator/ @natalieethell Shimmer/ @Vitalius1 @atneik @@ -133,6 +134,7 @@ Slider/ @aneeshack4 SpinButton/ @aneeshack4 Spinner/ @aneeshack4 Stack/ @khmakoto @kkjeer +Sticky/ @tharshada SwatchColorPicker/ @ecraig12345 TeachingBubble/ @micahgodbolt Text/ @natalieethell diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 7815d61b257f3..8955f093d1166 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -30,18 +30,28 @@ import { getTheme, mergeStyleSets } from 'office-ui-fabric-react/lib/Styling'; const classNames = mergeStyleSets({ wrapper: { - height: '80vh', + height: '860px', position: 'relative' }, filter: { - paddingBottom: 20, + padding: '0 32px 20px 32px', maxWidth: 300 }, header: { - margin: 0 + margin: 0, + padding: '0 32px' }, row: { display: 'inline-block' + }, + list: { + padding: '0 32px' + }, + stickyDetailsHeader: { + padding: '0 32px' + }, + stickyDetailsFooter: { + padding: '0 32px' } }); @@ -125,7 +135,7 @@ export class ScrollablePaneDetailsListExample extends React.Component< return (

Item list

- - - +
+ + + +
@@ -203,6 +215,7 @@ function onRenderDetailsHeader( isScrollSynced={true} order={3} stickyBackgroundColor={getTheme().palette.white} + stickyClassName={classNames.stickyDetailsHeader} > {defaultRender!({ ...props, @@ -224,6 +237,7 @@ function onRenderDetailsFooter( isScrollSynced={true} order={1} stickyBackgroundColor={getTheme().palette.white} + stickyClassName={classNames.stickyDetailsFooter} >
@@ -301,7 +327,7 @@ storiesOf('ScrollablePane-Sticky Details List', module) scrollablePaneProps={scrollablePaneProps} /> )) - .addStory('ScrollablePane Details List with sticky header & footer (2)', () => ( + .addStory('ScrollablePane Details List with sticky header & footer (3)', () => ( ; initialScrollPosition?: number; - // (undocumented) optimizeForPerformace?: boolean; scrollbarVisibility?: ScrollbarVisibility; stickyFooterContainerBehavior?: StickyContainerBehaviorType; @@ -8538,7 +8537,7 @@ export class ScrollablePaneBase extends BaseComponent boolean; // (undocumented) - verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: StickyContainerBehaviorType) => boolean; + verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: "Default" | "StickyOnScroll" | "StickyAlways") => boolean; } // @public (undocumented) @@ -8882,14 +8881,17 @@ export class Sticky extends BaseComponent { readonly stickyContentTop: HTMLDivElement | null; // (undocumented) syncScroll: (container: HTMLElement) => void; -} + } // @public (undocumented) -export enum StickyContainerBehaviorType { - Default = 0, - StickyAlways = 2, - StickyOnScroll = 1 -} +export const StickyContainerBehaviorType: { + Default: "Default"; + StickyOnScroll: "StickyOnScroll"; + StickyAlways: "StickyAlways"; +}; + +// @public (undocumented) +export type StickyContainerBehaviorType = typeof StickyContainerBehaviorType[keyof typeof StickyContainerBehaviorType]; // @public (undocumented) export enum StickyPositionType { diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 3c7960388d407..92efc171aa7ea 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -45,10 +45,11 @@ export class ScrollablePaneBase extends BaseComponent { + const stickyListSorted = this._sortStickyList(stickyList).filter(item => { const stickyContent = isStickyAboveContainer ? item.stickyContentTop : item.stickyContentBottom; if (stickyContent) { return stickyChildrenElements.indexOf(stickyContent) > -1; @@ -430,7 +430,7 @@ export class ScrollablePaneBase extends BaseComponent { return (a.props.order || 0) - (b.props.order || 0); }); @@ -572,8 +572,8 @@ export class ScrollablePaneBase extends BaseComponent (b.props.order || 0) : (a.state.distanceFromTop || 0) >= (b.state.distanceFromTop || 0); } diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index cf1cbd410824b..0740616529029 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -54,7 +54,8 @@ export interface IScrollablePaneProps extends React.HTMLAttributes

Item list

- - - +
+ + + +
); @@ -160,7 +172,13 @@ function _onItemInvoked(item: IScrollablePaneDetailsListExampleItem): void { function onRenderDetailsHeader(props: IDetailsHeaderProps, defaultRender?: IRenderFunction): JSX.Element { return ( - + {defaultRender!({ ...props, onRenderColumnHeaderTooltip: (tooltipHostProps: ITooltipHostProps) => @@ -171,7 +189,13 @@ function onRenderDetailsHeader(props: IDetailsHeaderProps, defaultRender?: IRend function onRenderDetailsFooter(props: IDetailsFooterProps, defaultRender?: IRenderFunction): JSX.Element { return ( - +
{ if (!scrollablePane) { return {}; } - const usePlaceholderForSticky = scrollablePane.usePlaceholderForSticky(); - const isVisible = isSticky ? (this.canStickyTop && usePlaceholderForSticky) || (this.canStickyBottom && usePlaceholderForSticky) : true; + const isVisible = isSticky + ? this._usePlaceholderForSticky(this.canStickyTop) || this._usePlaceholderForSticky(this.canStickyBottom) + : true; return { backgroundColor: this.props.stickyBackgroundColor || this._getBackground(), - overflow: isSticky ? 'hidden' : '', + overflow: isSticky ? 'hidden' : undefined, visibility: isVisible ? 'visible' : 'hidden' }; } @@ -250,9 +251,8 @@ export class Sticky extends BaseComponent { if (!scrollablePane) { return false; } - const usePlaceholderForSticky = scrollablePane.usePlaceholderForSticky(); - const usePlaceholderForStickyContentTop = this.canStickyTop && usePlaceholderForSticky; - const usePlaceholderForStickyContentBottom = this.canStickyBottom && usePlaceholderForSticky; + const usePlaceholderForStickyContentTop = this._usePlaceholderForSticky(this.canStickyTop); + const usePlaceholderForStickyContentBottom = this._usePlaceholderForSticky(this.canStickyBottom); return ( (usePlaceholderForStickyContentTop && _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentTop)) || (usePlaceholderForStickyContentBottom && _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentBottom)) || @@ -263,9 +263,8 @@ export class Sticky extends BaseComponent { private _getNonStickyPlaceholderHeightAndWidth(): React.CSSProperties { const { isStickyTop, isStickyBottom } = this.state; - const usePlaceholderForSticky = this.context.scrollablePane.usePlaceholderForSticky(); - const usePlaceholderForStickyTop = this.canStickyTop && usePlaceholderForSticky; - const usePlaceholderForStickyBottom = this.canStickyBottom && usePlaceholderForSticky; + const usePlaceholderForStickyTop = this._usePlaceholderForSticky(this.canStickyTop); + const usePlaceholderForStickyBottom = this._usePlaceholderForSticky(this.canStickyBottom); if ((isStickyTop && usePlaceholderForStickyTop) || (isStickyBottom && usePlaceholderForStickyBottom)) { let height = 0, width = 0; @@ -301,6 +300,10 @@ export class Sticky extends BaseComponent { } } + private _usePlaceholderForSticky(canSticky: boolean): boolean { + return canSticky && this.context.scrollablePane.usePlaceholderForSticky(); + } + private _onScrollEvent = (container: HTMLElement, footerStickyContainer: HTMLElement): void => { const { scrollablePane } = this.context; if (!this.root || !this.nonStickyContent || !scrollablePane) { From 360bafa57ec10ddb9512f99dabcecf08540cc960 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Mon, 5 Aug 2019 18:47:43 +0530 Subject: [PATCH 33/48] sort copy of input array --- .../src/components/ScrollablePane/ScrollablePane.base.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 92efc171aa7ea..443c127b92c10 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -561,12 +561,13 @@ export class ScrollablePaneBase extends BaseComponent { + return stickyListCopy.sort((a, b) => { return (a.props.order || 0) - (b.props.order || 0); }); } else { - return stickyList.sort((a, b) => { + return stickyListCopy.sort((a, b) => { return (a.state.distanceFromTop || 0) - (b.state.distanceFromTop || 0); }); } From aaa3b63e161aa4066ea5eee8b9a1e845488c04f7 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Mon, 5 Aug 2019 23:08:54 +0530 Subject: [PATCH 34/48] fixing build error --- .../src/components/ScrollablePane/ScrollablePane.base.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 443c127b92c10..ba1075bf297e1 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -561,7 +561,7 @@ export class ScrollablePaneBase extends BaseComponent { return (a.props.order || 0) - (b.props.order || 0); From 59675524941a096c70863d2a2c68d089ea0858f5 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Tue, 6 Aug 2019 01:06:42 +0530 Subject: [PATCH 35/48] updating screener test --- .../src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 8955f093d1166..2474eda745b9c 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -283,7 +283,7 @@ storiesOf('ScrollablePane-Sticky Details List', module) .executeScript(`${getElement}.scrollLeft=0`) .executeScript(`${getElement}.scrollTop=2`) .snapshot('scroll down by a small amount so that the first row is still visible', cropTo) - .executeScript(`${getElement}.scrollLeft=500`) + .executeScript(`${getElement}.scrollLeft=50`) .snapshot( 'scroll horizontally to see if header and footer are aligned for non-zero scroll', cropTo @@ -297,7 +297,7 @@ storiesOf('ScrollablePane-Sticky Details List', module) .executeScript(`${getElement}.scrollLeft=0`) .snapshot('scroll horizontally to see if header and footer are aligned', cropTo) .executeScript(`${getElement}.scrollTop=999999`) - .executeScript(`${getElement}.scrollLeft=500`) + .executeScript(`${getElement}.scrollLeft=50`) .snapshot( 'scroll horizontally to see if header and footer are aligned for non-zero scroll (2)', cropTo From bc11aa27940d70c04a2aea021998f3dbcd880c65 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 8 Aug 2019 12:36:12 +0530 Subject: [PATCH 36/48] removing localStorage --- .../ScrollablePane/ScrollablePane.base.tsx | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index ba1075bf297e1..fb43ed2fe3469 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -22,7 +22,6 @@ export interface IScrollablePaneState { const getClassNames = classNamesFunction(); export class ScrollablePaneBase extends BaseComponent implements IScrollablePane { - private static readonly _scrollbarHeightKey = 'ScrollablePaneBase_ScrollbarHeight'; private _scrollLeft: number; private _scrollTop: number; private _isMounted: boolean; @@ -41,12 +40,12 @@ export class ScrollablePaneBase extends BaseComponent(); this._stickies = new Set(); - const { scrollbarVisibility } = this.props; + this.state = { stickyTopHeight: 0, stickyBottomHeight: 0, - scrollbarWidth: scrollbarVisibility === ScrollbarVisibility.always ? this._getScrollbarHeightFromLocalStorage() : 0, - scrollbarHeight: scrollbarVisibility === ScrollbarVisibility.always ? this._getScrollbarHeightFromLocalStorage() : 0 + scrollbarWidth: 0, + scrollbarHeight: 0 }; this._scrollLeft = 0; this._scrollTop = 0; @@ -74,15 +73,12 @@ export class ScrollablePaneBase extends BaseComponent { const { contentContainer } = this; From ba7989bc6e0eea127318c45206d93dda3491e9c7 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Tue, 13 Aug 2019 13:07:07 +0530 Subject: [PATCH 37/48] adding new UI state for ScrollabePane --- .../ScrollablePane.Sticky.DetailsList.stories.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 2474eda745b9c..2a660bb20fd26 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -86,7 +86,7 @@ export interface IScrollablePaneDetailsListExampleProps { export class ScrollablePaneDetailsListExample extends React.Component< IScrollablePaneDetailsListExampleProps, IScrollablePaneDetailsListExampleState -> { + > { private _selection: Selection; private _allItems: IScrollablePaneDetailsListExampleItem[]; private _columns: IColumn[]; @@ -194,8 +194,8 @@ export class ScrollablePaneDetailsListExample extends React.Component< this.setState({ items: text ? this._allItems.filter((item: IScrollablePaneDetailsListExampleItem) => - hasText(item, text) - ) + hasText(item, text) + ) : this._allItems }); }; @@ -280,6 +280,11 @@ storiesOf('ScrollablePane-Sticky Details List', module) 'scroll horizontally to see if header and footer are aligned for zero vertical scroll', cropTo ) + .executeScript(`${getElement}.scrollTop=50`) + .snapshot( + 'scroll down so that header becomes sticky when horizontal scroll is non-zero', + cropTo + ) .executeScript(`${getElement}.scrollLeft=0`) .executeScript(`${getElement}.scrollTop=2`) .snapshot('scroll down by a small amount so that the first row is still visible', cropTo) From 82b22401827b0d727987e757ecf443ec6396b484 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Tue, 20 Aug 2019 01:44:41 +0530 Subject: [PATCH 38/48] using saferequestanimationframe while reading scrollTop and scrollLeft for ScrollablePane contentContainer; --- ...ollablePane.Sticky.DetailsList.stories.tsx | 13 ++-- .../etc/office-ui-fabric-react.api.md | 71 +++++++++---------- .../ScrollablePane/ScrollablePane.base.tsx | 66 +++++++++-------- .../ScrollablePane/ScrollablePane.types.ts | 41 ++--------- ...e.Sticky.Optimized.DetailsList.Example.tsx | 6 +- .../src/components/Sticky/Sticky.tsx | 14 +--- 6 files changed, 83 insertions(+), 128 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 2a660bb20fd26..010b6c0e0ce88 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -19,7 +19,6 @@ import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Toolt import { ScrollablePane, ScrollbarVisibility, - StickyContainerBehaviorType, IScrollablePaneProps } from 'office-ui-fabric-react/lib/ScrollablePane'; import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; @@ -86,7 +85,7 @@ export interface IScrollablePaneDetailsListExampleProps { export class ScrollablePaneDetailsListExample extends React.Component< IScrollablePaneDetailsListExampleProps, IScrollablePaneDetailsListExampleState - > { +> { private _selection: Selection; private _allItems: IScrollablePaneDetailsListExampleItem[]; private _columns: IColumn[]; @@ -194,8 +193,8 @@ export class ScrollablePaneDetailsListExample extends React.Component< this.setState({ items: text ? this._allItems.filter((item: IScrollablePaneDetailsListExampleItem) => - hasText(item, text) - ) + hasText(item, text) + ) : this._allItems }); }; @@ -260,13 +259,13 @@ const getElement = "document.getElementsByClassName('ms-ScrollablePane--contentC const cropTo = { cropTo: '.testWrapper' }; const scrollablePaneProps: IScrollablePaneProps = { scrollbarVisibility: ScrollbarVisibility.always, - stickyFooterContainerBehavior: StickyContainerBehaviorType.StickyAlways, - stickyHeaderContainerBehavior: StickyContainerBehaviorType.StickyOnScroll, + stickyFooterContainerBehavior: 'always', + stickyHeaderContainerBehavior: 'onScroll', optimizeForPerformace: true }; const scrollablePaneProps1: IScrollablePaneProps = { ...scrollablePaneProps, - stickyHeaderContainerBehavior: StickyContainerBehaviorType.StickyAlways + stickyHeaderContainerBehavior: 'always' }; storiesOf('ScrollablePane-Sticky Details List', module) 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 bc1fff5b53c1d..23b4179188566 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 @@ -494,7 +494,7 @@ export class Calendar extends BaseComponent impl } // Warning: (ae-forgotten-export) The symbol "ICalloutState" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export class Callout extends React.Component { // (undocumented) @@ -654,7 +654,7 @@ export class ComboBox extends BaseComponent { static defaultProps: IComboBoxProps; dismissMenu: () => void; // Warning: (ae-unresolved-inheritdoc-base) The @inheritDoc tag needs a TSDoc declaration reference; signature matching is not supported yet - // + // // (undocumented) focus: (shouldOpenOnFocus?: boolean | undefined, useFocusAsync?: boolean | undefined) => void; // (undocumented) @@ -2867,7 +2867,7 @@ export interface IContextualMenuItem { data?: any; disabled?: boolean; // Warning: (ae-forgotten-export) The symbol "IMenuItemClassNames" needs to be exported by the entry point index.d.ts - // + // // @deprecated getItemClassNames?: (theme: ITheme, disabled: boolean, expanded: boolean, checked: boolean, isAnchorLink: boolean, knownIcon: boolean, itemClassName?: string, dividerClassName?: string, iconClassName?: string, subMenuClassName?: string, primaryDisabled?: boolean) => IMenuItemClassNames; getSplitButtonVerticalDividerClassNames?: (theme: ITheme) => IVerticalDividerClassNames; @@ -2967,7 +2967,7 @@ export interface IContextualMenuListProps { } // Warning: (ae-forgotten-export) The symbol "IWithResponsiveModeState" needs to be exported by the entry point index.d.ts -// +// // @public export interface IContextualMenuProps extends IBaseProps, IWithResponsiveModeState { alignTargetEdge?: boolean; @@ -2987,7 +2987,7 @@ export interface IContextualMenuProps extends IBaseProps, IWith focusZoneProps?: IFocusZoneProps; gapSpace?: number; // Warning: (ae-forgotten-export) The symbol "IContextualMenuClassNames" needs to be exported by the entry point index.d.ts - // + // // @deprecated getMenuClassNames?: (theme: ITheme, className?: string) => IContextualMenuClassNames; hidden?: boolean; @@ -3736,7 +3736,7 @@ export interface IDialogFooterStyles { } // Warning: (ae-forgotten-export) The symbol "IAccessiblePopupProps" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDialogProps extends React.ClassAttributes, IWithResponsiveModeState, IAccessiblePopupProps { // @deprecated @@ -3840,7 +3840,7 @@ export interface IDocumentCardActions { } // Warning: (ae-forgotten-export) The symbol "DocumentCardActionsBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardActionsProps extends React.ClassAttributes { actions: IButtonProps[]; @@ -3883,7 +3883,7 @@ export interface IDocumentCardActivityPerson { } // Warning: (ae-forgotten-export) The symbol "DocumentCardActivityBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardActivityProps extends React.ClassAttributes { activity: string; @@ -3922,7 +3922,7 @@ export interface IDocumentCardDetails { } // Warning: (ae-forgotten-export) The symbol "DocumentCardDetailsBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardDetailsProps extends React.Props { className?: string; @@ -3981,7 +3981,7 @@ export interface IDocumentCardLocation { } // Warning: (ae-forgotten-export) The symbol "DocumentCardLocationBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardLocationProps extends React.ClassAttributes { ariaLabel?: string; @@ -4011,7 +4011,7 @@ export interface IDocumentCardLogo { } // Warning: (ae-forgotten-export) The symbol "DocumentCardLogoBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardLogoProps extends React.ClassAttributes { className?: string; @@ -4111,7 +4111,7 @@ export interface IDocumentCardStatus { } // Warning: (ae-forgotten-export) The symbol "DocumentCardStatusBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardStatusProps extends React.Props { className?: string; @@ -4153,7 +4153,7 @@ export interface IDocumentCardTitle { } // Warning: (ae-forgotten-export) The symbol "DocumentCardTitleBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardTitleProps extends React.ClassAttributes { className?: string; @@ -4356,7 +4356,7 @@ export interface IExpandingCard { } // Warning: (ae-forgotten-export) The symbol "IBaseCardProps" needs to be exported by the entry point index.d.ts -// +// // @public export interface IExpandingCardProps extends IBaseCardProps { compactCardHeight?: number; @@ -4375,7 +4375,7 @@ export interface IExpandingCardState { } // Warning: (ae-forgotten-export) The symbol "IBaseCardStyleProps" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IExpandingCardStyleProps extends IBaseCardStyleProps { compactCardHeight?: number; @@ -4385,7 +4385,7 @@ export interface IExpandingCardStyleProps extends IBaseCardStyleProps { } // Warning: (ae-forgotten-export) The symbol "IBaseCardStyles" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IExpandingCardStyles extends IBaseCardStyles { compactCard?: IStyle; @@ -5709,7 +5709,7 @@ export interface IPanelHeaderRenderer extends IRenderFunction { } // Warning: (ae-forgotten-export) The symbol "PanelBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IPanelProps extends React.HTMLAttributes { className?: string; @@ -6351,7 +6351,7 @@ export interface IScrollablePaneContext { syncScrollSticky: (sticky: Sticky) => void; usePlaceholderForSticky: () => boolean; getScrollPosition: (horizontal?: boolean) => number; - verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: StickyContainerBehaviorType) => boolean; + verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: IStickyContainerBehaviorType) => boolean; getUserInteractionStatus: () => boolean; }; } @@ -6363,8 +6363,8 @@ export interface IScrollablePaneProps extends React.HTMLAttributes; theme?: ITheme; } @@ -6873,7 +6873,7 @@ export interface ISpinButtonProps { keytipProps?: IKeytipProps; label?: string; // Warning: (ae-forgotten-export) The symbol "Position" needs to be exported by the entry point index.d.ts - // + // // (undocumented) labelPosition?: Position; max?: number; @@ -7050,6 +7050,9 @@ export interface IStackTokens { padding?: number | string; } +// @public (undocumented) +export type IStickyContainerBehaviorType = 'default' | 'onScroll' | 'always'; + // @public (undocumented) export interface IStickyContext { // (undocumented) @@ -7490,14 +7493,14 @@ export interface ITextFieldProps extends React.AllHTMLAttributes { } // Warning: (ae-forgotten-export) The symbol "IKeytipDataProps" needs to be exported by the entry point index.d.ts -// +// // @public export class KeytipData extends React.Component, {}> { // (undocumented) @@ -7802,7 +7805,7 @@ export class KeytipLayerBase extends BaseComponent): void; @@ -7844,7 +7847,7 @@ export class LayerBase extends React.Component { } // Warning: (ae-forgotten-export) The symbol "ILayerHostProps" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export class LayerHost extends React.Component { // (undocumented) @@ -8569,7 +8572,7 @@ export class ScrollablePaneBase extends BaseComponent boolean; // (undocumented) - verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: "Default" | "StickyOnScroll" | "StickyAlways") => boolean; + verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: IStickyContainerBehaviorType) => boolean; } // @public (undocumented) @@ -8901,16 +8904,6 @@ export class Sticky extends BaseComponent { syncScroll: (container: HTMLElement) => void; } -// @public (undocumented) -export const StickyContainerBehaviorType: { - Default: "Default"; - StickyOnScroll: "StickyOnScroll"; - StickyAlways: "StickyAlways"; -}; - -// @public (undocumented) -export type StickyContainerBehaviorType = typeof StickyContainerBehaviorType[keyof typeof StickyContainerBehaviorType]; - // @public (undocumented) export enum StickyPositionType { // (undocumented) @@ -9223,7 +9216,7 @@ export const TextField: React.StatelessComponent; // Warning: (ae-incompatible-release-tags) The symbol "TextFieldBase" is marked as @public, but its signature references "ITextFieldState" which is marked as @internal // Warning: (ae-incompatible-release-tags) The symbol "TextFieldBase" is marked as @public, but its signature references "ITextFieldSnapshot" which is marked as @internal -// +// // @public (undocumented) export class TextFieldBase extends React.Component implements ITextField { constructor(props: ITextFieldProps); @@ -9380,7 +9373,7 @@ export * from "@uifabric/styling"; export * from "@uifabric/utilities"; // Warnings were encountered during analysis: -// +// // lib/components/ColorPicker/ColorPicker.base.d.ts:8:9 - (ae-forgotten-export) The symbol "IRGBHex" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index fb43ed2fe3469..df641c8ac24df 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { BaseComponent, classNamesFunction, divProperties, getNativeProps, getRTL } from '../../Utilities'; +import { BaseComponent, classNamesFunction, divProperties, getNativeProps, getRTL, safeRequestAnimationFrame } from '../../Utilities'; import { IScrollablePane, IScrollablePaneContext, @@ -8,7 +8,7 @@ import { IScrollablePaneStyles, ScrollablePaneContext, ScrollbarVisibility, - StickyContainerBehaviorType + IStickyContainerBehaviorType } from './ScrollablePane.types'; import { Sticky, StickyPositionType } from '../../Sticky'; @@ -35,6 +35,7 @@ export class ScrollablePaneBase extends BaseComponent; private _mutationObserver: MutationObserver; private _notifyThrottled: () => void; + private _requestAnimationFrame = safeRequestAnimationFrame(this); constructor(props: IScrollablePaneProps) { super(props); @@ -193,23 +194,18 @@ export class ScrollablePaneBase extends BaseComponent { const { isStickyTop, isStickyBottom } = sticky.state; if (sticky.nonStickyContent) { - if (isStickyTop && !this.verifyStickyContainerBehavior(StickyPositionType.Header, StickyContainerBehaviorType.StickyAlways)) { + if (isStickyTop && !this.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')) { stickyTopHeight += sticky.nonStickyContent.offsetHeight; } - if (isStickyBottom && !this.verifyStickyContainerBehavior(StickyPositionType.Footer, StickyContainerBehaviorType.StickyAlways)) { + if (isStickyBottom && !this.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')) { stickyBottomHeight += sticky.nonStickyContent.offsetHeight; } this._checkStickyStatus(sticky); @@ -323,18 +319,18 @@ export class ScrollablePaneBase extends BaseComponent { return stickyContainerPosition === StickyPositionType.Header - ? (this._getStickyContainerBehavior(StickyPositionType.Header) || StickyContainerBehaviorType.Default) === stickyContainerBehavior - : (this._getStickyContainerBehavior(StickyPositionType.Footer) || StickyContainerBehaviorType.Default) === stickyContainerBehavior; + ? (this._getStickyContainerBehavior(StickyPositionType.Header) || 'default') === stickyContainerBehavior + : (this._getStickyContainerBehavior(StickyPositionType.Footer) || 'default') === stickyContainerBehavior; }; public getUserInteractionStatus = (): boolean => { return this._userInteractionStarted; }; - private _getStickyContainerBehavior(stickyContainerPosition: StickyPositionType): StickyContainerBehaviorType | undefined { + private _getStickyContainerBehavior(stickyContainerPosition: StickyPositionType): IStickyContainerBehaviorType | undefined { return stickyContainerPosition === StickyPositionType.Header ? this.props.stickyHeaderContainerBehavior : this.props.stickyFooterContainerBehavior; @@ -496,8 +492,8 @@ export class ScrollablePaneBase extends BaseComponent { - const { contentContainer } = this; - - if (contentContainer) { - // sync Sticky scroll if contentContainer has scrolled horizontally - if (this._scrollLeft !== contentContainer.scrollLeft) { - this._scrollLeft = contentContainer.scrollLeft; - this._stickies.forEach((sticky: Sticky) => { - sticky.syncScroll(contentContainer); - }); - } - if (this._scrollTop !== contentContainer.scrollTop) { - this._userInteractionStarted = true; - this._scrollTop = contentContainer.scrollTop; - this._notifyThrottled(); + this._requestAnimationFrame(() => { + const { contentContainer } = this; + + if (contentContainer) { + // sync Sticky scroll if contentContainer has scrolled horizontally + if (this._scrollLeft !== contentContainer.scrollLeft) { + this._scrollLeft = contentContainer.scrollLeft; + this._stickies.forEach((sticky: Sticky) => { + sticky.syncScroll(contentContainer); + }); + } + if (this._scrollTop !== contentContainer.scrollTop) { + this._userInteractionStarted = true; + this._scrollTop = contentContainer.scrollTop; + this._notifyThrottled(); + } } - } + }); }; private _sortBasedOnOrder(): boolean { diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index 0740616529029..ffd31f3cda4e1 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -54,20 +54,20 @@ export interface IScrollablePaneProps extends React.HTMLAttributes number; verifyStickyContainerBehavior: ( stickyContainerPosition: StickyPositionType, - stickyContainerBehavior: StickyContainerBehaviorType + stickyContainerBehavior: IStickyContainerBehaviorType ) => boolean; getUserInteractionStatus: () => boolean; }; diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx index b9ef3f3ba0c5a..a2dd372a9cda2 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -12,7 +12,7 @@ import { } from 'office-ui-fabric-react/lib/DetailsList'; import { IRenderFunction } from 'office-ui-fabric-react/lib/Utilities'; import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Tooltip'; -import { ScrollablePane, ScrollbarVisibility, StickyContainerBehaviorType } from 'office-ui-fabric-react/lib/ScrollablePane'; +import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane'; import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'; import { SelectionMode } from 'office-ui-fabric-react/lib/utilities/selection/index'; @@ -125,8 +125,8 @@ export class ScrollablePaneStickyOptimizedDetailsList extends React.Component<{}
diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index c18ce165ca0a5..0259023989568 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -1,7 +1,7 @@ import * as PropTypes from 'prop-types'; import * as React from 'react'; import { BaseComponent } from '../../Utilities'; -import { IScrollablePaneContext, ScrollablePaneContext, StickyContainerBehaviorType } from '../ScrollablePane/ScrollablePane.types'; +import { IScrollablePaneContext, ScrollablePaneContext } from '../ScrollablePane/ScrollablePane.types'; import { IStickyProps, StickyPositionType } from './Sticky.types'; export interface IStickyState { @@ -309,12 +309,7 @@ export class Sticky extends BaseComponent { if (!this.root || !this.nonStickyContent || !scrollablePane) { return; } - if ( - scrollablePane.verifyStickyContainerBehavior( - this.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, - StickyContainerBehaviorType.StickyAlways - ) - ) { + if (scrollablePane.verifyStickyContainerBehavior(this.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, 'always')) { // 1. ScrollablePane is mounted and has called notifySubscriber // 2. stickyAlways has to re-render if mutation could 've affected it's offsetHeight. this.setState({ @@ -324,10 +319,7 @@ export class Sticky extends BaseComponent { }); } else if ( !scrollablePane.getUserInteractionStatus() && - scrollablePane.verifyStickyContainerBehavior( - this.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, - StickyContainerBehaviorType.StickyOnScroll - ) + scrollablePane.verifyStickyContainerBehavior(this.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, 'onScroll') ) { // user interaction has not started // 1. ScrollablePane is mounted and has called notifySubscriber, sort is required. From 3d89668e2682b13733dde6d2d9a55caf79acd047 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Tue, 20 Aug 2019 20:21:10 +0530 Subject: [PATCH 39/48] fixing typos; removing safe rAF; --- ...ollablePane.Sticky.DetailsList.stories.tsx | 2 +- .../etc/office-ui-fabric-react.api.md | 2 +- .../ScrollablePane/ScrollablePane.base.tsx | 39 +++++++++---------- .../ScrollablePane/ScrollablePane.types.ts | 5 ++- ...e.Sticky.Optimized.DetailsList.Example.tsx | 2 +- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 010b6c0e0ce88..84402f5b68973 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -261,7 +261,7 @@ const scrollablePaneProps: IScrollablePaneProps = { scrollbarVisibility: ScrollbarVisibility.always, stickyFooterContainerBehavior: 'always', stickyHeaderContainerBehavior: 'onScroll', - optimizeForPerformace: true + optimizeForPerformance: true }; const scrollablePaneProps1: IScrollablePaneProps = { ...scrollablePaneProps, 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 23b4179188566..bfb61921d2fac 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 @@ -6361,7 +6361,7 @@ export interface IScrollablePaneProps extends React.HTMLAttributes; initialScrollPosition?: number; - optimizeForPerformace?: boolean; + optimizeForPerformance?: boolean; scrollbarVisibility?: ScrollbarVisibility; stickyFooterContainerBehavior?: IStickyContainerBehaviorType; stickyHeaderContainerBehavior?: IStickyContainerBehaviorType; diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index df641c8ac24df..ab5e9a53683f7 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { BaseComponent, classNamesFunction, divProperties, getNativeProps, getRTL, safeRequestAnimationFrame } from '../../Utilities'; +import { BaseComponent, classNamesFunction, divProperties, getNativeProps, getRTL } from '../../Utilities'; import { IScrollablePane, IScrollablePaneContext, @@ -35,7 +35,6 @@ export class ScrollablePaneBase extends BaseComponent; private _mutationObserver: MutationObserver; private _notifyThrottled: () => void; - private _requestAnimationFrame = safeRequestAnimationFrame(this); constructor(props: IScrollablePaneProps) { super(props); @@ -314,7 +313,7 @@ export class ScrollablePaneBase extends BaseComponent { - return !this.props.optimizeForPerformace; + return !this.props.optimizeForPerformance; }; public verifyStickyContainerBehavior = ( @@ -508,28 +507,26 @@ export class ScrollablePaneBase extends BaseComponent { - this._requestAnimationFrame(() => { - const { contentContainer } = this; - - if (contentContainer) { - // sync Sticky scroll if contentContainer has scrolled horizontally - if (this._scrollLeft !== contentContainer.scrollLeft) { - this._scrollLeft = contentContainer.scrollLeft; - this._stickies.forEach((sticky: Sticky) => { - sticky.syncScroll(contentContainer); - }); - } - if (this._scrollTop !== contentContainer.scrollTop) { - this._userInteractionStarted = true; - this._scrollTop = contentContainer.scrollTop; - this._notifyThrottled(); - } + const { contentContainer } = this; + + if (contentContainer) { + // sync Sticky scroll if contentContainer has scrolled horizontally + if (this._scrollLeft !== contentContainer.scrollLeft) { + this._scrollLeft = contentContainer.scrollLeft; + this._stickies.forEach((sticky: Sticky) => { + sticky.syncScroll(contentContainer); + }); } - }); + if (this._scrollTop !== contentContainer.scrollTop) { + this._userInteractionStarted = true; + this._scrollTop = contentContainer.scrollTop; + this._notifyThrottled(); + } + } }; private _sortBasedOnOrder(): boolean { - return !!this.props.optimizeForPerformace; + return !!this.props.optimizeForPerformance; } private _stickyContainerContainsStickyContent(sticky: Sticky, stickyPositionType: StickyPositionType): boolean { diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index ffd31f3cda4e1..1b3d72fa78fdd 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -54,10 +54,11 @@ export interface IScrollablePaneProps extends React.HTMLAttributes From abeca76a475da1f75f48864710ba14789a78a8d7 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Tue, 20 Aug 2019 21:14:38 +0530 Subject: [PATCH 40/48] adding comments for IStickyContainerBehavior --- .../etc/office-ui-fabric-react.api.md | 2 +- .../ScrollablePane/ScrollablePane.types.ts | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) 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 bfb61921d2fac..8729dbc1fa6ad 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 @@ -7050,7 +7050,7 @@ export interface IStackTokens { padding?: number | string; } -// @public (undocumented) +// @public export type IStickyContainerBehaviorType = 'default' | 'onScroll' | 'always'; // @public (undocumented) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index 1b3d72fa78fdd..676b4293df20e 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -115,6 +115,29 @@ export interface IScrollablePaneStyles { */ contentContainer: IStyle; } + +/** + * 'default': Sticky component(s) will become sticky or non-sticky whenever it is expected. + * If there is a large page having a Sticky component for which stickyPosition is 'StickyPosition.Footer' + * and this Sticky component is not in viewable area of the device screen, + * the 'default' behavior will make sure this component is displayed at it's sticky position, i.e., + * at the bottom of the viewable area of the device screen, without requiring manual scroll. + * + * + * 'onScroll' : Sticky component(s) will become sticky or non-sticky based on scrolling. + * The calculation which determine if a Sticky component is sticky or non-sticky, + * are done after user interaction (scrolling) and don't affect page load time. + * It is most suitable for stickyHeaderContainerBehavior. + * + * + * 'always': Sticky component(s) will always be sticky independent of scrolling. + * There are no calculations done as the component(s) would always be sticky. + * It is most suitable if stickyPosition is: + * 1. 'StickyPosition.Header' (i.e, for stickyHeaderContainerBehavior) and + * there is no non-sticky content above the Sticky component + * 2. 'StickyPosition.Footer' (i.e, for stickyFooterContainerBehavior) and + * there is no non-sticky content below the Sticky component. + */ export type IStickyContainerBehaviorType = 'default' | 'onScroll' | 'always'; /** From 633197e6e15cc956adc38cca0fead8969f9dacdd Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Thu, 29 Aug 2019 23:37:30 +0530 Subject: [PATCH 41/48] 1. renaming opt-in perf variable to experimentalLayoutImprovements 2. adding unit tests 3. adding new public method to getHorizontalScrollPosition() instead of modifying getScrollPosition() --- ...ollablePane.Sticky.DetailsList.stories.tsx | 2 +- .../etc/office-ui-fabric-react.api.md | 9 +- .../ScrollablePane/ScrollablePane.base.tsx | 17 +- .../ScrollablePane/ScrollablePane.test.tsx | 214 ++++++++++++++++++ .../ScrollablePane/ScrollablePane.types.ts | 9 +- ...e.Sticky.Optimized.DetailsList.Example.tsx | 2 +- .../src/components/Sticky/Sticky.tsx | 4 +- 7 files changed, 242 insertions(+), 15 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 84402f5b68973..371d8faf41421 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -261,7 +261,7 @@ const scrollablePaneProps: IScrollablePaneProps = { scrollbarVisibility: ScrollbarVisibility.always, stickyFooterContainerBehavior: 'always', stickyHeaderContainerBehavior: 'onScroll', - optimizeForPerformance: true + experimentalLayoutImprovements: true }; const scrollablePaneProps1: IScrollablePaneProps = { ...scrollablePaneProps, 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 8729dbc1fa6ad..5cd643f06c608 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 @@ -6334,6 +6334,7 @@ export interface IRGB { // @public (undocumented) export interface IScrollablePane { forceLayoutUpdate(): void; + getHorizontalScrollPosition(): number; getScrollPosition(): number; } @@ -6350,7 +6351,7 @@ export interface IScrollablePaneContext { notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; usePlaceholderForSticky: () => boolean; - getScrollPosition: (horizontal?: boolean) => number; + getHorizontalScrollPosition: () => number; verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: IStickyContainerBehaviorType) => boolean; getUserInteractionStatus: () => boolean; }; @@ -6360,8 +6361,8 @@ export interface IScrollablePaneContext { export interface IScrollablePaneProps extends React.HTMLAttributes { className?: string; componentRef?: IRefObject; + experimentalLayoutImprovements?: boolean; initialScrollPosition?: number; - optimizeForPerformance?: boolean; scrollbarVisibility?: ScrollbarVisibility; stickyFooterContainerBehavior?: IStickyContainerBehaviorType; stickyHeaderContainerBehavior?: IStickyContainerBehaviorType; @@ -8540,7 +8541,9 @@ export class ScrollablePaneBase extends BaseComponent number; + getHorizontalScrollPosition: () => number; + // (undocumented) + getScrollPosition: () => number; // (undocumented) getUserInteractionStatus: () => boolean; // (undocumented) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index ab5e9a53683f7..bbaf78be606d1 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -298,14 +298,21 @@ export class ScrollablePaneBase extends BaseComponent { + public getScrollPosition = (): number => { if (this.contentContainer) { - return horizontal ? this._scrollLeft : this._scrollTop; + return this._listeningToEvents ? this._scrollTop : this.contentContainer.scrollTop; } return 0; }; + public getHorizontalScrollPosition = (): number => { + if (this.contentContainer) { + return this._listeningToEvents ? this._scrollLeft : this.contentContainer.scrollLeft; + } + return 0; + }; + public syncScrollSticky = (sticky: Sticky): void => { if (sticky && this.contentContainer) { sticky.syncScroll(this.contentContainer); @@ -313,7 +320,7 @@ export class ScrollablePaneBase extends BaseComponent { - return !this.props.optimizeForPerformance; + return !this.props.experimentalLayoutImprovements; }; public verifyStickyContainerBehavior = ( @@ -347,7 +354,7 @@ export class ScrollablePaneBase extends BaseComponent { it('renders correctly', () => { @@ -10,4 +15,213 @@ describe('ScrollablePane', () => { const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); + + describe('ScrollablePane works with Sticky', () => { + let container: HTMLDivElement; + beforeEach(() => { + container = document.createElement('div'); + }); + it('It gives correct scroll values if experimentalLayoutImprovements is undefined|false', () => { + /** + * If experimentalLayoutImprovements is false, then there is no scroll event handler in ScrollablePaneBase. + * getScrollPosition() and getHorizontalScrollPosition() should still return correct values. + */ + const scrollablePaneRef = createRef(); + ReactDOM.render( + + + , + container + ); + + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; + const contentContainer = scrollablePane.children[0]; + + expect(scrollablePaneRef.current!.getScrollPosition()).toBe(0); + expect(contentContainer.scrollTop).toBe(0); + + expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(0); + expect(contentContainer.scrollLeft).toBe(0); + + contentContainer.scrollTop = 100; + contentContainer.dispatchEvent(new Event('scroll')); + expect(scrollablePaneRef.current).toBeDefined(); + expect(scrollablePaneRef.current!.getScrollPosition()).toBe(100); + expect(contentContainer.scrollTop).toBe(100); + + contentContainer.scrollLeft = 50; + contentContainer.dispatchEvent(new Event('scroll')); + expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(50); + expect(contentContainer.scrollLeft).toBe(50); + }); + + it('It gives correct scroll values if experimentalLayoutImprovements is true and there is at least one Sticky component', () => { + const scrollablePaneRef = createRef(); + ReactDOM.render( + + +
Header
+
+ +
, + container + ); + + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; + const contentContainer = scrollablePane.children[0]; + + expect(scrollablePaneRef.current!.getScrollPosition()).toBe(0); + expect(contentContainer.scrollTop).toBe(0); + + expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(0); + expect(contentContainer.scrollLeft).toBe(0); + + contentContainer.scrollTop = 100; + contentContainer.dispatchEvent(new Event('scroll')); + expect(scrollablePaneRef.current!.getScrollPosition()).toBe(100); + expect(contentContainer.scrollTop).toBe(100); + + contentContainer.scrollLeft = 20; + contentContainer.dispatchEvent(new Event('scroll')); + expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(20); + expect(contentContainer.scrollLeft).toBe(20); + }); + + it('It gives correct scroll values if experimentalLayoutImprovements is true and there is no Sticky component', () => { + /** + * If there is no Sticky component, then there is no scroll event handler in ScrollablePaneBase. + * getScrollPosition() and getHorizontalScrollPosition() should return 0. + */ + const scrollablePaneRef = createRef(); + ReactDOM.render( + + + , + container + ); + + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; + const contentContainer = scrollablePane.children[0]; + + expect(scrollablePaneRef.current!.getScrollPosition()).toBe(0); + expect(contentContainer.scrollTop).toBe(0); + + expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(0); + expect(contentContainer.scrollLeft).toBe(0); + + contentContainer.scrollTop = 100; + contentContainer.dispatchEvent(new Event('scroll')); + expect(scrollablePaneRef.current!.getScrollPosition()).toBe(100); + expect(contentContainer.scrollTop).toBe(100); + + contentContainer.scrollLeft = 20; + contentContainer.dispatchEvent(new Event('scroll')); + expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(20); + expect(contentContainer.scrollLeft).toBe(20); + }); + + it(`it replicates nonSticky content at it's sticky and non-sticky place + in DOM for stickyPosition={StickyPositionType.Header} if experimentalLayoutImprovements={true}`, () => { + ReactDOM.render( + + +
Header
+
+ +
, + container + ); + + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; + const contentContainer = scrollablePane.children[0]; + const stickyAboveContainer = scrollablePane.children[1] as HTMLDivElement; + const stickyBelowContainer = scrollablePane.children[2] as HTMLDivElement; + + expect(contentContainer.querySelectorAll('.sticky-header').length).toBe(1); + expect(stickyAboveContainer.querySelectorAll('.sticky-header').length).toBe(1); + expect(stickyBelowContainer.querySelectorAll('.sticky-header').length).toBe(0); + }); + + it(`it replicates nonSticky content at it's sticky and non-sticky place in DOM for + stickyPosition={StickyPositionType.Footer} if experimentalLayoutImprovements={true}`, () => { + ReactDOM.render( + + + +
Footer
+
+
, + container + ); + + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; + const contentContainer = scrollablePane.children[0]; + const stickyAboveContainer = scrollablePane.children[1] as HTMLDivElement; + const stickyBelowContainer = scrollablePane.children[2] as HTMLDivElement; + + expect(contentContainer.querySelectorAll('.sticky-footer').length).toBe(1); + expect(stickyAboveContainer.querySelectorAll('.sticky-footer').length).toBe(0); + expect(stickyBelowContainer.querySelectorAll('.sticky-footer').length).toBe(1); + }); + + it(`it replicates nonSticky content at it's sticky and non-sticky place in DOM if experimentalLayoutImprovements={true}`, () => { + ReactDOM.render( + + +
Header
+
+ + +
Footer
+
+
, + container + ); + + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; + const contentContainer = scrollablePane.children[0]; + const stickyAboveContainer = scrollablePane.children[1] as HTMLDivElement; + const stickyBelowContainer = scrollablePane.children[2] as HTMLDivElement; + + expect(contentContainer.querySelectorAll('.sticky-header').length).toBe(1); + expect(stickyAboveContainer.querySelectorAll('.sticky-header').length).toBe(1); + expect(stickyBelowContainer.querySelectorAll('.sticky-header').length).toBe(0); + + expect(contentContainer.querySelectorAll('.sticky-footer').length).toBe(1); + expect(stickyAboveContainer.querySelectorAll('.sticky-footer').length).toBe(0); + expect(stickyBelowContainer.querySelectorAll('.sticky-footer').length).toBe(1); + }); + + it(`it doesn't replicate nonSticky content in DOM if experimentalLayoutImprovements={false}`, () => { + ReactDOM.render( + + +
Header
+
+ + +
Footer
+
+
, + container + ); + + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; + const contentContainer = scrollablePane.children[0]; + const stickyAboveContainer = scrollablePane.children[1] as HTMLDivElement; + const stickyBelowContainer = scrollablePane.children[2] as HTMLDivElement; + + expect(contentContainer.querySelectorAll('.sticky-header').length).toBe(1); + expect(stickyAboveContainer.querySelectorAll('.sticky-header').length).toBe(0); + expect(stickyBelowContainer.querySelectorAll('.sticky-header').length).toBe(0); + + expect( + contentContainer.querySelectorAll('.sticky-footer').length || stickyBelowContainer.querySelectorAll('.sticky-footer').length + ).toBe(1); + expect(stickyAboveContainer.querySelectorAll('.sticky-footer').length).toBe(0); + expect( + contentContainer.querySelectorAll('.sticky-footer').length && stickyBelowContainer.querySelectorAll('.sticky-footer').length + ).toBe(0); + }); + }); }); diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index 676b4293df20e..0339eceb2f37a 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -11,8 +11,10 @@ import { StickyPositionType } from '../Sticky/Sticky.types'; export interface IScrollablePane { /** Triggers a layout update for the pane. */ forceLayoutUpdate(): void; - /** Gets the current scroll position of the scrollable pane */ + /** Gets the current vertical scroll position of the scrollable pane */ getScrollPosition(): number; + /** Gets the current horizontal scroll position of the scrollable pane */ + getHorizontalScrollPosition(): number; } /** @@ -57,8 +59,9 @@ export interface IScrollablePaneProps extends React.HTMLAttributes void; syncScrollSticky: (sticky: Sticky) => void; usePlaceholderForSticky: () => boolean; - getScrollPosition: (horizontal?: boolean) => number; + getHorizontalScrollPosition: () => number; verifyStickyContainerBehavior: ( stickyContainerPosition: StickyPositionType, stickyContainerBehavior: IStickyContainerBehaviorType diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx index a0cfa850075b2..dc165306be97a 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -127,7 +127,7 @@ export class ScrollablePaneStickyOptimizedDetailsList extends React.Component<{} scrollbarVisibility={ScrollbarVisibility.always} stickyHeaderContainerBehavior={'onScroll'} stickyFooterContainerBehavior={'always'} - optimizeForPerformance={true} + experimentalLayoutImprovements={true} > diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index 0259023989568..71c19b5945a32 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -75,7 +75,7 @@ export class Sticky extends BaseComponent { if (!scrollablePane || !this.props.isScrollSynced || !(isStickyBottom || isStickyTop)) { return; } - const containerScrollLeft = scrollablePane.getScrollPosition(true /** horizontal */); + const containerScrollLeft = scrollablePane.getHorizontalScrollPosition(); const usePlaceholderForSticky = scrollablePane.usePlaceholderForSticky(); const stickyContent = isStickyTop ? stickyContentTop : stickyContentBottom; if (!usePlaceholderForSticky && stickyContent && stickyContent.children && stickyContent.children.length > 0) { @@ -125,7 +125,7 @@ export class Sticky extends BaseComponent { scrollablePane.updateStickyRefHeights(); } // horizontal scroll has to be synced only if component is sticky - if ((isStickyBottom || isStickyTop) && scrollablePane.getScrollPosition(true /** horizontal */)) { + if ((isStickyBottom || isStickyTop) && scrollablePane.getHorizontalScrollPosition()) { // Sync Sticky scroll position with content container on each update scrollablePane.syncScrollSticky(this); } From 60c1e27a1b4106d21e7ecaf82a54c148f4372220 Mon Sep 17 00:00:00 2001 From: David Zearing Date: Tue, 3 Sep 2019 22:47:36 -0700 Subject: [PATCH 42/48] Update ScrollablePane.test.tsx --- .../src/components/ScrollablePane/ScrollablePane.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx index 69db22fc779bb..da26997533f6f 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx @@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom'; import { createRef } from 'react'; import { IScrollablePane } from './ScrollablePane.types'; import { DetailsListBasicExample } from '../DetailsList/examples/DetailsList.Basic.Example'; -import { Sticky, StickyPositionType } from '../Sticky'; +import { Sticky, StickyPositionType } from '../../Sticky'; describe('ScrollablePane', () => { it('renders correctly', () => { From ca65dab3d40ebc1fd7706c9858ae25af35db9a7b Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 4 Sep 2019 14:33:43 +0530 Subject: [PATCH 43/48] adding uts --- .../ScrollablePane/ScrollablePane.base.tsx | 12 +- .../ScrollablePane/ScrollablePane.test.tsx | 173 ++++++++++++++++-- 2 files changed, 166 insertions(+), 19 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index bbaf78be606d1..8bba8877d5912 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -242,11 +242,9 @@ export class ScrollablePaneBase extends BaseComponent { it('renders correctly', () => { @@ -34,6 +34,11 @@ describe('ScrollablePane', () => { container ); + expect(scrollablePaneRef.current).toBeDefined(); + const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; + // it sets userinteraction flag only after scroll + expect(!!scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(false); + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; const contentContainer = scrollablePane.children[0]; @@ -45,13 +50,18 @@ describe('ScrollablePane', () => { contentContainer.scrollTop = 100; contentContainer.dispatchEvent(new Event('scroll')); - expect(scrollablePaneRef.current).toBeDefined(); - expect(scrollablePaneRef.current!.getScrollPosition()).toBe(100); + + // it sets userinteraction flag only after scroll + // if there is no Sticky, then there is no scroll handler and + // this flag remains unset. + expect(!!scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(false); + + expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(100); expect(contentContainer.scrollTop).toBe(100); contentContainer.scrollLeft = 50; contentContainer.dispatchEvent(new Event('scroll')); - expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(50); + expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(50); expect(contentContainer.scrollLeft).toBe(50); }); @@ -67,24 +77,39 @@ describe('ScrollablePane', () => { container ); + const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; + expect(scrollablePaneRefCurrent._setStickyContainerHeight(true /** isTop */)).toBe(true); + expect(scrollablePaneRefCurrent._setStickyContainerHeight(false /** isTop */)).toBe(true); + + // it sets userinteraction flag only after scroll + expect(!!scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(false); + const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; const contentContainer = scrollablePane.children[0]; - expect(scrollablePaneRef.current!.getScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(0); expect(contentContainer.scrollTop).toBe(0); - expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(0); expect(contentContainer.scrollLeft).toBe(0); contentContainer.scrollTop = 100; contentContainer.dispatchEvent(new Event('scroll')); - expect(scrollablePaneRef.current!.getScrollPosition()).toBe(100); + + // it sets userinteraction flag only after scroll + expect(scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(true); + + expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(100); expect(contentContainer.scrollTop).toBe(100); + expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(0); + expect(contentContainer.scrollLeft).toBe(0); contentContainer.scrollLeft = 20; contentContainer.dispatchEvent(new Event('scroll')); expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(20); expect(contentContainer.scrollLeft).toBe(20); + expect(scrollablePaneRef.current!.getScrollPosition()).toBe(100); + expect(contentContainer.scrollTop).toBe(100); }); it('It gives correct scroll values if experimentalLayoutImprovements is true and there is no Sticky component', () => { @@ -99,24 +124,24 @@ describe('ScrollablePane', () => {
, container ); - + const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; const contentContainer = scrollablePane.children[0]; - expect(scrollablePaneRef.current!.getScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(0); expect(contentContainer.scrollTop).toBe(0); - expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(0); expect(contentContainer.scrollLeft).toBe(0); contentContainer.scrollTop = 100; contentContainer.dispatchEvent(new Event('scroll')); - expect(scrollablePaneRef.current!.getScrollPosition()).toBe(100); + expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(100); expect(contentContainer.scrollTop).toBe(100); contentContainer.scrollLeft = 20; contentContainer.dispatchEvent(new Event('scroll')); - expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(20); + expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(20); expect(contentContainer.scrollLeft).toBe(20); }); @@ -223,5 +248,129 @@ describe('ScrollablePane', () => { contentContainer.querySelectorAll('.sticky-footer').length && stickyBelowContainer.querySelectorAll('.sticky-footer').length ).toBe(0); }); + + describe('it correctly verifies sticky container behavior', () => { + it(`if experimentalLayoutImprovements is undefined|false`, () => { + const scrollablePaneRef = createRef(); + ReactDOM.render( + + +
Header
+
+ + +
Footer
+
+
, + container + ); + + const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; + expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(false); + + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'default')).toBe(true); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'onScroll')).toBe(false); + + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default')).toBe(true); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'onScroll')).toBe(false); + }); + + it(`if experimentalLayoutImprovements is true`, () => { + const scrollablePaneRef = createRef(); + ReactDOM.render( + + +
Header
+
+ + +
Footer
+
+
, + container + ); + + const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; + expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(true); + expect(scrollablePaneRefCurrent.usePlaceholderForSticky()).toBe(false); + + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'default')).toBe(true); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'onScroll')).toBe(false); + + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default')).toBe(true); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'onScroll')).toBe(false); + }); + + it(`if experimentalLayoutImprovements is undefined|false and behavior is not default`, () => { + const scrollablePaneRef = createRef(); + ReactDOM.render( + + +
Header
+
+ + +
Footer
+
+
, + container + ); + + const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; + expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(false); + expect(scrollablePaneRefCurrent.usePlaceholderForSticky()).toBe(true); + + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'default')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'onScroll')).toBe(true); + + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')).toBe(true); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'onScroll')).toBe(false); + }); + + it(`if experimentalLayoutImprovements is true and behavior is not default`, () => { + const scrollablePaneRef = createRef(); + ReactDOM.render( + + +
Header
+
+ + +
Footer
+
+
, + container + ); + + const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; + expect(scrollablePaneRefCurrent._setStickyContainerHeight(true /** isTop */)).toBe(false); + expect(scrollablePaneRefCurrent._setStickyContainerHeight(false /** isTop */)).toBe(false); + expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(true); + expect(scrollablePaneRefCurrent.usePlaceholderForSticky()).toBe(false); + + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'default')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'onScroll')).toBe(true); + + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default')).toBe(false); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')).toBe(true); + expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'onScroll')).toBe(false); + }); + }); }); }); From e42a6b44f500b9ef78acf1cac02b4329abdf6dcf Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 4 Sep 2019 14:50:22 +0530 Subject: [PATCH 44/48] update setStickyContainerHeight --- .../components/ScrollablePane/ScrollablePane.base.tsx | 11 +++-------- .../components/ScrollablePane/ScrollablePane.test.tsx | 6 ++---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 8bba8877d5912..85409a05c7432 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -470,7 +470,7 @@ export class ScrollablePaneBase extends BaseComponent { - const stickyContainerHeight = this._setStickyContainerHeight(isTop) ? height : undefined; + const stickyContainerHeight = this._setStickyContainerHeight() ? height : undefined; return { ...(stickyContainerHeight !== undefined ? { height: height } : {}), ...(getRTL() @@ -492,13 +492,8 @@ export class ScrollablePaneBase extends BaseComponent { ); const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; - expect(scrollablePaneRefCurrent._setStickyContainerHeight(true /** isTop */)).toBe(true); - expect(scrollablePaneRefCurrent._setStickyContainerHeight(false /** isTop */)).toBe(true); + expect(scrollablePaneRefCurrent._setStickyContainerHeight()).toBe(true); // it sets userinteraction flag only after scroll expect(!!scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(false); @@ -358,8 +357,7 @@ describe('ScrollablePane', () => { ); const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; - expect(scrollablePaneRefCurrent._setStickyContainerHeight(true /** isTop */)).toBe(false); - expect(scrollablePaneRefCurrent._setStickyContainerHeight(false /** isTop */)).toBe(false); + expect(scrollablePaneRefCurrent._setStickyContainerHeight()).toBe(false); expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(true); expect(scrollablePaneRefCurrent.usePlaceholderForSticky()).toBe(false); From 3ab11e981867c11e9d01a34e54cbe97bfebb9670 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 4 Sep 2019 14:57:39 +0530 Subject: [PATCH 45/48] updating unit test --- .../src/components/ScrollablePane/ScrollablePane.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx index 0bb038b4d9f7d..5eda9fc1210bf 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx @@ -78,7 +78,7 @@ describe('ScrollablePane', () => { ); const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; - expect(scrollablePaneRefCurrent._setStickyContainerHeight()).toBe(true); + expect(scrollablePaneRefCurrent._setStickyContainerHeight()).toBe(false); // it sets userinteraction flag only after scroll expect(!!scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(false); From 20f89f47287738774bcb72bad5819ca9d2ac6b68 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 4 Sep 2019 16:17:38 +0530 Subject: [PATCH 46/48] fixing import --- .../ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx index dc165306be97a..bf562b1101b3f 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -15,7 +15,7 @@ import { TooltipHost, ITooltipHostProps } from 'office-ui-fabric-react/lib/Toolt import { ScrollablePane, ScrollbarVisibility } from 'office-ui-fabric-react/lib/ScrollablePane'; import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'; -import { SelectionMode } from 'office-ui-fabric-react/lib/utilities/selection/index'; +import { SelectionMode } from 'office-ui-fabric-react/lib/Selection'; import { mergeStyleSets, getTheme } from 'office-ui-fabric-react/lib/Styling'; const classNames = mergeStyleSets({ From 3ffa093dc9a3768d1884758234a97f5587993f42 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 4 Sep 2019 23:24:03 +0530 Subject: [PATCH 47/48] adding UI state --- .../ScrollablePane.Sticky.DetailsList.stories.tsx | 11 +++++++++-- .../src/components/Sticky/Sticky.tsx | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index 371d8faf41421..d0fa7539fd231 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -292,10 +292,17 @@ storiesOf('ScrollablePane-Sticky Details List', module) 'scroll horizontally to see if header and footer are aligned for non-zero scroll', cropTo ) + .executeScript(`${getElement}.scrollTop=0`) + .executeScript(`${getElement}.scrollLeft=0`) + .executeScript(`${getElement}.scrollTop=50`) + .snapshot( + 'scroll horizontally to see if header and footer are aligned for non-zero scroll (1)', + cropTo + ) .executeScript(`${getElement}.scrollTop=100`) .executeScript(`${getElement}.scrollLeft=99999`) .snapshot( - 'scroll horizontally to see if header and footer are aligned for non-zero scroll (1)', + 'scroll horizontally to see if header and footer are aligned for non-zero scroll (2)', cropTo ) .executeScript(`${getElement}.scrollLeft=0`) @@ -303,7 +310,7 @@ storiesOf('ScrollablePane-Sticky Details List', module) .executeScript(`${getElement}.scrollTop=999999`) .executeScript(`${getElement}.scrollLeft=50`) .snapshot( - 'scroll horizontally to see if header and footer are aligned for non-zero scroll (2)', + 'scroll horizontally to see if header and footer are aligned for non-zero scroll (3)', cropTo ) .executeScript(`${getElement}.scrollLeft=0`) diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index 71c19b5945a32..8de54fad7218a 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -125,7 +125,7 @@ export class Sticky extends BaseComponent { scrollablePane.updateStickyRefHeights(); } // horizontal scroll has to be synced only if component is sticky - if ((isStickyBottom || isStickyTop) && scrollablePane.getHorizontalScrollPosition()) { + if ((isStickyBottom || isStickyTop) && scrollablePane.getUserInteractionStatus()) { // Sync Sticky scroll position with content container on each update scrollablePane.syncScrollSticky(this); } From 9959ae5ee26cd2728d0ec3c47ecc392fda04b0c5 Mon Sep 17 00:00:00 2001 From: Harshada Trivedi Date: Wed, 16 Oct 2019 23:46:17 +0530 Subject: [PATCH 48/48] refactoring code to minimize bundle size --- ...ollablePane.Sticky.DetailsList.stories.tsx | 13 - .../etc/office-ui-fabric-react.api.md | 78 +-- .../ScrollablePane/ScrollablePane.base.tsx | 653 ++++++++---------- .../ScrollablePane/ScrollablePane.styles.ts | 10 +- .../ScrollablePane/ScrollablePane.test.tsx | 109 +-- .../ScrollablePane/ScrollablePane.types.ts | 71 +- .../ScrollablePane.test.tsx.snap | 6 +- ...e.Sticky.Optimized.DetailsList.Example.tsx | 13 +- .../src/components/Sticky/Sticky.tsx | 534 +++++++------- 9 files changed, 679 insertions(+), 808 deletions(-) diff --git a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx index d0fa7539fd231..c1f1137a0deca 100644 --- a/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx +++ b/apps/vr-tests/src/stories/ScrollablePane.Sticky.DetailsList.stories.tsx @@ -259,14 +259,8 @@ const getElement = "document.getElementsByClassName('ms-ScrollablePane--contentC const cropTo = { cropTo: '.testWrapper' }; const scrollablePaneProps: IScrollablePaneProps = { scrollbarVisibility: ScrollbarVisibility.always, - stickyFooterContainerBehavior: 'always', - stickyHeaderContainerBehavior: 'onScroll', experimentalLayoutImprovements: true }; -const scrollablePaneProps1: IScrollablePaneProps = { - ...scrollablePaneProps, - stickyHeaderContainerBehavior: 'always' -}; storiesOf('ScrollablePane-Sticky Details List', module) .addDecorator(FabricDecorator) @@ -337,11 +331,4 @@ storiesOf('ScrollablePane-Sticky Details List', module) numberOfColumns={6} scrollablePaneProps={scrollablePaneProps} /> - )) - .addStory('ScrollablePane Details List with sticky header & footer (3)', () => ( - )); 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 ccbb6657820ba..cdc86938bc782 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 @@ -498,7 +498,7 @@ export class Calendar extends BaseComponent impl } // Warning: (ae-forgotten-export) The symbol "ICalloutState" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export class Callout extends React.Component { // (undocumented) @@ -654,7 +654,7 @@ export class ComboBox extends BaseComponent { static defaultProps: IComboBoxProps; dismissMenu: () => void; // Warning: (ae-unresolved-inheritdoc-base) The @inheritDoc tag needs a TSDoc declaration reference; signature matching is not supported yet - // + // // (undocumented) focus: (shouldOpenOnFocus?: boolean | undefined, useFocusAsync?: boolean | undefined) => void; // (undocumented) @@ -2909,7 +2909,7 @@ export interface IContextualMenuItem { data?: any; disabled?: boolean; // Warning: (ae-forgotten-export) The symbol "IMenuItemClassNames" needs to be exported by the entry point index.d.ts - // + // // @deprecated getItemClassNames?: (theme: ITheme, disabled: boolean, expanded: boolean, checked: boolean, isAnchorLink: boolean, knownIcon: boolean, itemClassName?: string, dividerClassName?: string, iconClassName?: string, subMenuClassName?: string, primaryDisabled?: boolean) => IMenuItemClassNames; getSplitButtonVerticalDividerClassNames?: (theme: ITheme) => IVerticalDividerClassNames; @@ -3010,7 +3010,7 @@ export interface IContextualMenuListProps { } // Warning: (ae-forgotten-export) The symbol "IWithResponsiveModeState" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IContextualMenuProps extends IBaseProps, IWithResponsiveModeState { alignTargetEdge?: boolean; @@ -3030,7 +3030,7 @@ export interface IContextualMenuProps extends IBaseProps, IWith focusZoneProps?: IFocusZoneProps; gapSpace?: number; // Warning: (ae-forgotten-export) The symbol "IContextualMenuClassNames" needs to be exported by the entry point index.d.ts - // + // // @deprecated getMenuClassNames?: (theme: ITheme, className?: string) => IContextualMenuClassNames; hidden?: boolean; @@ -3791,7 +3791,7 @@ export interface IDialogFooterStyles { } // Warning: (ae-forgotten-export) The symbol "IAccessiblePopupProps" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDialogProps extends React.ClassAttributes, IWithResponsiveModeState, IAccessiblePopupProps { // @deprecated @@ -3895,7 +3895,7 @@ export interface IDocumentCardActions { } // Warning: (ae-forgotten-export) The symbol "DocumentCardActionsBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardActionsProps extends React.ClassAttributes { actions: IButtonProps[]; @@ -3938,7 +3938,7 @@ export interface IDocumentCardActivityPerson { } // Warning: (ae-forgotten-export) The symbol "DocumentCardActivityBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardActivityProps extends React.ClassAttributes { activity: string; @@ -3977,7 +3977,7 @@ export interface IDocumentCardDetails { } // Warning: (ae-forgotten-export) The symbol "DocumentCardDetailsBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardDetailsProps extends React.Props { className?: string; @@ -4036,7 +4036,7 @@ export interface IDocumentCardLocation { } // Warning: (ae-forgotten-export) The symbol "DocumentCardLocationBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardLocationProps extends React.ClassAttributes { ariaLabel?: string; @@ -4066,7 +4066,7 @@ export interface IDocumentCardLogo { } // Warning: (ae-forgotten-export) The symbol "DocumentCardLogoBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardLogoProps extends React.ClassAttributes { className?: string; @@ -4166,7 +4166,7 @@ export interface IDocumentCardStatus { } // Warning: (ae-forgotten-export) The symbol "DocumentCardStatusBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardStatusProps extends React.Props { className?: string; @@ -4208,7 +4208,7 @@ export interface IDocumentCardTitle { } // Warning: (ae-forgotten-export) The symbol "DocumentCardTitleBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IDocumentCardTitleProps extends React.ClassAttributes { className?: string; @@ -4411,7 +4411,7 @@ export interface IExpandingCard { } // Warning: (ae-forgotten-export) The symbol "IBaseCardProps" needs to be exported by the entry point index.d.ts -// +// // @public export interface IExpandingCardProps extends IBaseCardProps { compactCardHeight?: number; @@ -4430,7 +4430,7 @@ export interface IExpandingCardState { } // Warning: (ae-forgotten-export) The symbol "IBaseCardStyleProps" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IExpandingCardStyleProps extends IBaseCardStyleProps { compactCardHeight?: number; @@ -4440,7 +4440,7 @@ export interface IExpandingCardStyleProps extends IBaseCardStyleProps { } // Warning: (ae-forgotten-export) The symbol "IBaseCardStyles" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IExpandingCardStyles extends IBaseCardStyles { compactCard?: IStyle; @@ -5791,7 +5791,7 @@ export interface IPanelHeaderRenderer extends IRenderFunction { } // Warning: (ae-forgotten-export) The symbol "PanelBase" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export interface IPanelProps extends React.HTMLAttributes { className?: string; @@ -6417,7 +6417,6 @@ export interface IRGB { // @public (undocumented) export interface IScrollablePane { forceLayoutUpdate(): void; - getHorizontalScrollPosition(): number; getScrollPosition(): number; } @@ -6433,10 +6432,9 @@ export interface IScrollablePaneContext { sortSticky: (sticky: Sticky, sortAgain?: boolean) => void; notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; - usePlaceholderForSticky: () => boolean; + optimizePerformance: () => boolean; + userInteractionStatus: () => boolean; getHorizontalScrollPosition: () => number; - verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: IStickyContainerBehaviorType) => boolean; - getUserInteractionStatus: () => boolean; }; } @@ -6446,9 +6444,8 @@ export interface IScrollablePaneProps extends React.HTMLAttributes; experimentalLayoutImprovements?: boolean; initialScrollPosition?: number; + // (undocumented) scrollbarVisibility?: ScrollbarVisibility; - stickyFooterContainerBehavior?: IStickyContainerBehaviorType; - stickyHeaderContainerBehavior?: IStickyContainerBehaviorType; styles?: IStyleFunctionOrObject; theme?: ITheme; } @@ -6469,6 +6466,8 @@ export interface IScrollablePaneState { export interface IScrollablePaneStyleProps { className?: string; // (undocumented) + experimentalLayoutImprovements?: boolean; + // (undocumented) scrollbarVisibility?: IScrollablePaneProps['scrollbarVisibility']; theme: ITheme; } @@ -6958,7 +6957,7 @@ export interface ISpinButtonProps { keytipProps?: IKeytipProps; label?: string; // Warning: (ae-forgotten-export) The symbol "Position" needs to be exported by the entry point index.d.ts - // + // // (undocumented) labelPosition?: Position; max?: number; @@ -7135,9 +7134,6 @@ export interface IStackTokens { padding?: number | string; } -// @public -export type IStickyContainerBehaviorType = 'default' | 'onScroll' | 'always'; - // @public (undocumented) export interface IStickyContext { // (undocumented) @@ -7578,14 +7574,14 @@ export interface ITextFieldProps extends React.AllHTMLAttributes { } // Warning: (ae-forgotten-export) The symbol "IKeytipDataProps" needs to be exported by the entry point index.d.ts -// +// // @public export class KeytipData extends React.Component, {}> { // (undocumented) @@ -7892,7 +7888,7 @@ export class KeytipLayerBase extends BaseComponent): void; @@ -7930,7 +7926,7 @@ export class LayerBase extends React.Component { } // Warning: (ae-forgotten-export) The symbol "ILayerHostProps" needs to be exported by the entry point index.d.ts -// +// // @public (undocumented) export class LayerHost extends React.Component { // (undocumented) @@ -8627,12 +8623,8 @@ export class ScrollablePaneBase extends BaseComponent number; - // (undocumented) getScrollPosition: () => number; // (undocumented) - getUserInteractionStatus: () => boolean; - // (undocumented) notifySubscribers: () => void; // (undocumented) removeSticky: (sticky: Sticky) => void; @@ -8658,11 +8650,7 @@ export class ScrollablePaneBase extends BaseComponent void; // (undocumented) updateStickyRefHeights: () => void; - // (undocumented) - usePlaceholderForSticky: () => boolean; - // (undocumented) - verifyStickyContainerBehavior: (stickyContainerPosition: StickyPositionType, stickyContainerBehavior: IStickyContainerBehaviorType) => boolean; -} + } // @public (undocumented) export const ScrollablePaneContext: React.Context; @@ -8969,7 +8957,7 @@ export class Sticky extends BaseComponent { // (undocumented) componentWillUnmount(): void; // (undocumented) - static contextType: React.Context; + static contextType: React.Context; // (undocumented) static defaultProps: IStickyProps; // (undocumented) @@ -8992,7 +8980,7 @@ export class Sticky extends BaseComponent { readonly stickyContentTop: HTMLDivElement | null; // (undocumented) syncScroll: (container: HTMLElement) => void; - } +} // @public (undocumented) export enum StickyPositionType { @@ -9306,7 +9294,7 @@ export const TextField: React.StatelessComponent; // Warning: (ae-incompatible-release-tags) The symbol "TextFieldBase" is marked as @public, but its signature references "ITextFieldState" which is marked as @internal // Warning: (ae-incompatible-release-tags) The symbol "TextFieldBase" is marked as @public, but its signature references "ITextFieldSnapshot" which is marked as @internal -// +// // @public (undocumented) export class TextFieldBase extends React.Component implements ITextField { constructor(props: ITextFieldProps); @@ -9463,7 +9451,7 @@ export * from "@uifabric/styling"; export * from "@uifabric/utilities"; // Warnings were encountered during analysis: -// +// // lib/components/ColorPicker/ColorPicker.base.d.ts:8:9 - (ae-forgotten-export) The symbol "IRGBHex" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx index 85409a05c7432..02e03de84da9f 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -6,11 +6,9 @@ import { IScrollablePaneProps, IScrollablePaneStyleProps, IScrollablePaneStyles, - ScrollablePaneContext, - ScrollbarVisibility, - IStickyContainerBehaviorType + ScrollablePaneContext } from './ScrollablePane.types'; -import { Sticky, StickyPositionType } from '../../Sticky'; +import { Sticky } from '../../Sticky'; export interface IScrollablePaneState { stickyTopHeight: number; @@ -22,11 +20,6 @@ export interface IScrollablePaneState { const getClassNames = classNamesFunction(); export class ScrollablePaneBase extends BaseComponent implements IScrollablePane { - private _scrollLeft: number; - private _scrollTop: number; - private _isMounted: boolean; - private _listeningToEvents: boolean; - private _userInteractionStarted: boolean; private _root = React.createRef(); private _stickyAboveRef = React.createRef(); private _stickyBelowRef = React.createRef(); @@ -35,6 +28,8 @@ export class ScrollablePaneBase extends BaseComponent; private _mutationObserver: MutationObserver; private _notifyThrottled: () => void; + private _userHasInteracted: boolean; + private _scrollLeft: number; constructor(props: IScrollablePaneProps) { super(props); @@ -48,63 +43,101 @@ export class ScrollablePaneBase extends BaseComponent { this.sortSticky(sticky); }); this.notifySubscribers(); - if (this._stickies.size) { - this._listenToEventsAndObserveMutations(); + + if ('MutationObserver' in window) { + this._mutationObserver = new MutationObserver(mutation => { + // Function to check if mutation is occuring in stickyAbove or stickyBelow + function checkIfMutationIsSticky(mutationRecord: MutationRecord): boolean { + if (this.stickyAbove !== null && this.stickyBelow !== null) { + return this.stickyAbove.contains(mutationRecord.target) || this.stickyBelow.contains(mutationRecord.target); + } + return false; + } + + // Compute the scrollbar height which might have changed due to change in width of the content which might cause overflow + // If this._perf() is true, state already has non-zero scrollbarHeight & scrollbarWidth computed in componentDidMount() + const scrollbarHeight = this._perf() ? _getStateScrollbarHeight(this.state) : _getScrollbarHeight(this.contentContainer); + // check if the scroll bar height has changed and update the state so that it's postioned correctly below sticky footer + if (scrollbarHeight !== _getStateScrollbarHeight(this.state)) { + this.setState({ + scrollbarHeight: scrollbarHeight + }); + } + + // Notify subscribers again to re-check whether Sticky should be Sticky'd or not + this.notifySubscribers(); + + // If mutation occurs in sticky header or footer, then update sticky top/bottom heights + if (mutation.some(checkIfMutationIsSticky.bind(this))) { + this.updateStickyRefHeights(); + } else { + // If mutation occurs in scrollable region, then find Sticky it belongs to and force update + const stickyList: Sticky[] = []; + this._stickies.forEach(sticky => { + if (sticky.root && sticky.root.contains(mutation[0].target)) { + stickyList.push(sticky); + } + }); + if (stickyList.length) { + stickyList.forEach(sticky => { + sticky.forceUpdate(); + }); + } + } + }); + + if (this.root) { + this._mutationObserver.observe(this.root, { + childList: true, + attributes: true, + subtree: true, + characterData: true + }); + } } - this._isMounted = true; } public componentWillUnmount() { - this._isMounted = false; - this._listeningToEvents = false; this._events.off(this.contentContainer); this._events.off(window); @@ -115,48 +148,75 @@ export class ScrollablePaneBase extends BaseComponent
{this.props.children}
-
-
+
+
@@ -166,9 +226,7 @@ export class ScrollablePaneBase extends BaseComponent { - if (!this._sortBasedOnOrder()) { - sticky.setDistanceFromTop(this.contentContainer as HTMLDivElement); - } + sticky.setDistanceFromTop(this.contentContainer as HTMLDivElement); }); } } @@ -186,41 +244,22 @@ export class ScrollablePaneBase extends BaseComponent { - if (this._isMounted) { - this._listenToEventsAndObserveMutations(); - } - const { stickyPosition } = sticky.props; - if ( - !stickyPosition && - (this._getStickyContainerBehavior(StickyPositionType.Header) !== this._getStickyContainerBehavior(StickyPositionType.Footer) || - (!this.verifyStickyContainerBehavior(StickyPositionType.Header, 'default') || - !this.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default'))) - ) { - throw `If a Sticky component has stickyPosition 'Both', stickyHeaderContainerBehavior & - stickyFooterContainerBehavior must be same & should not be onScroll or always`; - } - this._stickies.add(sticky); - + const optimizePerformance = this._perf(); // If ScrollablePane is mounted, then sort sticky in correct place if (this.contentContainer) { - if (this.verifyStickyContainerBehavior(sticky.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, 'always')) { + if (sticky.canStickyBottom && optimizePerformance) { sticky.setState({ distanceFromTop: 0, // must set distanceFromTop to add stickyContent Ref to stickyContainer in sorted order. - isStickyBottom: stickyPosition === StickyPositionType.Footer, - // must set isStickyTop or isStickyBottom to place nonStickyContent as a child of stickyContent Ref. - isStickyTop: stickyPosition === StickyPositionType.Header + isStickyBottom: true }); } else { - if (!this._sortBasedOnOrder()) { + if (!optimizePerformance) { sticky.setDistanceFromTop(this.contentContainer); } this.sortSticky(sticky); } - } // else ScrollablePane is yet to be mounted - // when scrollablePane mounts, it calls notifySubscribers() which - // 1. sets distanceFromTop to add stickyContent Ref to stickyContainer in sorted order. - // 2. sets isStickyTop or isStickyBottom to place nonStickyContent as a child of stickyContent Ref. + } }; public removeSticky = (sticky: Sticky): void => { @@ -231,60 +270,47 @@ export class ScrollablePaneBase extends BaseComponent { if (this.stickyAbove && this.stickyBelow) { - // When is sorting needed? - // 1. not a part of stickyContainer (or to be added first time) - // 2. part of stickyContainer and not sorted based on order - const isPartOfStickyAboveContainer = - sticky.canStickyTop && this._stickyContainerContainsStickyContent(sticky, StickyPositionType.Header); - const isPartOfStickyBelowContainer = - sticky.canStickyBottom && this._stickyContainerContainsStickyContent(sticky, StickyPositionType.Footer); - const isPartOfStickyContainer = sticky.props.stickyPosition - ? isPartOfStickyAboveContainer !== isPartOfStickyBelowContainer - : isPartOfStickyAboveContainer && isPartOfStickyBelowContainer; - - if (isPartOfStickyContainer && this._sortBasedOnOrder()) { - // already sorted based on order - return; - } if (sortAgain) { this._removeStickyFromContainers(sticky); } - if (sticky.canStickyTop && sticky.stickyContentTop) { - this._addToStickyContainer(sticky, this.stickyAbove, sticky.stickyContentTop); + if (sticky.canStickyTop && _getStickyContentTop(sticky)) { + this._addToStickyContainer(sticky, this.stickyAbove, _getStickyContentTop(sticky)!); } - if (sticky.canStickyBottom && sticky.stickyContentBottom) { - this._addToStickyContainer(sticky, this.stickyBelow, sticky.stickyContentBottom); + if (sticky.canStickyBottom && _getStickyContentBottom(sticky)) { + this._addToStickyContainer(sticky, this.stickyBelow, _getStickyContentBottom(sticky)!); } } }; public updateStickyRefHeights = (): void => { + if (this._perf()) { + return; + } + const stickyItems = this._stickies; let stickyTopHeight = 0; let stickyBottomHeight = 0; - const placeholderUsedForStickyContent = this.usePlaceholderForSticky(); - - if (placeholderUsedForStickyContent) { - stickyItems.forEach((sticky: Sticky) => { - const { isStickyTop, isStickyBottom } = sticky.state; - if (sticky.nonStickyContent) { - if (isStickyTop && !this.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')) { - stickyTopHeight += sticky.nonStickyContent.offsetHeight; - } - if (isStickyBottom && !this.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')) { - stickyBottomHeight += sticky.nonStickyContent.offsetHeight; - } - this._checkStickyStatus(sticky); + + stickyItems.forEach((sticky: Sticky) => { + const { isStickyTop, isStickyBottom } = sticky.state; + const nonStickyContent = _getNonStickyContent(sticky); + if (nonStickyContent) { + if (isStickyTop) { + stickyTopHeight += _getOffsetHeight(nonStickyContent); } - }); + if (isStickyBottom) { + stickyBottomHeight += _getOffsetHeight(nonStickyContent); + } + this._checkStickyStatus(sticky); + } + }); - this.setState({ - stickyTopHeight: stickyTopHeight, - stickyBottomHeight: stickyBottomHeight - }); - } + this.setState({ + stickyTopHeight: stickyTopHeight, + stickyBottomHeight: stickyBottomHeight + }); }; public notifySubscribers = (): void => { @@ -297,49 +323,21 @@ export class ScrollablePaneBase extends BaseComponent { - if (this.contentContainer) { - return this._listeningToEvents ? this._scrollTop : this.contentContainer.scrollTop; + const { contentContainer } = this; + if (contentContainer) { + return contentContainer.scrollTop; } return 0; }; - public getHorizontalScrollPosition = (): number => { - if (this.contentContainer) { - return this._listeningToEvents ? this._scrollLeft : this.contentContainer.scrollLeft; - } - return 0; - }; - public syncScrollSticky = (sticky: Sticky): void => { - if (sticky && this.contentContainer) { - sticky.syncScroll(this.contentContainer); + const { contentContainer } = this; + if (sticky && contentContainer) { + sticky.syncScroll(contentContainer); } }; - public usePlaceholderForSticky = (): boolean => { - return !this.props.experimentalLayoutImprovements; - }; - - public verifyStickyContainerBehavior = ( - stickyContainerPosition: StickyPositionType, - stickyContainerBehavior: IStickyContainerBehaviorType - ): boolean => { - return stickyContainerPosition === StickyPositionType.Header - ? (this._getStickyContainerBehavior(StickyPositionType.Header) || 'default') === stickyContainerBehavior - : (this._getStickyContainerBehavior(StickyPositionType.Footer) || 'default') === stickyContainerBehavior; - }; - - public getUserInteractionStatus = (): boolean => { - return this._userInteractionStarted; - }; - - private _getStickyContainerBehavior(stickyContainerPosition: StickyPositionType): IStickyContainerBehaviorType | undefined { - return stickyContainerPosition === StickyPositionType.Header - ? this.props.stickyHeaderContainerBehavior - : this.props.stickyFooterContainerBehavior; - } - private _getScrollablePaneContext = (): IScrollablePaneContext => { return { scrollablePane: { @@ -351,44 +349,25 @@ export class ScrollablePaneBase extends BaseComponent this._userHasInteracted, + getHorizontalScrollPosition: (): number => this._scrollLeft } }; }; private _checkStickyStatus(sticky: Sticky): void { - const placeholderUsed = this.usePlaceholderForSticky(); - const placeholderUsedForStickyContentTop = sticky.canStickyTop && placeholderUsed; - const placeholderUsedForStickyContentBottom = sticky.canStickyBottom && placeholderUsed; - - if (this.stickyAbove && this.stickyBelow && this.contentContainer && sticky.nonStickyContent) { - // If Sticky is sticky, then append content to appropriate container - const { isStickyBottom, isStickyTop } = sticky.state; - if (isStickyTop || isStickyBottom) { - if ( - placeholderUsedForStickyContentTop && - isStickyTop && - !this.stickyAbove.contains(sticky.nonStickyContent) && - sticky.stickyContentTop - ) { - sticky.addSticky(sticky.stickyContentTop); + if (this.stickyAbove && this.stickyBelow && this.contentContainer && _getNonStickyContent(sticky)) { + // If sticky is sticky, then append content to appropriate container + if (sticky.state.isStickyTop || sticky.state.isStickyBottom) { + if (sticky.state.isStickyTop && !this.stickyAbove.contains(_getNonStickyContent(sticky)) && _getStickyContentTop(sticky)) { + sticky.addSticky(_getStickyContentTop(sticky)!); } - if ( - placeholderUsedForStickyContentBottom && - isStickyBottom && - !this.stickyBelow.contains(sticky.nonStickyContent) && - sticky.stickyContentBottom - ) { - sticky.addSticky(sticky.stickyContentBottom); + if (sticky.state.isStickyBottom && !this.stickyBelow.contains(_getNonStickyContent(sticky)) && _getStickyContentBottom(sticky)) { + sticky.addSticky(_getStickyContentBottom(sticky)!); } - } else if ( - (placeholderUsedForStickyContentBottom || placeholderUsedForStickyContentTop) && - !this.contentContainer.contains(sticky.nonStickyContent) - ) { + } else if (!this.contentContainer.contains(_getNonStickyContent(sticky))) { // Reset sticky if it's not sticky and not in the contentContainer element sticky.resetSticky(); } @@ -403,9 +382,8 @@ export class ScrollablePaneBase extends BaseComponent { @@ -415,18 +393,29 @@ export class ScrollablePaneBase extends BaseComponent { - const stickyContent = isStickyAboveContainer ? item.stickyContentTop : item.stickyContentBottom; - if (stickyContent) { - return stickyChildrenElements.indexOf(stickyContent) > -1; - } - }); + const stickyListSorted = stickyList + .sort((a, b) => { + return optimizePerformance + ? (a.props.order || 0) - (b.props.order || 0) + : (a.state.distanceFromTop || 0) - (b.state.distanceFromTop || 0); + }) + .filter(item => { + const stickyContent = isStickyAboveContainer ? _getStickyContentTop(item) : _getStickyContentBottom(item); + if (stickyContent) { + return stickyChildrenElements.indexOf(stickyContent) > -1; + } + }); // Get first element that has a distance from top that is further than our sticky that is being added let targetStickyToAppendBefore: Sticky | undefined = undefined; for (const i in stickyListSorted) { - if (this._isTargetContainer(stickyListSorted[i], sticky)) { + if ( + optimizePerformance + ? (stickyListSorted[i].props.order || 0) > (sticky.props.order || 0) + : (stickyListSorted[i].state.distanceFromTop || 0) >= (sticky.state.distanceFromTop || 0) + ) { targetStickyToAppendBefore = stickyListSorted[i]; break; } @@ -436,8 +425,8 @@ export class ScrollablePaneBase extends BaseComponent { - if (this._stickyContainerContainsStickyContent(sticky, StickyPositionType.Header)) { - this.stickyAbove!.removeChild(sticky.stickyContentTop!); + if (this.stickyAbove && _getStickyContentTop(sticky) && this.stickyAbove.contains(_getStickyContentTop(sticky))) { + this.stickyAbove.removeChild(_getStickyContentTop(sticky)!); } - if (this._stickyContainerContainsStickyContent(sticky, StickyPositionType.Footer)) { - this.stickyBelow!.removeChild(sticky.stickyContentBottom!); + if (this.stickyBelow && _getStickyContentBottom(sticky) && this.stickyBelow.contains(_getStickyContentBottom(sticky))) { + this.stickyBelow.removeChild(_getStickyContentBottom(sticky)!); } }; + private _perf = (): boolean => !!this.props.experimentalLayoutImprovements; + private _onWindowResize = (): void => { - const { scrollbarHeight, scrollbarWidth } = this.state; - let newScrollbarWidth: number = scrollbarHeight; - let newScrollbarHeight: number = scrollbarWidth; - if (this.props.scrollbarVisibility !== ScrollbarVisibility.always) { - newScrollbarHeight = this._getScrollbarHeight(); - newScrollbarWidth = this._getScrollbarWidth(); - } + const { contentContainer } = this; + /** + * Scrollbar height/width changes on window resize + */ this.setState({ - scrollbarHeight: newScrollbarHeight, - scrollbarWidth: newScrollbarWidth + scrollbarWidth: _getScrollbarWidth(contentContainer), + scrollbarHeight: _getScrollbarHeight(contentContainer) }); this.notifySubscribers(); }; - private _getStickyContainerStyle = (height: number, isTop: boolean): React.CSSProperties => { - const stickyContainerHeight = this._setStickyContainerHeight() ? height : undefined; - return { - ...(stickyContainerHeight !== undefined ? { height: height } : {}), - ...(getRTL() - ? { - right: '0', - left: `${this.state.scrollbarWidth || this._getScrollbarWidth() || 0}px` - } - : { - left: '0', - right: `${this.state.scrollbarWidth || this._getScrollbarWidth() || 0}px` - }), - ...(isTop - ? { - top: '0' - } - : { - bottom: `${this.state.scrollbarHeight || this._getScrollbarHeight() || 0}px` - }) - }; - }; - - private _setStickyContainerHeight(): boolean { - return this.usePlaceholderForSticky(); - } - - private _getScrollbarWidth(): number { - const { contentContainer } = this; - return contentContainer ? contentContainer.offsetWidth - contentContainer.clientWidth : 0; - } - - private _getScrollbarHeight(): number { - const { contentContainer } = this; - return contentContainer ? contentContainer.offsetHeight - contentContainer.clientHeight : 0; - } - private _onScroll = () => { const { contentContainer } = this; if (contentContainer) { - // sync Sticky scroll if contentContainer has scrolled horizontally - if (this._scrollLeft !== contentContainer.scrollLeft) { - this._scrollLeft = contentContainer.scrollLeft; - this._stickies.forEach((sticky: Sticky) => { - sticky.syncScroll(contentContainer); - }); - } - if (this._scrollTop !== contentContainer.scrollTop) { - this._userInteractionStarted = true; - this._scrollTop = contentContainer.scrollTop; - this._notifyThrottled(); - } + this._userHasInteracted = true; + this._scrollLeft = contentContainer.scrollLeft; + this._stickies.forEach((sticky: Sticky) => { + sticky.syncScroll(contentContainer); + }); } + + this._notifyThrottled(); }; +} - private _sortBasedOnOrder(): boolean { - return !!this.props.experimentalLayoutImprovements; - } +function _getStickyContainerStyle(scrollbarHeight: number, scrollbarWidth: number, isTop: boolean, height?: number): React.CSSProperties { + const isRtl = getRTL(); + const left = `${scrollbarWidth || 0}px`; + const right = '0'; + return { + ...(height !== undefined ? { height: height } : {}), + right: isRtl ? right : left, + left: isRtl ? left : right, + ...(isTop + ? { + top: '0' + } + : { + bottom: `${scrollbarHeight || 0}px` + }) + }; +} - private _stickyContainerContainsStickyContent(sticky: Sticky, stickyPositionType: StickyPositionType): boolean { - return stickyPositionType === StickyPositionType.Header - ? !!this.stickyAbove && !!sticky.stickyContentTop && this.stickyAbove.contains(sticky.stickyContentTop) - : !!this.stickyBelow && !!sticky.stickyContentBottom && this.stickyBelow.contains(sticky.stickyContentBottom); - } +function _getScrollbarWidth(contentContainer: HTMLDivElement | null): number { + return contentContainer ? contentContainer.offsetWidth - contentContainer.clientWidth : 0; +} - private _sortStickyList(stickyList: Sticky[]): Sticky[] { - const stickyListCopy: Sticky[] = [...stickyList]; - if (this._sortBasedOnOrder()) { - return stickyListCopy.sort((a, b) => { - return (a.props.order || 0) - (b.props.order || 0); - }); - } else { - return stickyListCopy.sort((a, b) => { - return (a.state.distanceFromTop || 0) - (b.state.distanceFromTop || 0); - }); - } - } +function _getScrollbarHeight(contentContainer: HTMLDivElement | null): number { + return contentContainer ? _getOffsetHeight(contentContainer) - contentContainer.clientHeight : 0; +} - private _isTargetContainer(a: Sticky, b: Sticky): boolean { - return this._sortBasedOnOrder() - ? (a.props.order || 0) > (b.props.order || 0) - : (a.state.distanceFromTop || 0) >= (b.state.distanceFromTop || 0); - } +/** + * Returns state.scrollbarHeight + * @param state Returns + */ +function _getStateScrollbarHeight(state: IScrollablePaneState): number { + return state.scrollbarHeight; +} - private _listenToEventsAndObserveMutations() { - if (!this._listeningToEvents) { - this._listeningToEvents = true; - this._events.on(this.contentContainer, 'scroll', this._onScroll); - this._events.on(window, 'resize', this._onWindowResize); - } - if (!this._mutationObserver && 'MutationObserver' in window) { - this._mutationObserver = new MutationObserver(mutation => { - // Function to check if mutation is occuring in stickyAbove or stickyBelow - function checkIfMutationIsSticky(mutationRecord: MutationRecord): boolean { - if (this.stickyAbove !== null && this.stickyBelow !== null) { - return this.stickyAbove.contains(mutationRecord.target) || this.stickyBelow.contains(mutationRecord.target); - } - return false; - } - if (this.props.scrollbarVisibility !== ScrollbarVisibility.always) { - // Compute the scrollbar height which might have changed due to change in width of the content which might cause overflow - const scrollbarHeight = this._getScrollbarHeight(); - const scrollbarWidth = this._getScrollbarWidth(); - // check if the scroll bar height has changed and update the state so that it's postioned correctly below sticky footer - if (scrollbarHeight !== this.state.scrollbarHeight || scrollbarWidth !== this.state.scrollbarWidth) { - this.setState({ - scrollbarHeight: scrollbarHeight, - scrollbarWidth: scrollbarWidth - }); - } - } +/** + * Returns state.scrollbarWidth + * @param state Returns + */ +function _getStateScrollbarWidth(state: IScrollablePaneState): number { + return state.scrollbarWidth; +} - // Notify subscribers again to re-check whether Sticky should be Sticky'd or not - this.notifySubscribers(); +/** + * Returns state.stickyTopHeight + * @param state + */ +function _getStateStickyTopHeight(state: IScrollablePaneState): number { + return state.stickyTopHeight; +} - // If mutation occurs in sticky header or footer, then update sticky top/bottom heights - if (mutation.some(checkIfMutationIsSticky.bind(this))) { - this.updateStickyRefHeights(); - } else { - // If mutation occurs in scrollable region, then find Sticky it belongs to and force update - const stickyList: Sticky[] = []; - this._stickies.forEach(sticky => { - if (sticky.root && sticky.root.contains(mutation[0].target)) { - stickyList.push(sticky); - } - }); - if (stickyList.length) { - stickyList.forEach(sticky => { - sticky.forceUpdate(); - }); - } - } - }); +/** + * Returns state.stickyBottomHeight + * @param state + */ +function _getStateStickyBottomHeight(state: IScrollablePaneState): number { + return state.stickyBottomHeight; +} - if (this.root) { - this._mutationObserver.observe(this.root, { - childList: true, - attributes: true, - subtree: true, - characterData: true - }); - } - } - } +/** + * Returns props.initialScrollPosition + * @param props + */ +function _getPropsInitialScrollPosition(props: IScrollablePaneProps): number | undefined { + return props.initialScrollPosition; +} + +/** + * Returns sticky.stickyContentTop + * @param sticky + */ +function _getStickyContentTop(sticky: Sticky): HTMLDivElement | null { + return sticky.stickyContentTop; +} + +/** + * Returns sticky.stickyContentBottom + * @param sticky + */ +function _getStickyContentBottom(sticky: Sticky): HTMLDivElement | null { + return sticky.stickyContentBottom; +} + +/** + * Returns sticky.nonStickyContent + * @param sticky + */ +function _getNonStickyContent(sticky: Sticky): HTMLDivElement | null { + return sticky.nonStickyContent; +} +/** + * Returns reactRefObject.current + * @param reactRefObject - React.RefObject + */ +function _getRefObjectCurrent(reactRefObject: React.RefObject): T | null { + return reactRefObject.current; +} + +/** + * Returns elem.offsetHeight + * @param elem - HTMLElement for which offsetHeight is to be calculated + */ +function _getOffsetHeight(elem: HTMLElement): number { + return elem.offsetHeight; } diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.styles.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.styles.ts index 27e50a421f015..088f864104b7f 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.styles.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.styles.ts @@ -7,13 +7,13 @@ const GlobalClassNames = { }; export const getStyles = (props: IScrollablePaneStyleProps): IScrollablePaneStyles => { - const { className, theme } = props; + const { className, theme, experimentalLayoutImprovements } = props; const classNames = getGlobalClassNames(GlobalClassNames, theme); const AboveAndBelowStyles: IStyle = { position: 'absolute', - pointerEvents: 'none' + pointerEvents: experimentalLayoutImprovements ? 'none' : 'auto' }; const positioningStyle: IStyle = { @@ -25,13 +25,15 @@ export const getStyles = (props: IScrollablePaneStyleProps): IScrollablePaneStyl WebkitOverflowScrolling: 'touch' }; + const _scrollbarVisibility = experimentalLayoutImprovements || props.scrollbarVisibility === 'always' ? 'scroll' : 'auto'; + return { root: [classNames.root, theme.fonts.medium, positioningStyle, className], contentContainer: [ classNames.contentContainer, { - overflowY: props.scrollbarVisibility === 'always' ? 'scroll' : 'auto', - overflowX: props.scrollbarVisibility === 'always' ? 'scroll' : 'auto' + overflowY: _scrollbarVisibility, + overflowX: _scrollbarVisibility }, positioningStyle ], diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx index 5eda9fc1210bf..cec5d5f74606c 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.test.tsx @@ -3,7 +3,7 @@ import * as renderer from 'react-test-renderer'; import { ScrollablePane } from './ScrollablePane'; import * as ReactDOM from 'react-dom'; import { createRef } from 'react'; -import { IScrollablePane } from './ScrollablePane.types'; +import { IScrollablePane, ScrollbarVisibility } from './ScrollablePane.types'; import { DetailsListBasicExample } from '../DetailsList/examples/DetailsList.Basic.Example'; import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'; @@ -37,31 +37,28 @@ describe('ScrollablePane', () => { expect(scrollablePaneRef.current).toBeDefined(); const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; // it sets userinteraction flag only after scroll - expect(!!scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(false); + expect(!!scrollablePaneRefCurrent._userHasInteracted).toBe(false); const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; const contentContainer = scrollablePane.children[0]; - expect(scrollablePaneRef.current!.getScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(0); expect(contentContainer.scrollTop).toBe(0); - expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent._scrollLeft).toBe(0); expect(contentContainer.scrollLeft).toBe(0); contentContainer.scrollTop = 100; contentContainer.dispatchEvent(new Event('scroll')); - // it sets userinteraction flag only after scroll - // if there is no Sticky, then there is no scroll handler and - // this flag remains unset. - expect(!!scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(false); + expect(!!scrollablePaneRefCurrent._userHasInteracted).toBe(true); expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(100); expect(contentContainer.scrollTop).toBe(100); contentContainer.scrollLeft = 50; contentContainer.dispatchEvent(new Event('scroll')); - expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(50); + expect(scrollablePaneRefCurrent._scrollLeft).toBe(50); expect(contentContainer.scrollLeft).toBe(50); }); @@ -78,10 +75,9 @@ describe('ScrollablePane', () => { ); const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; - expect(scrollablePaneRefCurrent._setStickyContainerHeight()).toBe(false); // it sets userinteraction flag only after scroll - expect(!!scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(false); + expect(!!scrollablePaneRefCurrent._userHasInteracted).toBe(false); const scrollablePane = container.querySelector('.ms-ScrollablePane') as HTMLDivElement; const contentContainer = scrollablePane.children[0]; @@ -89,25 +85,25 @@ describe('ScrollablePane', () => { expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(0); expect(contentContainer.scrollTop).toBe(0); - expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent._scrollLeft).toBe(0); expect(contentContainer.scrollLeft).toBe(0); contentContainer.scrollTop = 100; contentContainer.dispatchEvent(new Event('scroll')); // it sets userinteraction flag only after scroll - expect(scrollablePaneRefCurrent.getUserInteractionStatus()).toBe(true); + expect(scrollablePaneRefCurrent._userHasInteracted).toBe(true); expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(100); expect(contentContainer.scrollTop).toBe(100); - expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent._scrollLeft).toBe(0); expect(contentContainer.scrollLeft).toBe(0); contentContainer.scrollLeft = 20; contentContainer.dispatchEvent(new Event('scroll')); - expect(scrollablePaneRef.current!.getHorizontalScrollPosition()).toBe(20); + expect(scrollablePaneRefCurrent._scrollLeft).toBe(20); expect(contentContainer.scrollLeft).toBe(20); - expect(scrollablePaneRef.current!.getScrollPosition()).toBe(100); + expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(100); expect(contentContainer.scrollTop).toBe(100); }); @@ -129,8 +125,7 @@ describe('ScrollablePane', () => { expect(scrollablePaneRefCurrent.getScrollPosition()).toBe(0); expect(contentContainer.scrollTop).toBe(0); - - expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(0); + expect(scrollablePaneRefCurrent._scrollLeft).toBe(0); expect(contentContainer.scrollLeft).toBe(0); contentContainer.scrollTop = 100; @@ -140,7 +135,7 @@ describe('ScrollablePane', () => { contentContainer.scrollLeft = 20; contentContainer.dispatchEvent(new Event('scroll')); - expect(scrollablePaneRefCurrent.getHorizontalScrollPosition()).toBe(20); + expect(scrollablePaneRefCurrent._scrollLeft).toBe(20); expect(contentContainer.scrollLeft).toBe(20); }); @@ -249,34 +244,7 @@ describe('ScrollablePane', () => { }); describe('it correctly verifies sticky container behavior', () => { - it(`if experimentalLayoutImprovements is undefined|false`, () => { - const scrollablePaneRef = createRef(); - ReactDOM.render( - - -
Header
-
- - -
Footer
-
-
, - container - ); - - const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; - expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(false); - - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'default')).toBe(true); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'onScroll')).toBe(false); - - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default')).toBe(true); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'onScroll')).toBe(false); - }); - - it(`if experimentalLayoutImprovements is true`, () => { + it(`if experimentalLayoutImprovements is true and scrollbarVisibility is not defined`, () => { const scrollablePaneRef = createRef(); ReactDOM.render( @@ -292,26 +260,13 @@ describe('ScrollablePane', () => { ); const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; - expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(true); - expect(scrollablePaneRefCurrent.usePlaceholderForSticky()).toBe(false); - - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'default')).toBe(true); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'onScroll')).toBe(false); - - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default')).toBe(true); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'onScroll')).toBe(false); + expect(scrollablePaneRefCurrent._perf()).toBe(true); }); - it(`if experimentalLayoutImprovements is undefined|false and behavior is not default`, () => { + it(`if experimentalLayoutImprovements is undefined|false`, () => { const scrollablePaneRef = createRef(); ReactDOM.render( - +
Header
@@ -324,26 +279,16 @@ describe('ScrollablePane', () => { ); const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; - expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(false); - expect(scrollablePaneRefCurrent.usePlaceholderForSticky()).toBe(true); - - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'default')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'onScroll')).toBe(true); - - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')).toBe(true); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'onScroll')).toBe(false); + expect(scrollablePaneRefCurrent._perf()).toBe(false); }); - it(`if experimentalLayoutImprovements is true and behavior is not default`, () => { + it(`if experimentalLayoutImprovements is true and scrollbarVisibility is defined`, () => { const scrollablePaneRef = createRef(); ReactDOM.render(
Header
@@ -357,17 +302,7 @@ describe('ScrollablePane', () => { ); const scrollablePaneRefCurrent = scrollablePaneRef.current! as any; - expect(scrollablePaneRefCurrent._setStickyContainerHeight()).toBe(false); - expect(scrollablePaneRefCurrent._sortBasedOnOrder()).toBe(true); - expect(scrollablePaneRefCurrent.usePlaceholderForSticky()).toBe(false); - - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'default')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'always')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Header, 'onScroll')).toBe(true); - - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'default')).toBe(false); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'always')).toBe(true); - expect(scrollablePaneRefCurrent.verifyStickyContainerBehavior(StickyPositionType.Footer, 'onScroll')).toBe(false); + expect(scrollablePaneRefCurrent._perf()).toBe(true); }); }); }); diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts index 0339eceb2f37a..144c4fed5fa82 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -3,7 +3,6 @@ import { IRefObject, IStyleFunctionOrObject } from '../../Utilities'; import { IStyle, ITheme } from '../../Styling'; import { ScrollablePaneBase } from './ScrollablePane.base'; import { Sticky } from '../Sticky/Sticky'; -import { StickyPositionType } from '../Sticky/Sticky.types'; /** * {@docCategory ScrollablePane} @@ -11,10 +10,8 @@ import { StickyPositionType } from '../Sticky/Sticky.types'; export interface IScrollablePane { /** Triggers a layout update for the pane. */ forceLayoutUpdate(): void; - /** Gets the current vertical scroll position of the scrollable pane */ + /** Gets the current scroll position of the scrollable pane */ getScrollPosition(): number; - /** Gets the current horizontal scroll position of the scrollable pane */ - getHorizontalScrollPosition(): number; } /** @@ -49,29 +46,28 @@ export interface IScrollablePaneProps extends React.HTMLAttributes void; notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; - usePlaceholderForSticky: () => boolean; + optimizePerformance: () => boolean; + userInteractionStatus: () => boolean; getHorizontalScrollPosition: () => number; - verifyStickyContainerBehavior: ( - stickyContainerPosition: StickyPositionType, - stickyContainerBehavior: IStickyContainerBehaviorType - ) => boolean; - getUserInteractionStatus: () => boolean; }; } diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/__snapshots__/ScrollablePane.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ScrollablePane/__snapshots__/ScrollablePane.test.tsx.snap index 36570cc9edfdd..38e770721993a 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/__snapshots__/ScrollablePane.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/__snapshots__/ScrollablePane.test.tsx.snap @@ -37,7 +37,7 @@ exports[`ScrollablePane renders correctly 1`] = ` className= { - pointer-events: none; + pointer-events: auto; position: absolute; top: 0px; } @@ -58,7 +58,7 @@ exports[`ScrollablePane renders correctly 1`] = ` { bottom: 0px; - pointer-events: none; + pointer-events: auto; position: absolute; } @media screen and (-ms-high-contrast: active){& { @@ -78,7 +78,7 @@ exports[`ScrollablePane renders correctly 1`] = ` { bottom: 0px; - pointer-events: none; + pointer-events: auto; position: absolute; width: 100%; } diff --git a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx index bf562b1101b3f..ac88e0b0ee560 100644 --- a/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ScrollablePane/examples/ScrollablePane.Sticky.Optimized.DetailsList.Example.tsx @@ -107,8 +107,8 @@ export class ScrollablePaneStickyOptimizedDetailsList extends React.Component<{} key: 'column' + i, name: 'Test ' + i, fieldName: 'test' + i, - minWidth: 100, - maxWidth: 200, + minWidth: 400, + maxWidth: 600, isResizable: true }); } @@ -120,15 +120,10 @@ export class ScrollablePaneStickyOptimizedDetailsList extends React.Component<{} public render(): JSX.Element { const { items } = this.state; - const stickyBackgroundColor = getTheme().palette.white; + const stickyBackgroundColor = getTheme().palette.red; return (
- + diff --git a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx index 8de54fad7218a..5c2bec4d09a72 100644 --- a/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx +++ b/packages/office-ui-fabric-react/src/components/Sticky/Sticky.tsx @@ -1,7 +1,7 @@ import * as PropTypes from 'prop-types'; import * as React from 'react'; import { BaseComponent } from '../../Utilities'; -import { IScrollablePaneContext, ScrollablePaneContext } from '../ScrollablePane/ScrollablePane.types'; +import { ScrollablePaneContext } from '../ScrollablePane/ScrollablePane.types'; import { IStickyProps, StickyPositionType } from './Sticky.types'; export interface IStickyState { @@ -14,9 +14,11 @@ export interface IStickyContext { scrollablePane: PropTypes.Requireable; } +const stickyPositionBoth = StickyPositionType.Both; + export class Sticky extends BaseComponent { public static defaultProps: IStickyProps = { - stickyPosition: StickyPositionType.Both, + stickyPosition: stickyPositionBoth, isScrollSynced: true }; @@ -40,45 +42,43 @@ export class Sticky extends BaseComponent { } public get root(): HTMLDivElement | null { - return this._root.current; + return _getRefObjectCurrent(this._root); } public get placeholder(): HTMLDivElement | null { - return this._placeHolder.current; + return _getRefObjectCurrent(this._placeHolder); } public get stickyContentTop(): HTMLDivElement | null { - return this._stickyContentTop.current; + return _getRefObjectCurrent(this._stickyContentTop); } public get stickyContentBottom(): HTMLDivElement | null { - return this._stickyContentBottom.current; + return _getRefObjectCurrent(this._stickyContentBottom); } public get nonStickyContent(): HTMLDivElement | null { - return this._nonStickyContent.current; + return _getRefObjectCurrent(this._nonStickyContent); } public get canStickyTop(): boolean { - return this.props.stickyPosition === StickyPositionType.Both || this.props.stickyPosition === StickyPositionType.Header; + return _canSticky(StickyPositionType.Header, this.props.stickyPosition); } public get canStickyBottom(): boolean { - return this.props.stickyPosition === StickyPositionType.Both || this.props.stickyPosition === StickyPositionType.Footer; + return _canSticky(StickyPositionType.Footer, this.props.stickyPosition); } public syncScroll = (container: HTMLElement): void => { - const { nonStickyContent, stickyContentTop, stickyContentBottom } = this; - const { isStickyBottom, isStickyTop } = this.state; - const { scrollablePane } = this.context; - // scroll sync is needed only if current state is sticky - if (!scrollablePane || !this.props.isScrollSynced || !(isStickyBottom || isStickyTop)) { + const scrollablePaneContext = this.context.scrollablePane; + const userInteractionHasStarted = scrollablePaneContext.userInteractionStatus(); + if (!this.props.isScrollSynced || !(this.state.isStickyBottom || this.state.isStickyTop) || !userInteractionHasStarted) { return; } - const containerScrollLeft = scrollablePane.getHorizontalScrollPosition(); - const usePlaceholderForSticky = scrollablePane.usePlaceholderForSticky(); - const stickyContent = isStickyTop ? stickyContentTop : stickyContentBottom; - if (!usePlaceholderForSticky && stickyContent && stickyContent.children && stickyContent.children.length > 0) { + const { nonStickyContent } = this; + const containerScrollLeft = scrollablePaneContext.getHorizontalScrollPosition(); + const stickyContent = this.state.isStickyTop ? this.stickyContentTop : this.stickyContentBottom; + if (this._perf() && stickyContent && stickyContent.children && stickyContent.children.length > 0) { stickyContent.children[0].scrollLeft = containerScrollLeft; } else if (nonStickyContent) { nonStickyContent.scrollLeft = containerScrollLeft; @@ -86,7 +86,7 @@ export class Sticky extends BaseComponent { }; public componentDidMount(): void { - const { scrollablePane } = this._getContext(); + const { scrollablePane } = this.context; if (!scrollablePane) { return; @@ -97,7 +97,7 @@ export class Sticky extends BaseComponent { } public componentWillUnmount(): void { - const { scrollablePane } = this._getContext(); + const { scrollablePane } = this.context; if (!scrollablePane) { return; @@ -108,27 +108,26 @@ export class Sticky extends BaseComponent { } public componentDidUpdate(prevProps: IStickyProps, prevState: IStickyState): void { - const { scrollablePane } = this._getContext(); + const { scrollablePane } = this.context; if (!scrollablePane) { return; } + const { state } = this; - const { isStickyBottom, isStickyTop, distanceFromTop } = this.state; - if (prevState.distanceFromTop !== distanceFromTop) { + if (prevState.distanceFromTop !== state.distanceFromTop) { scrollablePane.sortSticky(this, true /*sortAgain*/); } - if (prevState.isStickyTop !== isStickyTop || prevState.isStickyBottom !== isStickyBottom) { - if (this._activeElement) { - this._activeElement.focus(); + if (prevState.isStickyTop !== state.isStickyTop || prevState.isStickyBottom !== state.isStickyBottom) { + const activeElement = this._activeElement; + if (activeElement) { + activeElement.focus(); } scrollablePane.updateStickyRefHeights(); } - // horizontal scroll has to be synced only if component is sticky - if ((isStickyBottom || isStickyTop) && scrollablePane.getUserInteractionStatus()) { - // Sync Sticky scroll position with content container on each update - scrollablePane.syncScrollSticky(this); - } + + // Sync Sticky scroll position with content container on each update + scrollablePane.syncScrollSticky(this); } public shouldComponentUpdate(nextProps: IStickyProps, nextState: IStickyState): boolean { @@ -136,35 +135,42 @@ export class Sticky extends BaseComponent { return true; } - const { isStickyTop, isStickyBottom, distanceFromTop } = this.state; - - return (isStickyTop !== nextState.isStickyTop || - isStickyBottom !== nextState.isStickyBottom || - this.props.stickyPosition !== nextProps.stickyPosition || - this.props.children !== nextProps.children || - distanceFromTop !== nextState.distanceFromTop || - this._isOffsetHeightDifferent()) as boolean; + const { state, props } = this; + const nonStickyContent = this._nonStickyContent; + return (state.isStickyTop !== nextState.isStickyTop || + state.isStickyBottom !== nextState.isStickyBottom || + props.stickyPosition !== nextProps.stickyPosition || + props.children !== nextProps.children || + state.distanceFromTop !== nextState.distanceFromTop || + (!this._perf() && + (_isOffsetHeightDifferent(nonStickyContent, this._stickyContentTop) || + _isOffsetHeightDifferent(nonStickyContent, this._stickyContentBottom) || + _isOffsetHeightDifferent(nonStickyContent, this._placeHolder)))) as boolean; } public render(): JSX.Element { - const { isStickyTop, isStickyBottom } = this.state; - const { stickyClassName, children } = this.props; + const { props, state, nonStickyContent, root } = this; + const { isStickyTop, isStickyBottom } = state; if (!this.context.scrollablePane) { - return
{this.props.children}
; + return
{props.children}
; } - + const optimizePerformance = this._perf(); return (
- {this.canStickyTop && this._getStickyContent(StickyPositionType.Header, isStickyTop)} - {this.canStickyBottom && this._getStickyContent(StickyPositionType.Footer, isStickyBottom)} -
+ {this.canStickyTop && _getStickyContent(this._stickyContentTop, nonStickyContent, root, isStickyTop, optimizePerformance, props)} + {this.canStickyBottom && + _getStickyContent(this._stickyContentBottom, nonStickyContent, root, isStickyBottom, optimizePerformance, props)} +
- {children} + {props.children}
@@ -172,257 +178,285 @@ export class Sticky extends BaseComponent { } public addSticky(stickyContent: HTMLDivElement): void { - const placeholderUsedForStickyContent = this.context.scrollablePane.usePlaceholderForSticky(); - if (placeholderUsedForStickyContent && this.nonStickyContent) { - stickyContent.appendChild(this.nonStickyContent); + const nonStickyContent = this.nonStickyContent; + if (nonStickyContent) { + stickyContent.appendChild(nonStickyContent); } } public resetSticky(): void { - const placeholderUsedForContent = this.context.scrollablePane.usePlaceholderForSticky(); - if (placeholderUsedForContent && this.nonStickyContent && this.placeholder) { - this.placeholder.appendChild(this.nonStickyContent); + const { nonStickyContent, placeholder } = this; + if (nonStickyContent && placeholder) { + placeholder.appendChild(nonStickyContent); } } public setDistanceFromTop(container: HTMLDivElement): void { - const distanceFromTop = this._getNonStickyDistanceFromTop(container); + const distanceFromTop = _getNonStickyDistanceFromTop(container, this.root); this.setState({ distanceFromTop: distanceFromTop }); } - private _getContext = (): IScrollablePaneContext => this.context; - - private _getContentStyles(isSticky: boolean): React.CSSProperties { - const { scrollablePane } = this.context; - if (!scrollablePane) { - return {}; - } - const isVisible = isSticky - ? this._usePlaceholderForSticky(this.canStickyTop) || this._usePlaceholderForSticky(this.canStickyBottom) - : true; - return { - backgroundColor: this.props.stickyBackgroundColor || this._getBackground(), - overflow: isSticky ? 'hidden' : undefined, - visibility: isVisible ? 'visible' : 'hidden' - }; - } + private _perf = (): boolean => this.context && this.context.scrollablePane.optimizePerformance(); - private _getStickyContent(stickyPositionType: StickyPositionType, isSticky: boolean): JSX.Element { - const { stickyClassName, children } = this.props; - const { scrollablePane } = this.context; - // decide if actual element is to be replicated or placeholder is be to used. - const usePlaceholderForStickyContent = scrollablePane.usePlaceholderForSticky(); - return ( -
- {usePlaceholderForStickyContent ? ( -
- ) : ( -
- {children} -
- )} -
- ); - } - - private _getStickyContentStyles(isSticky: boolean): React.CSSProperties { - return { - visibility: isSticky ? 'visible' : 'hidden', - pointerEvents: isSticky ? 'auto' : 'none', - overflow: 'hidden', - backgroundColor: this.props.stickyBackgroundColor || this._getBackground() - }; - } - - private _getStickyPlaceholderHeight(isSticky: boolean): React.CSSProperties { - const height = this.nonStickyContent ? this.nonStickyContent.offsetHeight : 0; - return { - visibility: isSticky ? 'hidden' : 'visible', - height: isSticky ? 0 : height - }; - } - - private _isOffsetHeightDifferent(): boolean { - const { scrollablePane } = this.context; - if (!scrollablePane) { - return false; - } - const usePlaceholderForStickyContentTop = this._usePlaceholderForSticky(this.canStickyTop); - const usePlaceholderForStickyContentBottom = this._usePlaceholderForSticky(this.canStickyBottom); - return ( - (usePlaceholderForStickyContentTop && _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentTop)) || - (usePlaceholderForStickyContentBottom && _isOffsetHeightDifferent(this._nonStickyContent, this._stickyContentBottom)) || - ((usePlaceholderForStickyContentBottom || usePlaceholderForStickyContentTop) && - _isOffsetHeightDifferent(this._nonStickyContent, this._placeHolder)) - ); - } - - private _getNonStickyPlaceholderHeightAndWidth(): React.CSSProperties { - const { isStickyTop, isStickyBottom } = this.state; - const usePlaceholderForStickyTop = this._usePlaceholderForSticky(this.canStickyTop); - const usePlaceholderForStickyBottom = this._usePlaceholderForSticky(this.canStickyBottom); - if ((isStickyTop && usePlaceholderForStickyTop) || (isStickyBottom && usePlaceholderForStickyBottom)) { - let height = 0, - width = 0; - // Why is placeHolder width needed? - // ScrollablePane content--container is reponsible for providing scrollbars depending on content overflow. - // If the overflow is caused by content of sticky component when it is in non-sticky state, - // ScrollablePane content--conatiner will provide horizontal scrollbar. - // If the component becomes sticky, i.e., when state.isStickyTop || state.isStickyBottom becomes true, - // it's actual content is no more inside ScrollablePane content--container. - // ScrollablePane content--conatiner will see no need for horizontal scrollbar. (Assuming no other content is causing overflow) - // The complete content of sticky component will not be viewable. - // It is necessary to provide a placeHolder of a certain width (height is already being set) in the content--container, - // to get a horizontal scrollbar & be able to view the complete content of sticky component. - if (this.nonStickyContent && this.nonStickyContent.firstElementChild) { - height = this.nonStickyContent.offsetHeight; - // What value should be substituted for placeHolder width? - // Assumption: - // 1. Content inside should always be wrapped in a single div. - //
{intended_content}
- // 2. -ve padding, margin, etc. are not be used. - // 3. scrollWidth of a parent is greater than or equal to max of scrollWidths of it's children and same holds for children. - // placeHolder width should be computed in the best possible way to prevent overscroll/underscroll. - width = - this.nonStickyContent.firstElementChild.scrollWidth + - ((this.nonStickyContent.firstElementChild as HTMLElement).offsetWidth - this.nonStickyContent.firstElementChild.clientWidth); - } - return { - height: height, - width: width - }; - } else { - return {}; - } - } - - private _usePlaceholderForSticky(canSticky: boolean): boolean { - return canSticky && this.context.scrollablePane.usePlaceholderForSticky(); - } + private _interactionStatus = (): boolean => this.context && this.context.scrollablePane.userInteractionStatus(); private _onScrollEvent = (container: HTMLElement, footerStickyContainer: HTMLElement): void => { - const { scrollablePane } = this.context; - if (!this.root || !this.nonStickyContent || !scrollablePane) { - return; - } - if (scrollablePane.verifyStickyContainerBehavior(this.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, 'always')) { + const canStickyTop = this.canStickyTop; + const { isStickyBottom, isStickyTop } = this.state; + const optimizePerformance = this._perf(); + if (optimizePerformance && !canStickyTop) { + // Sticky always behavior for StickyPosition.Footer + // 1. ScrollablePane is mounted and has called notifySubscriber - // 2. stickyAlways has to re-render if mutation could 've affected it's offsetHeight. + // 2. stickyAlways has to re-render if mutation could 've affected it's children. + this.setState({ - isStickyTop: this.canStickyTop, - isStickyBottom: !this.canStickyTop, + isStickyBottom: true, distanceFromTop: 0 // must so that sorting happens. }); - } else if ( - !scrollablePane.getUserInteractionStatus() && - scrollablePane.verifyStickyContainerBehavior(this.canStickyTop ? StickyPositionType.Header : StickyPositionType.Footer, 'onScroll') - ) { - // user interaction has not started - // 1. ScrollablePane is mounted and has called notifySubscriber, sort is required. - // 2. StickyOnScroll has to re-render if mutation could 've affected it's offsetHeight. - const { isStickyBottom, isStickyTop } = this.state; - scrollablePane.sortSticky(this, false); + } else if (canStickyTop && optimizePerformance && !this._interactionStatus()) { + // Sticky onScroll behavior for StickyPosition.Header + + // 1. ScrollablePane is mounted and has called notifySubscriber + // 2. stickyAlways has to re-render if mutation could 've affected it's children. this.setState({ - isStickyBottom: isStickyBottom, - isStickyTop: isStickyTop + isStickyTop: false }); - } else { - const distanceFromTop = this._getNonStickyDistanceFromTop(container); - let isStickyTop = false; - let isStickyBottom = false; - - if (this.canStickyTop) { - const distanceToStickTop = distanceFromTop - this._getStickyDistanceFromTop(); - isStickyTop = distanceToStickTop < container.scrollTop; + } else if (this.root && this.nonStickyContent) { + const distanceFromTop = _getNonStickyDistanceFromTop(container, this.root); + let _isStickyTop = false, + _isStickyBottom = false; + + if (canStickyTop) { + const distanceToStickTop = distanceFromTop - _getStickyDistanceFromTop(this.stickyContentTop); + _isStickyTop = distanceToStickTop < container.scrollTop; } // Can sticky bottom if the scrollablePane - total sticky footer height is smaller than the sticky's distance from the top of the pane - if (this.canStickyBottom && container.clientHeight - footerStickyContainer.offsetHeight <= distanceFromTop) { - isStickyBottom = - distanceFromTop - Math.floor(container.scrollTop) >= this._getStickyDistanceFromTopForFooter(container, footerStickyContainer); + if (this.canStickyBottom && container.clientHeight - _getOffsetHeight(footerStickyContainer) <= distanceFromTop) { + _isStickyBottom = + distanceFromTop - Math.floor(container.scrollTop) >= + _getStickyDistanceFromTopForFooter(container, footerStickyContainer, this.stickyContentBottom); } + const documentActiveElement = document.activeElement; if ( - document.activeElement && - this.nonStickyContent.contains(document.activeElement) && - (this.state.isStickyTop !== isStickyTop || this.state.isStickyBottom !== isStickyBottom) + documentActiveElement && + this.nonStickyContent.contains(documentActiveElement) && + (isStickyTop !== _isStickyTop || isStickyBottom !== _isStickyBottom) ) { - this._activeElement = document.activeElement as HTMLElement; + this._activeElement = documentActiveElement as HTMLElement; } else { this._activeElement = undefined; } this.setState({ - isStickyTop: this.canStickyTop && isStickyTop, - isStickyBottom: isStickyBottom, + isStickyTop: canStickyTop && _isStickyTop, + isStickyBottom: _isStickyBottom, distanceFromTop: distanceFromTop }); } }; +} - private _getStickyDistanceFromTop = (): number => { - let distance = 0; - if (this.stickyContentTop) { - distance = this.stickyContentTop.offsetTop; +/** + * Gets background of nearest parent element that has a declared background-color attribute + */ +function _getBackgroundOfNearestAncestor(curr: HTMLElement | null): string | undefined { + if (!curr) { + return undefined; + } + let backgroundColor = _getComputedStyle(curr, BACKGROUNDCOLORSTRING); + while (backgroundColor === 'rgba(0, 0, 0, 0)' || backgroundColor === 'transparent') { + if (curr.tagName === 'HTML') { + // Fallback color if no element has a declared background-color attribute + return undefined; } + if (curr.parentElement) { + curr = curr.parentElement; + } + backgroundColor = _getComputedStyle(curr, BACKGROUNDCOLORSTRING); + } + return backgroundColor; +} - return distance; - }; +/** + * @param curr HTMLElement for which style is to be computed + * @param propertyName name of style property which is to be computed + */ +function _getComputedStyle(curr: HTMLElement, propertyName: string): string { + return window.getComputedStyle(curr).getPropertyValue(propertyName); +} - private _getStickyDistanceFromTopForFooter = (container: HTMLElement, footerStickyVisibleContainer: HTMLElement): number => { - let distance = 0; - if (this.stickyContentBottom) { - distance = container.clientHeight - footerStickyVisibleContainer.offsetHeight + this.stickyContentBottom.offsetTop; - } +const BACKGROUNDCOLORSTRING: string = 'background-color'; - return distance; - }; +function _isOffsetHeightDifferent(a: React.RefObject, b: React.RefObject): boolean { + const _a = _getRefObjectCurrent(a); + const _b = _getRefObjectCurrent(b); + return (_a && _b && _getOffsetHeight(_a) !== _getOffsetHeight(_b)) as boolean; +} - private _getNonStickyDistanceFromTop = (container: HTMLElement): number => { - let distance = 0; - let currElem = this.root; +function _canSticky(expectedStickyPosition: StickyPositionType, stickyPosition?: StickyPositionType) { + return stickyPosition === stickyPositionBoth || stickyPosition === expectedStickyPosition; +} - if (currElem) { - while (currElem && currElem.offsetParent !== container) { - distance += currElem.offsetTop; - currElem = currElem.offsetParent as HTMLDivElement; - } +function _getNonStickyPlaceholderHeightAndWidth(isSticky: boolean, nonStickyContent: HTMLDivElement | null): React.CSSProperties { + if (isSticky) { + let height = 0, + width = 0; + // Why is placeHolder width needed? + // ScrollablePane content--container is reponsible for providing scrollbars depending on content overflow. + // If the overflow is caused by content of sticky component when it is in non-sticky state, + // ScrollablePane content--conatiner will provide horizontal scrollbar. + // If the component becomes sticky, i.e., when state.isStickyTop || state.isStickyBottom becomes true, + // it's actual content is no more inside ScrollablePane content--container. + // ScrollablePane content--conatiner will see no need for horizontal scrollbar. (Assuming no other content is causing overflow) + // The complete content of sticky component will not be viewable. + // It is necessary to provide a placeHolder of a certain width (height is already being set) in the content--container, + // to get a horizontal scrollbar & be able to view the complete content of sticky component. + if (nonStickyContent && nonStickyContent.firstElementChild) { + height = _getOffsetHeight(nonStickyContent); + // What value should be substituted for placeHolder width? + // Assumption: + // 1. Content inside should always be wrapped in a single div. + //
{intended_content}
+ // 2. -ve padding, margin, etc. are not be used. + // 3. scrollWidth of a parent is greater than or equal to max of scrollWidths of it's children and same holds for children. + // placeHolder width should be computed in the best possible way to prevent overscroll/underscroll. + const nonStickyContentFirstElementChild = nonStickyContent.firstElementChild; + width = + nonStickyContentFirstElementChild.scrollWidth + + ((nonStickyContentFirstElementChild as HTMLElement).offsetWidth - nonStickyContentFirstElementChild.clientWidth); + } + return { + height: height, + width: width + }; + } else { + return {}; + } +} - if (currElem && currElem.offsetParent === container) { - distance += currElem.offsetTop; - } +function _getNonStickyDistanceFromTop(container: HTMLElement, currElem: HTMLDivElement | null): number { + let distance = 0; + if (currElem) { + while (currElem && _getoffsetParent(currElem) !== container) { + distance += _getOffsetTop(currElem); + currElem = _getoffsetParent(currElem) as HTMLDivElement; } - return distance; - }; - // Gets background of nearest parent element that has a declared background-color attribute - private _getBackground(): string | undefined { - if (!this.root) { - return undefined; + if (currElem && _getoffsetParent(currElem) === container) { + distance += _getOffsetTop(currElem); } + } + return distance; +} - let curr: HTMLElement = this.root; +function _getStickyDistanceFromTop(stickyContentTop: HTMLDivElement | null): number { + let distance = 0; + if (stickyContentTop) { + distance = _getOffsetTop(stickyContentTop); + } - while ( - window.getComputedStyle(curr).getPropertyValue('background-color') === 'rgba(0, 0, 0, 0)' || - window.getComputedStyle(curr).getPropertyValue('background-color') === 'transparent' - ) { - if (curr.tagName === 'HTML') { - // Fallback color if no element has a declared background-color attribute - return undefined; - } - if (curr.parentElement) { - curr = curr.parentElement; - } - } - return window.getComputedStyle(curr).getPropertyValue('background-color'); + return distance; +} + +function _getStickyDistanceFromTopForFooter( + container: HTMLElement, + footerStickyVisibleContainer: HTMLElement, + stickyContentBottom: HTMLDivElement | null +): number { + let distance = 0; + if (stickyContentBottom) { + distance = container.clientHeight - _getOffsetHeight(footerStickyVisibleContainer) + _getOffsetTop(stickyContentBottom); } + + return distance; } -function _isOffsetHeightDifferent(a: React.RefObject, b: React.RefObject): boolean { - return (a && b && a.current && b.current && a.current.offsetHeight !== b.current.offsetHeight) as boolean; +function _getStickyPlaceholderHeight(isSticky: boolean, nonStickyContent: HTMLDivElement | null): React.CSSProperties { + const height = nonStickyContent ? _getOffsetHeight(nonStickyContent) : 0; + return { + visibility: isSticky ? 'hidden' : 'visible', + height: isSticky ? 0 : height + }; +} + +function _getContentStyles( + isSticky: boolean, + optimizePerformance: boolean, + root: HTMLDivElement | null, + stickyBackgroundColor?: string +): React.CSSProperties { + const isVisible = isSticky ? !optimizePerformance : true; + return { + backgroundColor: stickyBackgroundColor || _getBackgroundOfNearestAncestor(root), + overflow: isSticky ? 'hidden' : '', + visibility: isVisible ? 'visible' : 'hidden' + }; +} + +function _getStickyContent( + stickyContentRef: React.RefObject, + nonStickyContent: HTMLDivElement | null, + root: HTMLDivElement | null, + isSticky: boolean, + optimizePerformace: boolean, + props: IStickyProps +): JSX.Element { + return ( +
+ {optimizePerformace ? ( +
+ {props.children} +
+ ) : ( +
+ )} +
+ ); +} + +function _getStickyContentStyles(isSticky: boolean, stickyBackgroundColor?: string): React.CSSProperties { + return { + visibility: isSticky ? 'visible' : 'hidden', + pointerEvents: isSticky ? 'auto' : 'none', + overflow: 'hidden', + backgroundColor: stickyBackgroundColor + }; +} +// debatable +/** + * Returns reactRefObject.current + * @param reactRefObject - React.RefObject + */ +function _getRefObjectCurrent(reactRefObject: React.RefObject): T | null { + return reactRefObject.current; +} + +/** + * Returns elem.offsetHeight + * @param elem - HTMLElement for which offsetHeight is to be calculated + */ +function _getOffsetHeight(elem: HTMLElement): number { + return elem.offsetHeight; +} + +/** + * Returns elem.offsetParent + * @param elem + */ +function _getoffsetParent(elem: HTMLElement): Element | null { + return elem.offsetParent; +} + +/** + * Returns elem.offsetTop + * @param elem + */ +function _getOffsetTop(elem: HTMLElement): number { + return elem.offsetTop; }