From fea782051ec0751ac5b86aef39ee16ed8ff7b07c Mon Sep 17 00:00:00 2001 From: lintuming Date: Fri, 23 Aug 2019 18:53:41 +0800 Subject: [PATCH 1/6] useMeasure --- README.md | 1 + docs/useMeasure.md | 23 +++++++++ package.json | 1 + src/__tests__/useMeasure.test.ts | 84 ++++++++++++++++++++++++++++++++ src/useMeasure.ts | 42 ++++++++++++++++ 5 files changed, 151 insertions(+) create mode 100644 docs/useMeasure.md create mode 100644 src/__tests__/useMeasure.test.ts create mode 100644 src/useMeasure.ts diff --git a/README.md b/README.md index 8e36339e0d..60314abc8c 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ - [`useStartTyping`](./docs/useStartTyping.md) — detects when user starts typing. - [`useWindowScroll`](./docs/useWindowScroll.md) — tracks `Window` scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usewindowscroll--docs) - [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668) + - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver)

- [**UI**](./docs/UI.md) diff --git a/docs/useMeasure.md b/docs/useMeasure.md new file mode 100644 index 0000000000..64123f2446 --- /dev/null +++ b/docs/useMeasure.md @@ -0,0 +1,23 @@ +# `useSize` + +React sensor hook that tracks size of an HTML element. + +## Usage + +```jsx +import { useMeasure } from "react-use"; + +const Demo = () => { + const [ref, { width, height }] = useSize(); + + return ( +
+
width: {width}
+
height: {height}
+
+ ); +}; +``` +## Related hooks + +- [useSize](./useSize.md) \ No newline at end of file diff --git a/package.json b/package.json index 4bb3a3de91..c1d5906a69 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "nano-css": "^5.1.0", "react-fast-compare": "^2.0.4", "react-wait": "^0.3.0", + "resize-observer-polyfill": "^1.5.1", "screenfull": "^4.1.0", "throttle-debounce": "^2.0.1", "ts-easing": "^0.2.0" diff --git a/src/__tests__/useMeasure.test.ts b/src/__tests__/useMeasure.test.ts new file mode 100644 index 0000000000..e12136e277 --- /dev/null +++ b/src/__tests__/useMeasure.test.ts @@ -0,0 +1,84 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useMeasure, { ContentRect } from '../useMeasure'; + +interface Entry { + target: HTMLElement; + contentRect: ContentRect; +} + +jest.mock('resize-observer-polyfill', () => { + return class ResizeObserver { + private cb: (entries: Entry[]) => void; + private map: WeakMap; + private targets: HTMLElement[]; + constructor(cb: () => void) { + this.cb = cb; + this.map = new WeakMap(); + this.targets = []; + } + public disconnect() { + this.targets.map(target => { + const originMethod = this.map.get(target); + target.setAttribute = originMethod; + this.map.delete(target); + }); + } + public observe(target: HTMLElement) { + const method = 'setAttribute'; + const originMethod = target[method]; + this.map.set(target, originMethod); + this.targets.push(target); + target[method] = (...args) => { + const [attrName, value] = args; + if (attrName === 'style') { + const rect: ContentRect = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }; + value.split(';').map(kv => { + const [key, v] = kv.split(':'); + if (['top', 'bottom', 'left', 'right', 'width', 'height'].includes(key)) { + rect[key] = parseInt(v, 10); + } + }); + target.getBoundingClientRect = () => rect; + } + originMethod.apply(target, args); + this.fireCallback(); + }; + } + private fireCallback() { + if (this.cb) { + this.cb( + this.targets.map(target => { + return { + target, + contentRect: target.getBoundingClientRect(), + }; + }) + ); + } + } + }; +}); + +it('reacts to changes in size of any of the observed elements', () => { + const { result } = renderHook(() => useMeasure()); + const div = document.createElement('div'); + result.current[0](div); + expect(result.current[1]).toMatchObject({ + width: 0, + height: 0, + top: 0, + bottom: 0, + left: 0, + right: 0, + }); + act(() => div.setAttribute('style', 'width:200px;height:200px;top:100;left:100')); + + expect(result.current[1]).toMatchObject({ + width: 200, + height: 200, + top: 100, + bottom: 0, + left: 100, + right: 0, + }); +}); diff --git a/src/useMeasure.ts b/src/useMeasure.ts new file mode 100644 index 0000000000..6a68d01599 --- /dev/null +++ b/src/useMeasure.ts @@ -0,0 +1,42 @@ +import { useCallback, useState } from 'react'; +import ResizeObserver from 'resize-observer-polyfill'; + +export interface ContentRect { + width: number; + height: number; + top: number; + right: number; + left: number; + bottom: number; +} +const useMeasure = (): [(instance: T) => void, ContentRect] => { + const [rect, set] = useState({ + width: 0, + height: 0, + top: 0, + left: 0, + bottom: 0, + right: 0, + }); + + const [observer] = useState( + () => + new ResizeObserver(entries => { + const entry = entries[0]; + set(entry.contentRect); + }) + ); + + const ref = useCallback( + node => { + observer.disconnect(); + if (node) { + observer.observe(node); + } + }, + [observer] + ); + return [ref, rect]; +}; + +export default useMeasure; From 4deb48b35efab4256fa3938ffbe2224d9e33036f Mon Sep 17 00:00:00 2001 From: lintuming Date: Wed, 28 Aug 2019 09:28:28 +0800 Subject: [PATCH 2/6] add `useMeasure` story --- src/__stories__/useMeasure.story.tsx | 27 +++++++++++++++++++++++++++ src/index.ts | 1 + src/useMeasure.ts | 5 +++-- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/__stories__/useMeasure.story.tsx diff --git a/src/__stories__/useMeasure.story.tsx b/src/__stories__/useMeasure.story.tsx new file mode 100644 index 0000000000..c91fa18bd4 --- /dev/null +++ b/src/__stories__/useMeasure.story.tsx @@ -0,0 +1,27 @@ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { useMeasure } from '..'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const [ref, { width, height }] = useMeasure(); + + return ( + <> +
width: {width}
+
height: {height}
+
+ resize me +
+ + ); +}; + +storiesOf('Sensors|useMeasure', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/index.ts b/src/index.ts index 644f587e73..ec66b1b773 100644 --- a/src/index.ts +++ b/src/index.ts @@ -85,3 +85,4 @@ export { default as useVideo } from './useVideo'; export { useWait, Waiter } from './useWait'; export { default as useWindowScroll } from './useWindowScroll'; export { default as useWindowSize } from './useWindowSize'; +export { default as useMeasure } from './useMeasure'; diff --git a/src/useMeasure.ts b/src/useMeasure.ts index 6a68d01599..8e0d82bf2e 100644 --- a/src/useMeasure.ts +++ b/src/useMeasure.ts @@ -1,6 +1,5 @@ import { useCallback, useState } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; - export interface ContentRect { width: number; height: number; @@ -23,7 +22,9 @@ const useMeasure = (): [(instance: T) => void, ContentRect] => { () => new ResizeObserver(entries => { const entry = entries[0]; - set(entry.contentRect); + if (entry) { + set(entry.contentRect); + } }) ); From 611a4263f6e5ab9402cb1c6029d41664e06ed5cc Mon Sep 17 00:00:00 2001 From: lintuming Date: Wed, 28 Aug 2019 10:20:40 +0800 Subject: [PATCH 3/6] fix doc typo --- README.md | 2 +- docs/useMeasure.md | 7 +-- src/__tests__/useMeasure.test.tsx | 84 +++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/useMeasure.test.tsx diff --git a/README.md b/README.md index 60314abc8c..dcd6ec8544 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ - [`useStartTyping`](./docs/useStartTyping.md) — detects when user starts typing. - [`useWindowScroll`](./docs/useWindowScroll.md) — tracks `Window` scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usewindowscroll--docs) - [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668) - - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver) + - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver).

- [**UI**](./docs/UI.md) diff --git a/docs/useMeasure.md b/docs/useMeasure.md index 64123f2446..92b5db7b11 100644 --- a/docs/useMeasure.md +++ b/docs/useMeasure.md @@ -1,6 +1,6 @@ -# `useSize` +# `useMeasure` -React sensor hook that tracks size of an HTML element. +React sensor hook that reacts to changes in size of any of the observed elements. ## Usage @@ -8,7 +8,7 @@ React sensor hook that tracks size of an HTML element. import { useMeasure } from "react-use"; const Demo = () => { - const [ref, { width, height }] = useSize(); + const [ref, { width, height }] = useMeasure(); return (
@@ -18,6 +18,7 @@ const Demo = () => { ); }; ``` + ## Related hooks - [useSize](./useSize.md) \ No newline at end of file diff --git a/src/__tests__/useMeasure.test.tsx b/src/__tests__/useMeasure.test.tsx new file mode 100644 index 0000000000..e12136e277 --- /dev/null +++ b/src/__tests__/useMeasure.test.tsx @@ -0,0 +1,84 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useMeasure, { ContentRect } from '../useMeasure'; + +interface Entry { + target: HTMLElement; + contentRect: ContentRect; +} + +jest.mock('resize-observer-polyfill', () => { + return class ResizeObserver { + private cb: (entries: Entry[]) => void; + private map: WeakMap; + private targets: HTMLElement[]; + constructor(cb: () => void) { + this.cb = cb; + this.map = new WeakMap(); + this.targets = []; + } + public disconnect() { + this.targets.map(target => { + const originMethod = this.map.get(target); + target.setAttribute = originMethod; + this.map.delete(target); + }); + } + public observe(target: HTMLElement) { + const method = 'setAttribute'; + const originMethod = target[method]; + this.map.set(target, originMethod); + this.targets.push(target); + target[method] = (...args) => { + const [attrName, value] = args; + if (attrName === 'style') { + const rect: ContentRect = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }; + value.split(';').map(kv => { + const [key, v] = kv.split(':'); + if (['top', 'bottom', 'left', 'right', 'width', 'height'].includes(key)) { + rect[key] = parseInt(v, 10); + } + }); + target.getBoundingClientRect = () => rect; + } + originMethod.apply(target, args); + this.fireCallback(); + }; + } + private fireCallback() { + if (this.cb) { + this.cb( + this.targets.map(target => { + return { + target, + contentRect: target.getBoundingClientRect(), + }; + }) + ); + } + } + }; +}); + +it('reacts to changes in size of any of the observed elements', () => { + const { result } = renderHook(() => useMeasure()); + const div = document.createElement('div'); + result.current[0](div); + expect(result.current[1]).toMatchObject({ + width: 0, + height: 0, + top: 0, + bottom: 0, + left: 0, + right: 0, + }); + act(() => div.setAttribute('style', 'width:200px;height:200px;top:100;left:100')); + + expect(result.current[1]).toMatchObject({ + width: 200, + height: 200, + top: 100, + bottom: 0, + left: 100, + right: 0, + }); +}); From c57b31061a892040d5371fc2033e4cbee6a08f08 Mon Sep 17 00:00:00 2001 From: lintuming Date: Wed, 28 Aug 2019 10:22:53 +0800 Subject: [PATCH 4/6] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dcd6ec8544..70cef14a50 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ - [`useStartTyping`](./docs/useStartTyping.md) — detects when user starts typing. - [`useWindowScroll`](./docs/useWindowScroll.md) — tracks `Window` scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usewindowscroll--docs) - [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668) - - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver). + - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver).[![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-useMeasure--demo)

- [**UI**](./docs/UI.md) From 4167140b5f52a0f65c9ed8e175f713284c3eb778 Mon Sep 17 00:00:00 2001 From: lintuming Date: Wed, 28 Aug 2019 10:25:20 +0800 Subject: [PATCH 5/6] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70cef14a50..d8b7fda23c 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ - [`useStartTyping`](./docs/useStartTyping.md) — detects when user starts typing. - [`useWindowScroll`](./docs/useWindowScroll.md) — tracks `Window` scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usewindowscroll--docs) - [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668) - - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver).[![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-useMeasure--demo) + - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver).[![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemeasure--demo)

- [**UI**](./docs/UI.md) From f1d51fcff623559e520c6079c554741a6484b611 Mon Sep 17 00:00:00 2001 From: lintuming Date: Wed, 28 Aug 2019 10:32:35 +0800 Subject: [PATCH 6/6] delete test file --- src/__tests__/useMeasure.test.tsx | 84 ------------------------------- 1 file changed, 84 deletions(-) delete mode 100644 src/__tests__/useMeasure.test.tsx diff --git a/src/__tests__/useMeasure.test.tsx b/src/__tests__/useMeasure.test.tsx deleted file mode 100644 index e12136e277..0000000000 --- a/src/__tests__/useMeasure.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { act, renderHook } from '@testing-library/react-hooks'; -import useMeasure, { ContentRect } from '../useMeasure'; - -interface Entry { - target: HTMLElement; - contentRect: ContentRect; -} - -jest.mock('resize-observer-polyfill', () => { - return class ResizeObserver { - private cb: (entries: Entry[]) => void; - private map: WeakMap; - private targets: HTMLElement[]; - constructor(cb: () => void) { - this.cb = cb; - this.map = new WeakMap(); - this.targets = []; - } - public disconnect() { - this.targets.map(target => { - const originMethod = this.map.get(target); - target.setAttribute = originMethod; - this.map.delete(target); - }); - } - public observe(target: HTMLElement) { - const method = 'setAttribute'; - const originMethod = target[method]; - this.map.set(target, originMethod); - this.targets.push(target); - target[method] = (...args) => { - const [attrName, value] = args; - if (attrName === 'style') { - const rect: ContentRect = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }; - value.split(';').map(kv => { - const [key, v] = kv.split(':'); - if (['top', 'bottom', 'left', 'right', 'width', 'height'].includes(key)) { - rect[key] = parseInt(v, 10); - } - }); - target.getBoundingClientRect = () => rect; - } - originMethod.apply(target, args); - this.fireCallback(); - }; - } - private fireCallback() { - if (this.cb) { - this.cb( - this.targets.map(target => { - return { - target, - contentRect: target.getBoundingClientRect(), - }; - }) - ); - } - } - }; -}); - -it('reacts to changes in size of any of the observed elements', () => { - const { result } = renderHook(() => useMeasure()); - const div = document.createElement('div'); - result.current[0](div); - expect(result.current[1]).toMatchObject({ - width: 0, - height: 0, - top: 0, - bottom: 0, - left: 0, - right: 0, - }); - act(() => div.setAttribute('style', 'width:200px;height:200px;top:100;left:100')); - - expect(result.current[1]).toMatchObject({ - width: 200, - height: 200, - top: 100, - bottom: 0, - left: 100, - right: 0, - }); -});