diff --git a/README.md b/README.md
index f54afa21d6..c93d6d123e 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)
@@ -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.
@@ -134,7 +135,6 @@
- [`useList`](./docs/useList.md) — tracks state of an array.
- [`useMap`](./docs/useMap.md) — tracks state of an object.
-
@@ -159,7 +159,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 0000000000..802ee1da6a
--- /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/package.json b/package.json
index 325195b19a..0c76004df2 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
"@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",
diff --git a/src/__stories__/useIntersection.story.tsx b/src/__stories__/useIntersection.story.tsx
new file mode 100644
index 0000000000..fb8c1226dc
--- /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/__tests__/useIntersection.test.tsx b/src/__tests__/useIntersection.test.tsx
new file mode 100644
index 0000000000..ee7342b2a7
--- /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/index.ts b/src/index.ts
index b3514a7179..b75e0a8d6a 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';
diff --git a/src/useIntersection.ts b/src/useIntersection.ts
new file mode 100644
index 0000000000..4a7b78eaca
--- /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/yarn.lock b/yarn.lock
index 4117ee5620..b92fcde8bb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1647,6 +1647,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"
@@ -2154,6 +2185,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"
@@ -2195,6 +2231,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"
@@ -2792,7 +2833,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=
@@ -3252,6 +3293,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"
@@ -5579,6 +5629,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"
@@ -6080,6 +6139,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"
@@ -8235,6 +8299,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"
@@ -9711,6 +9780,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"
@@ -10031,6 +10105,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"
@@ -10827,6 +10908,11 @@ regenerate@^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"
@@ -12441,6 +12527,11 @@ tslib@1.9.0, tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
integrity sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==
+tslib@^1.9.3:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
+ integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
+
tslint-config-prettier@1.18.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37"