-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[new] ResizeSensor component (#2745)
* ResizeSensor is a React wrapper for ResizeObserver * use ResizeSensor in OverflowList & Popover * export component * enable Promise in tests * add test suite * re-observe DOM node in DidUpdate (if necessary) * don't expose external types * allow changing props 👍 * add docs page * fix isotest * docs per comments * docs cleanup
- Loading branch information
Showing
10 changed files
with
293 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
packages/core/src/components/resize-sensor/resize-sensor.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
--- | ||
tag: new | ||
--- | ||
|
||
@# Resize sensor | ||
|
||
`ResizeSensor` is a component that provides a `"resize"` event for its single | ||
DOM element child. It is a thin wrapper around | ||
[`ResizeObserver`][resizeobserver] to provide React bindings. | ||
|
||
[resizeobserver]: https://developers.google.com/web/updates/2016/10/resizeobserver | ||
|
||
```tsx | ||
import { IResizeEntry, ResizeSensor } from "@blueprintjs/core"; | ||
|
||
function handleResize(entries: IResizeEntry[]) { | ||
console.log(entries.map(e => `${e.contentRect.width} x ${e.contentRect.height}`)); | ||
} | ||
|
||
<ResizeSensor onChange={handleResize}> | ||
<div style={{ width: this.props.width }} /> | ||
</ResizeSensor> | ||
``` | ||
|
||
@## Props | ||
|
||
<div class="@ns-callout @ns-intent-warning @ns-icon-warning-sign"> | ||
<h4 class="@ns-heading">Asynchronous behavior</h4> | ||
The `onResize` callback is invoked asynchronously after a resize is detected | ||
and typically happens at the end of a frame (after layout, before paint). | ||
Therefore, testing behavior that relies on this component involves setting a | ||
timeout for the next frame. | ||
</div> | ||
|
||
@interface IResizeSensorProps |
109 changes: 109 additions & 0 deletions
109
packages/core/src/components/resize-sensor/resizeSensor.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
* Copyright 2018 Palantir Technologies, Inc. All rights reserved. | ||
* | ||
* Licensed under the terms of the LICENSE file distributed with this project. | ||
*/ | ||
|
||
import * as React from "react"; | ||
import ResizeObserver from "resize-observer-polyfill"; | ||
|
||
import { findDOMNode } from "react-dom"; | ||
import { DISPLAYNAME_PREFIX } from "../../common/props"; | ||
import { safeInvoke } from "../../common/utils"; | ||
|
||
/** A parallel type to `ResizeObserverEntry` (from resize-observer-polyfill). */ | ||
export interface IResizeEntry { | ||
/** Measured dimensions of the target. */ | ||
contentRect: DOMRectReadOnly; | ||
|
||
/** The resized element. */ | ||
target: Element; | ||
} | ||
|
||
/** `ResizeSensor` requires a single DOM element child and will error otherwise. */ | ||
export interface IResizeSensorProps { | ||
/** | ||
* Callback invoked when the wrapped element resizes. | ||
* | ||
* The `entries` array contains an entry for each observed element. In the | ||
* default case (no `observeParents`), the array will contain only one | ||
* element: the single child of the `ResizeSensor`. | ||
* | ||
* Note that this method is called _asynchronously_ after a resize is | ||
* detected and typically it will be called no more than once per frame. | ||
*/ | ||
onResize: (entries: IResizeEntry[]) => void; | ||
|
||
/** | ||
* If `true`, all parent DOM elements of the container will also be | ||
* observed for size changes. The array of entries passed to `onResize` | ||
* will now contain an entry for each parent element up to the root of the | ||
* document. | ||
* | ||
* Only enable this prop if a parent element resizes in a way that does | ||
* not also cause the child element to resize. | ||
* @default false | ||
*/ | ||
observeParents?: boolean; | ||
} | ||
|
||
export class ResizeSensor extends React.PureComponent<IResizeSensorProps> { | ||
public static displayName = `${DISPLAYNAME_PREFIX}.ResizeSensor`; | ||
|
||
private element: Element | null = null; | ||
private observer = new ResizeObserver(entries => safeInvoke(this.props.onResize, entries)); | ||
|
||
public render() { | ||
// pass-through render of single child | ||
return React.Children.only(this.props.children); | ||
} | ||
|
||
public componentDidMount() { | ||
// using findDOMNode for two reasons: | ||
// 1. cloning to insert a ref is unwieldy and not performant. | ||
// 2. ensure that we get an actual DOM node for observing. | ||
this.observeElement(findDOMNode(this)); | ||
} | ||
|
||
public componentDidUpdate(prevProps: IResizeSensorProps) { | ||
this.observeElement(findDOMNode(this), this.props.observeParents !== prevProps.observeParents); | ||
} | ||
|
||
public componentWillUnmount() { | ||
this.observer.disconnect(); | ||
} | ||
|
||
/** | ||
* Observe the given element, if defined and different from the currently | ||
* observed element. Pass `force` argument to skip element checks and always | ||
* re-observe. | ||
*/ | ||
private observeElement(element: Element | null, force = false) { | ||
if (element == null) { | ||
// stop everything if not defined | ||
this.observer.disconnect(); | ||
return; | ||
} | ||
|
||
if (element === this.element && !force) { | ||
// quit if given same element -- nothing to update (unless forced) | ||
return; | ||
} else { | ||
// clear observer list if new element | ||
this.observer.disconnect(); | ||
// remember element reference for next time | ||
this.element = element; | ||
} | ||
|
||
// observer callback is invoked immediately when observing new elements | ||
this.observer.observe(element); | ||
|
||
if (this.props.observeParents) { | ||
let parent = element.parentElement; | ||
while (parent != null) { | ||
this.observer.observe(parent); | ||
parent = parent.parentElement; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
cf37dea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[new] ResizeSensor component (#2745)
Preview: documentation | landing | table