-
Notifications
You must be signed in to change notification settings - Fork 727
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create new
ParentSizeModern
component.
This component is a clone of `ParentSize` that doesn't use a `ResizeObserver` polyfill.
- Loading branch information
Showing
7 changed files
with
193 additions
and
5 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
92 changes: 92 additions & 0 deletions
92
packages/visx-responsive/src/components/ParentSizeModern.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,92 @@ | ||
import debounce from 'lodash/debounce'; | ||
import React, { useEffect, useMemo, useRef, useState } from 'react'; | ||
|
||
export type ParentSizeProps = { | ||
/** Optional `className` to add to the parent `div` wrapper used for size measurement. */ | ||
className?: string; | ||
/** Child render updates upon resize are delayed until `debounceTime` milliseconds _after_ the last resize event is observed. */ | ||
debounceTime?: number; | ||
/** Optional flag to toggle leading debounce calls. When set to true this will ensure that the component always renders immediately. (defaults to true) */ | ||
enableDebounceLeadingCall?: boolean; | ||
/** Optional dimensions provided won't trigger a state change when changed. */ | ||
ignoreDimensions?: keyof ParentSizeState | (keyof ParentSizeState)[]; | ||
/** Optional `style` object to apply to the parent `div` wrapper used for size measurement. */ | ||
parentSizeStyles?: React.CSSProperties; | ||
/** Child render function `({ width, height, top, left, ref, resize }) => ReactNode`. */ | ||
children: ( | ||
args: { | ||
ref: HTMLDivElement | null; | ||
resize: (state: ParentSizeState) => void; | ||
} & ParentSizeState, | ||
) => React.ReactNode; | ||
}; | ||
|
||
type ParentSizeState = { | ||
width: number; | ||
height: number; | ||
top: number; | ||
left: number; | ||
}; | ||
|
||
export type ParentSizeProvidedProps = ParentSizeState; | ||
|
||
export default function ParentSize({ | ||
className, | ||
children, | ||
debounceTime = 300, | ||
ignoreDimensions = [], | ||
parentSizeStyles = { width: '100%', height: '100%' }, | ||
enableDebounceLeadingCall = true, | ||
...restProps | ||
}: ParentSizeProps & Omit<JSX.IntrinsicElements['div'], keyof ParentSizeProps>) { | ||
const target = useRef<HTMLDivElement | null>(null); | ||
const animationFrameID = useRef(0); | ||
|
||
const [state, setState] = useState<ParentSizeState>({ width: 0, height: 0, top: 0, left: 0 }); | ||
|
||
const resize = useMemo(() => { | ||
const normalized = Array.isArray(ignoreDimensions) ? ignoreDimensions : [ignoreDimensions]; | ||
|
||
return debounce( | ||
(incoming: ParentSizeState) => { | ||
setState(existing => { | ||
const stateKeys = Object.keys(existing) as (keyof ParentSizeState)[]; | ||
const keysWithChanges = stateKeys.filter(key => existing[key] !== incoming[key]); | ||
const shouldBail = keysWithChanges.every(key => normalized.includes(key)); | ||
|
||
return shouldBail ? existing : incoming; | ||
}); | ||
}, | ||
debounceTime, | ||
{ leading: enableDebounceLeadingCall }, | ||
); | ||
}, [debounceTime, enableDebounceLeadingCall, ignoreDimensions]); | ||
|
||
useEffect(() => { | ||
const observer = new ResizeObserver((entries = [] /** , observer */) => { | ||
entries.forEach(entry => { | ||
const { left, top, width, height } = entry.contentRect; | ||
animationFrameID.current = window.requestAnimationFrame(() => { | ||
resize({ width, height, top, left }); | ||
}); | ||
}); | ||
}); | ||
if (target.current) observer.observe(target.current); | ||
|
||
return () => { | ||
window.cancelAnimationFrame(animationFrameID.current); | ||
observer.disconnect(); | ||
resize.cancel(); | ||
}; | ||
}, [resize]); | ||
|
||
return ( | ||
<div style={parentSizeStyles} ref={target} className={className} {...restProps}> | ||
{children({ | ||
...state, | ||
ref: target.current, | ||
resize, | ||
})} | ||
</div> | ||
); | ||
} |
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
87 changes: 87 additions & 0 deletions
87
packages/visx-responsive/src/enhancers/withParentSizeModern.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,87 @@ | ||
import React from 'react'; | ||
import debounce from 'lodash/debounce'; | ||
|
||
const CONTAINER_STYLES = { width: '100%', height: '100%' }; | ||
|
||
export type WithParentSizeProps = { | ||
debounceTime?: number; | ||
enableDebounceLeadingCall?: boolean; | ||
}; | ||
|
||
type WithParentSizeState = { | ||
parentWidth?: number; | ||
parentHeight?: number; | ||
initialWidth?: number; | ||
initialHeight?: number; | ||
}; | ||
|
||
export type WithParentSizeProvidedProps = WithParentSizeState; | ||
|
||
export default function withParentSize<BaseComponentProps extends WithParentSizeProps = {}>( | ||
BaseComponent: React.ComponentType<BaseComponentProps & WithParentSizeProvidedProps>, | ||
) { | ||
return class WrappedComponent extends React.Component< | ||
BaseComponentProps & WithParentSizeProvidedProps, | ||
WithParentSizeState | ||
> { | ||
static defaultProps = { | ||
debounceTime: 300, | ||
enableDebounceLeadingCall: true, | ||
}; | ||
state = { | ||
parentWidth: undefined, | ||
parentHeight: undefined, | ||
}; | ||
animationFrameID: number = 0; | ||
resizeObserver: ResizeObserver | undefined; | ||
container: HTMLDivElement | null = null; | ||
|
||
componentDidMount() { | ||
this.resizeObserver = new ResizeObserver((entries /** , observer */) => { | ||
entries.forEach(entry => { | ||
const { width, height } = entry.contentRect; | ||
this.animationFrameID = window.requestAnimationFrame(() => { | ||
this.resize({ | ||
width, | ||
height, | ||
}); | ||
}); | ||
}); | ||
}); | ||
if (this.container) this.resizeObserver.observe(this.container); | ||
} | ||
|
||
componentWillUnmount() { | ||
window.cancelAnimationFrame(this.animationFrameID); | ||
if (this.resizeObserver) this.resizeObserver.disconnect(); | ||
this.resize.cancel(); | ||
} | ||
|
||
setRef = (ref: HTMLDivElement) => { | ||
this.container = ref; | ||
}; | ||
|
||
resize = debounce( | ||
({ width, height }: { width: number; height: number }) => { | ||
this.setState({ | ||
parentWidth: width, | ||
parentHeight: height, | ||
}); | ||
}, | ||
this.props.debounceTime, | ||
{ leading: this.props.enableDebounceLeadingCall }, | ||
); | ||
|
||
render() { | ||
const { initialWidth, initialHeight } = this.props; | ||
const { parentWidth = initialWidth, parentHeight = initialHeight } = this.state; | ||
return ( | ||
<div style={CONTAINER_STYLES} ref={this.setRef}> | ||
{parentWidth != null && parentHeight != null && ( | ||
<BaseComponent parentWidth={parentWidth} parentHeight={parentHeight} {...this.props} /> | ||
)} | ||
</div> | ||
); | ||
} | ||
}; | ||
} |
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 |
---|---|---|
@@ -1,4 +1,6 @@ | ||
export { default as ScaleSVG } from './components/ScaleSVG'; | ||
export { default as ParentSize } from './components/ParentSize'; | ||
export { default as withParentSize } from './enhancers/withParentSize'; | ||
export { default as withScreenSize } from './enhancers/withScreenSize'; | ||
export { default as ScaleSVG } from "./components/ScaleSVG"; | ||
export { default as ParentSize } from "./components/ParentSize"; | ||
export { default as ParentSizeModern } from "./components/ParentSizeModern"; | ||
export { default as withParentSize } from "./enhancers/withParentSize"; | ||
export { default as withParentSizeModern } from "./enhancers/withParentSizeModern"; | ||
export { default as withScreenSize } from "./enhancers/withScreenSize"; |