diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f2c037c..b56cb858 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 refs: container: &container docker: - - image: node:12.9.1 + - image: node:12.11.1 working_directory: ~/repo steps: - &Versions diff --git a/CHANGELOG.md b/CHANGELOG.md index 3396f4c6..61180767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,59 @@ +# [12.5.0](https://github.com/streamich/react-use/compare/v12.4.0...v12.5.0) (2019-10-13) + + +### Features + +* useList allow pushing multiple items ([#621](https://github.com/streamich/react-use/issues/621)) ([a624364](https://github.com/streamich/react-use/commit/a624364)) + +# [12.4.0](https://github.com/streamich/react-use/compare/v12.3.2...v12.4.0) (2019-10-12) + + +### Features + +* useIntersection ([#652](https://github.com/streamich/react-use/issues/652)) ([d5f359f](https://github.com/streamich/react-use/commit/d5f359f)) + +## [12.3.2](https://github.com/streamich/react-use/compare/v12.3.1...v12.3.2) (2019-10-12) + + +### Bug Fixes + +* improve use of refs in dependency lists ([#655](https://github.com/streamich/react-use/issues/655)) ([ed8e26d](https://github.com/streamich/react-use/commit/ed8e26d)) + +## [12.3.1](https://github.com/streamich/react-use/compare/v12.3.0...v12.3.1) (2019-10-10) + + +### Bug Fixes + +* move [@types](https://github.com/types)/react-wait to dependencies, closes [#661](https://github.com/streamich/react-use/issues/661) ([#662](https://github.com/streamich/react-use/issues/662)) ([6bdd74e](https://github.com/streamich/react-use/commit/6bdd74e)) + +# [12.3.0](https://github.com/streamich/react-use/compare/v12.2.3...v12.3.0) (2019-10-07) + + +### Features + +* reset util callback for useList ([#654](https://github.com/streamich/react-use/issues/654)) ([9ea3548](https://github.com/streamich/react-use/commit/9ea3548)) + +## [12.2.3](https://github.com/streamich/react-use/compare/v12.2.2...v12.2.3) (2019-10-05) + + +### Bug Fixes + +* move react-wait types to dev dependencies, closes [#644](https://github.com/streamich/react-use/issues/644) ([49372ac](https://github.com/streamich/react-use/commit/49372ac)) + +## [12.2.2](https://github.com/streamich/react-use/compare/v12.2.1...v12.2.2) (2019-09-26) + + +### Bug Fixes + +* useDebounce remove deps from function arguments ([#623](https://github.com/streamich/react-use/issues/623)) ([23d6a5a](https://github.com/streamich/react-use/commit/23d6a5a)) + +## [12.2.1](https://github.com/streamich/react-use/compare/v12.2.0...v12.2.1) (2019-09-23) + + +### Bug Fixes + +* remove attempt from deps of retry in useAsyncRetry ([#614](https://github.com/streamich/react-use/issues/614)) ([adce59e](https://github.com/streamich/react-use/commit/adce59e)) + # [12.2.0](https://github.com/streamich/react-use/compare/v12.1.0...v12.2.0) (2019-09-02) diff --git a/README.md b/README.md index f54afa21..ebe6c734 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ - [`useGeolocation`](./docs/useGeolocation.md) — tracks geo location state of user's device. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usegeolocation--demo) - [`useHover` and `useHoverDirty`](./docs/useHover.md) — tracks mouse hover state of some element. [![][img-demo]](https://codesandbox.io/s/zpn583rvx) - [`useIdle`](./docs/useIdle.md) — tracks whether user is being inactive. + - [`useIntersection`](./docs/useIntersection.md) — tracks an HTML element's intersection. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-useintersection--demo) - [`useKey`](./docs/useKey.md), [`useKeyPress`](./docs/useKeyPress.md), [`useKeyboardJs`](./docs/useKeyboardJs.md), and [`useKeyPressEvent`](./docs/useKeyPressEvent.md) — track keys. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usekeypressevent--demo) - [`useLocation`](./docs/useLocation.md) and [`useSearchParam`](./docs/useSearchParam.md) — tracks page navigation bar location state. - [`useMedia`](./docs/useMedia.md) — tracks state of a CSS media query. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemedia--demo) @@ -60,11 +61,11 @@ - [`usePageLeave`](./docs/usePageLeave.md) — triggers when mouse leaves page boundaries. - [`useScroll`](./docs/useScroll.md) — tracks an HTML element's scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usescroll--docs) - [`useScrolling`](./docs/useScrolling.md) — tracks whether HTML element is scrolling. - - [`useSize`](./docs/useSize.md) — tracks an HTML element's dimensions. + - [`useSize`](./docs/useSize.md) — tracks an HTML element's size. - [`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 using the Resize Observer API.[![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemeasure--demo)

- [**UI**](./docs/UI.md) @@ -104,7 +105,7 @@ - [`useTitle`](./docs/useTitle.md) — sets title of the page. - [`usePermission`](./docs/usePermission.md) — query permission status for browser APIs.
-
+
- [**Lifecycles**](./docs/Lifecycles.md) - [`useEffectOnce`](./docs/useEffectOnce.md) — a modified [`useEffect`](https://reactjs.org/docs/hooks-reference.html#useeffect) hook that only runs once. - [`useEvent`](./docs/useEvent.md) — subscribe to events. @@ -125,15 +126,15 @@ - [`useDefault`](./docs/useDefault.md) — returns the default value when state is `null` or `undefined`. - [`useGetSet`](./docs/useGetSet.md) — returns state getter `get()` instead of raw state. - [`useGetSetState`](./docs/useGetSetState.md) — as if [`useGetSet`](./docs/useGetSet.md) and [`useSetState`](./docs/useSetState.md) had a baby. - - [`usePrevious`](./docs/usePrevious.md) — returns the previous state or props. + - [`usePrevious`](./docs/usePrevious.md) — returns the previous state or props. [![][img-demo]](https://codesandbox.io/s/fervent-galileo-krgx6) - [`useObservable`](./docs/useObservable.md) — tracks latest value of an `Observable`. - [`useSetState`](./docs/useSetState.md) — creates `setState` method which works like `this.setState`. [![][img-demo]](https://codesandbox.io/s/n75zqn1xp0) - - [`useStateList`](./docs/useStateList.md) — circularly iterates over an array. - - [`useToggle` and `useBoolean`](./docs/useToggle.md) — tracks state of a boolean. + - [`useStateList`](./docs/useStateList.md) — circularly iterates over an array. [![][img-demo]](https://codesandbox.io/s/bold-dewdney-pjzkd) + - [`useToggle` and `useBoolean`](./docs/useToggle.md) — tracks state of a boolean. [![][img-demo]](https://codesandbox.io/s/focused-sammet-brw2d) - [`useCounter` and `useNumber`](./docs/useCounter.md) — tracks state of a number. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usecounter--demo) - - [`useList`](./docs/useList.md) — tracks state of an array. - - [`useMap`](./docs/useMap.md) — tracks state of an object. - + - [`useList`](./docs/useList.md) and [`useUpsert`](./docs/useUpsert.md) — tracks state of an array. [![][img-demo]](https://codesandbox.io/s/wonderful-mahavira-1sm0w) + - [`useMap`](./docs/useMap.md) — tracks state of an object. [![][img-demo]](https://codesandbox.io/s/quirky-dewdney-gi161) + - [`useStateValidator`](./docs/useStateValidator.md) — tracks state of an object. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usestatevalidator--demo)

@@ -159,7 +160,6 @@ [img-demo]: https://img.shields.io/badge/demo-%20%20%20%F0%9F%9A%80-green.svg -

Contributors

diff --git a/docs/useIntersection.md b/docs/useIntersection.md new file mode 100644 index 00000000..802ee1da --- /dev/null +++ b/docs/useIntersection.md @@ -0,0 +1,36 @@ +# `useIntersection` + +React sensor hook that tracks the changes in the intersection of a target element with an ancestor element or with a top-level document's viewport. Uses the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) and returns a [IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry). + +## Usage + +```jsx +import * as React from 'react'; +import { useIntersection } from 'react-use'; + +const Demo = () => { + const intersectionRef = React.useRef(null); + const intersection = useIntersection(intersectionRef, { + root: null, + rootMargin: '0px', + threshold: 1 + }); + + return ( +
+ {intersection && intersection.intersectionRatio < 1 + ? 'Obscured' + : 'Fully in view'} +
+ ); +}; +``` + +## Reference + +```ts +useIntersection( + ref: RefObject, + options: IntersectionObserverInit, +): IntersectionObserverEntry | null; +``` diff --git a/docs/useInterval.md b/docs/useInterval.md index 03679acf..5c3ae9a7 100644 --- a/docs/useInterval.md +++ b/docs/useInterval.md @@ -1,6 +1,6 @@ # `useInterval` -React hook that allow you using declarative `setInterval`. +A declarative interval hook based on [Dan Abramov's article on overreacted.io](https://overreacted.io/making-setinterval-declarative-with-react-hooks). The interval can be paused by setting the delay to `null`. ## Usage @@ -11,32 +11,31 @@ import {useInterval} from 'react-use'; const Demo = () => { const [count, setCount] = React.useState(0); const [delay, setDelay] = React.useState(1000); + const [isRunning, toggleIsRunning] = useBoolean(true); - useInterval(() => { - setCount(count + 1); - }, delay); - - function handleDelayChange(e) { - setDelay(Number(e.target.value)); - } + useInterval( + () => { + setCount(count + 1); + }, + isRunning ? delay : null + ); return (
- delay: + delay: setDelay(Number(event.target.value))} />

count: {count}

- +
); }; ``` - ## Reference ```js -useInterval(fn, delay?: number) +useInterval(callback, delay?: number) ``` diff --git a/docs/useList.md b/docs/useList.md index 48b2c9a0..2e33e188 100644 --- a/docs/useList.md +++ b/docs/useList.md @@ -2,21 +2,31 @@ React state hook that tracks a value of an array. - ## Usage ```jsx import {useList} from 'react-use'; const Demo = () => { - const [list, {set, push}] = useList(); + const [list, { clear, filter, push, remove, set, sort, updateAt, reset }] = useList(); return (
-
{list.join(',')}
- - + + + + + + + + + +
{JSON.stringify(list, null, 2)}
); }; ``` + +## Related hooks + +- [useUpsert](./useUpsert.md) diff --git a/docs/useMap.md b/docs/useMap.md index 99e5e131..cf3273af 100644 --- a/docs/useMap.md +++ b/docs/useMap.md @@ -2,22 +2,28 @@ React state hook that tracks a value of an object. - ## Usage ```jsx import {useMap} from 'react-use'; const Demo = () => { - const [map, {set, reset}] = useMap({ + const [map, {set, remove, reset}] = useMap({ hello: 'there', }); return (
+ + +
{JSON.stringify(map, null, 2)}
- -
); }; diff --git a/docs/useMeasure.md b/docs/useMeasure.md index 92b5db7b..0f574fbc 100644 --- a/docs/useMeasure.md +++ b/docs/useMeasure.md @@ -1,6 +1,6 @@ # `useMeasure` -React sensor hook that reacts to changes in size of any of the observed elements. +React sensor hook that tracks dimensions of an HTML element using the [Resize Observer API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver). ## Usage @@ -8,12 +8,18 @@ React sensor hook that reacts to changes in size of any of the observed elements import { useMeasure } from "react-use"; const Demo = () => { - const [ref, { width, height }] = useMeasure(); + const [ref, { x, y, width, height, top, right, bottom, left }] = useMeasure(); return (
+
x: {x}
+
y: {y}
width: {width}
height: {height}
+
top: {top}
+
right: {right}
+
bottom: {bottom}
+
left: {left}
); }; @@ -21,4 +27,4 @@ const Demo = () => { ## Related hooks -- [useSize](./useSize.md) \ No newline at end of file +- [useSize](./useSize.md) diff --git a/docs/usePrevious.md b/docs/usePrevious.md index 953dee14..76c49d4a 100644 --- a/docs/usePrevious.md +++ b/docs/usePrevious.md @@ -13,7 +13,11 @@ const Demo = () => { return (

- Now: {count}, before: {prevCount} + + +

+ Now: {count}, before: {prevCount} +

); }; diff --git a/docs/useSearchParam.md b/docs/useSearchParam.md index f1184676..6d9fe29c 100644 --- a/docs/useSearchParam.md +++ b/docs/useSearchParam.md @@ -2,7 +2,6 @@ React sensor hook that tracks browser's location search param. - ## Usage ```jsx @@ -27,3 +26,7 @@ const Demo = () => { ); }; ``` + +## Caveats/Gotchas + +When using a hash router, like `react-router`'s [``](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/HashRouter.md), this hook won't be able to read the search parameters as they are considered part of the hash of the URL by browsers. diff --git a/docs/useSize.md b/docs/useSize.md index ec0d90b8..3784d017 100644 --- a/docs/useSize.md +++ b/docs/useSize.md @@ -9,7 +9,8 @@ import {useSize} from 'react-use'; const Demo = () => { const [sized, {width, height}] = useSize( - ({width}) =>
Size me up! ({width}px)
+ ({width}) =>
Size me up! ({width}px)
, + { width: 100, height: 100 } ); return ( @@ -21,3 +22,16 @@ const Demo = () => { ); }; ``` + +## Reference + +```js +useSize(element, initialSize); +``` + +- `element` — sized element. +- `initialSize` — initial size containing a `width` and `height` key. + +## Related hooks + +- [useMeasure](./useMeasure.md) diff --git a/docs/useStateValidator.md b/docs/useStateValidator.md new file mode 100644 index 00000000..0b434fca --- /dev/null +++ b/docs/useStateValidator.md @@ -0,0 +1,47 @@ +# `useStateValidator` + +Each time given state changes - validator function is invoked. + +## Usage +```ts +import * as React from 'react'; +import { useCallback } from 'react'; +import { useStateValidator } from 'react-use'; + +const DemoStateValidator = s => [s === '' ? null : (s * 1) % 2 === 0]; +const Demo = () => { + const [state, setState] = React.useState(0); + const [[isValid]] = useStateValidator(state, DemoStateValidator); + + return ( +
+
Below field is valid only if number is even
+ ) => { + setState(ev.target.value); + }} + /> + {isValid !== null && {isValid ? 'Valid!' : 'Invalid'}} +
+ ); +}; +``` + +## Reference +```ts +const [validity, revalidate] = useStateValidator( + state: any, + validator: (state, setValidity?)=>[boolean|null, ...any[]], + initialValidity: any +); +``` +- **`validity`**_`: [boolean|null, ...any[]]`_ result of validity check. First element is strictly nullable boolean, but others can contain arbitrary data; +- **`revalidate`**_`: ()=>void`_ runs validator once again +- **`validator`**_`: (state, setValidity?)=>[boolean|null, ...any[]]`_ should return an array suitable for validity state described above; + - `state` - current state; + - `setValidity` - if defined hook will not trigger validity change automatically. Useful for async validators; +- `initialValidity` - validity value which set when validity is nt calculated yet; diff --git a/docs/useToggle.md b/docs/useToggle.md index e1a342e7..0405c6ce 100644 --- a/docs/useToggle.md +++ b/docs/useToggle.md @@ -7,7 +7,7 @@ React state hook that tracks value of a boolean. ## Usage ```jsx -import {useToggle, useBoolean} from 'react-use'; +import {useToggle} from 'react-use'; const Demo = () => { const [on, toggle] = useToggle(true); diff --git a/docs/useUpsert.md b/docs/useUpsert.md index 2384660d..48c3de55 100644 --- a/docs/useUpsert.md +++ b/docs/useUpsert.md @@ -1,6 +1,7 @@ # `useUpsert` -Superset of `useList`. Provides an additional method to upsert (update or insert) an element into the list. +Superset of [`useList`](./useList.md). Provides an additional method to upsert (update or insert) an element into the list. + ## Usage ```jsx @@ -26,3 +27,7 @@ const Demo = () => { ); }; ``` + +## Related hooks + +- [useList](./useList.md) diff --git a/package.json b/package.json index 7e5f9d34..82941fbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "12.2.0", + "version": "12.5.0", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", @@ -51,7 +51,7 @@ "react-fast-compare": "^2.0.4", "react-wait": "^0.3.0", "resize-observer-polyfill": "^1.5.1", - "screenfull": "^4.1.0", + "screenfull": "^5.0.0", "set-harmonic-interval": "^1.0.0", "throttle-debounce": "^2.0.1", "ts-easing": "^0.2.0", @@ -62,52 +62,53 @@ "react-dom": "^16.8.0" }, "devDependencies": { - "@babel/core": "7.6.0", + "@babel/core": "7.6.4", "@babel/plugin-syntax-dynamic-import": "7.2.0", - "@babel/preset-env": "7.6.0", - "@babel/preset-react": "7.0.0", + "@babel/preset-env": "7.6.3", + "@babel/preset-react": "7.6.3", "@babel/preset-typescript": "7.6.0", "@semantic-release/changelog": "3.0.4", "@semantic-release/git": "7.0.16", "@semantic-release/npm": "5.1.13", + "@shopify/jest-dom-mocks": "2.8.2", "@storybook/addon-actions": "5.1.11", "@storybook/addon-knobs": "5.1.11", "@storybook/addon-notes": "5.1.11", "@storybook/addon-options": "5.1.11", "@storybook/react": "5.1.11", - "@testing-library/react-hooks": "2.0.1", + "@testing-library/react-hooks": "2.0.3", "@types/jest": "24.0.18", "@types/react": "16.9.2", "babel-core": "6.26.3", "babel-loader": "8.0.6", "babel-plugin-dynamic-import-node": "2.3.0", - "fork-ts-checker-webpack-plugin": "1.5.0", + "fork-ts-checker-webpack-plugin": "1.5.1", "gh-pages": "2.1.1", - "husky": "3.0.5", + "husky": "3.0.9", "jest": "24.9.0", "keyboardjs": "2.5.1", - "lint-staged": "9.2.5", + "lint-staged": "9.4.2", "markdown-loader": "5.1.0", "prettier": "1.18.2", "raf-stub": "3.0.0", - "react": "16.9.0", - "react-dom": "16.9.0", + "react": "16.10.2", + "react-dom": "16.10.2", "react-frame-component": "4.1.1", "react-spring": "8.0.27", - "react-test-renderer": "16.9.0", + "react-test-renderer": "16.10.2", "rebound": "0.1.0", "redux-logger": "3.0.6", "redux-thunk": "2.3.0", "rimraf": "3.0.0", "rxjs": "6.5.3", "semantic-release": "15.13.24", - "ts-loader": "6.1.0", - "ts-node": "8.3.0", + "ts-loader": "6.2.0", + "ts-node": "8.4.1", "tslint": "5.20.0", "tslint-config-prettier": "1.18.0", "tslint-eslint-rules": "5.4.0", "tslint-plugin-prettier": "2.0.1", - "tslint-react": "4.0.0", + "tslint-react": "4.1.0", "typescript": "3.5.3" }, "config": { @@ -141,8 +142,8 @@ ] }, "volta": { - "node": "10.16.0", - "yarn": "1.16.0" + "node": "10.16.3", + "yarn": "1.19.1" }, "collective": { "type": "opencollective", diff --git a/src/__stories__/useIntersection.story.tsx b/src/__stories__/useIntersection.story.tsx new file mode 100644 index 00000000..fb8c1226 --- /dev/null +++ b/src/__stories__/useIntersection.story.tsx @@ -0,0 +1,53 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import { useIntersection } from '..'; +import ShowDocs from './util/ShowDocs'; + +const Spacer = () => ( +
+); + +const Demo = () => { + const intersectionRef = React.useRef(null); + const intersection = useIntersection(intersectionRef, { + root: null, + rootMargin: '0px', + threshold: 1, + }); + + return ( +
+ Scroll me + +
+ {intersection && intersection.intersectionRatio < 1 ? 'Obscured' : 'Fully in view'} +
+ +
+ ); +}; + +storiesOf('Sensors/useIntersection', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/__stories__/useInterval.story.tsx b/src/__stories__/useInterval.story.tsx index 7ac3d37c..2e460308 100644 --- a/src/__stories__/useInterval.story.tsx +++ b/src/__stories__/useInterval.story.tsx @@ -1,28 +1,28 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; -import { useInterval } from '..'; +import { useInterval, useBoolean } from '..'; import ShowDocs from './util/ShowDocs'; const Demo = () => { const [count, setCount] = React.useState(0); const [delay, setDelay] = React.useState(1000); + const [isRunning, toggleIsRunning] = useBoolean(true); - useInterval(() => { - setCount(count + 1); - }, delay); - - function handleDelayChange(e) { - setDelay(Number(e.target.value)); - } + useInterval( + () => { + setCount(count + 1); + }, + isRunning ? delay : null + ); return (
- delay: + delay: setDelay(Number(event.target.value))} />

count: {count}

- +
); diff --git a/src/__stories__/useList.story.tsx b/src/__stories__/useList.story.tsx index 912552a2..2ccfb611 100644 --- a/src/__stories__/useList.story.tsx +++ b/src/__stories__/useList.story.tsx @@ -4,13 +4,20 @@ import { useList } from '..'; import ShowDocs from './util/ShowDocs'; const Demo = () => { - const [list, { set, push }] = useList(); + const [list, { clear, filter, push, remove, set, sort, updateAt, reset }] = useList([1, 2, 3, 4, 5]); return (
-
{list.join(',')}
- - + + + + + + + + + +
{JSON.stringify(list, null, 2)}
); }; diff --git a/src/__stories__/useMap.story.tsx b/src/__stories__/useMap.story.tsx index 83c2b95e..1e5d085f 100644 --- a/src/__stories__/useMap.story.tsx +++ b/src/__stories__/useMap.story.tsx @@ -4,15 +4,18 @@ import { useMap } from '..'; import ShowDocs from './util/ShowDocs'; const Demo = () => { - const [map, { set, reset }] = useMap({ + const [map, { set, remove, reset }] = useMap({ hello: 'there', }); return (
-
{JSON.stringify(map, null, 2)}
+ +
{JSON.stringify(map, null, 2)}
); }; diff --git a/src/__stories__/useMeasure.story.tsx b/src/__stories__/useMeasure.story.tsx index c91fa18b..2431f993 100644 --- a/src/__stories__/useMeasure.story.tsx +++ b/src/__stories__/useMeasure.story.tsx @@ -4,18 +4,12 @@ import { useMeasure } from '..'; import ShowDocs from './util/ShowDocs'; const Demo = () => { - const [ref, { width, height }] = useMeasure(); + const [ref, state] = useMeasure(); return ( <> -
width: {width}
-
height: {height}
-
+
{JSON.stringify(state, null, 2)}
+
resize me
diff --git a/src/__stories__/useSize.story.tsx b/src/__stories__/useSize.story.tsx index c029359a..9310b377 100644 --- a/src/__stories__/useSize.story.tsx +++ b/src/__stories__/useSize.story.tsx @@ -4,15 +4,14 @@ import { useSize } from '..'; import ShowDocs from './util/ShowDocs'; const Demo = () => { - const [sized, { width, height }] = useSize(({ width: currentWidth }) => ( -
Size me up! ({currentWidth}px)
+ const [sized, state] = useSize(({ width: currentWidth }) => ( +
Size me up! ({currentWidth}px)
)); return (
+
{JSON.stringify(state, null, 2)}
{sized} -
width: {width}
-
height: {height}
); }; diff --git a/src/__stories__/useStateValidator.story.tsx b/src/__stories__/useStateValidator.story.tsx new file mode 100644 index 00000000..906de0cb --- /dev/null +++ b/src/__stories__/useStateValidator.story.tsx @@ -0,0 +1,30 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import useStateValidator from '../useStateValidator'; +import ShowDocs from './util/ShowDocs'; + +const DemoStateValidator = s => [s === '' ? null : (s * 1) % 2 === 0]; +const Demo = () => { + const [state, setState] = React.useState(0); + const [[isValid]] = useStateValidator(state, DemoStateValidator); + + return ( +
+
Below field is valid only if number is even
+ ) => { + setState(ev.target.value); + }} + /> + {isValid !== null && {isValid ? 'Valid!' : 'Invalid'}} +
+ ); +}; + +storiesOf('State|useStateValidator', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/__tests__/useDeepCompareEffect.test.ts b/src/__tests__/useDeepCompareEffect.test.ts new file mode 100644 index 00000000..965495de --- /dev/null +++ b/src/__tests__/useDeepCompareEffect.test.ts @@ -0,0 +1,39 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useDeepCompareEffect } from '..'; +import { useEffect } from 'react'; + +let options = { max: 10 }; +const mockEffectNormal = jest.fn(); +const mockEffectDeep = jest.fn(); +const mockEffectCleanup = jest.fn(); +const mockEffectCallback = jest.fn().mockReturnValue(mockEffectCleanup); + +it('should run provided object once', () => { + const { rerender: rerenderNormal } = renderHook(() => useEffect(mockEffectNormal, [options])); + const { rerender: rerenderDeep } = renderHook(() => useDeepCompareEffect(mockEffectDeep, [options])); + + expect(mockEffectNormal).toHaveBeenCalledTimes(1); + expect(mockEffectDeep).toHaveBeenCalledTimes(1); + + options = { max: 10 }; + rerenderDeep(); + rerenderNormal(); + + expect(mockEffectNormal).toHaveBeenCalledTimes(2); + expect(mockEffectDeep).toHaveBeenCalledTimes(1); + + options = { max: 10 }; + rerenderNormal(); + rerenderDeep(); + + expect(mockEffectNormal).toHaveBeenCalledTimes(3); + expect(mockEffectDeep).toHaveBeenCalledTimes(1); +}); + +it('should run clean-up provided on unmount', () => { + const { unmount } = renderHook(() => useDeepCompareEffect(mockEffectCallback, [options])); + expect(mockEffectCleanup).not.toHaveBeenCalled(); + + unmount(); + expect(mockEffectCleanup).toHaveBeenCalledTimes(1); +}); diff --git a/src/__tests__/useFavicon.test.tsx b/src/__tests__/useFavicon.test.tsx new file mode 100644 index 00000000..fcbbda43 --- /dev/null +++ b/src/__tests__/useFavicon.test.tsx @@ -0,0 +1,55 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useFavicon from '../useFavicon'; + +afterEach(() => { + const favicon = document.querySelector("link[rel*='icon']"); + if (favicon) { + favicon.remove(); + } +}); + +describe('useFavicon', () => { + it('should be defined', () => { + expect(useFavicon).toBeDefined(); + }); + + it('should create a HTMLLinkElement', () => { + const faviconBeforeHook = document.querySelector("link[rel*='icon']"); + + expect(faviconBeforeHook).toBe(null); + renderHook(() => useFavicon('My-favicon')); + + const faviconAfterHook = document.querySelector("link[rel*='icon']"); + expect(faviconAfterHook).toBeInstanceOf(HTMLLinkElement); + }); + + it('should set the elements type to "image/x-icon"', () => { + renderHook(() => useFavicon('My-favicon')); + const favicon = document.querySelector("link[rel*='icon']") as HTMLLinkElement; + + expect(favicon.type).toBe('image/x-icon'); + }); + + it('should set the elements rel to "shortcut icon"', () => { + renderHook(() => useFavicon('My-favicon')); + const favicon = document.querySelector("link[rel*='icon']") as HTMLLinkElement; + + expect(favicon.rel).toBe('shortcut icon'); + }); + + it('should set the elements href to the provided string', () => { + renderHook(() => useFavicon('https://github.com/streamich/react-use')); + const favicon = document.querySelector("link[rel*='icon']") as HTMLLinkElement; + + expect(favicon.href).toBe('https://github.com/streamich/react-use'); + }); + + it('should update an existing favicon', () => { + const hook = renderHook(props => useFavicon(props), { initialProps: 'https://github.com/streamich/react-use' }); + const favicon = document.querySelector("link[rel*='icon']") as HTMLLinkElement; + + expect(favicon.href).toBe('https://github.com/streamich/react-use'); + hook.rerender('https://en.wikipedia.org/wiki/Favicon'); + expect(favicon.href).toBe('https://en.wikipedia.org/wiki/Favicon'); + }); +}); diff --git a/src/__tests__/useIntersection.test.tsx b/src/__tests__/useIntersection.test.tsx new file mode 100644 index 00000000..ee7342b2 --- /dev/null +++ b/src/__tests__/useIntersection.test.tsx @@ -0,0 +1,119 @@ +import React, { createRef } from 'react'; +import ReactDOM from 'react-dom'; +import TestUtils from 'react-dom/test-utils'; +import TestRenderer from 'react-test-renderer'; +import { intersectionObserver } from '@shopify/jest-dom-mocks'; +import { renderHook } from '@testing-library/react-hooks'; +import { useIntersection } from '..'; + +beforeEach(() => { + intersectionObserver.mock(); +}); + +afterEach(() => { + intersectionObserver.restore(); +}); + +describe('useIntersection', () => { + const container = document.createElement('div'); + let targetRef; + + it('should be defined', () => { + expect(useIntersection).toBeDefined(); + }); + + it('should setup an IntersectionObserver targeting the ref element and using the options provided', () => { + TestUtils.act(() => { + targetRef = createRef(); + ReactDOM.render(
, container); + }); + + expect(intersectionObserver.observers).toHaveLength(0); + const observerOptions = { root: null, threshold: 0.8 }; + + renderHook(() => useIntersection(targetRef, observerOptions)); + + expect(intersectionObserver.observers).toHaveLength(1); + expect(intersectionObserver.observers[0].target).toEqual(targetRef.current); + expect(intersectionObserver.observers[0].options).toEqual(observerOptions); + }); + + it('should return null if a ref without a current value is provided', () => { + targetRef = createRef(); + + const { result } = renderHook(() => useIntersection(targetRef, { root: null, threshold: 1 })); + expect(result.current).toBe(null); + }); + + it('should return the first IntersectionObserverEntry when the IntersectionObserver registers an intersection', () => { + TestUtils.act(() => { + targetRef = createRef(); + ReactDOM.render(
, container); + }); + + const { result } = renderHook(() => useIntersection(targetRef, { root: container, threshold: 0.8 })); + + const mockIntersectionObserverEntry = { + boundingClientRect: targetRef.current.getBoundingClientRect(), + intersectionRatio: 0.81, + intersectionRect: container.getBoundingClientRect(), + isIntersecting: true, + rootBounds: container.getBoundingClientRect(), + target: targetRef.current, + time: 300, + }; + TestRenderer.act(() => { + intersectionObserver.simulate(mockIntersectionObserverEntry); + }); + + expect(result.current).toEqual(mockIntersectionObserverEntry); + }); + + it('should setup a new IntersectionObserver when the ref changes', () => { + let newRef; + TestUtils.act(() => { + targetRef = createRef(); + newRef = createRef(); + ReactDOM.render( +
+ +
, + container + ); + }); + + const observerOptions = { root: null, threshold: 0.8 }; + const { rerender } = renderHook(({ ref, options }) => useIntersection(ref, options), { + initialProps: { ref: targetRef, options: observerOptions }, + }); + + expect(intersectionObserver.observers[0].target).toEqual(targetRef.current); + + TestRenderer.act(() => { + rerender({ ref: newRef, options: observerOptions }); + }); + + expect(intersectionObserver.observers[0].target).toEqual(newRef.current); + }); + + it('should setup a new IntersectionObserver when the options change', () => { + TestUtils.act(() => { + targetRef = createRef(); + ReactDOM.render(
, container); + }); + + const initialObserverOptions = { root: null, threshold: 0.8 }; + const { rerender } = renderHook(({ ref, options }) => useIntersection(ref, options), { + initialProps: { ref: targetRef, options: initialObserverOptions }, + }); + + expect(intersectionObserver.observers[0].options).toEqual(initialObserverOptions); + + const newObserverOptions = { root: container, threshold: 1 }; + TestRenderer.act(() => { + rerender({ ref: targetRef, options: newObserverOptions }); + }); + + expect(intersectionObserver.observers[0].options).toEqual(newObserverOptions); + }); +}); diff --git a/src/__tests__/useList.test.ts b/src/__tests__/useList.test.ts index 047e6e68..47b5beb7 100644 --- a/src/__tests__/useList.test.ts +++ b/src/__tests__/useList.test.ts @@ -16,6 +16,7 @@ it('should init list and utils', () => { push: expect.any(Function), filter: expect.any(Function), sort: expect.any(Function), + reset: expect.any(Function), }); }); @@ -103,6 +104,19 @@ it('should push duplicated element at the end of the list', () => { expect(result.current[0]).not.toBe(initList); // checking immutability }); +it('should push multiple elements at the end of the list', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.push(4, 5, 6); + }); + + expect(result.current[0]).toEqual([1, 2, 3, 4, 5, 6]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + it('should filter current list by provided function', () => { const initList = [1, -1, 2, -2, 3, -3]; const { result } = setUp(initList); @@ -150,3 +164,43 @@ it('should sort current list by provided function', () => { expect(result.current[0]).toEqual(['March', 'Jan', 'Feb', 'Dec']); expect(result.current[0]).not.toBe(initList); // checking immutability }); + +it('should reset the list to initial list provided', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.push(4); + }); + + expect(result.current[0]).toEqual([1, 2, 3, 4]); + + act(() => { + utils.reset(); + }); + + expect(result.current[0]).toEqual([1, 2, 3]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should memoized its utils methods', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + const { set, clear, updateAt, remove, push, filter, sort, reset } = utils; + + act(() => { + push(4); + }); + + expect(result.current[1]).toBe(utils); + expect(result.current[1].set).toBe(set); + expect(result.current[1].clear).toBe(clear); + expect(result.current[1].updateAt).toBe(updateAt); + expect(result.current[1].remove).toBe(remove); + expect(result.current[1].push).toBe(push); + expect(result.current[1].filter).toBe(filter); + expect(result.current[1].sort).toBe(sort); + expect(result.current[1].reset).toBe(reset); +}); diff --git a/src/__tests__/useLogger.test.ts b/src/__tests__/useLogger.test.ts new file mode 100644 index 00000000..85989b9f --- /dev/null +++ b/src/__tests__/useLogger.test.ts @@ -0,0 +1,41 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useLogger from '../useLogger'; + +const logSpy = jest.spyOn(global.console, 'log').mockImplementation(() => {}); + +describe('useLogger', () => { + it('should be defined', () => { + expect(useLogger).toBeDefined(); + }); + + it('should log the provided props on mount', () => { + const props = { question: 'What is the meaning?', answer: 42 }; + renderHook(() => useLogger('Test', props)); + + expect(logSpy).toBeCalledTimes(1); + expect(logSpy).toHaveBeenLastCalledWith('Test mounted', props); + }); + + it('should log when the component has unmounted', () => { + const props = { question: 'What is the meaning?', answer: 42 }; + const { unmount } = renderHook(() => useLogger('Test', props)); + + unmount(); + + expect(logSpy).toHaveBeenLastCalledWith('Test unmounted'); + }); + + it('should log updates as props change', () => { + const { rerender } = renderHook( + ({ componentName, props }: { componentName: string; props: any }) => useLogger(componentName, props), + { + initialProps: { componentName: 'Test', props: { one: 1 } }, + } + ); + + const newProps = { one: 1, two: 2 }; + rerender({ componentName: 'Test', props: newProps }); + + expect(logSpy).toHaveBeenLastCalledWith('Test updated', newProps); + }); +}); diff --git a/src/__tests__/useStateValidator.test.ts b/src/__tests__/useStateValidator.test.ts new file mode 100644 index 00000000..7b717541 --- /dev/null +++ b/src/__tests__/useStateValidator.test.ts @@ -0,0 +1,100 @@ +import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks'; +import { useState } from 'react'; +import useStateValidator, { UseValidatorReturn, Validator } from '../useStateValidator'; + +interface Mock extends jest.Mock {} + +describe('useStateValidator', () => { + it('should be defined', () => { + expect(useStateValidator).toBeDefined(); + }); + + function getHook( + fn: Validator = jest.fn(state => [!!(state % 2)]) + ): [jest.Mock | Function, RenderHookResult]>] { + return [ + fn, + renderHook(() => { + const [state, setState] = useState(1); + + return [setState, useStateValidator(state, fn)]; + }), + ]; + } + + it('should return an array of two elements', () => { + const [, hook] = getHook(); + const res = hook.result.current[1]; + + expect(Array.isArray(res)).toBe(true); + expect(res[0]).toEqual([true]); + expect(typeof res[1]).toBe('function'); + }); + + it('first element should represent current validity state', () => { + const [, hook] = getHook(); + let [setState, [validity]] = hook.result.current; + expect(validity).toEqual([true]); + + act(() => setState(3)); + [setState, [validity]] = hook.result.current; + expect(validity).toEqual([true]); + + act(() => setState(4)); + [setState, [validity]] = hook.result.current; + expect(validity).toEqual([false]); + }); + + it('second element should re-call validation', () => { + const [spy, hook] = getHook(); + const [, [, revalidate]] = hook.result.current; + + expect(spy).toHaveBeenCalledTimes(1); + act(() => revalidate()); + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('validator have to be called on init plus on each state update', () => { + const [spy, hook] = getHook(jest.fn()); + const [setState] = hook.result.current; + + expect(spy).toHaveBeenCalledTimes(1); + act(() => setState(4)); + expect(spy).toHaveBeenCalledTimes(2); + act(() => setState(prevState => prevState + 1)); + expect(spy).toHaveBeenCalledTimes(3); + }); + + it('should pass to validator one parameter - current state', () => { + const [spy, hook] = getHook(jest.fn()); + const [setState] = hook.result.current; + + act(() => setState(4)); + act(() => setState(5)); + expect((spy as Mock).mock.calls[0].length).toBe(1); + expect((spy as Mock).mock.calls[0].length).toBe(1); + expect((spy as Mock).mock.calls[0][0]).toBe(1); + expect((spy as Mock).mock.calls[1].length).toBe(1); + expect((spy as Mock).mock.calls[1][0]).toBe(4); + expect((spy as Mock).mock.calls[2].length).toBe(1); + expect((spy as Mock).mock.calls[2][0]).toBe(5); + }); + + it('if validator expects 2nd parameters it should pass a validity setter there', () => { + const [spy, hook] = getHook(jest.fn((state, setValidity) => setValidity!([state % 2 === 0]))); + let [setState, [[isValid]]] = hook.result.current; + + expect((spy as Mock).mock.calls[0].length).toBe(2); + expect(typeof (spy as Mock).mock.calls[0][1]).toBe('function'); + + expect(isValid).toBe(false); + act(() => setState(prevState => prevState + 1)); + + [setState, [[isValid]]] = hook.result.current; + expect(isValid).toBe(true); + act(() => setState(5)); + + [setState, [[isValid]]] = hook.result.current; + expect(isValid).toBe(false); + }); +}); diff --git a/src/__tests__/useUpdateEffect.test.ts b/src/__tests__/useUpdateEffect.test.ts new file mode 100644 index 00000000..933395d4 --- /dev/null +++ b/src/__tests__/useUpdateEffect.test.ts @@ -0,0 +1,13 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useUpdateEffect } from '..'; + +const mockEffectCleanup = jest.fn(); +const mockEffectCallback = jest.fn().mockReturnValue(mockEffectCleanup); + +it('should run effect on update', () => { + const { rerender } = renderHook(() => useUpdateEffect(mockEffectCallback)); + expect(mockEffectCallback).not.toHaveBeenCalled(); + + rerender(); + expect(mockEffectCallback).toHaveBeenCalledTimes(1); +}); diff --git a/src/index.ts b/src/index.ts index b3514a71..65872134 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,7 @@ export { default as useHarmonicIntervalFn } from './useHarmonicIntervalFn'; export { default as useHover } from './useHover'; export { default as useHoverDirty } from './useHoverDirty'; export { default as useIdle } from './useIdle'; +export { default as useIntersection } from './useIntersection'; export { default as useInterval } from './useInterval'; export { default as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; export { default as useKey } from './useKey'; @@ -86,6 +87,7 @@ export { default as useUpdate } from './useUpdate'; export { default as useUpdateEffect } from './useUpdateEffect'; export { default as useUpsert } from './useUpsert'; export { default as useVideo } from './useVideo'; +export { default as useStateValidator } from './useStateValidator'; export { useWait, Waiter } from './useWait'; export { default as useWindowScroll } from './useWindowScroll'; export { default as useWindowSize } from './useWindowSize'; diff --git a/src/useAsyncRetry.ts b/src/useAsyncRetry.ts index 1c11578f..519c2b5d 100644 --- a/src/useAsyncRetry.ts +++ b/src/useAsyncRetry.ts @@ -20,7 +20,7 @@ const useAsyncRetry = (fn: () => Promise, deps: DependencyList = []) => { } setAttempt(currentAttempt => currentAttempt + 1); - }, [...deps, stateLoading, attempt]); + }, [...deps, stateLoading]); return { ...state, retry }; }; diff --git a/src/useDebounce.ts b/src/useDebounce.ts index 3c7d4664..bb7bffb7 100644 --- a/src/useDebounce.ts +++ b/src/useDebounce.ts @@ -1,14 +1,14 @@ +import { DependencyList } from 'react'; import useUpdateEffect from './useUpdateEffect'; -const useDebounce = (fn: () => any, ms: number = 0, args: any[] = []) => { +const useDebounce = (fn: () => any, ms: number = 0, deps: DependencyList = []) => { useUpdateEffect(() => { - const handle = setTimeout(fn.bind(null, args), ms); + const timeout = setTimeout(fn, ms); return () => { - // if args change then clear timeout - clearTimeout(handle); + clearTimeout(timeout); }; - }, args); + }, deps); }; export default useDebounce; diff --git a/src/useFullscreen.ts b/src/useFullscreen.ts index 52f63705..c55b0d3d 100644 --- a/src/useFullscreen.ts +++ b/src/useFullscreen.ts @@ -26,7 +26,7 @@ const useFullscreen = (ref: RefObject, on: boolean, options: FullScreen }; const onChange = () => { - if (screenfull) { + if (screenfull.isEnabled) { const isScreenfullFullscreen = screenfull.isFullscreen; setIsFullscreen(isScreenfullFullscreen); if (!isScreenfullFullscreen) { @@ -35,7 +35,7 @@ const useFullscreen = (ref: RefObject, on: boolean, options: FullScreen } }; - if (screenfull && screenfull.enabled) { + if (screenfull.isEnabled) { try { screenfull.request(ref.current); setIsFullscreen(true); @@ -55,7 +55,7 @@ const useFullscreen = (ref: RefObject, on: boolean, options: FullScreen return () => { setIsFullscreen(false); - if (screenfull && screenfull.enabled) { + if (screenfull.isEnabled) { try { screenfull.off('change', onChange); screenfull.exit(); @@ -65,7 +65,7 @@ const useFullscreen = (ref: RefObject, on: boolean, options: FullScreen video.current.webkitExitFullscreen(); } }; - }, [ref.current, video, on]); + }, [on, video, ref]); return isFullscreen; }; diff --git a/src/useIntersection.ts b/src/useIntersection.ts new file mode 100644 index 00000000..4a7b78ea --- /dev/null +++ b/src/useIntersection.ts @@ -0,0 +1,30 @@ +import { RefObject, useEffect, useState } from 'react'; + +const useIntersection = ( + ref: RefObject, + options: IntersectionObserverInit +): IntersectionObserverEntry | null => { + const [intersectionObserverEntry, setIntersectionObserverEntry] = useState(null); + + useEffect(() => { + if (ref.current) { + const handler = (entries: IntersectionObserverEntry[]) => { + setIntersectionObserverEntry(entries[0]); + }; + + const observer = new IntersectionObserver(handler, options); + observer.observe(ref.current); + + return () => { + if (ref.current) { + observer.disconnect(); + } + }; + } + return () => {}; + }, [ref, options.threshold, options.root, options.rootMargin]); + + return intersectionObserverEntry; +}; + +export default useIntersection; diff --git a/src/useInterval.ts b/src/useInterval.ts index f411cc74..4613d5e9 100644 --- a/src/useInterval.ts +++ b/src/useInterval.ts @@ -1,17 +1,18 @@ import { useEffect, useRef } from 'react'; const useInterval = (callback: Function, delay?: number | null) => { - const latestCallback = useRef(() => {}); + const savedCallback = useRef(() => {}); useEffect(() => { - latestCallback.current = callback; + savedCallback.current = callback; }); useEffect(() => { if (delay !== null) { - const interval = setInterval(() => latestCallback.current(), delay || 0); + const interval = setInterval(() => savedCallback.current(), delay || 0); return () => clearInterval(interval); } + return undefined; }, [delay]); }; diff --git a/src/useList.ts b/src/useList.ts index 5e292987..eecdc507 100644 --- a/src/useList.ts +++ b/src/useList.ts @@ -1,31 +1,35 @@ -import { useState } from 'react'; +import { useState, useMemo } from 'react'; export interface Actions { set: (list: T[]) => void; clear: () => void; updateAt: (index: number, item: T) => void; remove: (index: number) => void; - push: (item: T) => void; + push: (...items: T[]) => void; filter: (fn: (value: T) => boolean) => void; sort: (fn?: (a: T, b: T) => number) => void; + reset: () => void; } const useList = (initialList: T[] = []): [T[], Actions] => { const [list, set] = useState(initialList); - return [ - list, - { + const utils = useMemo>( + () => ({ set, clear: () => set([]), updateAt: (index, entry) => set(currentList => [...currentList.slice(0, index), entry, ...currentList.slice(index + 1)]), remove: index => set(currentList => [...currentList.slice(0, index), ...currentList.slice(index + 1)]), - push: entry => set(currentList => [...currentList, entry]), + push: (...entry) => set(currentList => [...currentList, ...entry]), filter: fn => set(currentList => currentList.filter(fn)), sort: (fn?) => set(currentList => [...currentList].sort(fn)), - }, - ]; + reset: () => set([...initialList]), + }), + [set] + ); + + return [list, utils]; }; export default useList; diff --git a/src/useMouse.ts b/src/useMouse.ts index 7e692a8d..665a126b 100644 --- a/src/useMouse.ts +++ b/src/useMouse.ts @@ -62,7 +62,7 @@ const useMouse = (ref: RefObject): State => { cancelAnimationFrame(frame.current); document.removeEventListener('mousemove', moveHandler); }; - }, [ref.current]); + }, [ref]); return state; }; diff --git a/src/useScroll.ts b/src/useScroll.ts index a98a7c3f..caf78f49 100644 --- a/src/useScroll.ts +++ b/src/useScroll.ts @@ -48,7 +48,7 @@ const useScroll = (ref: RefObject): State => { ref.current.removeEventListener('scroll', handler); } }; - }, [ref.current]); + }, [ref]); return state; }; diff --git a/src/useScrolling.ts b/src/useScrolling.ts index 01dd1174..c95ae6c2 100644 --- a/src/useScrolling.ts +++ b/src/useScrolling.ts @@ -25,7 +25,7 @@ const useScrolling = (ref: RefObject): boolean => { }; } return () => {}; - }, [ref.current]); + }, [ref]); return scrolling; }; diff --git a/src/useStateValidator.ts b/src/useStateValidator.ts new file mode 100644 index 00000000..cf9070f0 --- /dev/null +++ b/src/useStateValidator.ts @@ -0,0 +1,36 @@ +import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; + +export type ValidityState = [boolean | undefined, ...any[]]; +export type DispatchValidity = Dispatch>; + +export type Validator = + | { + (state?: S): V; + (state?: S, dispatch?: DispatchValidity): void; + } + | Function; + +export type UseValidatorReturn = [V, () => void]; + +export default function useStateValidator( + state: S, + validator: Validator, + initialValidity: V = [undefined] as V +): UseValidatorReturn { + const validatorFn = useRef(validator); + + const [validity, setValidity] = useState(initialValidity); + const validate = useCallback(() => { + if (validatorFn.current.length === 2) { + validatorFn.current(state, setValidity); + } else { + setValidity(validatorFn.current(state)); + } + }, [state]); + + useEffect(() => { + validate(); + }, [state]); + + return [validity, validate]; +} diff --git a/yarn.lock b/yarn.lock index eb5ede3e..47e22d64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,12 +42,12 @@ integrity sha512-FuRhDRtsd6IptKpHXAa+4WPZYY2ZzgowkbLBecEDDSje1X/apG7jQM33or3NdOmjXBKWGOg4JmSiRfUfuTtHXw== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" - "@babel/helpers" "^7.6.0" - "@babel/parser" "^7.6.0" + "@babel/generator" "^7.6.4" + "@babel/helpers" "^7.6.2" + "@babel/parser" "^7.6.4" "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" - "@babel/types" "^7.6.0" + "@babel/traverse" "^7.6.3" + "@babel/types" "^7.6.3" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" @@ -264,6 +264,21 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.3.tgz#9eff8b9c3eeae16a74d8d4ff30da2bd0d6f0487e" integrity sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg== +"@babel/parser@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1" + integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg== + +"@babel/parser@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.3.tgz#9eff8b9c3eeae16a74d8d4ff30da2bd0d6f0487e" + integrity sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg== + +"@babel/parser@^7.6.4": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81" + integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A== + "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" @@ -322,6 +337,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" +"@babel/plugin-proposal-object-rest-spread@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz#8ffccc8f3a6545e9f78988b6bf4fe881b88e8096" + integrity sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-proposal-optional-catch-binding@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" @@ -339,6 +362,15 @@ "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" +"@babel/plugin-proposal-unicode-property-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz#05413762894f41bfe42b9a5e80919bd575dcc802" + integrity sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.4.4" + regexpu-core "^4.6.0" + "@babel/plugin-syntax-async-generators@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" @@ -491,6 +523,15 @@ "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" +"@babel/plugin-transform-dotall-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz#44abb948b88f0199a627024e1508acaf8dc9b2f9" + integrity sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.4.4" + regexpu-core "^4.6.0" + "@babel/plugin-transform-duplicate-keys@^7.2.0", "@babel/plugin-transform-duplicate-keys@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853" @@ -695,6 +736,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-spread@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz#fc77cf798b24b10c46e1b51b1b88c2bf661bb8dd" + integrity sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-sticky-regex@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1" @@ -736,6 +784,15 @@ "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" +"@babel/plugin-transform-unicode-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz#b692aad888a7e8d8b1b214be6b9dc03d5031f698" + integrity sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.4.4" + regexpu-core "^4.6.0" + "@babel/preset-env@7.4.3": version "7.4.3" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.3.tgz#e71e16e123dc0fbf65a52cbcbcefd072fbd02880" @@ -800,9 +857,9 @@ "@babel/plugin-proposal-async-generator-functions" "^7.2.0" "@babel/plugin-proposal-dynamic-import" "^7.5.0" "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.5.5" + "@babel/plugin-proposal-object-rest-spread" "^7.6.2" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.6.2" "@babel/plugin-syntax-async-generators" "^7.2.0" "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-syntax-json-strings" "^7.2.0" @@ -811,11 +868,11 @@ "@babel/plugin-transform-arrow-functions" "^7.2.0" "@babel/plugin-transform-async-to-generator" "^7.5.0" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.6.0" + "@babel/plugin-transform-block-scoping" "^7.6.3" "@babel/plugin-transform-classes" "^7.5.5" "@babel/plugin-transform-computed-properties" "^7.2.0" "@babel/plugin-transform-destructuring" "^7.6.0" - "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.6.2" "@babel/plugin-transform-duplicate-keys" "^7.5.0" "@babel/plugin-transform-exponentiation-operator" "^7.2.0" "@babel/plugin-transform-for-of" "^7.4.4" @@ -826,7 +883,7 @@ "@babel/plugin-transform-modules-commonjs" "^7.6.0" "@babel/plugin-transform-modules-systemjs" "^7.5.0" "@babel/plugin-transform-modules-umd" "^7.2.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.3" "@babel/plugin-transform-new-target" "^7.4.4" "@babel/plugin-transform-object-super" "^7.5.5" "@babel/plugin-transform-parameters" "^7.4.4" @@ -834,12 +891,12 @@ "@babel/plugin-transform-regenerator" "^7.4.5" "@babel/plugin-transform-reserved-words" "^7.2.0" "@babel/plugin-transform-shorthand-properties" "^7.2.0" - "@babel/plugin-transform-spread" "^7.2.0" + "@babel/plugin-transform-spread" "^7.6.2" "@babel/plugin-transform-sticky-regex" "^7.2.0" "@babel/plugin-transform-template-literals" "^7.4.4" "@babel/plugin-transform-typeof-symbol" "^7.2.0" - "@babel/plugin-transform-unicode-regex" "^7.4.4" - "@babel/types" "^7.6.0" + "@babel/plugin-transform-unicode-regex" "^7.6.2" + "@babel/types" "^7.6.3" browserslist "^4.6.0" core-js-compat "^3.1.1" invariant "^2.2.2" @@ -865,6 +922,17 @@ "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" +"@babel/preset-react@7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.6.3.tgz#d5242c828322520205ae4eda5d4f4f618964e2f6" + integrity sha512-07yQhmkZmRAfwREYIQgW0HEwMY9GBJVuPY4Q12UC72AbfaawuupVWa8zQs2tlL+yun45Nv/1KreII/0PLfEsgA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + "@babel/preset-typescript@7.3.3": version "7.3.3" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz#88669911053fa16b2b276ea2ede2ca603b3f307a" @@ -935,6 +1003,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09" + integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" @@ -1414,6 +1491,37 @@ into-stream "^4.0.0" lodash "^4.17.4" +"@shopify/async@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@shopify/async/-/async-2.0.7.tgz#944992bc1721df6c363b3f0f31be1dad0e75e929" + integrity sha512-wYGjqPhpna4ShYbUmlD2fPv5ZkjNlCZtU7huUU8/snnyPmdgL/Rn5M5FPP6Apr7/hU5RgqMj2tJFs37ORz/VaQ== + +"@shopify/decorators@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@shopify/decorators/-/decorators-1.1.5.tgz#b8da0bd5fffb04cde9730898fc04428f964cab1c" + integrity sha512-cFAwd7T5IjkPs1ef11dbA6cbJA+CtgCDanbalPlQdl5ItwDzqJXGpvbhbQXw7zPyNMLijrgrpQqltalqAy9wnQ== + dependencies: + "@shopify/function-enhancers" "^1.0.5" + +"@shopify/function-enhancers@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@shopify/function-enhancers/-/function-enhancers-1.0.5.tgz#7c3e516e26ce7a9b63c263679bdcf5121d994a10" + integrity sha512-34ML8DX4RmmA9hXDlf2BAz4SA37unShZxoBRPz585a+FaEzNcMvw5NzLD+Ih9XrP/wrxTUcN+p6pazvoS+jB7w== + +"@shopify/jest-dom-mocks@2.8.2": + version "2.8.2" + resolved "https://registry.yarnpkg.com/@shopify/jest-dom-mocks/-/jest-dom-mocks-2.8.2.tgz#477c3159897807cc8d7797c33e8a79e787051779" + integrity sha512-4drt+S1cQ1ZSP1DaEHAj5XPPCiI2R8IIt+ZnH9h08Ngy8PjtjFFNHNcSJ6bKBmk7eO2c6+5UaJQzNcg56nt7gg== + dependencies: + "@shopify/async" "^2.0.7" + "@shopify/decorators" "^1.1.5" + "@types/fetch-mock" "^6.0.1" + "@types/lolex" "^2.1.3" + fetch-mock "^6.3.0" + lolex "^2.7.5" + promise "^8.0.3" + tslib "^1.9.3" + "@storybook/addon-actions@5.1.11": version "5.1.11" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.11.tgz#ebc299b9dfe476b5c65eb5d148c4b064f682ca08" @@ -1875,14 +1983,13 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" -"@testing-library/react-hooks@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-2.0.1.tgz#1c3ec40882d0830df3078ddae0056fdf7366c81d" - integrity sha512-MLTvWX7/csq/uQzP4WJntGz0QJDq6H4EzjV0VTL5YJE7KBZbaQ9DGT0IbtjuB33L4R4YKZ55rGZQ5eL+WiZtQA== +"@testing-library/react-hooks@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-2.0.3.tgz#305a6c76facb5fa1d185792b9eb11b1ca1b63fb7" + integrity sha512-adm+7b1gcysGka8VuYq/ObBrIBJTT9QmCEIqPpuxozWFfVDgxSbzBGc44ia/WYLGVt2dqFIOc6/DmAmu/pa0gQ== dependencies: "@babel/runtime" "^7.5.4" - "@types/react" ">=16.9.0" - "@types/react-test-renderer" ">=16.9.0" + "@types/testing-library__react-hooks" "^2.0.0" "@types/babel__core@^7.1.0": version "7.1.0" @@ -1922,6 +2029,11 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/fetch-mock@^6.0.1": + version "6.0.5" + resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-6.0.5.tgz#acbc6771d43d7ebc1f0a8b7e3d57147618f8eacb" + integrity sha512-rV8O2j/TIi0PtFCOlK55JnfKpE8Hm6PKFgrUZY/3FNHw4uBEMHnM+5ZickDO1duOyKxbpY3VES5T4NIwZXvodA== + "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -1963,6 +2075,11 @@ dependencies: "@types/jest-diff" "*" +"@types/lolex@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/lolex/-/lolex-2.1.3.tgz#793557c9b8ad319b4c8e4c6548b90893f4aa5f69" + integrity sha512-nEipOLYyZJ4RKHCg7tlR37ewFy91oggmip2MBzPdVQ8QhTFqjcRhE8R0t4tfpDnSlxGWHoEGJl0UCC4kYhqoiw== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -1988,7 +2105,7 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== -"@types/react-test-renderer@>=16.9.0": +"@types/react-test-renderer@*": version "16.9.0" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.0.tgz#d60f530ecf4c906721511603cca711b4fa830d41" integrity sha512-bN5EyjtuTY35xX7N5j0KP1vg5MpUXHpFTX6tGsqkNOthjNvet4VQOYRxFh+NT5cDSJrATmAFK9NLeYZ4mp/o0Q== @@ -2023,6 +2140,14 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/testing-library__react-hooks@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-2.0.0.tgz#7b289d64945517ae8ba9cbcb0c5b282432aaeffa" + integrity sha512-YUVqXGCChJKEJ4aAnMXqPCq0NfPAFVsJeGIb2y/iiMjxwyu+45+vR+AHOwjJHHKEHeC0ZhOGrZ5gSEmaJe4tyQ== + dependencies: + "@types/react" "*" + "@types/react-test-renderer" "*" + "@types/yargs-parser@*": version "13.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0" @@ -2560,7 +2685,7 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@^2.0.0, asap@~2.0.3: +asap@^2.0.0, asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -3020,6 +3145,15 @@ babel-plugin-transform-undefined-to-void@^6.9.4: resolved "https://registry.yarnpkg.com/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz#be241ca81404030678b748717322b89d0c8fe280" integrity sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA= +babel-polyfill@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" + integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= + dependencies: + babel-runtime "^6.26.0" + core-js "^2.5.0" + regenerator-runtime "^0.10.5" + babel-preset-jest@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" @@ -5347,6 +5481,15 @@ fbjs@^0.8.0, fbjs@^0.8.1: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +fetch-mock@^6.3.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-6.5.2.tgz#b3842b305c13ea0f81c85919cfaa7de387adfa3e" + integrity sha512-EIvbpCLBTYyDLu4HJiqD7wC8psDwTUaPaWXNKZbhNO/peUYKiNp5PkZGKRJtnTxaPQu71ivqafvjpM7aL+MofQ== + dependencies: + babel-polyfill "^6.26.0" + glob-to-regexp "^0.4.0" + path-to-regexp "^2.2.1" + figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -5554,10 +5697,10 @@ fork-ts-checker-webpack-plugin@1.1.1: tapable "^1.0.0" worker-rpc "^0.1.0" -fork-ts-checker-webpack-plugin@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.5.0.tgz#ce1d77190b44d81a761b10b6284a373795e41f0c" - integrity sha512-zEhg7Hz+KhZlBhILYpXy+Beu96gwvkROWJiTXOCyOOMMrdBIRPvsBpBqgTI4jfJGrJXcqGwJR8zsBGDmzY0jsA== +fork-ts-checker-webpack-plugin@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.5.1.tgz#f82d078ba8911c7b2c70703ffb3cbe588b33fbaa" + integrity sha512-IbVh1Z46dmCXJMg6We8s9jYwCAzzSv2Tgj+G2Sg/8pFantHDBrAg/rQyPnmAWLS/djW7n4VEltoEglbtTvt0wQ== dependencies: babel-code-frame "^6.22.0" chalk "^2.4.1" @@ -5848,6 +5991,11 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= +glob-to-regexp@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.4" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" @@ -6286,20 +6434,20 @@ humanize-url@^1.0.0: normalize-url "^1.0.0" strip-url-auth "^1.0.0" -husky@3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.5.tgz#d7db27c346645a8dc52df02aa534a377ad7925e0" - integrity sha512-cKd09Jy9cDyNIvAdN2QQAP/oA21sle4FWXjIMDttailpLAYZuBE7WaPmhrkj+afS8Sj9isghAtFvWSQ0JiwOHg== +husky@3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.9.tgz#a2c3e9829bfd6b4957509a9500d2eef5dbfc8044" + integrity sha512-Yolhupm7le2/MqC1VYLk/cNmYxsSsqKkTyBhzQHhPK1jFnC89mmmNVuGtLNabjDI6Aj8UNIr0KpRNuBkiC4+sg== dependencies: chalk "^2.4.2" + ci-info "^2.0.0" cosmiconfig "^5.2.1" execa "^1.0.0" get-stdin "^7.0.0" - is-ci "^2.0.0" opencollective-postinstall "^2.0.2" pkg-dir "^4.2.0" please-upgrade-node "^3.2.0" - read-pkg "^5.1.1" + read-pkg "^5.2.0" run-node "^1.0.0" slash "^3.0.0" @@ -7746,10 +7894,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@9.2.5: - version "9.2.5" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-9.2.5.tgz#5a3e1e0a539a403bd7f88542bc3d34ce52efdbb3" - integrity sha512-d99gTBFMJ29159+9iRvaMEQstmNcPAbQbhHSYw6D/1FncvFdIj8lWHztaq3Uq+tbZPABHXQ/fyN7Rp1QwF8HIw== +lint-staged@9.4.2: + version "9.4.2" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-9.4.2.tgz#14cb577a9512f520691f8b5aefce6a8f7ead6c04" + integrity sha512-OFyGokJSWTn2M6vngnlLXjaHhi8n83VIZZ5/1Z26SULRUWgR3ITWpAEQC9Pnm3MC/EpCxlwts/mQWDHNji2+zA== dependencies: chalk "^2.4.2" commander "^2.20.0" @@ -8003,6 +8151,11 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" +lolex@^2.7.5: + version "2.7.5" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.5.tgz#113001d56bfc7e02d56e36291cc5c413d1aa0733" + integrity sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -9419,6 +9572,16 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse-json@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" + integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + lines-and-columns "^1.1.6" + parse5@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" @@ -9479,6 +9642,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^2.2.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" + integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -9799,6 +9967,13 @@ promise@^7.1.1: dependencies: asap "~2.0.3" +promise@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.3.tgz#f592e099c6cddc000d538ee7283bb190452b0bf6" + integrity sha512-HeRDUL1RJiLhyA0/grn+PTShlBAcLuh/1BJGtrvjwbvRDCTLLMEz9rOGCV+R3vHY4MixIuoMEd9Yq/XvsTPcjw== + dependencies: + asap "~2.0.6" + prompts@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.4.tgz#179f9d4db3128b9933aa35f93a800d8fce76a682" @@ -10113,7 +10288,17 @@ react-docgen@^4.1.0: node-dir "^0.1.10" recast "^0.17.3" -react-dom@16.9.0, react-dom@^16.8.3: +react-dom@16.10.2: + version "16.10.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.10.2.tgz#4840bce5409176bc3a1f2bd8cb10b92db452fda6" + integrity sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.16.2" + +react-dom@^16.8.3: version "16.9.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.9.0.tgz#5e65527a5e26f22ae3701131bcccaee9fb0d3962" integrity sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ== @@ -10195,6 +10380,11 @@ react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.2.tgz#984120fd4d16800e9a738208ab1fba422d23b5ab" integrity sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA== +react-is@^16.8.6: + version "16.10.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.0.tgz#3d6a031e57fff73c3cfa0347feb3e8f40c5141e5" + integrity sha512-WRki2sBb7MTpYp7FtDEmSeGKX2vamYyq3rc9o7fKUG+/DHVyJu69NnvJsiSwwhh2Tt8XN40MQHkDBEXwyfxncQ== + react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -10263,15 +10453,15 @@ react-syntax-highlighter@^8.0.1: prismjs "^1.8.4" refractor "^2.4.1" -react-test-renderer@16.9.0: - version "16.9.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.9.0.tgz#7ed657a374af47af88f66f33a3ef99c9610c8ae9" - integrity sha512-R62stB73qZyhrJo7wmCW9jgl/07ai+YzvouvCXIJLBkRlRqLx4j9RqcLEAfNfU3OxTGucqR2Whmn3/Aad6L3hQ== +react-test-renderer@16.10.2: + version "16.10.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.10.2.tgz#4d8492f8678c9b43b721a7d79ed0840fdae7c518" + integrity sha512-k9Qzyev6cTIcIfrhgrFlYQAFxh5EEDO6ALNqYqmKsWVA7Q/rUMTay5nD3nthi6COmYsd4ghVYyi8U86aoeMqYQ== dependencies: object-assign "^4.1.1" prop-types "^15.6.2" - react-is "^16.9.0" - scheduler "^0.15.0" + react-is "^16.8.6" + scheduler "^0.16.2" react-textarea-autosize@^7.1.0: version "7.1.0" @@ -10296,7 +10486,16 @@ react-wait@^0.3.0: resolved "https://registry.yarnpkg.com/react-wait/-/react-wait-0.3.0.tgz#0cdd4d919012451a5bc3ab0a16d00c6fd9a8c10b" integrity sha512-kB5x/kMKWcn0uVr9gBdNz21/oGbQwEQnF3P9p6E9yLfJ9DRcKS0fagbgYMFI0YFOoyKDj+2q6Rwax0kTYJF37g== -react@16.9.0, react@^16.8.3: +react@16.10.2: + version "16.10.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.10.2.tgz#a5ede5cdd5c536f745173c8da47bda64797a4cf0" + integrity sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +react@^16.8.3: version "16.9.0" resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa" integrity sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w== @@ -10400,6 +10599,16 @@ read-pkg@^5.0.0, read-pkg@^5.1.1: parse-json "^4.0.0" type-fest "^0.4.1" +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + read@1, read@~1.0.1, read@~1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" @@ -10559,11 +10768,23 @@ regenerate-unicode-properties@^8.0.2, regenerate-unicode-properties@^8.1.0: dependencies: regenerate "^1.4.0" +regenerate-unicode-properties@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" + integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== + dependencies: + regenerate "^1.4.0" + regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== +regenerator-runtime@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg= + regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" @@ -10618,6 +10839,18 @@ regexpu-core@^4.5.4, regexpu-core@^4.6.0: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.1.0" +regexpu-core@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" + integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.1.0" + regjsgen "^0.5.0" + regjsparser "^0.6.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.1.0" + registry-auth-token@^3.0.1: version "3.3.2" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" @@ -10940,6 +11173,14 @@ scheduler@^0.15.0: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.16.2.tgz#f74cd9d33eff6fc554edfb79864868e4819132c1" + integrity sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -12119,10 +12360,10 @@ ts-easing@^0.2.0: resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== -ts-loader@6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.1.0.tgz#999cb0a7644f9c7c6c0901802dce50ceb0a76e5b" - integrity sha512-7JedeOu2rsYHQDEr2fwmMozABwbQTZXEaEMZPSIWG7gpzRefOLJCqwdazcegHtyaxp04PeEgs/b0m08WMpnIzQ== +ts-loader@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.0.tgz#52d3993ecbc5474c1513242388e1049da0fce880" + integrity sha512-Da8h3fD+HiZ9GvZJydqzk3mTC9nuOKYlJcpuk+Zv6Y1DPaMvBL+56GRzZFypx2cWrZFMsQr869+Ua2slGoLxvQ== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -12130,10 +12371,10 @@ ts-loader@6.1.0: micromatch "^4.0.0" semver "^6.0.0" -ts-node@8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.3.0.tgz#e4059618411371924a1fb5f3b125915f324efb57" - integrity sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ== +ts-node@8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.4.1.tgz#270b0dba16e8723c9fa4f9b4775d3810fd994b4f" + integrity sha512-5LpRN+mTiCs7lI5EtbXmF/HfMeCjzt7DH9CZwtkr6SywStrNQC723wG+aOWFiLNn7zT3kD/RnFqi3ZUfr4l5Qw== dependencies: arg "^4.1.0" diff "^4.0.1" @@ -12179,10 +12420,10 @@ tslint-plugin-prettier@2.0.1: lines-and-columns "^1.1.6" tslib "^1.7.1" -tslint-react@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-4.0.0.tgz#b4bb4c01c32448cb14d23f143a2f5e4989bb961e" - integrity sha512-9fNE0fm9zNDx1+b6hgy8rgDN2WsQLRiIrn3+fbqm0tazBVF6jiaCFAITxmU+WSFWYE03Xhp1joCircXOe1WVAQ== +tslint-react@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-4.1.0.tgz#7153b724a8cfbea52423d0ffa469e8eba3bcc834" + integrity sha512-Y7CbFn09X7Mpg6rc7t/WPbmjx9xPI8p1RsQyiGCLWgDR6sh3+IBSlT+bEkc0PSZcWwClOkqq2wPsID8Vep6szQ== dependencies: tsutils "^3.9.1" @@ -12258,6 +12499,11 @@ type-fest@^0.5.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw== +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -12949,3 +13195,4 @@ yn@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/yn/-/yn-3.0.0.tgz#0073c6b56e92aed652fbdfd62431f2d6b9a7a091" integrity sha512-+Wo/p5VRfxUgBUGy2j/6KX2mj9AYJWOHuhMjMcbBFc3y54o9/4buK1ksBvuiK01C3kby8DH9lSmJdSxw+4G/2Q== +