Skip to content

Commit

Permalink
feat(StickyPlaceholder): update placeholder dimensions on children up…
Browse files Browse the repository at this point in the history
…dates

Fix #28
  • Loading branch information
garthenweb committed Dec 13, 2020
1 parent e4467ce commit 7210254
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 21 deletions.
26 changes: 25 additions & 1 deletion examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import './styles.css';
const Placeholder = () => <div className="placeholder" />;
const Test = () => {
const div = React.useRef(null);
const rect = useRect(div);
return <div ref={div} />;
};

Expand All @@ -23,6 +22,7 @@ class Example extends React.PureComponent<
private container4: React.RefObject<any>;
private container5: React.RefObject<any>;
private container6: React.RefObject<any>;
private container7: React.RefObject<any>;

constructor(props) {
super(props);
Expand All @@ -32,6 +32,7 @@ class Example extends React.PureComponent<
this.container4 = React.createRef();
this.container5 = React.createRef();
this.container6 = React.createRef();
this.container7 = React.createRef();
this.state = {
disableHeader: false,
disableAll: false,
Expand Down Expand Up @@ -178,6 +179,14 @@ class Example extends React.PureComponent<
</div>
</div>

<div ref={this.container7} style={{ height: 1500 }}>
<Sticky container={this.container7} overflowScroll="flow" experimentalNative>
<div>
<DynamicContent />
</div>
</Sticky>
</div>

<Sticky disableHardwareAcceleration>
<div className="sticky-inline">disableHardwareAcceleration: true</div>
</Sticky>
Expand All @@ -190,6 +199,21 @@ class Example extends React.PureComponent<
}
}

const DynamicContent = () => {
const [count, setCount] = React.useState(1);

return (
<div className="sticky-inline">
<button onClick={() => setCount(count + 1)}>add</button>
{Array(count)
.fill(null)
.map((_, index) => (
<div>{index}</div>
))}
</div>
);
};

render(
<React.StrictMode>
<StickyProvider>
Expand Down
58 changes: 58 additions & 0 deletions lib/ElementResizeObserver.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as React from 'react';
import { IRect } from 'react-viewport-utils';

interface IElementResizeObserverProps {
stickyRef: React.RefObject<any>;
onUpdate: (rect: IRect) => void;
}

class ElementResizeObserver extends React.PureComponent<
IElementResizeObserverProps
> {
private resizeObserver: ResizeObserver | null;
constructor(props: IElementResizeObserverProps) {
super(props);
this.resizeObserver = null;
}

componentDidMount() {
this.resetObserver();
this.installObserver();
}

componentDidUpdate() {
this.resetObserver();
this.installObserver();
}

componentWillUnmount() {
this.resetObserver();
}

installObserver() {
if (!this.props.stickyRef.current) {
return;
}
if (typeof window.ResizeObserver !== 'undefined') {
this.resizeObserver = new window.ResizeObserver(entries => {
if (entries && entries[0] && entries[0].contentRect) {
this.props.onUpdate(entries[0].contentRect);
}
});
this.resizeObserver!.observe(this.props.stickyRef.current);
}
}

resetObserver() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
}

render(): null {
return null;
}
}

export default ElementResizeObserver;
64 changes: 44 additions & 20 deletions lib/StickyPlaceholder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
requestAnimationFrame,
cancelAnimationFrame,
} from 'react-viewport-utils';
import ElementResizeObserver from './ElementResizeObserver';

interface IProps {
disableResizing: boolean;
Expand All @@ -22,11 +23,12 @@ interface IState {
isWaitingForRecalculation: boolean;
stickyHeight: number | null;
stickyWidth: number | null;
clientSize: string | null;
clientHash: string | null;
}

class StickyPlaceholder extends React.Component<IProps, IState> {
private recalculationTick?: number;
private lastDimensions?: IDimensions;
static defaultProps = {
style: {},
};
Expand All @@ -36,7 +38,7 @@ class StickyPlaceholder extends React.Component<IProps, IState> {
isWaitingForRecalculation: false,
stickyHeight: null,
stickyWidth: null,
clientSize: null,
clientHash: null,
};

componentWillUnmount() {
Expand All @@ -58,16 +60,17 @@ class StickyPlaceholder extends React.Component<IProps, IState> {
{ dimensions }: { dimensions: IDimensions },
stickyRect: IRect | null,
) => {
this.lastDimensions = dimensions;
const { width, clientWidth } = dimensions;
const nextClientSize = [width, clientWidth].join(',');
const nextClientHash = [width, clientWidth].join(',');

if (
!this.state.isWaitingForRecalculation &&
this.state.clientSize !== nextClientSize
this.state.clientHash !== nextClientHash
) {
this.setState(
{
clientSize: nextClientSize,
clientHash: nextClientHash,
isRecalculating: true,
isWaitingForRecalculation: true,
},
Expand All @@ -83,14 +86,29 @@ class StickyPlaceholder extends React.Component<IProps, IState> {
return;
}

if (stickyRect && this.state.isWaitingForRecalculation) {
this.setState({
clientSize: nextClientSize,
stickyHeight: stickyRect.height,
stickyWidth: stickyRect.width,
isWaitingForRecalculation: false,
});
return;
if (stickyRect) {
if (
this.state.isWaitingForRecalculation ||
stickyRect.height !== this.state.stickyHeight ||
stickyRect.width !== this.state.stickyWidth
) {
this.setState({
clientHash: nextClientHash,
stickyHeight: stickyRect.height,
stickyWidth: stickyRect.width,
isWaitingForRecalculation: false,
});
return;
}
}
};

handleElementResize = (stickyRect: IRect) => {
if (this.lastDimensions) {
this.handleDimensionsUpdate(
{ dimensions: this.lastDimensions },
stickyRect,
);
}
};

Expand Down Expand Up @@ -119,13 +137,19 @@ class StickyPlaceholder extends React.Component<IProps, IState> {
})}
</div>
{!this.props.disableResizing && (
<ObserveViewport
disableScrollUpdates
disableDimensionsUpdates={isRecalculating}
onUpdate={this.handleDimensionsUpdate}
recalculateLayoutBeforeUpdate={this.calculateSize}
priority="highest"
/>
<>
<ObserveViewport
disableScrollUpdates
disableDimensionsUpdates={isRecalculating}
onUpdate={this.handleDimensionsUpdate}
recalculateLayoutBeforeUpdate={this.calculateSize}
priority="highest"
/>
<ElementResizeObserver
stickyRef={this.props.stickyRef}
onUpdate={this.handleElementResize}
/>
</>
)}
</>
);
Expand Down
67 changes: 67 additions & 0 deletions lib/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// @see https://gist.github.com/strothj/708afcf4f01dd04de8f49c92e88093c3
interface Window {
ResizeObserver: ResizeObserver;
}

/**
* The ResizeObserver interface is used to observe changes to Element's content
* rect.
*
* It is modeled after MutationObserver and IntersectionObserver.
*/
interface ResizeObserver {
new (callback: ResizeObserverCallback): ResizeObserver;

/**
* Adds target to the list of observed elements.
*/
observe: (target: Element) => void;

/**
* Removes target from the list of observed elements.
*/
unobserve: (target: Element) => void;

/**
* Clears both the observationTargets and activeTargets lists.
*/
disconnect: () => void;
}

/**
* This callback delivers ResizeObserver's notifications. It is invoked by a
* broadcast active observations algorithm.
*/
interface ResizeObserverCallback {
(entries: ResizeObserverEntry[], observer: ResizeObserver): void;
}

interface ResizeObserverEntry {
/**
* @param target The Element whose size has changed.
*/
new (target: Element): ResizeObserverEntry;

/**
* The Element whose size has changed.
*/
readonly target: Element;

/**
* Element's content rect when ResizeObserverCallback is invoked.
*/
readonly contentRect: DOMRectReadOnly;
}

interface DOMRectReadOnly {
readonly x: number;
readonly y: number;
readonly width: number;
readonly height: number;
readonly top: number;
readonly right: number;
readonly bottom: number;
readonly left: number;

toJSON: () => any;
}

0 comments on commit 7210254

Please sign in to comment.