From 2cbede8285df27d04543b5eed9756a41d577ab38 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Mon, 6 Apr 2020 08:45:38 +0200 Subject: [PATCH 01/14] feat: expose usePopper hook --- src/index.js | 3 +- src/usePopper.js | 107 +++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 23 ++-------- 3 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 src/usePopper.js diff --git a/src/index.js b/src/index.js index e8a3a34..0bb23ce 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,8 @@ import Popper from './Popper'; import Manager from './Manager'; import Reference from './Reference'; -export { Popper, Manager, Reference }; +import usePopper from './usePopper'; +export { Popper, Manager, Reference, usePopper }; // Public types import type { ManagerProps } from './Manager'; diff --git a/src/usePopper.js b/src/usePopper.js new file mode 100644 index 0000000..cb31c41 --- /dev/null +++ b/src/usePopper.js @@ -0,0 +1,107 @@ +// @flow +import { useLayoutEffect, useRef, useState, useMemo } from 'react'; +import { + createPopper as defaultCreatePopper, + type Options as PopperOptions, +} from '@popperjs/core'; + +type Options = $Shape<{ + ...PopperOptions, + createPopper: typeof defaultCreatePopper, +}>; + +type State = { + styles: { + [key: string]: $Shape, + }, + attributes: { + [key: string]: { [key: string]: string | boolean }, + }, +}; + +export const usePopper = ( + referenceElement: Element, + popperElement: HTMLElement, + options: Options = {} +) => { + const popperOptions = useMemo(() => { + // eslint-disable-next-line no-unused-vars + const { createPopper, ...popperOptions } = options; + return { + ...popperOptions, + placement: popperOptions.placement || 'bottom', + strategy: popperOptions.strategy || 'absolute', + }; + }, [options]); + + const popperInstanceRef = useRef(); + const [state, setState] = useState({ + styles: { + popper: { position: popperOptions.strategy, left: '0', top: '0' }, + }, + attributes: {}, + }); + const createPopper = useMemo( + () => options.createPopper || defaultCreatePopper, + [options.createPopper] + ); + + const updateStateModifier = useMemo( + () => ({ + name: 'updateState', + enabled: true, + phase: 'write', + fn: ({ state }) => { + const elements = Object.keys(state.elements); + + setState({ + styles: Object.fromEntries( + elements.map((element) => [element, state.styles[element] || {}]) + ), + attributes: Object.fromEntries( + elements.map((element) => [element, state.attributes[element]]) + ), + }); + }, + requires: ['computeStyles'], + }), + [setState] + ); + + useLayoutEffect(() => { + let popperInstance = null; + if (referenceElement != null && popperElement != null) { + popperInstance = createPopper(referenceElement, popperElement, { + ...popperOptions, + modifiers: [ + ...popperOptions.modifiers, + updateStateModifier, + { name: 'applyStyles', enabled: false }, + ], + }); + + popperInstanceRef.current = popperInstance; + } + + return () => { + popperInstance != null && popperInstance.destroy(); + }; + }, [referenceElement, popperElement]); + + useLayoutEffect(() => { + if (popperInstanceRef.current) { + popperInstanceRef.current.setOptions(popperOptions); + } + }, [popperOptions]); + + return { + styles: state.styles, + attributes: state.attributes, + update: popperInstanceRef.current ? popperInstanceRef.current.update : null, + forceUpdate: popperInstanceRef.current + ? popperInstanceRef.current.forceUpdate + : null, + }; +}; + +export default usePopper; diff --git a/yarn.lock b/yarn.lock index 5db0e18..735debb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1270,25 +1270,10 @@ "@parcel/utils" "^1.11.0" physical-cpu-count "^2.0.0" -"@popperjs/core@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.1.1.tgz#12c572ab88ef7345b43f21883fca26631c223085" - integrity sha512-sLqWxCzC5/QHLhziXSCAksBxHfOnQlhPRVgPK0egEw+ktWvG75T2k+aYWVjVh9+WKeT3tlG3ZNbZQvZLmfuOIw== - -"@rollup/plugin-replace@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.1.tgz#16fb0563628f9e6c6ef9e05d48d3608916d466f5" - integrity sha512-qDcXj2VOa5+j0iudjb+LiwZHvBRRgWbHPhRmo1qde2KItTjuxDVQO21rp9/jOlzKR5YO0EsgRQoyox7fnL7y/A== - dependencies: - "@rollup/pluginutils" "^3.0.4" - magic-string "^0.25.5" - -"@rollup/pluginutils@^3.0.4": - version "3.0.8" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.8.tgz#4e94d128d94b90699e517ef045422960d18c8fde" - integrity sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw== - dependencies: - estree-walker "^1.0.1" +"@popperjs/core@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.2.1.tgz#d7d1d7fbdc1f2aa24e62f4ef4b001be7727340c5" + integrity sha512-BChdj3idQiLi+7vPhE6gEDiPzpozvSrUqbSMoSTlRbOQkU0p6u4si0UBydegTyphsYSZC2AUHGYYICP0gqmEVg== "@sinonjs/commons@^1.7.0": version "1.7.1" From 9c376e2e618ce1cbbcc7aaad19e30f9f4f54498f Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Tue, 7 Apr 2020 16:11:59 +0200 Subject: [PATCH 02/14] docs: fix demo warnings --- demo/index.js | 10 +++++----- demo/styles.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/demo/index.js b/demo/index.js index ba9ebf1..143f2ae 100644 --- a/demo/index.js +++ b/demo/index.js @@ -117,7 +117,7 @@ const Demo = () => { {({ ref }) => ( - + { {({ ref, style, placement, arrowProps }) => ( - + {placement} @@ -146,7 +146,7 @@ const Demo = () => { {({ ref, style }) => ( setActivePlacement(p)} title={p} @@ -163,7 +163,7 @@ const Demo = () => { {({ ref }) => ( togglePopper2(!isPopper2Open)} > Click to toggle diff --git a/demo/styles.js b/demo/styles.js index ab2a0b0..a1991c5 100644 --- a/demo/styles.js +++ b/demo/styles.js @@ -20,10 +20,10 @@ export const Main = styled('main')` background-image: ${(props) => gradients[props.gradient]}; color: #ffffff; clip-path: polygon(99% 1%, 99% 95%, 50% 99%, 1% 95%, 1% 1%, 50% 5%); - &:first-child { + &:first-of-type { clip-path: polygon(99% 2%, 99% 97%, 50% 100%, 1% 97%, 1% 2%); } - &:last-child { + &:last-of-type { height: calc(100vh - 0.5em); clip-path: polygon(99% 0%, 99% 98%, 50% 98%, 1% 98%, 1% 0%, 50% 3%); } From 2ca07eb5777a34278ec700f49b367bc2346bc887 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Tue, 7 Apr 2020 16:12:21 +0200 Subject: [PATCH 03/14] test: InnerPopper => Popper --- src/Manager.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Manager.test.js b/src/Manager.test.js index ccb69ef..8d90af2 100644 --- a/src/Manager.test.js +++ b/src/Manager.test.js @@ -5,8 +5,6 @@ import { mount } from 'enzyme'; // Public API import { Manager, Popper, Reference } from '.'; -import { InnerPopper } from './Popper'; - // Private API import { ManagerReferenceNodeContext, ManagerReferenceNodeSetterContext } from './Manager'; @@ -57,7 +55,7 @@ describe('Managed Reference', () => { {() => null} ); - const PopperInstance = wrapper.find(InnerPopper); + const PopperInstance = wrapper.find(Popper); expect(PopperInstance.prop('referenceElement')).toBe(element); }); it('If the referenceElement prop is undefined, use the referenceNode from context', () => { @@ -78,7 +76,7 @@ describe('Managed Reference', () => { {() => null} ); - const PopperInstance = wrapper.find(InnerPopper); + const PopperInstance = wrapper.find(Popper); expect(PopperInstance.prop('referenceElement')).toBe(referenceElement); }); From df725a28a4e7b3b771b03a18fe97772a0a8c4523 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Tue, 7 Apr 2020 16:12:45 +0200 Subject: [PATCH 04/14] fix: usePopper fixes --- src/usePopper.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/usePopper.js b/src/usePopper.js index cb31c41..de4545a 100644 --- a/src/usePopper.js +++ b/src/usePopper.js @@ -3,6 +3,7 @@ import { useLayoutEffect, useRef, useState, useMemo } from 'react'; import { createPopper as defaultCreatePopper, type Options as PopperOptions, + type VirtualElement, } from '@popperjs/core'; type Options = $Shape<{ @@ -20,8 +21,8 @@ type State = { }; export const usePopper = ( - referenceElement: Element, - popperElement: HTMLElement, + referenceElement: ?(Element | VirtualElement), + popperElement: ?HTMLElement, options: Options = {} ) => { const popperOptions = useMemo(() => { @@ -95,6 +96,7 @@ export const usePopper = ( }, [popperOptions]); return { + state: popperInstanceRef.current ? popperInstanceRef.current.state : null, styles: state.styles, attributes: state.attributes, update: popperInstanceRef.current ? popperInstanceRef.current.update : null, From 21dc44b6869ceb177580f8e7b3f5b164ebdedbdc Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Tue, 7 Apr 2020 16:13:10 +0200 Subject: [PATCH 05/14] feat: attempt to rewrite render-props implementation with hooks --- src/Popper.js | 278 +++++++++------------------------------------ src/Popper.test.js | 94 ++++++++------- src/Reference.js | 49 +++----- src/usePopper.js | 51 +++++---- 4 files changed, 145 insertions(+), 327 deletions(-) diff --git a/src/Popper.js b/src/Popper.js index 5b59b63..651c425 100644 --- a/src/Popper.js +++ b/src/Popper.js @@ -1,19 +1,16 @@ // @flow -import deepEqual from 'deep-equal'; import * as React from 'react'; -import { createPopper } from '@popperjs/core'; import { type State, type Placement, type PositioningStrategy, - type Instance, type VirtualElement, type Modifier, - type ModifierArguments, } from '@popperjs/core/lib'; import { ManagerReferenceNodeContext } from './Manager'; -import { unwrapArray, setRef, shallowEqual } from './utils'; -import { type Ref } from './RefTypes'; +import type { Ref } from './RefTypes'; +import { unwrapArray } from './utils'; +import usePopper from './usePopper'; type ReferenceElement = ?(VirtualElement | HTMLElement); type Modifiers = Array<$Shape>>; @@ -31,7 +28,7 @@ export type PopperChildrenProps = {| hasPopperEscaped: ?boolean, update: () => Promise>, - forceUpdate: () => $Shape, + forceUpdate: () => void, arrowProps: PopperArrowProps, |}; export type PopperChildren = (PopperChildrenProps) => React.Node; @@ -46,236 +43,65 @@ export type PopperProps = {| onFirstUpdate?: ($Shape) => void, |}; -type PopperState = {| - placement: ?Placement, - styles: ?{ - popper: CSSStyleDeclaration, - arrow: CSSStyleDeclaration, - }, - isReferenceHidden: ?boolean, - hasPopperEscaped: ?boolean, -|}; - -const initialPopperStyle = { - top: 0, - left: 0, - opacity: 0, - pointerEvents: 'none', -}; - -const initialArrowStyle = {}; - -export class InnerPopper extends React.Component { - static defaultProps = { - placement: 'bottom', - strategy: 'absolute', - modifiers: [], - referenceElement: undefined, - }; - - state: PopperState = { - placement: undefined, - styles: undefined, - isReferenceHidden: undefined, - hasPopperEscaped: undefined, - }; - - popperInstance: ?Instance; - - popperNode: ?HTMLElement = null; - arrowNode: ?HTMLElement = null; - - setPopperNode = (popperNode: ?HTMLElement) => { - if (!popperNode || this.popperNode === popperNode) return; +const NOOP = () => void 0; +const NOOP_PROMISE = () => Promise.resolve(null); - setRef(this.props.innerRef, popperNode); - this.popperNode = popperNode; +export default function Popper({ + placement = 'bottom', + strategy = 'absolute', + modifiers = [], + referenceElement, + onFirstUpdate, + children, +}: PopperProps) { + const referenceNode = React.useContext(ManagerReferenceNodeContext); - this.updatePopperInstance(); - }; + const [popperElement, setPopperElement] = React.useState(null); + const [arrowElement, setArrowElement] = React.useState(null); - setArrowNode = (arrowNode: ?HTMLElement) => { - this.arrowNode = arrowNode; - }; - - updateStateModifier: Modifier<{||}> = { - name: 'reactPopperState', - enabled: true, - phase: 'write', - fn: ({ state }: ModifierArguments<{||}>) => { - const { placement, styles, modifiersData } = state; - let isReferenceHidden: boolean; - let hasPopperEscaped: boolean; - - if (modifiersData.hide) { - isReferenceHidden = modifiersData.hide.isReferenceHidden; - hasPopperEscaped = modifiersData.hide.hasPopperEscaped; - } - - this.setState({ - placement, - styles, - isReferenceHidden, - hasPopperEscaped, - }); - }, - }; - - getOptions = () => { - const { modifiers = [] } = this.props; - const arrowModifier = modifiers.find( - (modifier) => modifier.name === 'arrow' - ); - - return { - placement: this.props.placement, - strategy: this.props.strategy, + const options = React.useMemo( + () => ({ + placement, + strategy, + onFirstUpdate, modifiers: [ - ...modifiers.filter((modifier) => modifier.name !== 'arrow'), + ...modifiers, { name: 'arrow', - enabled: !!this.arrowNode, - options: { - ...(arrowModifier && arrowModifier.options), - element: this.arrowNode, - }, - }, - { - name: 'applyStyles', - enabled: false, + enabled: arrowElement != null, + options: { element: arrowElement }, }, - this.updateStateModifier, ], - onFirstUpdate: this.props.onFirstUpdate, - }; - }; - - getPopperStyle = () => { - const computedInitialStyle = { - ...initialPopperStyle, - position: this.props.strategy === 'fixed' ? 'fixed' : 'absolute', - }; - - return !this.popperNode || !this.state.styles - ? computedInitialStyle - : this.state.styles.popper; - }; - - getArrowStyle = () => - !this.arrowNode || !this.state.styles - ? initialArrowStyle - : this.state.styles.arrow; - - destroyPopperInstance = () => { - if (!this.popperInstance) return; - - this.popperInstance.destroy(); - this.popperInstance = null; - }; - - updatePopperInstance = () => { - this.destroyPopperInstance(); - - const { popperNode } = this; - const { referenceElement } = this.props; - - if (!referenceElement || !popperNode) return; - - this.popperInstance = createPopper( - referenceElement, - popperNode, - this.getOptions() - ); - }; - - update = (): Promise> => { - if (this.popperInstance) { - return this.popperInstance.update(); - } else { - return Promise.resolve(null); - } - }; - - componentDidUpdate(prevProps: PopperProps, prevState: PopperState) { - // If the Popper.js reference element has changed, update the instance (destroy + create) - if (this.props.referenceElement !== prevProps.referenceElement) { - this.updatePopperInstance(); - } - - // If the Popper.js options have changed, set options - if ( - this.props.placement !== prevProps.placement || - this.props.strategy !== prevProps.strategy || - !deepEqual(this.props.modifiers, prevProps.modifiers, { strict: true }) - ) { - // develop only check that modifiers isn't being updated needlessly - if (process.env.NODE_ENV === 'development') { - if ( - this.props.modifiers !== prevProps.modifiers && - this.props.modifiers != null && - prevProps.modifiers != null && - shallowEqual(this.props.modifiers, prevProps.modifiers) - ) { - console.warn( - "'modifiers' prop reference updated even though all values appear the same.\nConsider memoizing the 'modifiers' object to avoid needless rendering." - ); - } - } - - if (this.popperInstance) { - this.popperInstance.setOptions(this.getOptions()); - } - } - - // A placement difference in state means popper determined a new placement - // apart from the props value. By the time the popper element is rendered with - // the new position Popper has already measured it, if the place change triggers - // a size change it will result in a misaligned popper. So we schedule an update to be sure. - if (prevState.placement !== this.state.placement) { - this.update(); - } - } - - componentWillUnmount() { - setRef(this.props.innerRef, null); - this.destroyPopperInstance(); - } + }), + [placement, strategy, onFirstUpdate, modifiers, arrowElement] + ); - render() { - const { placement, isReferenceHidden, hasPopperEscaped } = this.state; + const { state, styles, forceUpdate, update } = usePopper( + referenceElement || referenceNode, + popperElement, + options + ); - return unwrapArray(this.props.children)({ - ref: this.setPopperNode, - style: this.getPopperStyle(), - placement, - isReferenceHidden, - hasPopperEscaped, - update: this.update, - forceUpdate: this.popperInstance ? this.popperInstance.forceUpdate : null, + const childrenProps = React.useMemo( + () => ({ + ref: setPopperElement, + style: styles.popper, + placement: state ? state.placement : placement, + hasPopperEscaped: state + ? state.modifiersData.hide.hasPopperEscaped + : null, + isReferenceHidden: state + ? state.modifiersData.hide.isReferenceHidden + : null, arrowProps: { - ref: this.setArrowNode, - style: this.getArrowStyle(), + style: styles.arrow, + ref: setArrowElement, }, - }); - } -} - -export default function Popper({ referenceElement, ...props }: PopperProps) { - return ( - - {(referenceNode) => ( - - )} - + forceUpdate: forceUpdate || NOOP, + update: update || NOOP_PROMISE, + }), + [setPopperElement] ); + + return unwrapArray(children)(childrenProps); } diff --git a/src/Popper.test.js b/src/Popper.test.js index de4768f..4572945 100644 --- a/src/Popper.test.js +++ b/src/Popper.test.js @@ -3,19 +3,20 @@ import React from 'react'; import { mount } from 'enzyme'; // Private API -import { InnerPopper } from './Popper'; - -const mountPopper = props => new Promise(resolve => { - const wrapper = mount( - resolve(wrapper.update())} {...props}> - {({ref, style, placement, arrowProps}) => ( -
-
-
- )} - - ) -}); +import Popper from './Popper'; + +const mountPopper = (props) => + new Promise((resolve) => { + const wrapper = mount( + resolve(wrapper.update())} {...props}> + {({ ref, style, placement, arrowProps }) => ( +
+
+
+ )} + + ); + }); describe('Popper component', () => { it('renders the expected markup', async () => { @@ -24,7 +25,7 @@ describe('Popper component', () => { expect(wrapper).toMatchSnapshot(); }); - it('initializes the Popper.js instance on first update', async () => { + it.skip('initializes the Popper.js instance on first update', async () => { const referenceElement = document.createElement('div'); const wrapper = await mountPopper({ referenceElement }); expect(wrapper.instance().popperInstance).toBeDefined(); @@ -32,7 +33,10 @@ describe('Popper component', () => { it("doesn't update Popper.js instance on props update if not needed by Popper.js", async () => { const referenceElement = document.createElement('div'); - const wrapper = await mountPopper({ referenceElement, placement: 'bottom' }); + const wrapper = await mountPopper({ + referenceElement, + placement: 'bottom', + }); const instance = wrapper.instance().popperInstance; expect(instance).toBeDefined(); @@ -42,7 +46,7 @@ describe('Popper component', () => { expect(wrapper.instance().popperInstance).toBe(instance); }); - it('updates Popper.js on explicitly listed props change', async () => { + it.skip('updates Popper.js on explicitly listed props change', async () => { const referenceElement = document.createElement('div'); const wrapper = await mountPopper({ referenceElement }); const instance = wrapper.instance().popperInstance; @@ -54,7 +58,7 @@ describe('Popper component', () => { expect(wrapper.instance().popperInstance.state.placement).toBe('top'); }); - it('does not update Popper.js on generic props change', async () => { + it.skip('does not update Popper.js on generic props change', async () => { const referenceElement = document.createElement('div'); const wrapper = await mountPopper({ referenceElement }); const instance = wrapper.instance().popperInstance; @@ -63,7 +67,7 @@ describe('Popper component', () => { expect(wrapper.instance().popperInstance).toBe(instance); }); - it('destroys Popper.js instance on unmount', async () => { + it.skip('destroys Popper.js instance on unmount', async () => { const referenceElement = document.createElement('div'); const wrapper = await mountPopper({ referenceElement }); const component = wrapper.instance(); @@ -75,17 +79,17 @@ describe('Popper component', () => { const referenceElement = document.createElement('div'); expect(() => mount( - + {({ ref, style, placement, arrowProps }) => (
ref(current)} + ref={(current) => ref(current)} style={style} data-placement={placement} > -
arrowProps.ref(current)} /> +
arrowProps.ref(current)} />
)} - + ) ).not.toThrow(); }); @@ -94,16 +98,12 @@ describe('Popper component', () => { const myRef = jest.fn(); const referenceElement = document.createElement('div'); mount( - - {({ ref, style, placement}) => ( -
+ + {({ ref, style, placement }) => ( +
)} - - ) + + ); expect(myRef).toBeCalled(); }); @@ -111,16 +111,12 @@ describe('Popper component', () => { const myRef = React.createRef(); const referenceElement = document.createElement('div'); mount( - - {({ ref, style, placement}) => ( -
+ + {({ ref, style, placement }) => ( +
)} - - ) + + ); expect(myRef.current).toBeDefined(); }); @@ -135,9 +131,11 @@ describe('Popper component', () => { width: 90, height: 10, }; - } + }, }; - const wrapper = await mountPopper({ referenceElement: virtualReferenceElement }); + const wrapper = await mountPopper({ + referenceElement: virtualReferenceElement, + }); expect(wrapper.instance().popperInstance.state.elements.reference).toBe( virtualReferenceElement @@ -147,19 +145,19 @@ describe('Popper component', () => { it(`should render 3 times when placement is changed`, async () => { const referenceElement = document.createElement('div'); let renderCounter = 0; - const wrapper = await new Promise(resolve => { + const wrapper = await new Promise((resolve) => { const wrapper = mount( - resolve(wrapper.update())} > - {({ref, style, placement}) => { + {({ ref, style, placement }) => { renderCounter++; - return
; + return
; }} - - ) + + ); }); expect(renderCounter).toBe(3); diff --git a/src/Reference.js b/src/Reference.js index f16279d..0c960fa 100644 --- a/src/Reference.js +++ b/src/Reference.js @@ -3,49 +3,34 @@ import * as React from 'react'; import warning from 'warning'; import { ManagerReferenceNodeSetterContext } from './Manager'; import { safeInvoke, unwrapArray, setRef } from './utils'; -import { type Ref } from "./RefTypes"; +import { type Ref } from './RefTypes'; export type ReferenceChildrenProps = { ref: Ref }; export type ReferenceProps = {| - children: ReferenceChildrenProps => React.Node, + children: (ReferenceChildrenProps) => React.Node, innerRef?: Ref, |}; -type InnerReferenceProps = { - children: ReferenceChildrenProps => React.Node, - innerRef?: Ref, - setReferenceNode?: (?HTMLElement) => void, -}; +export default function Reference({ children, innerRef }: ReferenceProps) { + const setReferenceNode = React.useContext(ManagerReferenceNodeSetterContext); -class InnerReference extends React.Component { - refHandler = (node: ?HTMLElement) => { - setRef(this.props.innerRef, node) - safeInvoke(this.props.setReferenceNode, node); - }; + const refHandler = React.useCallback( + (node: ?HTMLElement) => { + setRef(innerRef, node); + safeInvoke(setReferenceNode, node); + }, + [innerRef, setReferenceNode] + ); - componentWillUnmount() { - setRef(this.props.innerRef, null) - } + // ran on unmount + React.useEffect(() => () => setRef(innerRef, null)); - render() { + React.useEffect(() => { warning( - Boolean(this.props.setReferenceNode), + Boolean(setReferenceNode), '`Reference` should not be used outside of a `Manager` component.' ); - return unwrapArray(this.props.children)({ ref: this.refHandler }); - } -} + }, [setReferenceNode]); -export default function Reference(props: ReferenceProps) { - return ( - - {(setReferenceNode) => ( - - )} - - ); + return unwrapArray(children)({ ref: refHandler }); } diff --git a/src/usePopper.js b/src/usePopper.js index de4545a..0a7d1a7 100644 --- a/src/usePopper.js +++ b/src/usePopper.js @@ -1,5 +1,5 @@ // @flow -import { useLayoutEffect, useRef, useState, useMemo } from 'react'; +import * as React from 'react'; import { createPopper as defaultCreatePopper, type Options as PopperOptions, @@ -25,29 +25,14 @@ export const usePopper = ( popperElement: ?HTMLElement, options: Options = {} ) => { - const popperOptions = useMemo(() => { - // eslint-disable-next-line no-unused-vars - const { createPopper, ...popperOptions } = options; - return { - ...popperOptions, - placement: popperOptions.placement || 'bottom', - strategy: popperOptions.strategy || 'absolute', - }; - }, [options]); - - const popperInstanceRef = useRef(); - const [state, setState] = useState({ + const [state, setState] = React.useState({ styles: { - popper: { position: popperOptions.strategy, left: '0', top: '0' }, + popper: { position: options.strategy, left: '0', top: '0' }, }, attributes: {}, }); - const createPopper = useMemo( - () => options.createPopper || defaultCreatePopper, - [options.createPopper] - ); - const updateStateModifier = useMemo( + const updateStateModifier = React.useMemo( () => ({ name: 'updateState', enabled: true, @@ -69,7 +54,28 @@ export const usePopper = ( [setState] ); - useLayoutEffect(() => { + const popperOptions = React.useMemo(() => { + // eslint-disable-next-line no-unused-vars + const { createPopper, ...popperOptions } = options; + return { + ...popperOptions, + placement: popperOptions.placement || 'bottom', + strategy: popperOptions.strategy || 'absolute', + modifiers: [ + ...popperOptions.modifiers, + updateStateModifier, + { name: 'applyStyles', enabled: false }, + ], + }; + }, [options]); + + const popperInstanceRef = React.useRef(); + const createPopper = React.useMemo( + () => options.createPopper || defaultCreatePopper, + [options.createPopper] + ); + + React.useLayoutEffect(() => { let popperInstance = null; if (referenceElement != null && popperElement != null) { popperInstance = createPopper(referenceElement, popperElement, { @@ -86,15 +92,18 @@ export const usePopper = ( return () => { popperInstance != null && popperInstance.destroy(); + popperInstanceRef.current = null; }; }, [referenceElement, popperElement]); - useLayoutEffect(() => { + React.useLayoutEffect(() => { if (popperInstanceRef.current) { popperInstanceRef.current.setOptions(popperOptions); } }, [popperOptions]); + console.log(state); + return { state: popperInstanceRef.current ? popperInstanceRef.current.state : null, styles: state.styles, From 6f2ac9b7d6af95f55b5457b78e231f71de732a2c Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 8 Apr 2020 11:18:33 +0200 Subject: [PATCH 06/14] refactor: migrate Manager to hooks --- src/Manager.js | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/Manager.js b/src/Manager.js index 9645da1..d35d37e 100644 --- a/src/Manager.js +++ b/src/Manager.js @@ -1,38 +1,30 @@ // @flow import * as React from 'react'; -export const ManagerReferenceNodeContext = React.createContext(); +export const ManagerReferenceNodeContext = React.createContext(); export const ManagerReferenceNodeSetterContext = React.createContext< - void | ((?HTMLElement) => void) + void | ((?Element) => void) >(); export type ManagerProps = { children: React.Node, }; -export default class Manager extends React.Component { - referenceNode: ?HTMLElement; +export default function Manager({ children }: ManagerProps) { + const [referenceNode, setReferenceNode] = React.useState(null); - setReferenceNode = (newReferenceNode: ?HTMLElement) => { - if (newReferenceNode && this.referenceNode !== newReferenceNode) { - this.referenceNode = newReferenceNode; - this.forceUpdate(); - } - }; + React.useEffect( + () => () => { + setReferenceNode(null); + }, + [] + ); - componentWillUnmount() { - this.referenceNode = null; - } - - render() { - return ( - - - {this.props.children} - - - ); - } + return ( + + + {children} + + + ); } From d63f77db8046ec8171d0c708e732622fcbb22dbc Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 8 Apr 2020 11:31:40 +0200 Subject: [PATCH 07/14] fix: remove @babel/runtime dependency --- package.json | 3 -- src/usePopper.js | 29 +++++++------- src/utils.js | 19 --------- yarn.lock | 100 +++++------------------------------------------ 4 files changed, 24 insertions(+), 127 deletions(-) diff --git a/package.json b/package.json index 00ab11c..34fe2b6 100644 --- a/package.json +++ b/package.json @@ -65,9 +65,6 @@ "react": "^16.8.0" }, "dependencies": { - "@babel/runtime": "^7.9.2", - "deep-equal": "^2.0.1", - "prop-types": "^15.6.1", "warning": "^4.0.2" }, "devDependencies": { diff --git a/src/usePopper.js b/src/usePopper.js index 0a7d1a7..441b488 100644 --- a/src/usePopper.js +++ b/src/usePopper.js @@ -55,14 +55,12 @@ export const usePopper = ( ); const popperOptions = React.useMemo(() => { - // eslint-disable-next-line no-unused-vars - const { createPopper, ...popperOptions } = options; return { - ...popperOptions, - placement: popperOptions.placement || 'bottom', - strategy: popperOptions.strategy || 'absolute', + onFirstUpdate: options.onFirstUpdate, + placement: options.placement || 'bottom', + strategy: options.strategy || 'absolute', modifiers: [ - ...popperOptions.modifiers, + ...options.modifiers, updateStateModifier, { name: 'applyStyles', enabled: false }, ], @@ -78,14 +76,17 @@ export const usePopper = ( React.useLayoutEffect(() => { let popperInstance = null; if (referenceElement != null && popperElement != null) { - popperInstance = createPopper(referenceElement, popperElement, { - ...popperOptions, - modifiers: [ - ...popperOptions.modifiers, - updateStateModifier, - { name: 'applyStyles', enabled: false }, - ], - }); + popperInstance = createPopper( + referenceElement, + popperElement, + Object.assign({}, popperOptions, { + modifiers: [ + ...popperOptions.modifiers, + updateStateModifier, + { name: 'applyStyles', enabled: false }, + ], + }) + ); popperInstanceRef.current = popperInstance; } diff --git a/src/utils.js b/src/utils.js index 67f9b28..736dd1e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -18,25 +18,6 @@ export const safeInvoke = (fn: ?Function, ...args: *) => { } } -/** - * Does a shallow equality check of two objects by comparing the reference - * equality of each value. - */ -export const shallowEqual = (objA: any[], objB: any[]): boolean => { - - if (objA.length !== objB.length) { - return false; - } - - for (var i = 0; i < objB.length; i++) { - if (objA[i] !== objB[i]) { - return false; - } - } - - return true; -} - /** * Sets a ref using either a ref callback or a ref object */ diff --git a/yarn.lock b/yarn.lock index 735debb..350f153 100644 --- a/yarn.lock +++ b/yarn.lock @@ -884,7 +884,7 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== @@ -2812,24 +2812,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.1.tgz#fc12bbd6850e93212f21344748682ccc5a8813cf" - integrity sha512-7Et6r6XfNW61CPPCIYfm1YPGSmh6+CliYeL4km7GWJcpX5LTAflGF8drLLR+MZX+2P3NZfAfSduutBbSWqER4g== - dependencies: - es-abstract "^1.16.3" - es-get-iterator "^1.0.1" - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - isarray "^2.0.5" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - side-channel "^1.0.1" - which-boxed-primitive "^1.0.1" - which-collection "^1.0.0" - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -3180,7 +3162,7 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.16.3, es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5: +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5: version "1.17.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== @@ -3197,19 +3179,6 @@ es-abstract@^1.16.3, es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstrac string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" -es-get-iterator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== - dependencies: - es-abstract "^1.17.4" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" - is-string "^1.0.5" - isarray "^2.0.5" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -4346,11 +4315,6 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -4361,11 +4325,6 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== - is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -4380,7 +4339,7 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.0, is-boolean-object@^1.0.1: +is-boolean-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== @@ -4509,17 +4468,12 @@ is-html@^1.1.0: dependencies: html-tags "^1.0.0" -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== - is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= -is-number-object@^1.0.3, is-number-object@^1.0.4: +is-number-object@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== @@ -4577,11 +4531,6 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== - is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -4592,7 +4541,7 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== -is-string@^1.0.4, is-string@^1.0.5: +is-string@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== @@ -4626,16 +4575,6 @@ is-url@^1.2.2: resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" - integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -4656,7 +4595,7 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@^2.0.1, isarray@^2.0.5: +isarray@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== @@ -6732,7 +6671,7 @@ prop-types-exact@^1.2.0: object.assign "^4.1.0" reflect.ownkeys "^0.2.0" -prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7013,7 +6952,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: +regexp.prototype.flags@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== @@ -7483,7 +7422,7 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -side-channel@^1.0.1, side-channel@^1.0.2: +side-channel@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947" integrity sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA== @@ -8468,27 +8407,6 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== - dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" - -which-collection@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" From 7c985160275aff78809397b23e4a7accf14d23c8 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 8 Apr 2020 11:47:47 +0200 Subject: [PATCH 08/14] fix: useMemo dependencies --- .eslintrc.js | 3 ++- package.json | 1 + src/Popper.js | 24 +++++++++++++++++------- src/usePopper.js | 15 ++++----------- yarn.lock | 5 +++++ 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3477875..736138f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,7 +4,8 @@ module.exports = { env: { browser: true, }, - plugins: ['react'], + extends: ['plugin:react-hooks/recommended'], + plugins: ['react', 'react-hooks'], rules: { 'no-unused-vars': 'error', 'react/jsx-uses-vars': 'error', diff --git a/package.json b/package.json index 34fe2b6..a932bb5 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "eslint-plugin-jest": "^23.8.2", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.19.0", + "eslint-plugin-react-hooks": "^3.0.0", "flow-bin": "^0.121.0", "flow-copy-source": "^2.0.9", "gh-pages": "^2.2.0", diff --git a/src/Popper.js b/src/Popper.js index 651c425..d80c4c7 100644 --- a/src/Popper.js +++ b/src/Popper.js @@ -87,12 +87,14 @@ export default function Popper({ ref: setPopperElement, style: styles.popper, placement: state ? state.placement : placement, - hasPopperEscaped: state - ? state.modifiersData.hide.hasPopperEscaped - : null, - isReferenceHidden: state - ? state.modifiersData.hide.isReferenceHidden - : null, + hasPopperEscaped: + state && state.modifiersData.hide + ? state.modifiersData.hide.hasPopperEscaped + : null, + isReferenceHidden: + state && state.modifiersData.hide + ? state.modifiersData.hide.isReferenceHidden + : null, arrowProps: { style: styles.arrow, ref: setArrowElement, @@ -100,7 +102,15 @@ export default function Popper({ forceUpdate: forceUpdate || NOOP, update: update || NOOP_PROMISE, }), - [setPopperElement] + [ + setPopperElement, + setArrowElement, + placement, + state, + styles, + update, + forceUpdate, + ] ); return unwrapArray(children)(childrenProps); diff --git a/src/usePopper.js b/src/usePopper.js index 441b488..1e0eb8d 100644 --- a/src/usePopper.js +++ b/src/usePopper.js @@ -65,7 +65,7 @@ export const usePopper = ( { name: 'applyStyles', enabled: false }, ], }; - }, [options]); + }, [options, updateStateModifier]); const popperInstanceRef = React.useRef(); const createPopper = React.useMemo( @@ -79,13 +79,7 @@ export const usePopper = ( popperInstance = createPopper( referenceElement, popperElement, - Object.assign({}, popperOptions, { - modifiers: [ - ...popperOptions.modifiers, - updateStateModifier, - { name: 'applyStyles', enabled: false }, - ], - }) + popperOptions ); popperInstanceRef.current = popperInstance; @@ -95,7 +89,8 @@ export const usePopper = ( popperInstance != null && popperInstance.destroy(); popperInstanceRef.current = null; }; - }, [referenceElement, popperElement]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [referenceElement, popperElement, createPopper]); React.useLayoutEffect(() => { if (popperInstanceRef.current) { @@ -103,8 +98,6 @@ export const usePopper = ( } }, [popperOptions]); - console.log(state); - return { state: popperInstanceRef.current ? popperInstanceRef.current.state : null, styles: state.styles, diff --git a/yarn.lock b/yarn.lock index 350f153..ac93cf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3248,6 +3248,11 @@ eslint-plugin-promise@^4.2.1: resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== +eslint-plugin-react-hooks@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-3.0.0.tgz#9e80c71846eb68dd29c3b21d832728aa66e5bd35" + integrity sha512-EjxTHxjLKIBWFgDJdhKKzLh5q+vjTFrqNZX36uIxWS4OfyXe5DawqPj3U5qeJ1ngLwatjzQnmR0Lz0J0YH3kxw== + eslint-plugin-react@^7.19.0: version "7.19.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" From e3da5e00eb02137798aad8e5d8ac39a3ec54ae88 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 8 Apr 2020 11:51:15 +0200 Subject: [PATCH 09/14] chore: add bundleSize rollup plugin --- package.json | 1 + rollup.config.js | 4 +++- yarn.lock | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a932bb5..8adbd40 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "warning": "^4.0.2" }, "devDependencies": { + "@atomico/rollup-plugin-sizes": "^1.1.3", "@babel/cli": "^7.8.4", "@babel/core": "^7.9.0", "@babel/plugin-external-helpers": "^7.8.3", diff --git a/rollup.config.js b/rollup.config.js index 3fcb5b7..7586f10 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -3,6 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'; import babel from 'rollup-plugin-babel'; import replace from 'rollup-plugin-replace'; import { uglify } from 'rollup-plugin-uglify'; +import bundleSize from '@atomico/rollup-plugin-sizes'; const input = './src/index.js'; @@ -30,6 +31,7 @@ export default [ commonjs({ include: '**/node_modules/**' }), babel(getBabelOptions()), replace({ 'process.env.NODE_ENV': JSON.stringify('development') }), + bundleSize(), ], }, @@ -54,7 +56,7 @@ export default [ { input, output: { file: 'dist/index.esm.js', format: 'esm' }, - external: id => + external: (id) => !id.startsWith('\0') && !id.startsWith('.') && !id.startsWith('/'), plugins: [babel(getBabelOptions())], }, diff --git a/yarn.lock b/yarn.lock index ac93cf1..a57966f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,15 @@ # yarn lockfile v1 +"@atomico/rollup-plugin-sizes@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@atomico/rollup-plugin-sizes/-/rollup-plugin-sizes-1.1.3.tgz#75cf3f317d29e7269d258febbe4300fe3aa56611" + integrity sha512-a1AKq+k2lWQXywnDp1n9m5O29bx53eUhKzsOKPXwCNPu431X+duDmAiVByZ4808ZzW7oF4fK1bljTZsay6faZw== + dependencies: + brotli-size "^4.0.0" + gzip-size "^5.1.1" + simple-string-table "^1.0.0" + "@babel/cli@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.8.4.tgz#505fb053721a98777b2b175323ea4f090b7d3c1c" @@ -1958,6 +1967,13 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +brotli-size@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/brotli-size/-/brotli-size-4.0.0.tgz#a05ee3faad3c0e700a2f2da826ba6b4d76e69e5e" + integrity sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA== + dependencies: + duplexer "0.1.1" + browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -3004,6 +3020,11 @@ duplexer2@~0.1.4: dependencies: readable-stream "^2.0.2" +duplexer@0.1.1, duplexer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -3956,6 +3977,14 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gzip-size@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" + integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== + dependencies: + duplexer "^0.1.1" + pify "^4.0.1" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -7440,6 +7469,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +simple-string-table@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-string-table/-/simple-string-table-1.0.0.tgz#301ce3175260240e7db7f4b734b0a8125b98997e" + integrity sha512-iflPccjsYtTN+Rqj35v/G+i9A04g2HgOPkPp/B5evznUD4VZ4egi/qcFwrUHgGZwJMZz+Aq5elow4Qqsodfflw== + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" From 32418f064c9c4a0924834f667f810761e1b28ea4 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 8 Apr 2020 15:03:51 +0200 Subject: [PATCH 10/14] fix: simplify build, fix tests, add hook tests, replace enzyme --- .babelrc | 25 +- flow-typed/npm/jest_v22.x.x.js | 603 ----------- flow-typed/npm/jest_v25.x.x.js | 1182 ++++++++++++++++++++++ jest.setup.js | 5 - package.json | 14 +- src/Manager.js | 4 +- src/Manager.test.js | 127 +-- src/Popper.js | 15 +- src/Popper.test.js | 160 ++- src/Reference.js | 2 +- src/Reference.test.js | 56 +- src/__snapshots__/Manager.test.js.snap | 4 +- src/__snapshots__/Popper.test.js.snap | 30 +- src/__snapshots__/Reference.test.js.snap | 24 +- src/index.js | 8 +- src/usePopper.js | 38 +- src/usePopper.test.js | 91 ++ src/utils.js | 19 +- yarn.lock | 564 +++-------- 19 files changed, 1569 insertions(+), 1402 deletions(-) delete mode 100644 flow-typed/npm/jest_v22.x.x.js create mode 100644 flow-typed/npm/jest_v25.x.x.js create mode 100644 src/usePopper.test.js diff --git a/.babelrc b/.babelrc index 36af62a..be797cf 100644 --- a/.babelrc +++ b/.babelrc @@ -4,33 +4,16 @@ "@babel/flow", "@babel/react" ], - "plugins": [ - "@babel/proposal-class-properties" - ], "env": { "esm": { - "plugins": [ - "@babel/transform-runtime" - ], - "ignore": [ - "**/*.test.js", - "**/__mocks__/**" - ] + "ignore": ["**/*.test.js", "**/__mocks__/**"] }, "cjs": { - "plugins": [ - "@babel/transform-runtime", - "@babel/transform-modules-commonjs" - ], - "ignore": [ - "**/*.test.js", - "**/__mocks__/**" - ] + "plugins": [["@babel/transform-modules-commonjs", { "noInterop": true }]], + "ignore": ["**/*.test.js", "**/__mocks__/**"] }, "test": { - "plugins": [ - "@babel/transform-modules-commonjs" - ] + "plugins": ["@babel/transform-modules-commonjs"] } } } diff --git a/flow-typed/npm/jest_v22.x.x.js b/flow-typed/npm/jest_v22.x.x.js deleted file mode 100644 index c064a22..0000000 --- a/flow-typed/npm/jest_v22.x.x.js +++ /dev/null @@ -1,603 +0,0 @@ -// flow-typed signature: 159e15f694a5e3ba2a989dea756a6e22 -// flow-typed version: 0215e23590/jest_v22.x.x/flow_>=v0.39.x - -type JestMockFn, TReturn> = { - (...args: TArguments): TReturn, - /** - * An object for introspecting mock calls - */ - mock: { - /** - * An array that represents all calls that have been made into this mock - * function. Each call is represented by an array of arguments that were - * passed during the call. - */ - calls: Array, - /** - * An array that contains all the object instances that have been - * instantiated from this mock function. - */ - instances: Array - }, - /** - * Resets all information stored in the mockFn.mock.calls and - * mockFn.mock.instances arrays. Often this is useful when you want to clean - * up a mock's usage data between two assertions. - */ - mockClear(): void, - /** - * Resets all information stored in the mock. This is useful when you want to - * completely restore a mock back to its initial state. - */ - mockReset(): void, - /** - * Removes the mock and restores the initial implementation. This is useful - * when you want to mock functions in certain test cases and restore the - * original implementation in others. Beware that mockFn.mockRestore only - * works when mock was created with jest.spyOn. Thus you have to take care of - * restoration yourself when manually assigning jest.fn(). - */ - mockRestore(): void, - /** - * Accepts a function that should be used as the implementation of the mock. - * The mock itself will still record all calls that go into and instances - * that come from itself -- the only difference is that the implementation - * will also be executed when the mock is called. - */ - mockImplementation( - fn: (...args: TArguments) => TReturn - ): JestMockFn, - /** - * Accepts a function that will be used as an implementation of the mock for - * one call to the mocked function. Can be chained so that multiple function - * calls produce different results. - */ - mockImplementationOnce( - fn: (...args: TArguments) => TReturn - ): JestMockFn, - /** - * Just a simple sugar function for returning `this` - */ - mockReturnThis(): void, - /** - * Deprecated: use jest.fn(() => value) instead - */ - mockReturnValue(value: TReturn): JestMockFn, - /** - * Sugar for only returning a value once inside your mock - */ - mockReturnValueOnce(value: TReturn): JestMockFn -}; - -type JestAsymmetricEqualityType = { - /** - * A custom Jasmine equality tester - */ - asymmetricMatch(value: mixed): boolean -}; - -type JestCallsType = { - allArgs(): mixed, - all(): mixed, - any(): boolean, - count(): number, - first(): mixed, - mostRecent(): mixed, - reset(): void -}; - -type JestClockType = { - install(): void, - mockDate(date: Date): void, - tick(milliseconds?: number): void, - uninstall(): void -}; - -type JestMatcherResult = { - message?: string | (() => string), - pass: boolean -}; - -type JestMatcher = (actual: any, expected: any) => JestMatcherResult; - -type JestPromiseType = { - /** - * Use rejects to unwrap the reason of a rejected promise so any other - * matcher can be chained. If the promise is fulfilled the assertion fails. - */ - rejects: JestExpectType, - /** - * Use resolves to unwrap the value of a fulfilled promise so any other - * matcher can be chained. If the promise is rejected the assertion fails. - */ - resolves: JestExpectType -}; - -/** - * Jest allows functions and classes to be used as test names in test() and - * describe() - */ -type JestTestName = string | Function; - -/** - * Plugin: jest-enzyme - */ -type EnzymeMatchersType = { - toBeChecked(): void, - toBeDisabled(): void, - toBeEmpty(): void, - toBeEmptyRender(): void, - toBePresent(): void, - toContainReact(element: React$Element): void, - toExist(): void, - toHaveClassName(className: string): void, - toHaveHTML(html: string): void, - toHaveProp: ((propKey: string, propValue?: any) => void) & ((props: Object) => void), - toHaveRef(refName: string): void, - toHaveState: ((stateKey: string, stateValue?: any) => void) & ((state: Object) => void), - toHaveStyle: ((styleKey: string, styleValue?: any) => void) & ((style: Object) => void), - toHaveTagName(tagName: string): void, - toHaveText(text: string): void, - toIncludeText(text: string): void, - toHaveValue(value: any): void, - toMatchElement(element: React$Element): void, - toMatchSelector(selector: string): void -}; - -type JestExpectType = { - not: JestExpectType & EnzymeMatchersType, - /** - * If you have a mock function, you can use .lastCalledWith to test what - * arguments it was last called with. - */ - lastCalledWith(...args: Array): void, - /** - * toBe just checks that a value is what you expect. It uses === to check - * strict equality. - */ - toBe(value: any): void, - /** - * Use .toHaveBeenCalled to ensure that a mock function got called. - */ - toBeCalled(): void, - /** - * Use .toBeCalledWith to ensure that a mock function was called with - * specific arguments. - */ - toBeCalledWith(...args: Array): void, - /** - * Using exact equality with floating point numbers is a bad idea. Rounding - * means that intuitive things fail. - */ - toBeCloseTo(num: number, delta: any): void, - /** - * Use .toBeDefined to check that a variable is not undefined. - */ - toBeDefined(): void, - /** - * Use .toBeFalsy when you don't care what a value is, you just want to - * ensure a value is false in a boolean context. - */ - toBeFalsy(): void, - /** - * To compare floating point numbers, you can use toBeGreaterThan. - */ - toBeGreaterThan(number: number): void, - /** - * To compare floating point numbers, you can use toBeGreaterThanOrEqual. - */ - toBeGreaterThanOrEqual(number: number): void, - /** - * To compare floating point numbers, you can use toBeLessThan. - */ - toBeLessThan(number: number): void, - /** - * To compare floating point numbers, you can use toBeLessThanOrEqual. - */ - toBeLessThanOrEqual(number: number): void, - /** - * Use .toBeInstanceOf(Class) to check that an object is an instance of a - * class. - */ - toBeInstanceOf(cls: Class<*>): void, - /** - * .toBeNull() is the same as .toBe(null) but the error messages are a bit - * nicer. - */ - toBeNull(): void, - /** - * Use .toBeTruthy when you don't care what a value is, you just want to - * ensure a value is true in a boolean context. - */ - toBeTruthy(): void, - /** - * Use .toBeUndefined to check that a variable is undefined. - */ - toBeUndefined(): void, - /** - * Use .toContain when you want to check that an item is in a list. For - * testing the items in the list, this uses ===, a strict equality check. - */ - toContain(item: any): void, - /** - * Use .toContainEqual when you want to check that an item is in a list. For - * testing the items in the list, this matcher recursively checks the - * equality of all fields, rather than checking for object identity. - */ - toContainEqual(item: any): void, - /** - * Use .toEqual when you want to check that two objects have the same value. - * This matcher recursively checks the equality of all fields, rather than - * checking for object identity. - */ - toEqual(value: any): void, - /** - * Use .toHaveBeenCalled to ensure that a mock function got called. - */ - toHaveBeenCalled(): void, - /** - * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact - * number of times. - */ - toHaveBeenCalledTimes(number: number): void, - /** - * Use .toHaveBeenCalledWith to ensure that a mock function was called with - * specific arguments. - */ - toHaveBeenCalledWith(...args: Array): void, - /** - * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called - * with specific arguments. - */ - toHaveBeenLastCalledWith(...args: Array): void, - /** - * Check that an object has a .length property and it is set to a certain - * numeric value. - */ - toHaveLength(number: number): void, - /** - * - */ - toHaveProperty(propPath: string, value?: any): void, - /** - * Use .toMatch to check that a string matches a regular expression or string. - */ - toMatch(regexpOrString: RegExp | string): void, - /** - * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. - */ - toMatchObject(object: Object | Array): void, - /** - * This ensures that a React component matches the most recent snapshot. - */ - toMatchSnapshot(name?: string): void, - /** - * Use .toThrow to test that a function throws when it is called. - * If you want to test that a specific error gets thrown, you can provide an - * argument to toThrow. The argument can be a string for the error message, - * a class for the error, or a regex that should match the error. - * - * Alias: .toThrowError - */ - toThrow(message?: string | Error | Class | RegExp): void, - toThrowError(message?: string | Error | Class | RegExp): void, - /** - * Use .toThrowErrorMatchingSnapshot to test that a function throws a error - * matching the most recent snapshot when it is called. - */ - toThrowErrorMatchingSnapshot(): void -}; - -type JestObjectType = { - /** - * Disables automatic mocking in the module loader. - * - * After this method is called, all `require()`s will return the real - * versions of each module (rather than a mocked version). - */ - disableAutomock(): JestObjectType, - /** - * An un-hoisted version of disableAutomock - */ - autoMockOff(): JestObjectType, - /** - * Enables automatic mocking in the module loader. - */ - enableAutomock(): JestObjectType, - /** - * An un-hoisted version of enableAutomock - */ - autoMockOn(): JestObjectType, - /** - * Clears the mock.calls and mock.instances properties of all mocks. - * Equivalent to calling .mockClear() on every mocked function. - */ - clearAllMocks(): JestObjectType, - /** - * Resets the state of all mocks. Equivalent to calling .mockReset() on every - * mocked function. - */ - resetAllMocks(): JestObjectType, - /** - * Restores all mocks back to their original value. - */ - restoreAllMocks(): JestObjectType, - /** - * Removes any pending timers from the timer system. - */ - clearAllTimers(): void, - /** - * The same as `mock` but not moved to the top of the expectation by - * babel-jest. - */ - doMock(moduleName: string, moduleFactory?: any): JestObjectType, - /** - * The same as `unmock` but not moved to the top of the expectation by - * babel-jest. - */ - dontMock(moduleName: string): JestObjectType, - /** - * Returns a new, unused mock function. Optionally takes a mock - * implementation. - */ - fn, TReturn>( - implementation?: (...args: TArguments) => TReturn - ): JestMockFn, - /** - * Determines if the given function is a mocked function. - */ - isMockFunction(fn: Function): boolean, - /** - * Given the name of a module, use the automatic mocking system to generate a - * mocked version of the module for you. - */ - genMockFromModule(moduleName: string): any, - /** - * Mocks a module with an auto-mocked version when it is being required. - * - * The second argument can be used to specify an explicit module factory that - * is being run instead of using Jest's automocking feature. - * - * The third argument can be used to create virtual mocks -- mocks of modules - * that don't exist anywhere in the system. - */ - mock( - moduleName: string, - moduleFactory?: any, - options?: Object - ): JestObjectType, - /** - * Returns the actual module instead of a mock, bypassing all checks on - * whether the module should receive a mock implementation or not. - */ - requireActual(moduleName: string): any, - /** - * Returns a mock module instead of the actual module, bypassing all checks - * on whether the module should be required normally or not. - */ - requireMock(moduleName: string): any, - /** - * Resets the module registry - the cache of all required modules. This is - * useful to isolate modules where local state might conflict between tests. - */ - resetModules(): JestObjectType, - /** - * Exhausts the micro-task queue (usually interfaced in node via - * process.nextTick). - */ - runAllTicks(): void, - /** - * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), - * setInterval(), and setImmediate()). - */ - runAllTimers(): void, - /** - * Exhausts all tasks queued by setImmediate(). - */ - runAllImmediates(): void, - /** - * Executes only the macro task queue (i.e. all tasks queued by setTimeout() - * or setInterval() and setImmediate()). - */ - advanceTimersByTime(msToRun: number): void, - /** - * Executes only the macro task queue (i.e. all tasks queued by setTimeout() - * or setInterval() and setImmediate()). - * - * Renamed to `advanceTimersByTime`. - */ - runTimersToTime(msToRun: number): void, - /** - * Executes only the macro-tasks that are currently pending (i.e., only the - * tasks that have been queued by setTimeout() or setInterval() up to this - * point) - */ - runOnlyPendingTimers(): void, - /** - * Explicitly supplies the mock object that the module system should return - * for the specified module. Note: It is recommended to use jest.mock() - * instead. - */ - setMock(moduleName: string, moduleExports: any): JestObjectType, - /** - * Indicates that the module system should never return a mocked version of - * the specified module from require() (e.g. that it should always return the - * real module). - */ - unmock(moduleName: string): JestObjectType, - /** - * Instructs Jest to use fake versions of the standard timer functions - * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, - * setImmediate and clearImmediate). - */ - useFakeTimers(): JestObjectType, - /** - * Instructs Jest to use the real versions of the standard timer functions. - */ - useRealTimers(): JestObjectType, - /** - * Creates a mock function similar to jest.fn but also tracks calls to - * object[methodName]. - */ - spyOn(object: Object, methodName: string): JestMockFn, - /** - * Set the default timeout interval for tests and before/after hooks in milliseconds. - * Note: The default timeout interval is 5 seconds if this method is not called. - */ - setTimeout(timeout: number): JestObjectType -}; - -type JestSpyType = { - calls: JestCallsType -}; - -/** Runs this function after every test inside this context */ -declare function afterEach( - fn: (done: () => void) => ?Promise, - timeout?: number -): void; -/** Runs this function before every test inside this context */ -declare function beforeEach( - fn: (done: () => void) => ?Promise, - timeout?: number -): void; -/** Runs this function after all tests have finished inside this context */ -declare function afterAll( - fn: (done: () => void) => ?Promise, - timeout?: number -): void; -/** Runs this function before any tests have started inside this context */ -declare function beforeAll( - fn: (done: () => void) => ?Promise, - timeout?: number -): void; - -/** A context for grouping tests together */ -declare var describe: { - /** - * Creates a block that groups together several related tests in one "test suite" - */ - (name: JestTestName, fn: () => void): void, - - /** - * Only run this describe block - */ - only(name: JestTestName, fn: () => void): void, - - /** - * Skip running this describe block - */ - skip(name: JestTestName, fn: () => void): void -}; - -/** An individual test unit */ -declare var it: { - /** - * An individual test unit - * - * @param {JestTestName} Name of Test - * @param {Function} Test - * @param {number} Timeout for the test, in milliseconds. - */ - ( - name: JestTestName, - fn?: (done: () => void) => ?Promise, - timeout?: number - ): void, - /** - * Only run this test - * - * @param {JestTestName} Name of Test - * @param {Function} Test - * @param {number} Timeout for the test, in milliseconds. - */ - only( - name: JestTestName, - fn?: (done: () => void) => ?Promise, - timeout?: number - ): void, - /** - * Skip running this test - * - * @param {JestTestName} Name of Test - * @param {Function} Test - * @param {number} Timeout for the test, in milliseconds. - */ - skip( - name: JestTestName, - fn?: (done: () => void) => ?Promise, - timeout?: number - ): void, - /** - * Run the test concurrently - * - * @param {JestTestName} Name of Test - * @param {Function} Test - * @param {number} Timeout for the test, in milliseconds. - */ - concurrent( - name: JestTestName, - fn?: (done: () => void) => ?Promise, - timeout?: number - ): void -}; -declare function fit( - name: JestTestName, - fn: (done: () => void) => ?Promise, - timeout?: number -): void; -/** An individual test unit */ -declare var test: typeof it; -/** A disabled group of tests */ -declare var xdescribe: typeof describe; -/** A focused group of tests */ -declare var fdescribe: typeof describe; -/** A disabled individual test */ -declare var xit: typeof it; -/** A disabled individual test */ -declare var xtest: typeof it; - -/** The expect function is used every time you want to test a value */ -declare var expect: { - /** The object that you want to make assertions against */ - (value: any): JestExpectType & JestPromiseType & EnzymeMatchersType, - /** Add additional Jasmine matchers to Jest's roster */ - extend(matchers: { [name: string]: JestMatcher }): void, - /** Add a module that formats application-specific data structures. */ - addSnapshotSerializer(serializer: (input: Object) => string): void, - assertions(expectedAssertions: number): void, - hasAssertions(): void, - any(value: mixed): JestAsymmetricEqualityType, - anything(): any, - arrayContaining(value: Array): Array, - objectContaining(value: Object): Object, - /** Matches any received string that contains the exact expected string. */ - stringContaining(value: string): string, - stringMatching(value: string | RegExp): string -}; - -// TODO handle return type -// http://jasmine.github.io/2.4/introduction.html#section-Spies -declare function spyOn(value: mixed, method: string): Object; - -/** Holds all functions related to manipulating test runner */ -declare var jest: JestObjectType; - -/** - * The global Jasmine object, this is generally not exposed as the public API, - * using features inside here could break in later versions of Jest. - */ -declare var jasmine: { - DEFAULT_TIMEOUT_INTERVAL: number, - any(value: mixed): JestAsymmetricEqualityType, - anything(): any, - arrayContaining(value: Array): Array, - clock(): JestClockType, - createSpy(name: string): JestSpyType, - createSpyObj( - baseName: string, - methodNames: Array - ): { [methodName: string]: JestSpyType }, - objectContaining(value: Object): Object, - stringMatching(value: string): string -}; diff --git a/flow-typed/npm/jest_v25.x.x.js b/flow-typed/npm/jest_v25.x.x.js new file mode 100644 index 0000000..b58a352 --- /dev/null +++ b/flow-typed/npm/jest_v25.x.x.js @@ -0,0 +1,1182 @@ +// flow-typed signature: b074a60bb87f6f6d496e019aec4703b2 +// flow-typed version: 7223a8293e/jest_v25.x.x/flow_>=v0.104.x + +type JestMockFn, TReturn> = { + (...args: TArguments): TReturn, + /** + * An object for introspecting mock calls + */ + mock: { + /** + * An array that represents all calls that have been made into this mock + * function. Each call is represented by an array of arguments that were + * passed during the call. + */ + calls: Array, + /** + * An array that contains all the object instances that have been + * instantiated from this mock function. + */ + instances: Array, + /** + * An array that contains all the object results that have been + * returned by this mock function call + */ + results: Array<{ + isThrow: boolean, + value: TReturn, + ... + }>, + ... + }, + /** + * Resets all information stored in the mockFn.mock.calls and + * mockFn.mock.instances arrays. Often this is useful when you want to clean + * up a mock's usage data between two assertions. + */ + mockClear(): void, + /** + * Resets all information stored in the mock. This is useful when you want to + * completely restore a mock back to its initial state. + */ + mockReset(): void, + /** + * Removes the mock and restores the initial implementation. This is useful + * when you want to mock functions in certain test cases and restore the + * original implementation in others. Beware that mockFn.mockRestore only + * works when mock was created with jest.spyOn. Thus you have to take care of + * restoration yourself when manually assigning jest.fn(). + */ + mockRestore(): void, + /** + * Accepts a function that should be used as the implementation of the mock. + * The mock itself will still record all calls that go into and instances + * that come from itself -- the only difference is that the implementation + * will also be executed when the mock is called. + */ + mockImplementation( + fn: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Accepts a function that will be used as an implementation of the mock for + * one call to the mocked function. Can be chained so that multiple function + * calls produce different results. + */ + mockImplementationOnce( + fn: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Accepts a string to use in test result output in place of "jest.fn()" to + * indicate which mock function is being referenced. + */ + mockName(name: string): JestMockFn, + /** + * Just a simple sugar function for returning `this` + */ + mockReturnThis(): void, + /** + * Accepts a value that will be returned whenever the mock function is called. + */ + mockReturnValue(value: TReturn): JestMockFn, + /** + * Sugar for only returning a value once inside your mock + */ + mockReturnValueOnce(value: TReturn): JestMockFn, + /** + * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value)) + */ + mockResolvedValue(value: TReturn): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) + */ + mockResolvedValueOnce( + value: TReturn + ): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementation(() => Promise.reject(value)) + */ + mockRejectedValue(value: TReturn): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value)) + */ + mockRejectedValueOnce(value: TReturn): JestMockFn>, + ... +}; + +type JestAsymmetricEqualityType = { /** + * A custom Jasmine equality tester + */ +asymmetricMatch(value: mixed): boolean, ... }; + +type JestCallsType = { + allArgs(): mixed, + all(): mixed, + any(): boolean, + count(): number, + first(): mixed, + mostRecent(): mixed, + reset(): void, + ... +}; + +type JestClockType = { + install(): void, + mockDate(date: Date): void, + tick(milliseconds?: number): void, + uninstall(): void, + ... +}; + +type JestMatcherResult = { + message?: string | (() => string), + pass: boolean, + ... +}; + +type JestMatcher = ( + received: any, + ...actual: Array +) => JestMatcherResult | Promise; + +type JestPromiseType = { + /** + * Use rejects to unwrap the reason of a rejected promise so any other + * matcher can be chained. If the promise is fulfilled the assertion fails. + */ + rejects: JestExpectType, + /** + * Use resolves to unwrap the value of a fulfilled promise so any other + * matcher can be chained. If the promise is rejected the assertion fails. + */ + resolves: JestExpectType, + ... +}; + +/** + * Jest allows functions and classes to be used as test names in test() and + * describe() + */ +type JestTestName = string | Function; + +/** + * Plugin: jest-styled-components + */ + +type JestStyledComponentsMatcherValue = + | string + | JestAsymmetricEqualityType + | RegExp + | typeof undefined; + +type JestStyledComponentsMatcherOptions = { + media?: string, + modifier?: string, + supports?: string, + ... +}; + +type JestStyledComponentsMatchersType = { toHaveStyleRule( + property: string, + value: JestStyledComponentsMatcherValue, + options?: JestStyledComponentsMatcherOptions +): void, ... }; + +/** + * Plugin: jest-enzyme + */ +type EnzymeMatchersType = { + // 5.x + toBeEmpty(): void, + toBePresent(): void, + // 6.x + toBeChecked(): void, + toBeDisabled(): void, + toBeEmptyRender(): void, + toContainMatchingElement(selector: string): void, + toContainMatchingElements(n: number, selector: string): void, + toContainExactlyOneMatchingElement(selector: string): void, + toContainReact(element: React$Element): void, + toExist(): void, + toHaveClassName(className: string): void, + toHaveHTML(html: string): void, + toHaveProp: ((propKey: string, propValue?: any) => void) & + ((props: {...}) => void), + toHaveRef(refName: string): void, + toHaveState: ((stateKey: string, stateValue?: any) => void) & + ((state: {...}) => void), + toHaveStyle: ((styleKey: string, styleValue?: any) => void) & + ((style: {...}) => void), + toHaveTagName(tagName: string): void, + toHaveText(text: string): void, + toHaveValue(value: any): void, + toIncludeText(text: string): void, + toMatchElement( + element: React$Element, + options?: {| ignoreProps?: boolean, verbose?: boolean |} + ): void, + toMatchSelector(selector: string): void, + // 7.x + toHaveDisplayName(name: string): void, + ... +}; + +// DOM testing library extensions (jest-dom) +// https://github.com/testing-library/jest-dom +type DomTestingLibraryType = { + /** + * @deprecated + */ + toBeInTheDOM(container?: HTMLElement): void, + toBeInTheDocument(): void, + toBeVisible(): void, + toBeEmpty(): void, + toBeDisabled(): void, + toBeEnabled(): void, + toBeInvalid(): void, + toBeRequired(): void, + toBeValid(): void, + toContainElement(element: HTMLElement | null): void, + toContainHTML(htmlText: string): void, + toHaveAttribute(attr: string, value?: any): void, + toHaveClass(...classNames: string[]): void, + toHaveFocus(): void, + toHaveFormValues(expectedValues: { [name: string]: any, ... }): void, + toHaveStyle(css: string): void, + toHaveTextContent( + text: string | RegExp, + options?: { normalizeWhitespace: boolean, ... } + ): void, + toHaveValue(value?: string | string[] | number): void, + ... +}; + +// Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers +type JestJQueryMatchersType = { + toExist(): void, + toHaveLength(len: number): void, + toHaveId(id: string): void, + toHaveClass(className: string): void, + toHaveTag(tag: string): void, + toHaveAttr(key: string, val?: any): void, + toHaveProp(key: string, val?: any): void, + toHaveText(text: string | RegExp): void, + toHaveData(key: string, val?: any): void, + toHaveValue(val: any): void, + toHaveCss(css: { [key: string]: any, ... }): void, + toBeChecked(): void, + toBeDisabled(): void, + toBeEmpty(): void, + toBeHidden(): void, + toBeSelected(): void, + toBeVisible(): void, + toBeFocused(): void, + toBeInDom(): void, + toBeMatchedBy(sel: string): void, + toHaveDescendant(sel: string): void, + toHaveDescendantWithText(sel: string, text: string | RegExp): void, + ... +}; + +// Jest Extended Matchers: https://github.com/jest-community/jest-extended +type JestExtendedMatchersType = { + /** + * Note: Currently unimplemented + * Passing assertion + * + * @param {String} message + */ + // pass(message: string): void; + + /** + * Note: Currently unimplemented + * Failing assertion + * + * @param {String} message + */ + // fail(message: string): void; + + /** + * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. + */ + toBeEmpty(): void, + /** + * Use .toBeOneOf when checking if a value is a member of a given Array. + * @param {Array.<*>} members + */ + toBeOneOf(members: any[]): void, + /** + * Use `.toBeNil` when checking a value is `null` or `undefined`. + */ + toBeNil(): void, + /** + * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. + * @param {Function} predicate + */ + toSatisfy(predicate: (n: any) => boolean): void, + /** + * Use `.toBeArray` when checking if a value is an `Array`. + */ + toBeArray(): void, + /** + * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. + * @param {Number} x + */ + toBeArrayOfSize(x: number): void, + /** + * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. + * @param {Array.<*>} members + */ + toIncludeAllMembers(members: any[]): void, + /** + * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. + * @param {Array.<*>} members + */ + toIncludeAnyMembers(members: any[]): void, + /** + * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array. + * @param {Function} predicate + */ + toSatisfyAll(predicate: (n: any) => boolean): void, + /** + * Use `.toBeBoolean` when checking if a value is a `Boolean`. + */ + toBeBoolean(): void, + /** + * Use `.toBeTrue` when checking a value is equal (===) to `true`. + */ + toBeTrue(): void, + /** + * Use `.toBeFalse` when checking a value is equal (===) to `false`. + */ + toBeFalse(): void, + /** + * Use .toBeDate when checking if a value is a Date. + */ + toBeDate(): void, + /** + * Use `.toBeFunction` when checking if a value is a `Function`. + */ + toBeFunction(): void, + /** + * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. + * + * Note: Required Jest version >22 + * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same + * + * @param {Mock} mock + */ + toHaveBeenCalledBefore(mock: JestMockFn): void, + /** + * Use `.toBeNumber` when checking if a value is a `Number`. + */ + toBeNumber(): void, + /** + * Use `.toBeNaN` when checking a value is `NaN`. + */ + toBeNaN(): void, + /** + * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. + */ + toBeFinite(): void, + /** + * Use `.toBePositive` when checking if a value is a positive `Number`. + */ + toBePositive(): void, + /** + * Use `.toBeNegative` when checking if a value is a negative `Number`. + */ + toBeNegative(): void, + /** + * Use `.toBeEven` when checking if a value is an even `Number`. + */ + toBeEven(): void, + /** + * Use `.toBeOdd` when checking if a value is an odd `Number`. + */ + toBeOdd(): void, + /** + * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). + * + * @param {Number} start + * @param {Number} end + */ + toBeWithin(start: number, end: number): void, + /** + * Use `.toBeObject` when checking if a value is an `Object`. + */ + toBeObject(): void, + /** + * Use `.toContainKey` when checking if an object contains the provided key. + * + * @param {String} key + */ + toContainKey(key: string): void, + /** + * Use `.toContainKeys` when checking if an object has all of the provided keys. + * + * @param {Array.} keys + */ + toContainKeys(keys: string[]): void, + /** + * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. + * + * @param {Array.} keys + */ + toContainAllKeys(keys: string[]): void, + /** + * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. + * + * @param {Array.} keys + */ + toContainAnyKeys(keys: string[]): void, + /** + * Use `.toContainValue` when checking if an object contains the provided value. + * + * @param {*} value + */ + toContainValue(value: any): void, + /** + * Use `.toContainValues` when checking if an object contains all of the provided values. + * + * @param {Array.<*>} values + */ + toContainValues(values: any[]): void, + /** + * Use `.toContainAllValues` when checking if an object only contains all of the provided values. + * + * @param {Array.<*>} values + */ + toContainAllValues(values: any[]): void, + /** + * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. + * + * @param {Array.<*>} values + */ + toContainAnyValues(values: any[]): void, + /** + * Use `.toContainEntry` when checking if an object contains the provided entry. + * + * @param {Array.} entry + */ + toContainEntry(entry: [string, string]): void, + /** + * Use `.toContainEntries` when checking if an object contains all of the provided entries. + * + * @param {Array.>} entries + */ + toContainEntries(entries: [string, string][]): void, + /** + * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. + * + * @param {Array.>} entries + */ + toContainAllEntries(entries: [string, string][]): void, + /** + * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. + * + * @param {Array.>} entries + */ + toContainAnyEntries(entries: [string, string][]): void, + /** + * Use `.toBeExtensible` when checking if an object is extensible. + */ + toBeExtensible(): void, + /** + * Use `.toBeFrozen` when checking if an object is frozen. + */ + toBeFrozen(): void, + /** + * Use `.toBeSealed` when checking if an object is sealed. + */ + toBeSealed(): void, + /** + * Use `.toBeString` when checking if a value is a `String`. + */ + toBeString(): void, + /** + * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. + * + * @param {String} string + */ + toEqualCaseInsensitive(string: string): void, + /** + * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. + * + * @param {String} prefix + */ + toStartWith(prefix: string): void, + /** + * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. + * + * @param {String} suffix + */ + toEndWith(suffix: string): void, + /** + * Use `.toInclude` when checking if a `String` includes the given `String` substring. + * + * @param {String} substring + */ + toInclude(substring: string): void, + /** + * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. + * + * @param {String} substring + * @param {Number} times + */ + toIncludeRepeated(substring: string, times: number): void, + /** + * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. + * + * @param {Array.} substring + */ + toIncludeMultiple(substring: string[]): void, + ... +}; + +interface JestExpectType { + not: JestExpectType & + EnzymeMatchersType & + DomTestingLibraryType & + JestJQueryMatchersType & + JestStyledComponentsMatchersType & + JestExtendedMatchersType; + /** + * If you have a mock function, you can use .lastCalledWith to test what + * arguments it was last called with. + */ + lastCalledWith(...args: Array): void; + /** + * toBe just checks that a value is what you expect. It uses === to check + * strict equality. + */ + toBe(value: any): void; + /** + * Use .toBeCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toBeCalledWith(...args: Array): void; + /** + * Using exact equality with floating point numbers is a bad idea. Rounding + * means that intuitive things fail. + */ + toBeCloseTo(num: number, delta: any): void; + /** + * Use .toBeDefined to check that a variable is not undefined. + */ + toBeDefined(): void; + /** + * Use .toBeFalsy when you don't care what a value is, you just want to + * ensure a value is false in a boolean context. + */ + toBeFalsy(): void; + /** + * To compare floating point numbers, you can use toBeGreaterThan. + */ + toBeGreaterThan(number: number): void; + /** + * To compare floating point numbers, you can use toBeGreaterThanOrEqual. + */ + toBeGreaterThanOrEqual(number: number): void; + /** + * To compare floating point numbers, you can use toBeLessThan. + */ + toBeLessThan(number: number): void; + /** + * To compare floating point numbers, you can use toBeLessThanOrEqual. + */ + toBeLessThanOrEqual(number: number): void; + /** + * Use .toBeInstanceOf(Class) to check that an object is an instance of a + * class. + */ + toBeInstanceOf(cls: Class<*>): void; + /** + * .toBeNull() is the same as .toBe(null) but the error messages are a bit + * nicer. + */ + toBeNull(): void; + /** + * Use .toBeTruthy when you don't care what a value is, you just want to + * ensure a value is true in a boolean context. + */ + toBeTruthy(): void; + /** + * Use .toBeUndefined to check that a variable is undefined. + */ + toBeUndefined(): void; + /** + * Use .toContain when you want to check that an item is in a list. For + * testing the items in the list, this uses ===, a strict equality check. + */ + toContain(item: any): void; + /** + * Use .toContainEqual when you want to check that an item is in a list. For + * testing the items in the list, this matcher recursively checks the + * equality of all fields, rather than checking for object identity. + */ + toContainEqual(item: any): void; + /** + * Use .toEqual when you want to check that two objects have the same value. + * This matcher recursively checks the equality of all fields, rather than + * checking for object identity. + */ + toEqual(value: any): void; + /** + * Use .toHaveBeenCalled to ensure that a mock function got called. + */ + toHaveBeenCalled(): void; + toBeCalled(): void; + /** + * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact + * number of times. + */ + toHaveBeenCalledTimes(number: number): void; + toBeCalledTimes(number: number): void; + /** + * + */ + toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; + nthCalledWith(nthCall: number, ...args: Array): void; + /** + * + */ + toHaveReturned(): void; + toReturn(): void; + /** + * + */ + toHaveReturnedTimes(number: number): void; + toReturnTimes(number: number): void; + /** + * + */ + toHaveReturnedWith(value: any): void; + toReturnWith(value: any): void; + /** + * + */ + toHaveLastReturnedWith(value: any): void; + lastReturnedWith(value: any): void; + /** + * + */ + toHaveNthReturnedWith(nthCall: number, value: any): void; + nthReturnedWith(nthCall: number, value: any): void; + /** + * Use .toHaveBeenCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toHaveBeenCalledWith(...args: Array): void; + toBeCalledWith(...args: Array): void; + /** + * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called + * with specific arguments. + */ + toHaveBeenLastCalledWith(...args: Array): void; + lastCalledWith(...args: Array): void; + /** + * Check that an object has a .length property and it is set to a certain + * numeric value. + */ + toHaveLength(number: number): void; + /** + * + */ + toHaveProperty(propPath: string | $ReadOnlyArray, value?: any): void; + /** + * Use .toMatch to check that a string matches a regular expression or string. + */ + toMatch(regexpOrString: RegExp | string): void; + /** + * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. + */ + toMatchObject(object: Object | Array): void; + /** + * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. + */ + toStrictEqual(value: any): void; + /** + * This ensures that an Object matches the most recent snapshot. + */ + toMatchSnapshot(propertyMatchers?: any, name?: string): void; + /** + * This ensures that an Object matches the most recent snapshot. + */ + toMatchSnapshot(name: string): void; + + toMatchInlineSnapshot(snapshot?: string): void; + toMatchInlineSnapshot(propertyMatchers?: any, snapshot?: string): void; + /** + * Use .toThrow to test that a function throws when it is called. + * If you want to test that a specific error gets thrown, you can provide an + * argument to toThrow. The argument can be a string for the error message, + * a class for the error, or a regex that should match the error. + * + * Alias: .toThrowError + */ + toThrow(message?: string | Error | Class | RegExp): void; + toThrowError(message?: string | Error | Class | RegExp): void; + /** + * Use .toThrowErrorMatchingSnapshot to test that a function throws a error + * matching the most recent snapshot when it is called. + */ + toThrowErrorMatchingSnapshot(): void; + toThrowErrorMatchingInlineSnapshot(snapshot?: string): void; +} + +type JestObjectType = { + /** + * Disables automatic mocking in the module loader. + * + * After this method is called, all `require()`s will return the real + * versions of each module (rather than a mocked version). + */ + disableAutomock(): JestObjectType, + /** + * An un-hoisted version of disableAutomock + */ + autoMockOff(): JestObjectType, + /** + * Enables automatic mocking in the module loader. + */ + enableAutomock(): JestObjectType, + /** + * An un-hoisted version of enableAutomock + */ + autoMockOn(): JestObjectType, + /** + * Clears the mock.calls and mock.instances properties of all mocks. + * Equivalent to calling .mockClear() on every mocked function. + */ + clearAllMocks(): JestObjectType, + /** + * Resets the state of all mocks. Equivalent to calling .mockReset() on every + * mocked function. + */ + resetAllMocks(): JestObjectType, + /** + * Restores all mocks back to their original value. + */ + restoreAllMocks(): JestObjectType, + /** + * Removes any pending timers from the timer system. + */ + clearAllTimers(): void, + /** + * Returns the number of fake timers still left to run. + */ + getTimerCount(): number, + /** + * The same as `mock` but not moved to the top of the expectation by + * babel-jest. + */ + doMock(moduleName: string, moduleFactory?: any): JestObjectType, + /** + * The same as `unmock` but not moved to the top of the expectation by + * babel-jest. + */ + dontMock(moduleName: string): JestObjectType, + /** + * Returns a new, unused mock function. Optionally takes a mock + * implementation. + */ + fn, TReturn>( + implementation?: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Determines if the given function is a mocked function. + */ + isMockFunction(fn: Function): boolean, + /** + * Given the name of a module, use the automatic mocking system to generate a + * mocked version of the module for you. + */ + genMockFromModule(moduleName: string): any, + /** + * Mocks a module with an auto-mocked version when it is being required. + * + * The second argument can be used to specify an explicit module factory that + * is being run instead of using Jest's automocking feature. + * + * The third argument can be used to create virtual mocks -- mocks of modules + * that don't exist anywhere in the system. + */ + mock( + moduleName: string, + moduleFactory?: any, + options?: Object + ): JestObjectType, + /** + * Returns the actual module instead of a mock, bypassing all checks on + * whether the module should receive a mock implementation or not. + */ + requireActual(moduleName: string): any, + /** + * Returns a mock module instead of the actual module, bypassing all checks + * on whether the module should be required normally or not. + */ + requireMock(moduleName: string): any, + /** + * Resets the module registry - the cache of all required modules. This is + * useful to isolate modules where local state might conflict between tests. + */ + resetModules(): JestObjectType, + /** + * Creates a sandbox registry for the modules that are loaded inside the + * callback function. This is useful to isolate specific modules for every + * test so that local module state doesn't conflict between tests. + */ + isolateModules(fn: () => void): JestObjectType, + /** + * Exhausts the micro-task queue (usually interfaced in node via + * process.nextTick). + */ + runAllTicks(): void, + /** + * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), + * setInterval(), and setImmediate()). + */ + runAllTimers(): void, + /** + * Exhausts all tasks queued by setImmediate(). + */ + runAllImmediates(): void, + /** + * Executes only the macro task queue (i.e. all tasks queued by setTimeout() + * or setInterval() and setImmediate()). + */ + advanceTimersByTime(msToRun: number): void, + /** + * Executes only the macro task queue (i.e. all tasks queued by setTimeout() + * or setInterval() and setImmediate()). + * + * Renamed to `advanceTimersByTime`. + */ + runTimersToTime(msToRun: number): void, + /** + * Executes only the macro-tasks that are currently pending (i.e., only the + * tasks that have been queued by setTimeout() or setInterval() up to this + * point) + */ + runOnlyPendingTimers(): void, + /** + * Explicitly supplies the mock object that the module system should return + * for the specified module. Note: It is recommended to use jest.mock() + * instead. + */ + setMock(moduleName: string, moduleExports: any): JestObjectType, + /** + * Indicates that the module system should never return a mocked version of + * the specified module from require() (e.g. that it should always return the + * real module). + */ + unmock(moduleName: string): JestObjectType, + /** + * Instructs Jest to use fake versions of the standard timer functions + * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, + * setImmediate and clearImmediate). + */ + useFakeTimers(): JestObjectType, + /** + * Instructs Jest to use the real versions of the standard timer functions. + */ + useRealTimers(): JestObjectType, + /** + * Creates a mock function similar to jest.fn but also tracks calls to + * object[methodName]. + */ + spyOn( + object: Object, + methodName: string, + accessType?: 'get' | 'set' + ): JestMockFn, + /** + * Set the default timeout interval for tests and before/after hooks in milliseconds. + * Note: The default timeout interval is 5 seconds if this method is not called. + */ + setTimeout(timeout: number): JestObjectType, + ... +}; + +type JestSpyType = { calls: JestCallsType, ... }; + +type JestDoneFn = {| + (): void, + fail: (error: Error) => void, +|}; + +/** Runs this function after every test inside this context */ +declare function afterEach( + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; +/** Runs this function before every test inside this context */ +declare function beforeEach( + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; +/** Runs this function after all tests have finished inside this context */ +declare function afterAll( + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; +/** Runs this function before any tests have started inside this context */ +declare function beforeAll( + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; + +/** A context for grouping tests together */ +declare var describe: { + /** + * Creates a block that groups together several related tests in one "test suite" + */ + (name: JestTestName, fn: () => void): void, + /** + * Only run this describe block + */ + only(name: JestTestName, fn: () => void): void, + /** + * Skip running this describe block + */ + skip(name: JestTestName, fn: () => void): void, + /** + * each runs this test against array of argument arrays per each run + * + * @param {table} table of Test + */ + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void, + ... +}; + +/** An individual test unit */ +declare var it: { + /** + * An individual test unit + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + ( + name: JestTestName, + fn?: (done: JestDoneFn) => ?Promise, + timeout?: number + ): void, + /** + * Only run this test + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + only: {| + ( + name: JestTestName, + fn?: (done: JestDoneFn) => ?Promise, + timeout?: number + ): void, + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void + |}, + /** + * Skip running this test + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + skip( + name: JestTestName, + fn?: (done: JestDoneFn) => ?Promise, + timeout?: number + ): void, + /** + * Highlight planned tests in the summary output + * + * @param {String} Name of Test to do + */ + todo(name: string): void, + /** + * Run the test concurrently + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + concurrent( + name: JestTestName, + fn?: (done: JestDoneFn) => ?Promise, + timeout?: number + ): void, + /** + * each runs this test against array of argument arrays per each run + * + * @param {table} table of Test + */ + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void, + ... +}; + +declare function fit( + name: JestTestName, + fn: (done: JestDoneFn) => ?Promise, + timeout?: number +): void; +/** An individual test unit */ +declare var test: typeof it; +/** A disabled group of tests */ +declare var xdescribe: typeof describe; +/** A focused group of tests */ +declare var fdescribe: typeof describe; +/** A disabled individual test */ +declare var xit: typeof it; +/** A disabled individual test */ +declare var xtest: typeof it; + +type JestPrettyFormatColors = { + comment: { + close: string, + open: string, + ... + }, + content: { + close: string, + open: string, + ... + }, + prop: { + close: string, + open: string, + ... + }, + tag: { + close: string, + open: string, + ... + }, + value: { + close: string, + open: string, + ... + }, + ... +}; + +type JestPrettyFormatIndent = string => string; +type JestPrettyFormatRefs = Array; +type JestPrettyFormatPrint = any => string; +type JestPrettyFormatStringOrNull = string | null; + +type JestPrettyFormatOptions = {| + callToJSON: boolean, + edgeSpacing: string, + escapeRegex: boolean, + highlight: boolean, + indent: number, + maxDepth: number, + min: boolean, + plugins: JestPrettyFormatPlugins, + printFunctionName: boolean, + spacing: string, + theme: {| + comment: string, + content: string, + prop: string, + tag: string, + value: string, + |}, +|}; + +type JestPrettyFormatPlugin = { + print: ( + val: any, + serialize: JestPrettyFormatPrint, + indent: JestPrettyFormatIndent, + opts: JestPrettyFormatOptions, + colors: JestPrettyFormatColors + ) => string, + test: any => boolean, + ... +}; + +type JestPrettyFormatPlugins = Array; + +/** The expect function is used every time you want to test a value */ +declare var expect: { + /** The object that you want to make assertions against */ + ( + value: any + ): JestExpectType & + JestPromiseType & + EnzymeMatchersType & + DomTestingLibraryType & + JestJQueryMatchersType & + JestStyledComponentsMatchersType & + JestExtendedMatchersType, + /** Add additional Jasmine matchers to Jest's roster */ + extend(matchers: { [name: string]: JestMatcher, ... }): void, + /** Add a module that formats application-specific data structures. */ + addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, + assertions(expectedAssertions: number): void, + hasAssertions(): void, + any(value: mixed): JestAsymmetricEqualityType, + anything(): any, + arrayContaining(value: Array): Array, + objectContaining(value: Object): Object, + /** Matches any received string that contains the exact expected string. */ + stringContaining(value: string): string, + stringMatching(value: string | RegExp): string, + not: { + arrayContaining: (value: $ReadOnlyArray) => Array, + objectContaining: (value: {...}) => Object, + stringContaining: (value: string) => string, + stringMatching: (value: string | RegExp) => string, + ... + }, + ... +}; + +// TODO handle return type +// http://jasmine.github.io/2.4/introduction.html#section-Spies +declare function spyOn(value: mixed, method: string): Object; + +/** Holds all functions related to manipulating test runner */ +declare var jest: JestObjectType; + +/** + * The global Jasmine object, this is generally not exposed as the public API, + * using features inside here could break in later versions of Jest. + */ +declare var jasmine: { + DEFAULT_TIMEOUT_INTERVAL: number, + any(value: mixed): JestAsymmetricEqualityType, + anything(): any, + arrayContaining(value: Array): Array, + clock(): JestClockType, + createSpy(name: string): JestSpyType, + createSpyObj( + baseName: string, + methodNames: Array + ): { [methodName: string]: JestSpyType, ... }, + objectContaining(value: Object): Object, + stringMatching(value: string): string, + ... +}; diff --git a/jest.setup.js b/jest.setup.js index 9639484..f2b50db 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,7 +1,2 @@ // @flow import '@babel/polyfill'; -import Enzyme from 'enzyme'; -import EnzymeAdapter from 'enzyme-adapter-react-16'; - -// Setup enzyme's react adapter -Enzyme.configure({ adapter: new EnzymeAdapter() }); diff --git a/package.json b/package.json index 8adbd40..fd2de73 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,6 @@ "jest": { "setupFilesAfterEnv": [ "jest.setup.js" - ], - "snapshotSerializers": [ - "enzyme-to-json/serializer" ] }, "repository": { @@ -71,25 +68,20 @@ "@atomico/rollup-plugin-sizes": "^1.1.3", "@babel/cli": "^7.8.4", "@babel/core": "^7.9.0", - "@babel/plugin-external-helpers": "^7.8.3", - "@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-runtime": "^7.9.0", "@babel/polyfill": "^7.8.7", "@babel/preset-env": "^7.9.0", "@babel/preset-flow": "^7.9.0", "@babel/preset-react": "^7.9.4", - "@babel/preset-stage-2": "^7.8.3", "@emotion/core": "^10.0.28", "@emotion/styled": "^10.0.27", "@popperjs/core": "^2.2.1", + "@testing-library/react": "^10.0.2", + "@testing-library/react-hooks": "^3.2.1", "@types/react": "^16.9.29", "babel-eslint": "^10.1.0", "babel-jest": "^25.2.4", "cross-env": "^7.0.2", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.2", - "enzyme-to-json": "^3.4.4", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.1", "eslint-plugin-flowtype": "^4.7.0", @@ -108,7 +100,7 @@ "react": "16.13.1", "react-dom": "^16.13.1", "react-spring": "^8.0.27", - "recompose": "^0.30.0", + "react-test-renderer": "^16.13.1", "rimraf": "^3.0.2", "rollup": "^2.3.1", "rollup-plugin-babel": "^4.4.0", diff --git a/src/Manager.js b/src/Manager.js index d35d37e..636750f 100644 --- a/src/Manager.js +++ b/src/Manager.js @@ -10,14 +10,14 @@ export type ManagerProps = { children: React.Node, }; -export default function Manager({ children }: ManagerProps) { +export function Manager({ children }: ManagerProps) { const [referenceNode, setReferenceNode] = React.useState(null); React.useEffect( () => () => { setReferenceNode(null); }, - [] + [setReferenceNode] ); return ( diff --git a/src/Manager.test.js b/src/Manager.test.js index 8d90af2..ae40e63 100644 --- a/src/Manager.test.js +++ b/src/Manager.test.js @@ -1,130 +1,39 @@ // @flow import React from 'react'; -import { mount } from 'enzyme'; +import { render, waitFor } from '@testing-library/react'; +import * as Popperjs from '@popperjs/core'; // Public API -import { Manager, Popper, Reference } from '.'; - -// Private API -import { ManagerReferenceNodeContext, ManagerReferenceNodeSetterContext } from './Manager'; +import { Manager, Reference, Popper } from '.'; describe('Manager component', () => { it('renders the expected markup', () => { - const wrapper = mount( + const { asFragment } = render(
); - expect(wrapper).toMatchSnapshot(); - }); - - it('provides the related context', () => { - const Reference = () => null; - const referenceNode = document.createElement('div'); - - const wrapper = mount( - - - {(setReferenceNode) => ( - - {(referenceNode) => ( - - )} - - )} - - - ); - - wrapper.find(Reference).prop('setReferenceNode')(referenceNode); - wrapper.update(); - expect(wrapper.find(Reference).prop('referenceNode')).toBe(referenceNode); + expect(asFragment()).toMatchSnapshot(); }); -}); -describe('Managed Reference', () => { - it('If passed a referenceElement prop value, uses the referenceElement prop value', () => { - const element = document.createElement('div'); - const wrapper = mount( - - {({ ref }) =>
hello
}
- {() => null} -
- ); - const PopperInstance = wrapper.find(Popper); - expect(PopperInstance.prop('referenceElement')).toBe(element); - }); - it('If the referenceElement prop is undefined, use the referenceNode from context', () => { - let referenceElement; - let ReferenceComp = ({ innerRef }) => ( -
{ - innerRef(node); - referenceElement = node; - }} - > - hello -
- ); - const wrapper = mount( - - {({ ref }) => } - {() => null} - - ); - const PopperInstance = wrapper.find(Popper); - expect(PopperInstance.prop('referenceElement')).toBe(referenceElement); - }); + it('connects Popper and Reference', async () => { + const spy = jest.spyOn(Popperjs, 'createPopper'); - it('updates the referenceNode if setReferenceNode is called with a new value', () => { - let referenceElement; - let ReferenceComp = ({ innerRef }) => ( -
{ - innerRef(node); - referenceElement = node; - }} - > - hello -
- ); - const wrapper = mount( + render( - {({ ref }) => } - {() => null} + {({ ref }) =>
} + {({ ref }) =>
} ); - expect(wrapper.instance().referenceNode).toBe(referenceElement); - wrapper.instance().componentWillUnmount() - expect(wrapper.instance().referenceNode).toBeNull(); - }); -}); - -describe('ReferenceNodeContext', () => { - it('provides proper default values', () => { - const Reference = () => null; - const wrapper = mount( -
- - {(setReferenceNode) => ( - - {(referenceNode) => ( - - )} - - )} - -
- ); - - expect(wrapper.find(Reference).prop('setReferenceNode')).toBeUndefined(); + await waitFor(() => { + expect(spy.mock.calls[0].slice(0, 2)).toMatchInlineSnapshot(` + Array [ +
, +
, + ] + `); + }); }); }); diff --git a/src/Popper.js b/src/Popper.js index d80c4c7..f7e9c85 100644 --- a/src/Popper.js +++ b/src/Popper.js @@ -9,8 +9,8 @@ import { } from '@popperjs/core/lib'; import { ManagerReferenceNodeContext } from './Manager'; import type { Ref } from './RefTypes'; -import { unwrapArray } from './utils'; -import usePopper from './usePopper'; +import { unwrapArray, setRef } from './utils'; +import { usePopper } from './usePopper'; type ReferenceElement = ?(VirtualElement | HTMLElement); type Modifiers = Array<$Shape>>; @@ -45,13 +45,15 @@ export type PopperProps = {| const NOOP = () => void 0; const NOOP_PROMISE = () => Promise.resolve(null); +const EMPTY_MODIFIERS = []; -export default function Popper({ +export function Popper({ placement = 'bottom', strategy = 'absolute', - modifiers = [], + modifiers = EMPTY_MODIFIERS, referenceElement, onFirstUpdate, + innerRef, children, }: PopperProps) { const referenceNode = React.useContext(ManagerReferenceNodeContext); @@ -59,6 +61,11 @@ export default function Popper({ const [popperElement, setPopperElement] = React.useState(null); const [arrowElement, setArrowElement] = React.useState(null); + React.useEffect(() => setRef(innerRef, popperElement), [ + innerRef, + popperElement, + ]); + const options = React.useMemo( () => ({ placement, diff --git a/src/Popper.test.js b/src/Popper.test.js index 4572945..25c5a08 100644 --- a/src/Popper.test.js +++ b/src/Popper.test.js @@ -1,84 +1,36 @@ // @flow import React from 'react'; -import { mount } from 'enzyme'; - -// Private API -import Popper from './Popper'; - -const mountPopper = (props) => - new Promise((resolve) => { - const wrapper = mount( - resolve(wrapper.update())} {...props}> - {({ ref, style, placement, arrowProps }) => ( -
-
-
- )} - - ); - }); +import { render, waitFor } from '@testing-library/react'; +import * as PopperJs from '@popperjs/core'; + +// Public API +import { Popper } from '.'; + +const renderPopper = (props) => + render( + + {({ ref, style, placement, arrowProps }) => ( +
+
+
+ )} + + ); describe('Popper component', () => { it('renders the expected markup', async () => { const referenceElement = document.createElement('div'); - const wrapper = await mountPopper({ referenceElement }); - expect(wrapper).toMatchSnapshot(); - }); - - it.skip('initializes the Popper.js instance on first update', async () => { - const referenceElement = document.createElement('div'); - const wrapper = await mountPopper({ referenceElement }); - expect(wrapper.instance().popperInstance).toBeDefined(); - }); + const { asFragment } = await renderPopper({ referenceElement }); - it("doesn't update Popper.js instance on props update if not needed by Popper.js", async () => { - const referenceElement = document.createElement('div'); - const wrapper = await mountPopper({ - referenceElement, - placement: 'bottom', + await waitFor(() => { + expect(asFragment()).toMatchSnapshot(); }); - const instance = wrapper.instance().popperInstance; - - expect(instance).toBeDefined(); - - wrapper.setProps({ placement: 'bottom' }); - - expect(wrapper.instance().popperInstance).toBe(instance); - }); - - it.skip('updates Popper.js on explicitly listed props change', async () => { - const referenceElement = document.createElement('div'); - const wrapper = await mountPopper({ referenceElement }); - const instance = wrapper.instance().popperInstance; - wrapper.setProps({ placement: 'top' }); - wrapper.update(); - expect(wrapper.instance().popperInstance).toBe(instance); - - await wrapper.instance().popperInstance.update(); - expect(wrapper.instance().popperInstance.state.placement).toBe('top'); - }); - - it.skip('does not update Popper.js on generic props change', async () => { - const referenceElement = document.createElement('div'); - const wrapper = await mountPopper({ referenceElement }); - const instance = wrapper.instance().popperInstance; - wrapper.setProps({ foo: 'bar' }); - wrapper.update(); - expect(wrapper.instance().popperInstance).toBe(instance); - }); - - it.skip('destroys Popper.js instance on unmount', async () => { - const referenceElement = document.createElement('div'); - const wrapper = await mountPopper({ referenceElement }); - const component = wrapper.instance(); - wrapper.unmount(); - expect(component.popperInstance).toBeNull(); }); it('handles changing refs gracefully', () => { const referenceElement = document.createElement('div'); expect(() => - mount( + render( {({ ref, style, placement, arrowProps }) => (
{ ).not.toThrow(); }); - it('accepts a ref function', () => { + it('accepts a ref function', async () => { const myRef = jest.fn(); const referenceElement = document.createElement('div'); - mount( + await render( {({ ref, style, placement }) => (
)} ); - expect(myRef).toBeCalled(); + await waitFor(() => { + expect(myRef).toBeCalled(); + }); }); - it('accepts a ref object', () => { + it('accepts a ref object', async () => { const myRef = React.createRef(); const referenceElement = document.createElement('div'); - mount( + await render( {({ ref, style, placement }) => (
)} ); - expect(myRef.current).toBeDefined(); + await waitFor(() => { + expect(myRef.current).toBeDefined(); + }); }); it('accepts a `referenceElement` property', async () => { + const spy = jest.spyOn(PopperJs, 'createPopper'); const virtualReferenceElement = { getBoundingClientRect(): any { return { @@ -133,37 +90,40 @@ describe('Popper component', () => { }; }, }; - const wrapper = await mountPopper({ + await renderPopper({ referenceElement: virtualReferenceElement, }); - - expect(wrapper.instance().popperInstance.state.elements.reference).toBe( - virtualReferenceElement - ); + await waitFor(() => { + expect(spy.mock.calls[0][0]).toBe(virtualReferenceElement); + }); }); - it(`should render 3 times when placement is changed`, async () => { + fit(`should update placement when property is changed`, async () => { const referenceElement = document.createElement('div'); - let renderCounter = 0; - const wrapper = await new Promise((resolve) => { - const wrapper = mount( - resolve(wrapper.update())} - > - {({ ref, style, placement }) => { - renderCounter++; - return
; - }} - - ); - }); - expect(renderCounter).toBe(3); - renderCounter = 0; - wrapper.setProps({ placement: 'bottom' }); - await wrapper.instance().popperInstance.update(); - expect(renderCounter).toBe(3); + const Component = ({ placement }) => ( + + {({ ref, style, placement }) => ( +
+ {placement} +
+ )} +
+ ); + + const { rerender, getByTestId } = await render( + + ); + + expect(getByTestId('placement').textContent).toBe('top'); + + await rerender(); + + expect(getByTestId('placement').textContent).toBe('bottom'); }); }); diff --git a/src/Reference.js b/src/Reference.js index 0c960fa..eb12ff2 100644 --- a/src/Reference.js +++ b/src/Reference.js @@ -11,7 +11,7 @@ export type ReferenceProps = {| innerRef?: Ref, |}; -export default function Reference({ children, innerRef }: ReferenceProps) { +export function Reference({ children, innerRef }: ReferenceProps) { const setReferenceNode = React.useContext(ManagerReferenceNodeSetterContext); const refHandler = React.useCallback( diff --git a/src/Reference.test.js b/src/Reference.test.js index aa449b4..e15b47f 100644 --- a/src/Reference.test.js +++ b/src/Reference.test.js @@ -1,7 +1,7 @@ // @flow import React from 'react'; import warning from 'warning'; -import { mount } from 'enzyme'; +import { render } from '@testing-library/react'; // Public API import { Reference } from '.'; @@ -19,31 +19,21 @@ describe('Arrow component', () => { it('renders the expected markup', () => { const setReferenceNode = jest.fn(); - // HACK: wrapping DIV needed to make Enzyme happy for now - const wrapper = mount( -
- - {({ ref }) =>
} - -
+ const { asFragment } = render( + + {({ ref }) =>
} + ); - expect(wrapper.children()).toMatchSnapshot(); + expect(asFragment()).toMatchSnapshot(); }); it('consumes the ManagerReferenceNodeSetterContext from Manager', () => { const setReferenceNode = jest.fn(); - // HACK: wrapping DIV needed to make Enzyme happy for now - mount( -
- - {({ ref }) =>
} - -
+ render( + + {({ ref }) =>
} + ); expect(setReferenceNode).toHaveBeenCalled(); }); @@ -51,15 +41,10 @@ describe('Arrow component', () => { it('warns when setReferenceNode is present', () => { const setReferenceNode = jest.fn(); - // HACK: wrapping DIV needed to make Enzyme happy for now - mount( -
- - {({ ref }) =>
} - -
+ render( + + {({ ref }) =>
} + ); expect(warning).toHaveBeenCalledWith( true, @@ -68,15 +53,10 @@ describe('Arrow component', () => { }); it('does not warn when setReferenceNode is not present', () => { - // HACK: wrapping DIV needed to make Enzyme happy for now - mount( -
- - {({ ref }) =>
} - -
+ render( + + {({ ref }) =>
} + ); expect(warning).toHaveBeenCalledWith( false, diff --git a/src/__snapshots__/Manager.test.js.snap b/src/__snapshots__/Manager.test.js.snap index 431a20a..a65840d 100644 --- a/src/__snapshots__/Manager.test.js.snap +++ b/src/__snapshots__/Manager.test.js.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Manager component renders the expected markup 1`] = ` - +
- + `; diff --git a/src/__snapshots__/Popper.test.js.snap b/src/__snapshots__/Popper.test.js.snap index 8da3ea1..11e7cc5 100644 --- a/src/__snapshots__/Popper.test.js.snap +++ b/src/__snapshots__/Popper.test.js.snap @@ -1,36 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Popper component renders the expected markup 1`] = ` -} - strategy="absolute" -> +
- + `; diff --git a/src/__snapshots__/Reference.test.js.snap b/src/__snapshots__/Reference.test.js.snap index 985bfe0..8b5f24e 100644 --- a/src/__snapshots__/Reference.test.js.snap +++ b/src/__snapshots__/Reference.test.js.snap @@ -1,25 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Arrow component renders the expected markup 1`] = ` - - , - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - } - } - > -
- - + +
+ `; diff --git a/src/index.js b/src/index.js index 0bb23ce..eef8010 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,10 @@ // @flow // Public components -import Popper from './Popper'; -import Manager from './Manager'; -import Reference from './Reference'; -import usePopper from './usePopper'; +import { Popper } from './Popper'; +import { Manager } from './Manager'; +import { Reference } from './Reference'; +import { usePopper } from './usePopper'; export { Popper, Manager, Reference, usePopper }; // Public types diff --git a/src/usePopper.js b/src/usePopper.js index 1e0eb8d..2c860b8 100644 --- a/src/usePopper.js +++ b/src/usePopper.js @@ -5,6 +5,7 @@ import { type Options as PopperOptions, type VirtualElement, } from '@popperjs/core'; +import { fromEntries } from './utils'; type Options = $Shape<{ ...PopperOptions, @@ -20,14 +21,27 @@ type State = { }, }; +const EMPTY_MODIFIERS = []; + export const usePopper = ( referenceElement: ?(Element | VirtualElement), popperElement: ?HTMLElement, options: Options = {} ) => { + const optionsWithDefaults = { + onFirstUpdate: options.onFirstUpdate, + placement: options.placement || 'bottom', + strategy: options.strategy || 'absolute', + modifiers: options.modifiers || EMPTY_MODIFIERS, + }; + const [state, setState] = React.useState({ styles: { - popper: { position: options.strategy, left: '0', top: '0' }, + popper: { + position: optionsWithDefaults.strategy || 'absolute', + left: '0', + top: '0', + }, }, attributes: {}, }); @@ -41,10 +55,10 @@ export const usePopper = ( const elements = Object.keys(state.elements); setState({ - styles: Object.fromEntries( + styles: fromEntries( elements.map((element) => [element, state.styles[element] || {}]) ), - attributes: Object.fromEntries( + attributes: fromEntries( elements.map((element) => [element, state.attributes[element]]) ), }); @@ -56,16 +70,22 @@ export const usePopper = ( const popperOptions = React.useMemo(() => { return { - onFirstUpdate: options.onFirstUpdate, - placement: options.placement || 'bottom', - strategy: options.strategy || 'absolute', + onFirstUpdate: optionsWithDefaults.onFirstUpdate, + placement: optionsWithDefaults.placement || 'bottom', + strategy: optionsWithDefaults.strategy || 'absolute', modifiers: [ - ...options.modifiers, + ...optionsWithDefaults.modifiers, updateStateModifier, { name: 'applyStyles', enabled: false }, ], }; - }, [options, updateStateModifier]); + }, [ + optionsWithDefaults.onFirstUpdate, + optionsWithDefaults.placement, + optionsWithDefaults.strategy, + optionsWithDefaults.modifiers, + updateStateModifier, + ]); const popperInstanceRef = React.useRef(); const createPopper = React.useMemo( @@ -108,5 +128,3 @@ export const usePopper = ( : null, }; }; - -export default usePopper; diff --git a/src/usePopper.test.js b/src/usePopper.test.js new file mode 100644 index 0000000..ada8564 --- /dev/null +++ b/src/usePopper.test.js @@ -0,0 +1,91 @@ +import { renderHook } from '@testing-library/react-hooks'; +import * as PopperJs from '@popperjs/core'; + +// Public API +import { usePopper } from '.'; + +const referenceElement = document.createElement('div'); +const popperElement = document.createElement('div'); + +describe('userPopper', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('initializes the Popper instance', async () => { + const { result, wait } = renderHook(() => + usePopper(referenceElement, popperElement) + ); + + await wait(() => { + expect(result.current.state).not.toBe(null); + }); + }); + + it("doesn't update Popper instance on props update if not needed by Popper", async () => { + const spy = jest.spyOn(PopperJs, 'createPopper'); + const { wait, rerender } = renderHook( + ({ referenceElement, popperElement }) => + usePopper(referenceElement, popperElement), + { initialProps: { referenceElement, popperElement } } + ); + + rerender({ referenceElement, popperElement }); + + await wait(() => { + expect(spy).toHaveBeenCalledTimes(1); + }); + }); + + it('updates Popper on explicitly listed props change', async () => { + const spy = jest.spyOn(PopperJs, 'createPopper'); + + const { waitForNextUpdate, rerender } = renderHook( + ({ referenceElement, popperElement }) => + usePopper(referenceElement, popperElement), + { initialProps: { referenceElement, popperElement } } + ); + + rerender({ + referenceElement, + popperElement: document.createElement('div'), + }); + + await waitForNextUpdate(); + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('does not update Popper on generic props change', async () => { + const spy = jest.spyOn(PopperJs, 'createPopper'); + const { waitForNextUpdate, rerender } = renderHook( + ({ referenceElement, popperElement, options }) => + usePopper(referenceElement, popperElement, options), + { initialProps: { referenceElement, popperElement } } + ); + + rerender({ + referenceElement, + popperElement, + options: { foo: 'bar' }, + }); + + await waitForNextUpdate(); + + expect(spy).not.toHaveBeenCalledTimes(2); + }); + + it('destroys Popper on instance on unmount', async () => { + const spy = jest.spyOn(PopperJs, 'createPopper'); + const { waitForNextUpdate, unmount } = renderHook(() => + usePopper(referenceElement, popperElement) + ); + + await waitForNextUpdate(); + const popperInstance = spy.mock.results[0].value; + const destroy = jest.spyOn(popperInstance, 'destroy'); + + await unmount(); + + expect(destroy).toHaveBeenCalled(); + }); +}); diff --git a/src/utils.js b/src/utils.js index 736dd1e..16ce71c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,6 @@ // @flow -import { type Ref } from "./RefTypes"; +import { type Ref } from './RefTypes'; /** * Takes an argument and if it's an array, returns the first item in the array, @@ -13,21 +13,30 @@ export const unwrapArray = (arg: *): * => (Array.isArray(arg) ? arg[0] : arg); * only if it is defined. */ export const safeInvoke = (fn: ?Function, ...args: *) => { - if (typeof fn === "function") { + if (typeof fn === 'function') { return fn(...args); } -} +}; /** * Sets a ref using either a ref callback or a ref object */ export const setRef = (ref: ?Ref, node: ?HTMLElement) => { // if its a function call it - if (typeof ref === "function") { + if (typeof ref === 'function') { return safeInvoke(ref, node); } // otherwise we should treat it as a ref object else if (ref != null) { ref.current = node; } -} +}; + +/** + * Simple ponyfill for Object.fromEntries + */ +export const fromEntries = (entries: Array<[string, any]>) => + entries.reduce((acc, [key, value]) => { + acc[key] = value; + return acc; + }, {}); diff --git a/yarn.lock b/yarn.lock index a57966f..7ed4560 100644 --- a/yarn.lock +++ b/yarn.lock @@ -118,18 +118,6 @@ levenary "^1.1.1" semver "^5.5.0" -"@babel/helper-create-class-features-plugin@^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0" - integrity sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg== - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": version "7.8.8" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" @@ -299,13 +287,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== -"@babel/plugin-external-helpers@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.8.3.tgz#5a94164d9af393b2820a3cdc407e28ebf237de4b" - integrity sha512-mx0WXDDiIl5DwzMtzWGRSPugXi9BxROS05GQrhLNbEamhBiicgn994ibwkyiBH+6png7bm/yA7AUsvHyCXi4Vw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" @@ -315,14 +296,6 @@ "@babel/helper-remap-async-to-generator" "^7.8.3" "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-class-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e" - integrity sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" @@ -720,16 +693,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-runtime@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" - integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - resolve "^1.8.1" - semver "^5.5.1" - "@babel/plugin-transform-shorthand-properties@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" @@ -880,12 +843,7 @@ "@babel/plugin-transform-react-jsx-self" "^7.9.0" "@babel/plugin-transform-react-jsx-source" "^7.9.0" -"@babel/preset-stage-2@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/preset-stage-2/-/preset-stage-2-7.8.3.tgz#35055d2d22457706048deb5599c487943c05c241" - integrity sha512-dStnEQgejNYIHFNACdDCigK4BF7wgW6Zahv9Dc2un7rGjbeVtZhBfR3sy0I7ZJOhBexkFxVdMZ5hqmll7BFShw== - -"@babel/runtime-corejs3@^7.8.3": +"@babel/runtime-corejs3@^7.7.4", "@babel/runtime-corejs3@^7.8.3": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz#26fe4aa77e9f1ecef9b776559bbb8e84d34284b7" integrity sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA== @@ -893,7 +851,7 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== @@ -1225,6 +1183,16 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" +"@jest/types@^25.2.6": + version "25.2.6" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.2.6.tgz#c12f44af9bed444438091e4b59e7ed05f8659cb6" + integrity sha512-myJTTV37bxK7+3NgKc4Y/DlQ5q92/NOwZsZ+Uch7OXdElxOg61QYc72fPYNAjlvbnJ2YvbXLamIsa9tj48BmyQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -1291,6 +1259,34 @@ dependencies: type-detect "4.0.8" +"@testing-library/dom@^7.1.0": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.2.1.tgz#bb3b31d669bbe0c4939dadd95d69caa3c1d0b372" + integrity sha512-xIGoHlQ2ZiEL1dJIFKNmLDypzYF+4OJTTASRctl/aoIDaS5y/pRVHRigoqvPUV11mdJoR71IIgi/6UviMgyz4g== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__dom" "^7.0.0" + aria-query "^4.0.2" + dom-accessibility-api "^0.4.2" + pretty-format "^25.1.0" + +"@testing-library/react-hooks@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.2.1.tgz#19b6caa048ef15faa69d439c469033873ea01294" + integrity sha512-1OB6Ksvlk6BCJA1xpj8/WWz0XVd1qRcgqdaFAq+xeC6l61Ucj0P6QpA5u+Db/x9gU4DCX8ziR5b66Mlfg0M2RA== + dependencies: + "@babel/runtime" "^7.5.4" + "@types/testing-library__react-hooks" "^3.0.0" + +"@testing-library/react@^10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.0.2.tgz#8eca7aa52d810cf7150048a2829fdc487162006d" + integrity sha512-YT6Mw0oJz7R6vlEkmo1FlUD+K15FeXApOB5Ffm9zooFVnrwkt00w18dUJFMOh1yRp9wTdVRonbor7o4PIpFCmA== + dependencies: + "@babel/runtime" "^7.9.2" + "@testing-library/dom" "^7.1.0" + "@types/testing-library__react" "^10.0.0" + "@types/babel__core@^7.1.0": version "7.1.6" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610" @@ -1389,6 +1385,28 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== +"@types/react-dom@*": + version "16.9.6" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.6.tgz#9e7f83d90566521cc2083be2277c6712dcaf754c" + integrity sha512-S6ihtlPMDotrlCJE9ST1fRmYrQNNwfgL61UB4I1W7M6kPulUKx9fXAleW5zpdIjUQ4fTaaog8uERezjsGUj9HQ== + dependencies: + "@types/react" "*" + +"@types/react-test-renderer@*": + version "16.9.2" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5" + integrity sha512-4eJr1JFLIAlWhzDkBCkhrOIWOvOxcCAfQh+jiKg7l/nNZcCIL2MHl2dZhogIFKyHzedVWHaVP1Yydq/Ruu4agw== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "16.9.32" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.32.tgz#f6368625b224604148d1ddf5920e4fefbd98d383" + integrity sha512-fmejdp0CTH00mOJmxUPPbWCEBWPvRIL4m8r0qD+BSDUqmutPyGQCHifzMpMzdvZwROdEdL78IuZItntFWgPXHQ== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/react@^16.9.29": version "16.9.29" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.29.tgz#41c0353b5ea916cdb7a7e89b80b09b268c87a2f8" @@ -1409,6 +1427,30 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/testing-library__dom@*", "@types/testing-library__dom@^7.0.0": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-7.0.1.tgz#426bef0aa306a603fe071859d4b485941b28aca6" + integrity sha512-WokGRksRJb3Dla6h02/0/NNHTkjsj4S8aJZiwMj/5/UL8VZ1iCe3H8SHzfpmBeH8Vp4SPRT8iC2o9kYULFhDIw== + dependencies: + pretty-format "^25.1.0" + +"@types/testing-library__react-hooks@^3.0.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.2.0.tgz#52f3a109bef06080e3b1e3ae7ea1c014ce859897" + integrity sha512-dE8iMTuR5lzB+MqnxlzORlXzXyCL0EKfzH0w/lau20OpkHD37EaWjZDz0iNG8b71iEtxT4XKGmSKAGVEqk46mw== + dependencies: + "@types/react" "*" + "@types/react-test-renderer" "*" + +"@types/testing-library__react@^10.0.0": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-10.0.1.tgz#92bb4a02394bf44428e35f1da2970ed77f803593" + integrity sha512-RbDwmActAckbujLZeVO/daSfdL1pnjVqas25UueOkAY5r7vriavWf0Zqg7ghXMHa8ycD/kLkv8QOj31LmSYwww== + dependencies: + "@types/react-dom" "*" + "@types/testing-library__dom" "*" + pretty-format "^25.1.0" + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -1477,22 +1519,6 @@ acorn@^7.1.0, acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== -airbnb-prop-types@^2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz#5287820043af1eb469f5b0af0d6f70da6c52aaef" - integrity sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA== - dependencies: - array.prototype.find "^2.1.0" - function.prototype.name "^1.1.1" - has "^1.0.3" - is-regex "^1.0.4" - object-is "^1.0.1" - object.assign "^4.1.0" - object.entries "^1.1.0" - prop-types "^15.7.2" - prop-types-exact "^1.2.0" - react-is "^16.9.0" - ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: version "6.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" @@ -1585,6 +1611,14 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-query@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.0.2.tgz#250687b4ccde1ab86d127da0432ae3552fc7b145" + integrity sha512-S1G1V790fTaigUSM/Gd0NngzEfiMy9uTUfMyHhKhVyy4cH5O/eTuR01ydhGL0z4Za1PXFTRGH3qL8VhUQuEO5w== + dependencies: + "@babel/runtime" "^7.7.4" + "@babel/runtime-corejs3" "^7.7.4" + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1610,11 +1644,6 @@ array-equal@^1.0.0: resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - array-includes@^3.0.3, array-includes@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" @@ -1646,32 +1675,11 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.find@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c" - integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.4" - -array.prototype.flat@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" - integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - arrify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -2200,28 +2208,11 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -change-emitter@^0.1.2: - version "0.1.6" - resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" - integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= - chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -cheerio@^1.0.0-rc.3: - version "1.0.0-rc.3" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" - integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.1" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash "^4.15.0" - parse5 "^3.0.1" - chokidar@^2.1.5, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -2475,11 +2466,6 @@ core-js-pure@^3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= - core-js@^2.4.0, core-js@^2.6.5: version "2.6.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" @@ -2626,16 +2612,6 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" -css-select@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - css-selector-tokenizer@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz#11e5e27c9a48d90284f22d45061c303d7a25ad87" @@ -2661,11 +2637,6 @@ css-tree@1.0.0-alpha.39: mdn-data "2.0.6" source-map "^0.6.1" -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== - css-what@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" @@ -2916,11 +2887,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -discontinuous-range@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" - integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo= - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -2935,6 +2901,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz#93ca9002eb222fd5a343b6e5e6b9cf5929411c4c" + integrity sha512-JZ8iPuEHDQzq6q0k7PKMGbrIdsgBB7TRrtVOUm4nSMCExlg5qQG4KXWTH2k90yggjM4tTumRGwTKJSldMzKyLA== + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -2943,20 +2914,12 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -dom-serializer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" - integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== - dependencies: - domelementtype "^1.3.0" - entities "^1.1.1" - domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: +domelementtype@1, domelementtype@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== @@ -2980,14 +2943,6 @@ domhandler@^2.3.0: dependencies: domelementtype "1" -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - domutils@^1.5.1, domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -3076,13 +3031,6 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= - dependencies: - iconv-lite "~0.4.13" - end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -3090,7 +3038,7 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -entities@^1.1.1, entities@^1.1.2, entities@~1.1.1: +entities@^1.1.1, entities@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== @@ -3105,77 +3053,6 @@ envinfo@^7.3.1: resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== -enzyme-adapter-react-16@^1.15.2: - version "1.15.2" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501" - integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q== - dependencies: - enzyme-adapter-utils "^1.13.0" - enzyme-shallow-equal "^1.0.1" - has "^1.0.3" - object.assign "^4.1.0" - object.values "^1.1.1" - prop-types "^15.7.2" - react-is "^16.12.0" - react-test-renderer "^16.0.0-0" - semver "^5.7.0" - -enzyme-adapter-utils@^1.13.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz#01c885dde2114b4690bf741f8dc94cee3060eb78" - integrity sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ== - dependencies: - airbnb-prop-types "^2.15.0" - function.prototype.name "^1.1.2" - object.assign "^4.1.0" - object.fromentries "^2.0.2" - prop-types "^15.7.2" - semver "^5.7.1" - -enzyme-shallow-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz#7afe03db3801c9b76de8440694096412a8d9d49e" - integrity sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ== - dependencies: - has "^1.0.3" - object-is "^1.0.2" - -enzyme-to-json@^3.4.4: - version "3.4.4" - resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.4.4.tgz#b30726c59091d273521b6568c859e8831e94d00e" - integrity sha512-50LELP/SCPJJGic5rAARvU7pgE3m1YaNj7JLM+Qkhl5t7PAs6fiyc8xzc50RnkKPFQCv0EeFVjEWdIFRGPWMsA== - dependencies: - lodash "^4.17.15" - react-is "^16.12.0" - -enzyme@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28" - integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw== - dependencies: - array.prototype.flat "^1.2.3" - cheerio "^1.0.0-rc.3" - enzyme-shallow-equal "^1.0.1" - function.prototype.name "^1.1.2" - has "^1.0.3" - html-element-map "^1.2.0" - is-boolean-object "^1.0.1" - is-callable "^1.1.5" - is-number-object "^1.0.4" - is-regex "^1.0.5" - is-string "^1.0.5" - is-subset "^0.1.1" - lodash.escape "^4.0.1" - lodash.isequal "^4.5.0" - object-inspect "^1.7.0" - object-is "^1.0.2" - object.assign "^4.1.0" - object.entries "^1.1.1" - object.values "^1.1.1" - raf "^3.4.1" - rst-selector-parser "^2.2.3" - string.prototype.trim "^1.2.1" - error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -3183,7 +3060,7 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5: +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: version "1.17.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== @@ -3614,19 +3491,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fbjs@^0.8.1: - version "0.8.17" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" - integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.18" - figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -3820,25 +3684,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.1, function.prototype.name@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" - integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - functions-have-names "^1.2.0" - functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -functions-have-names@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91" - integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA== - gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" @@ -4093,11 +3943,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^2.3.1: - version "2.5.5" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" - integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== - hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" @@ -4113,13 +3958,6 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -html-element-map@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.2.0.tgz#dfbb09efe882806af63d990cf6db37993f099f22" - integrity sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw== - dependencies: - array-filter "^1.0.0" - html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" @@ -4151,7 +3989,7 @@ htmlnano@^0.2.2: terser "^4.3.9" uncss "^0.17.2" -htmlparser2@^3.9.1, htmlparser2@^3.9.2: +htmlparser2@^3.9.2: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -4201,7 +4039,7 @@ humanize-url@^1.0.0: normalize-url "^1.0.0" strip-url-auth "^1.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -4373,11 +4211,6 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== - is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -4507,11 +4340,6 @@ is-module@^1.0.0: resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= -is-number-object@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -4553,7 +4381,7 @@ is-reference@^1.1.2: dependencies: "@types/estree" "0.0.39" -is-regex@^1.0.4, is-regex@^1.0.5: +is-regex@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== @@ -4565,7 +4393,7 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -4580,11 +4408,6 @@ is-string@^1.0.5: resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= - is-svg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" @@ -4651,14 +4474,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -5314,21 +5129,6 @@ lodash.clone@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= -lodash.escape@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" - integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= - -lodash.flattendeep@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" - integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= - -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -5344,7 +5144,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.15.0, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4: +lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -5553,11 +5353,6 @@ mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -moo@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4" - integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w== - mri@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.5.tgz#ce21dba2c69f74a9b7cf8a1ec62307e089e223e0" @@ -5621,17 +5416,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -nearley@^2.7.10: - version "2.19.1" - resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.1.tgz#4af4006e16645ff800e9f993c3af039857d9dbdc" - integrity sha512-xq47GIUGXxU9vQg7g/y1o1xuKnkO7ev4nRWqftmQrLkfnE/FjRqDaGOUakM8XHPn/6pW3bGjU2wgoJyId90rqg== - dependencies: - commander "^2.19.0" - moo "^0.5.0" - railroad-diagrams "^1.0.0" - randexp "0.4.6" - semver "^5.4.1" - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -5642,14 +5426,6 @@ node-addon-api@^1.7.1: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - node-forge@^0.7.1: version "0.7.6" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" @@ -5763,7 +5539,7 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -nth-check@^1.0.2, nth-check@~1.0.1: +nth-check@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== @@ -5804,11 +5580,6 @@ object-inspect@~1.4.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== -object-is@^1.0.1, object-is@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" - integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== - object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -5831,7 +5602,7 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.entries@^1.1.0, object.entries@^1.1.1: +object.entries@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== @@ -6122,13 +5893,6 @@ parse5@5.1.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== -parse5@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" - integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== - dependencies: - "@types/node" "*" - parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -6639,6 +6403,16 @@ prettier@^2.0.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08" integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg== +pretty-format@^25.1.0: + version "25.2.6" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.2.6.tgz#542a1c418d019bbf1cca2e3620443bc1323cb8d7" + integrity sha512-DEiWxLBaCHneffrIT4B+TpMvkV9RNvvJrd3lY9ew1CEQobDzEXmYT1mg0hJhljZty7kCc10z13ohOFAE8jrUDg== + dependencies: + "@jest/types" "^25.2.6" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + pretty-format@^25.2.3: version "25.2.3" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.2.3.tgz#ba6e9603a0d80fa2e470b1fed55de1f9bfd81421" @@ -6681,13 +6455,6 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - prompts@^2.0.1: version "2.3.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" @@ -6696,15 +6463,6 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.4" -prop-types-exact@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869" - integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA== - dependencies: - has "^1.0.3" - object.assign "^4.1.0" - reflect.ownkeys "^0.2.0" - prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" @@ -6801,26 +6559,6 @@ quote-stream@^1.0.1, quote-stream@~1.0.2: minimist "^1.1.3" through2 "^2.0.0" -raf@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - -railroad-diagrams@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" - integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= - -randexp@0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" - integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== - dependencies: - discontinuous-range "1.0.0" - ret "~0.1.10" - randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -6851,16 +6589,11 @@ react-dom@^16.13.1: prop-types "^15.6.2" scheduler "^0.19.1" -react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0: +react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-lifecycles-compat@^3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - react-spring@^8.0.27: version "8.0.27" resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a" @@ -6869,7 +6602,7 @@ react-spring@^8.0.27: "@babel/runtime" "^7.3.1" prop-types "^15.5.8" -react-test-renderer@^16.0.0-0: +react-test-renderer@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1" integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ== @@ -6931,23 +6664,6 @@ realpath-native@^2.0.0: resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q== -recompose@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.30.0.tgz#82773641b3927e8c7d24a0d87d65aeeba18aabd0" - integrity sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w== - dependencies: - "@babel/runtime" "^7.0.0" - change-emitter "^0.1.2" - fbjs "^0.8.1" - hoist-non-react-statics "^2.3.1" - react-lifecycles-compat "^3.0.2" - symbol-observable "^1.0.4" - -reflect.ownkeys@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" - integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= - regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -7122,7 +6838,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.5, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1: +resolve@^1.1.5, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.4.0: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== @@ -7251,14 +6967,6 @@ rollup@^2.3.1: optionalDependencies: fsevents "~2.1.2" -rst-selector-parser@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" - integrity sha1-gbIw6i/MYGbInjRy3nlChdmwPZE= - dependencies: - lodash.flattendeep "^4.4.0" - nearley "^2.7.10" - rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -7340,7 +7048,7 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -7404,7 +7112,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -7730,15 +7438,6 @@ string.prototype.matchall@^4.0.2: regexp.prototype.flags "^1.3.0" side-channel "^1.0.2" -string.prototype.trim@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz#141233dff32c82bfad80684d7e5f0869ee0fb782" - integrity sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - string.prototype.trimend@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373" @@ -7921,11 +7620,6 @@ symbol-observable@1.0.4: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" integrity sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0= -symbol-observable@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -8179,11 +7873,6 @@ typescript@^3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -ua-parser-js@^0.7.18: - version "0.7.21" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" - integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== - uglify-js@^3.4.9: version "3.8.1" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.8.1.tgz#43bb15ce6f545eaa0a64c49fd29375ea09fa0f93" @@ -8427,11 +8116,6 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@>=0.10.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== - whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" From 6e321527e28af06f558985f1411731da113a78f1 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 8 Apr 2020 15:14:03 +0200 Subject: [PATCH 11/14] fix: useIsomorphicLayoutEffect --- src/usePopper.js | 6 +++--- src/utils.js | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/usePopper.js b/src/usePopper.js index 2c860b8..5ffecd4 100644 --- a/src/usePopper.js +++ b/src/usePopper.js @@ -5,7 +5,7 @@ import { type Options as PopperOptions, type VirtualElement, } from '@popperjs/core'; -import { fromEntries } from './utils'; +import { fromEntries, useIsomorphicLayoutEffect } from './utils'; type Options = $Shape<{ ...PopperOptions, @@ -93,7 +93,7 @@ export const usePopper = ( [options.createPopper] ); - React.useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { let popperInstance = null; if (referenceElement != null && popperElement != null) { popperInstance = createPopper( @@ -112,7 +112,7 @@ export const usePopper = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [referenceElement, popperElement, createPopper]); - React.useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (popperInstanceRef.current) { popperInstanceRef.current.setOptions(popperOptions); } diff --git a/src/utils.js b/src/utils.js index 16ce71c..75cd477 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,5 @@ // @flow - +import * as React from 'react'; import { type Ref } from './RefTypes'; /** @@ -40,3 +40,13 @@ export const fromEntries = (entries: Array<[string, any]>) => acc[key] = value; return acc; }, {}); + +/** + * Small wrapper around `useLayoutEffect` to get rid of the warning on SSR envs + */ +export const useIsomorphicLayoutEffect = + typeof window !== 'undefined' && + window.document && + window.document.createElement + ? React.useLayoutEffect + : React.useEffect; From 4c5d8b734d2634fef1c04810405ca2146e9cdc37 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Thu, 9 Apr 2020 14:36:23 +0200 Subject: [PATCH 12/14] docs: fix demo arrows --- demo/styles.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/demo/styles.js b/demo/styles.js index a1991c5..2da6f2e 100644 --- a/demo/styles.js +++ b/demo/styles.js @@ -104,8 +104,6 @@ export const Arrow = styled('div')` top: 0; left: 0; margin-top: -0.9em; - width: 3em; - height: 1em; &::before { border-width: 0 1.5em 1em 1.5em; border-color: transparent transparent #232323 transparent; @@ -114,9 +112,7 @@ export const Arrow = styled('div')` &[data-placement*='top'] { bottom: 0; left: 0; - margin-bottom: -0.9em; - width: 3em; - height: 1em; + margin-bottom: -2.9em; &::before { border-width: 1em 1.5em 0 1.5em; border-color: #232323 transparent transparent transparent; @@ -124,9 +120,7 @@ export const Arrow = styled('div')` } &[data-placement*='right'] { left: 0; - margin-left: -0.9em; - height: 3em; - width: 1em; + margin-left: -1.9em; &::before { border-width: 1.5em 1em 1.5em 0; border-color: transparent #232323 transparent transparent; @@ -134,9 +128,7 @@ export const Arrow = styled('div')` } &[data-placement*='left'] { right: 0; - margin-right: -0.9em; - height: 3em; - width: 1em; + margin-right: -1.9em; &::before { border-width: 1.5em 0 1.5em 1em; border-color: transparent transparent transparent#232323; From 083ce9a5d74a148337bb7f75b92739bfcab9e7a9 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Thu, 9 Apr 2020 15:03:17 +0200 Subject: [PATCH 13/14] docs: add usePopper documentation --- .prettierrc.js | 1 + README.md | 207 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 162 insertions(+), 46 deletions(-) diff --git a/.prettierrc.js b/.prettierrc.js index a425d3f..8a93033 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,4 +1,5 @@ module.exports = { singleQuote: true, trailingComma: 'es5', + proseWrap: 'always', }; diff --git a/README.md b/README.md index e4950cc..cd52281 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## React Popper +# React Popper [![Build Status](https://travis-ci.org/popperjs/react-popper.svg?branch=master)](https://travis-ci.org/popperjs/react-popper) [![npm version](https://img.shields.io/npm/v/react-popper.svg)](https://www.npmjs.com/package/react-popper) @@ -7,11 +7,12 @@ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![Get support or discuss](https://img.shields.io/badge/chat-on_spectrum-6833F9.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyBpZD0iTGl2ZWxsb18xIiBkYXRhLW5hbWU9IkxpdmVsbG8gMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTAgOCI%2BPGRlZnM%2BPHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7fTwvc3R5bGU%2BPC9kZWZzPjx0aXRsZT5zcGVjdHJ1bTwvdGl0bGU%2BPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNSwwQy40MiwwLDAsLjYzLDAsMy4zNGMwLDEuODQuMTksMi43MiwxLjc0LDMuMWgwVjcuNThhLjQ0LjQ0LDAsMCwwLC42OC4zNUw0LjM1LDYuNjlINWM0LjU4LDAsNS0uNjMsNS0zLjM1UzkuNTgsMCw1LDBaTTIuODMsNC4xOGEuNjMuNjMsMCwxLDEsLjY1LS42M0EuNjQuNjQsMCwwLDEsMi44Myw0LjE4Wk01LDQuMThhLjYzLjYzLDAsMSwxLC42NS0uNjNBLjY0LjY0LDAsMCwxLDUsNC4xOFptMi4xNywwYS42My42MywwLDEsMSwuNjUtLjYzQS42NC42NCwwLDAsMSw3LjE3LDQuMThaIi8%2BPC9zdmc%2B)](https://spectrum.chat/popper-js/react-popper) -React wrapper around [Popper.js](https://popper.js.org). +React wrapper around [Popper](https://popper.js.org). -**important note:** popper.js is **not** a tooltip library, it's a _positioning engine_ to be used to build features such as (but not restricted to) tooltips. +**important note:** Popper is **not** a tooltip library, it's a _positioning +engine_ to be used to build features such as (but not restricted to) tooltips. -## Install +# Install Via package managers: @@ -21,7 +22,8 @@ npm install react-popper @popperjs/core --save yarn add react-popper @popperjs/core ``` -**Note:** `@popperjs/core` must be installed in your project in order for `react-popper` to work. +**Note:** `@popperjs/core` must be installed in your project in order for +`react-popper` to work. Via `script` tag (UMD library exposed as `ReactPopper`): @@ -29,11 +31,52 @@ Via `script` tag (UMD library exposed as `ReactPopper`): ``` -## Usage +# Usage -> Using `react-popper@0.x`? You can find its documentation [clicking here](https://github.com/souporserious/react-popper/tree/v0.x) +> Using `react-popper@0.x`? You can find its documentation +> [clicking here](https://github.com/souporserious/react-popper/tree/v0.x) -Example: +`react-popper` provides two different APIs to help consume it: + +## React Hooks + +The `usePopper` hook can be used to quickly initialize a Popper, it requires a +basic understanding of how the +[React Hooks](https://reactjs.org/docs/hooks-overview.html) work. + +```jsx +import { useState } from 'react'; +import { usePopper } from 'react-popper'; + +const Example = () => { + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + const [arrowElement, setArrowElement] = useState(null); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + modifiers: [{ name: 'arrow', options: { element: arrowElement } }], + }); + + return ( + <> + + +
+ Popper element +
+
+ + ); +}; +``` + +## Render Props + +The `Manager`, `Reference` and `Popper` render-props components offer an +alternative API to initialize a Popper instance, they require a basic +understanding of how the +[React Render Props](https://reactjs.org/docs/render-props.html) work. ```jsx import { Manager, Reference, Popper } from 'react-popper'; @@ -59,19 +102,60 @@ const Example = () => ( ); ``` -`react-popper` makes use of a React pattern called **"render prop"**, if you are not -familiar with it, please read more [on the official React documentation](https://reactjs.org/docs/render-props.html). +## API documentation + +### Hooks + +The `usePopper` hook provides an API almost identical to the ones of +[`createPopper`](https://popper.js.org/docs/v2/constructors/#createpopper) +constructor. + +Rather than returning a Popper instance, it will return an object containing the +following properties: + +##### `styles` + +The `styles` property is an object, its properties are `popper`, and `arrow`. +The two properties are a +[`CSSStyleDeclaration` interface](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration) +describing the necessary CSS properties needed to properly position the two +elements. + +You can directly assign the value of the two properties to the React `style` +property of your components. + +```js +
+``` + +##### `attributes` -> Using React <=15 or Preact? The components created with them don't support to return -> [fragments](https://reactjs.org/docs/fragments.html), this means that you will need to -> wrap `` and `` into a single, common, `
` to make `react-popper` work. +Similar to `styles`, the `attributes` object lists the `popper` and `arrow` HTML +attributes, by default, only `popper` will hold some attributes (e.g. +`data-popper-placement`), but more generically, any HTML attribute described by +the Popper documentation will be available inside these properties. -### API documentation +The easiest way to consume their values is by destructuring them directly onto +your React component. -The `Manager` component is a simple wrapper that needs to surround all the other `react-popper` components in order -to make them communicate with each others. +```js +
+``` + +##### `update`, `forceUpdate`, and `state` + +These properties match the ones described in the +[Popper docs](https://popper.js.org/docs/v2/constructors/#types), the only +difference is that they can be `null` if Popper isn't yet been initialized or +has been destroyed. -The `Popper` component accepts the properties `children`, `placement`, `modifiers` and `strategy`. +### Render Props + +The `Manager` component is a simple wrapper that needs to surround all the other +`react-popper` components in order to make them communicate with each others. + +The `Popper` component accepts the properties `children`, `placement`, +`modifiers` and `strategy`. ```jsx Node ``` -A function (render prop) that takes as argument an object containing the following properties: - -- **`ref`**: used to retrieve the [React refs](https://reactjs.org/docs/refs-and-the-dom.html) of the **popper** element. -- **`style`**: contains the necessary CSS styles (React CSS properties) which are computed by Popper.js to correctly position the **popper** element. -- **`placement`**: describes the placement of your popper after Popper.js has applied all the modifiers - that may have flipped or altered the originally provided `placement` property. You can use this to alter the - style of the popper and or of the arrow according to the definitive placement. For instance, you can use this - property to orient the arrow to the right direction. -- **`isReferenceHidden`**: a boolean signifying the reference element is fully clipped and hidden from view. -- **`hasPopperEscaped`**: a boolean signifying the popper escapes the reference element's boundary (and so it appears detached). -- **`update`**: a function you can ask Popper to recompute your tooltip's position . It will directly call the [Popper#update](https://popper.js.org/docs/v2/lifecycle/#manual-update) method. -- **`arrowProps`**: an object, containing `style` and `ref` properties that are identical to the - ones provided as the first and second arguments of `children`, but relative to the **arrow** element. The `style` property contains `left` and `top` offset values, which are used to center the arrow within the popper. These values can be merged with further custom styling and positioning. See [the demo](https://github.com/FezVrasta/react-popper/blob/8994933c430e48ab62e71495be71e4f440b48a5a/demo/styles.js#L100) for an example. +A function (render prop) that takes as argument an object containing the +following properties: + +- **`ref`**: used to retrieve the + [React refs](https://reactjs.org/docs/refs-and-the-dom.html) of the **popper** + element. +- **`style`**: contains the necessary CSS styles (React CSS properties) which + are computed by Popper.js to correctly position the **popper** element. +- **`placement`**: describes the placement of your popper after Popper.js has + applied all the modifiers that may have flipped or altered the originally + provided `placement` property. You can use this to alter the style of the + popper and or of the arrow according to the definitive placement. For + instance, you can use this property to orient the arrow to the right + direction. +- **`isReferenceHidden`**: a boolean signifying the reference element is fully + clipped and hidden from view. +- **`hasPopperEscaped`**: a boolean signifying the popper escapes the reference + element's boundary (and so it appears detached). +- **`update`**: a function you can ask Popper to recompute your tooltip's + position . It will directly call the + [Popper#update](https://popper.js.org/docs/v2/lifecycle/#manual-update) + method. +- **`arrowProps`**: an object, containing `style` and `ref` properties that are + identical to the ones provided as the first and second arguments of + `children`, but relative to the **arrow** element. The `style` property + contains `left` and `top` offset values, which are used to center the arrow + within the popper. These values can be merged with further custom styling and + positioning. See + [the demo](https://github.com/FezVrasta/react-popper/blob/8994933c430e48ab62e71495be71e4f440b48a5a/demo/styles.js#L100) + for an example. ##### `innerRef` @@ -130,13 +231,17 @@ Function that can be used to obtain popper reference placement?: PopperJS$Placement; ``` -One of the accepted placement values listed in the [Popper.js documentation](https://popper.js.org/popper-documentation.html#Popper.placements). +One of the accepted placement values listed in the +[Popper.js documentation](https://popper.js.org/popper-documentation.html#Popper.placements). Your popper is going to be placed according to the value of this property. Defaults to `bottom`. ##### `strategy` -Describes the positioning strategy to use. By default, it is `absolute`, which in the simplest cases does not require repositioning of the popper. If your reference element is in a `fixed` container, use the `fixed` strategy. [Read More](https://popper.js.org/docs/v2/constructors/#strategy) +Describes the positioning strategy to use. By default, it is `absolute`, which +in the simplest cases does not require repositioning of the popper. If your +reference element is in a `fixed` container, use the `fixed` strategy. +[Read More](https://popper.js.org/docs/v2/constructors/#strategy) ##### `modifiers` @@ -144,19 +249,23 @@ Describes the positioning strategy to use. By default, it is `absolute`, which i modifiers?: PopperJS$Modifiers; ``` -An object containing custom settings for the [Popper.js modifiers](https://popper.js.org/docs/v2/modifiers/). -You can use this property to override their settings or to inject your custom ones. +An object containing custom settings for the +[Popper.js modifiers](https://popper.js.org/docs/v2/modifiers/). +You can use this property to override their settings or to inject your custom +ones. ## Usage with `ReactDOM.createPortal` -Popper.js is smart enough to work even if the **popper** and **reference** elements aren't -in the same DOM context. -This means that you can use [`ReactDOM.createPortal`](https://reactjs.org/docs/portals.html) -(or any pre React 16 alternative) to move the popper component somewhere else in the DOM. +Popper.js is smart enough to work even if the **popper** and **reference** +elements aren't in the same DOM context. +This means that you can use +[`ReactDOM.createPortal`](https://reactjs.org/docs/portals.html) (or any pre +React 16 alternative) to move the popper component somewhere else in the DOM. -This can be useful if you want to position a tooltip inside an `overflow: hidden` container -that you want to make overflow. Please note that you can also try the `positionFixed` strategy -to obtain a similar effect with less hassle. +This can be useful if you want to position a tooltip inside an +`overflow: hidden` container that you want to make overflow. Please note that +you can also try the `positionFixed` strategy to obtain a similar effect with +less hassle. ```jsx import { Manager, Reference, Popper } from 'react-popper'; @@ -186,11 +295,17 @@ const Example = () => ( ## Usage without a reference `HTMLElement` -Whenever you need to position a popper based on some arbitrary coordinates, you can provide `Popper` with a `referenceElement` property that is going to be used in place of the `referenceProps.getRef` React ref. +Whenever you need to position a popper based on some arbitrary coordinates, you +can provide `Popper` with a `referenceElement` property that is going to be used +in place of the `referenceProps.getRef` React ref. -The `referenceElement` property must be an object with an interface compatible with an `HTMLElement` as described in the [Popper.js virtualElement documentation](https://popper.js.org/docs/v2/virtual-elements/), this implies that you may also provide a real HTMLElement if needed. +The `referenceElement` property must be an object with an interface compatible +with an `HTMLElement` as described in the +[Popper.js virtualElement documentation](https://popper.js.org/docs/v2/virtual-elements/), +this implies that you may also provide a real HTMLElement if needed. -If `referenceElement` is defined, it will take precedence over any `referenceProps.ref` provided refs. +If `referenceElement` is defined, it will take precedence over any +`referenceProps.ref` provided refs. ```jsx import { Popper } from 'react-popper'; @@ -231,8 +346,8 @@ const Example = () => ( This library is built with Flow but it supports TypeScript as well. -You can find the exported Flow types in `src/index.js`, and the -TypeScript definitions in `typings/react-popper.d.ts`. +You can find the exported Flow types in `src/index.js`, and the TypeScript +definitions in `typings/react-popper.d.ts`. ## Running Locally From 6aff227cca1f3b21273b88ab9668e61fd28756ad Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Thu, 9 Apr 2020 15:28:42 +0200 Subject: [PATCH 14/14] fix: prevent loop renders --- package.json | 1 + src/usePopper.js | 14 ++++++++++++-- yarn.lock | 5 +++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fd2de73..fab309f 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "react": "^16.8.0" }, "dependencies": { + "react-fast-compare": "^3.0.1", "warning": "^4.0.2" }, "devDependencies": { diff --git a/src/usePopper.js b/src/usePopper.js index 5ffecd4..91ed283 100644 --- a/src/usePopper.js +++ b/src/usePopper.js @@ -5,6 +5,7 @@ import { type Options as PopperOptions, type VirtualElement, } from '@popperjs/core'; +import isEqual from 'react-fast-compare'; import { fromEntries, useIsomorphicLayoutEffect } from './utils'; type Options = $Shape<{ @@ -17,7 +18,7 @@ type State = { [key: string]: $Shape, }, attributes: { - [key: string]: { [key: string]: string | boolean }, + [key: string]: { [key: string]: string }, }, }; @@ -28,6 +29,8 @@ export const usePopper = ( popperElement: ?HTMLElement, options: Options = {} ) => { + const prevOptions = React.useRef(null); + const optionsWithDefaults = { onFirstUpdate: options.onFirstUpdate, placement: options.placement || 'bottom', @@ -69,7 +72,7 @@ export const usePopper = ( ); const popperOptions = React.useMemo(() => { - return { + const newOptions = { onFirstUpdate: optionsWithDefaults.onFirstUpdate, placement: optionsWithDefaults.placement || 'bottom', strategy: optionsWithDefaults.strategy || 'absolute', @@ -79,6 +82,13 @@ export const usePopper = ( { name: 'applyStyles', enabled: false }, ], }; + + if (isEqual(prevOptions.current, newOptions)) { + return prevOptions.current || newOptions; + } else { + prevOptions.current = newOptions; + return newOptions; + } }, [ optionsWithDefaults.onFirstUpdate, optionsWithDefaults.placement, diff --git a/yarn.lock b/yarn.lock index 7ed4560..c0bdfe9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6589,6 +6589,11 @@ react-dom@^16.13.1: prop-types "^15.6.2" scheduler "^0.19.1" +react-fast-compare@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.0.1.tgz#884d339ce1341aad22392e7a88664c71da48600e" + integrity sha512-C5vP0J644ofZGd54P8++O7AvrqMEbrGf8Ue0eAUJLJyw168dAX2aiYyX/zcY/eSNwO0IDjsKUaLE6n83D+TnEg== + react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"