Skip to content

Commit

Permalink
Make callouts aware of the WindowSegments
Browse files Browse the repository at this point in the history
  • Loading branch information
jspurlin committed Sep 5, 2019
1 parent 17e5e3a commit 68fffc2
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
getNativeProps,
getWindow,
on,
shallowCompare
shallowCompare,
getRenderedContainer
} from '../../Utilities';
import {
positionCallout,
Expand All @@ -28,6 +29,10 @@ import { Popup } from '../../Popup';
import { classNamesFunction } from '../../Utilities';
import { AnimationClassNames } from '../../Styling';

interface IWindow extends Window {
getBoundingWindowRects?: Function;
}

const ANIMATIONS: { [key: number]: string | undefined } = {
[RectangleEdge.top]: AnimationClassNames.slideUpIn10,
[RectangleEdge.bottom]: AnimationClassNames.slideDownIn10,
Expand Down Expand Up @@ -71,8 +76,11 @@ export class CalloutContentBase extends React.Component<ICalloutProps, ICalloutS
private _didSetInitialFocus: boolean;
private _hostElement = React.createRef<HTMLDivElement>();
private _calloutElement = React.createRef<HTMLDivElement>();
private _targetWindow: Window;
private _targetWindow: IWindow;
private _targetWindowSegmentRect?: DOMRect | ClientRect;
private _bounds: IRectangle | undefined;
private _targetWindowSegmentBounds?: IRectangle | undefined;
private _windowRects?: DOMRect[] | ClientRect[];
private _positionAttempts: number;
private _target: Element | MouseEvent | IPoint | null;
private _setHeightOffsetTimer: number;
Expand Down Expand Up @@ -153,6 +161,9 @@ export class CalloutContentBase extends React.Component<ICalloutProps, ICalloutS
// Ensure positioning is recalculated when we are about to show a persisted menu.
if (this._didPositionPropsChange(newProps, this.props)) {
this._maxHeight = undefined;
this._windowRects = undefined;
this._targetWindowSegmentRect = undefined;
this._targetWindowSegmentBounds = undefined;
// Target might have been updated while hidden.
this._setTargetWindowAndElement(newTarget);
this.setState({
Expand Down Expand Up @@ -342,6 +353,12 @@ export class CalloutContentBase extends React.Component<ICalloutProps, ICalloutS
}, 0);
}

private _updateWindowRects = () => {
if (this._targetWindow.getBoundingWindowRects) {
this._windowRects = this._targetWindow.getBoundingWindowRects();
}
};

private _removeListeners() {
this._disposables.forEach((dispose: () => void) => dispose());
this._disposables = [];
Expand Down Expand Up @@ -408,20 +425,46 @@ export class CalloutContentBase extends React.Component<ICalloutProps, ICalloutS
}

private _getBounds(): IRectangle {
if (!this._bounds) {
const useTargetWindowSegment = this._targetWindowSegmentRect && this._windowRects && this._windowRects.length > 1;

// If we have not have yet taken the bounds or have a targetWindowSegment
// and the bounds have not yet taken it into account, update the bounds
if (!this._bounds || (useTargetWindowSegment && this._targetWindowSegmentBounds !== this._bounds)) {
let currentBounds = this.props.bounds;

if (!currentBounds) {
let top: number;
let left: number;
let width: number;
let height: number;

if (useTargetWindowSegment) {
top = this._targetWindowSegmentRect!.top;
left = this._targetWindowSegmentRect!.left;
width = left + this._targetWindowSegmentRect!.width;
height = top + this._targetWindowSegmentRect!.height;
} else {
top = 0;
left = 0;
width = this._targetWindow.innerWidth;
height = this._targetWindow.innerHeight;
}

currentBounds = {
top: 0 + this.props.minPagePadding!,
left: 0 + this.props.minPagePadding!,
right: this._targetWindow.innerWidth - this.props.minPagePadding!,
bottom: this._targetWindow.innerHeight - this.props.minPagePadding!,
width: this._targetWindow.innerWidth - this.props.minPagePadding! * 2,
height: this._targetWindow.innerHeight - this.props.minPagePadding! * 2
top: top + this.props.minPagePadding!,
left: left + this.props.minPagePadding!,
right: width - this.props.minPagePadding!,
bottom: height - this.props.minPagePadding!,
width: width - this.props.minPagePadding! * 2,
height: height - this.props.minPagePadding! * 2
};
}
this._bounds = currentBounds;

// Remember the bounds so that we only measure once
if (useTargetWindowSegment) {
this._targetWindowSegmentBounds = this._bounds;
}
}
return this._bounds;
}
Expand Down Expand Up @@ -497,6 +540,12 @@ export class CalloutContentBase extends React.Component<ICalloutProps, ICalloutS
this._targetWindow = getWindow()!;
this._target = target as IPoint;
}

// Take note of the windowRects if we have not already
if (!this._windowRects || this._windowRects.length <= 0) {
this._updateWindowRects();
this._targetWindowSegmentRect = getRenderedContainer(this._target, this._windowRects);
}
} else {
this._targetWindow = getWindow()!;
}
Expand Down
7 changes: 7 additions & 0 deletions packages/utilities/etc/utilities.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
```ts

import * as CSS from 'csstype';
import { IProcessedStyleSet } from '@uifabric/merge-styles';
import { IStyleFunction } from '@uifabric/merge-styles';
import { IStyleFunctionOrObject } from '@uifabric/merge-styles';
import { IStyleSet } from '@uifabric/merge-styles';
import { Omit } from '@uifabric/merge-styles';
import * as PropTypes from 'prop-types';
import * as React from 'react';

// @public
Expand Down Expand Up @@ -328,6 +330,11 @@ export function getPreviousElement(rootElement: HTMLElement, currentElement: HTM
// @public
export function getRect(element: HTMLElement | Window | null): IRectangle | undefined;

// Warning: (ae-forgotten-export) The symbol "React" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export function getRenderedContainer(content?: Element | string | MouseEvent | IPoint | null | React_2.RefObject<Element>, possibleContainers?: DOMRect[] | ClientRect[]): DOMRect | ClientRect | undefined;

// @public
export function getResourceUrl(url: string): string;

Expand Down
1 change: 1 addition & 0 deletions packages/utilities/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './dom/getChildren';
export * from './dom/getDocument';
export * from './dom/getParent';
export * from './dom/getRect';
export * from './dom/getRenderedContainer';
export * from './dom/getVirtualParent';
export * from './dom/getWindow';
export * from './dom/isVirtualElement';
Expand Down
37 changes: 37 additions & 0 deletions packages/utilities/src/dom/getRenderedContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { IPoint } from '../IPoint';
import { Rectangle } from '../Rectangle';

export function getRenderedContainer(
content?: Element | string | MouseEvent | IPoint | null | React.RefObject<Element>,
possibleContainers?: DOMRect[] | ClientRect[]
): DOMRect | ClientRect | undefined {
if (!content || !possibleContainers || possibleContainers.length <= 0) {
return undefined;
}

let contentRect: DOMRect | ClientRect | Rectangle;

if ((content as MouseEvent).preventDefault) {
const ev = content as MouseEvent;
contentRect = new Rectangle(ev.clientX, ev.clientX, ev.clientY, ev.clientY);
} else if ((content as Element).getBoundingClientRect) {
contentRect = (content as Element).getBoundingClientRect();
} else if (content as IPoint) {
const point: IPoint = content as IPoint;
contentRect = new Rectangle(point.x, point.x, point.y, point.y);
} else {
return undefined;
}

return (
possibleContainers &&
possibleContainers.find((possibleContainerRect: DOMRect | ClientRect) => {
return (
contentRect.left >= possibleContainerRect.left &&
contentRect.top >= possibleContainerRect.top &&
contentRect.right <= possibleContainerRect.left + possibleContainerRect.width &&
contentRect.bottom <= possibleContainerRect.top + possibleContainerRect.height
);
})
);
}

0 comments on commit 68fffc2

Please sign in to comment.