├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .flowconfig ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── tests.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── demo ├── index.html ├── index.js └── styles.js ├── flow-typed └── npm │ └── jest_v25.x.x.js ├── jest.setup.js ├── package.json ├── rollup.config.js ├── src ├── Manager.js ├── Manager.test.js ├── Popper.js ├── Popper.test.js ├── RefTypes.js ├── Reference.js ├── Reference.test.js ├── __snapshots__ │ ├── Manager.test.js.snap │ ├── Popper.test.js.snap │ └── Reference.test.js.snap ├── __typings__ │ └── main-test.js ├── index.js ├── usePopper.js ├── usePopper.test.js └── utils.js ├── typings ├── react-popper.d.ts └── tests │ ├── main-test.tsx │ ├── svg-test.tsx │ └── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { "modules": false, "loose": true }], 4 | "@babel/flow", 5 | "@babel/react" 6 | ], 7 | "env": { 8 | "esm": { 9 | "ignore": ["**/*.test.js", "**/__mocks__/**"] 10 | }, 11 | "cjs": { 12 | "plugins": [["@babel/transform-modules-commonjs"]], 13 | "ignore": ["**/*.test.js", "**/__mocks__/**"] 14 | }, 15 | "test": { 16 | "plugins": ["@babel/transform-modules-commonjs"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | reportUnusedDisableDirectives: true, 4 | parser: 'babel-eslint', 5 | env: { 6 | browser: true, 7 | }, 8 | extends: ['plugin:react-hooks/recommended'], 9 | plugins: ['react', 'react-hooks'], 10 | rules: { 11 | 'no-unused-vars': 'error', 12 | 'react/jsx-uses-vars': 'error', 13 | 'react/jsx-uses-react': 'error', 14 | 'react/react-in-jsx-scope': 'error', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /lib/.* 3 | .*/.cache 4 | 5 | [include] 6 | 7 | [libs] 8 | 9 | [lints] 10 | sketchy-number=error 11 | sketchy-null=error 12 | 13 | [options] 14 | react.runtime=automatic 15 | exact_by_default=true 16 | 17 | [strict] 18 | 19 | [declarations] 20 | /node_modules 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 18 | 19 | ### Reproduction demo 20 | 21 | 28 | 29 | 30 | ### Steps to reproduce the problem 31 | 32 | 1. 33 | 2. 34 | 3. 35 | 36 | ### What is the expected behavior? 37 | 38 | 39 | 40 | ### What went wrong? 41 | 42 | 43 | 44 | ### Any other comments? 45 | 46 | 47 | 48 | ### Packages versions 49 | 50 | - Popper.js: 51 | - react-popper: 52 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CI: true 7 | 8 | jobs: 9 | checks: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | - run: yarn install 15 | - run: yarn test 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OSX Files 2 | .DS_Store 3 | .Trashes 4 | .Spotlight-V100 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # NPM / Yarn 9 | node_modules 10 | npm-debug.log 11 | dist 12 | lib 13 | yarn-error.log 14 | coverage 15 | 16 | # General Files 17 | .sass-cache 18 | .hg 19 | .idea 20 | .svn 21 | .cache 22 | .project 23 | .tmp 24 | .vscode 25 | *.log 26 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'flow', 3 | singleQuote: true, 4 | trailingComma: 'es5', 5 | proseWrap: 'always', 6 | }; 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | Here are listed the changelogs until 0.8.2, if you are looking for more recent releases changelog please refer to the dedicated GitHub [releases](https://github.com/souporserious/react-popper/releases) page, where you will find all the releases plus the changelog for each of them. 4 | 5 | 6 | ---------- 7 | 8 | ### 0.8.2 9 | 10 | fix es5 build [#90](https://github.com/souporserious/react-popper/pull/90) 11 | 12 | ### 0.8.1 13 | 14 | Move back to controlling DOM updates through React 15 | 16 | Update & clean dependencies [#89](https://github.com/souporserious/react-popper/pull/89) 17 | 18 | ### 0.8.0 19 | 20 | Upgrade PopperJS dependency to `1.12.9` 21 | 22 | Fix `Popper` ref getting called too many times [#81](https://github.com/souporserious/react-popper/issues/81) 23 | 24 | Let PopperJS style DOM for better performance as described in [vjeux's talk](https://speakerdeck.com/vjeux/react-rally-animated-react-performance-toolbox). 25 | 26 | ### 0.7.5 27 | 28 | Fix PopperJS instantiation [#77](https://github.com/souporserious/react-popper/pull/77) 29 | 30 | ### 0.7.4 31 | 32 | Allow React 16 as a peerDependency [#59](https://github.com/souporserious/react-popper/pull/59) 33 | 34 | Updates TypeScript definition for IPopperChildrenProps [#61](https://github.com/souporserious/react-popper/pull/61) 35 | 36 | Made scripts platform independent [#63](https://github.com/souporserious/react-popper/pull/63) 37 | 38 | ### 0.7.3 39 | 40 | Upgraded dependencies [#44](https://github.com/souporserious/react-popper/pull/44) 41 | 42 | Fix missing data-x-out-of-boundaries attribute [#45](https://github.com/souporserious/react-popper/pull/45) 43 | 44 | Update how react-popper.d.ts imports PopperJS [#51](https://github.com/souporserious/react-popper/pull/51) 45 | 46 | ### 0.7.2 47 | 48 | Fix `top` and `left` arrow calculation. Disregard the note below about changing the CSS positioning, this was an error on `react-popper`'s part. 49 | 50 | ### 0.7.1 51 | 52 | Support `top` and `left` arrow offsets together, be aware this most likely broke any prior CSS positioning your arrows [#37](https://github.com/souporserious/react-popper/pull/37) 53 | 54 | Fix `scheduleUpdate` call if `this._popper` does not exist [#38](https://github.com/souporserious/react-popper/pull/38) 55 | 56 | Add typescript definitions [#40](https://github.com/souporserious/react-popper/pull/40) 57 | 58 | Upgrade to Popper.js 1.10.8 59 | 60 | ### 0.7.0 61 | 62 | Change `Target`, `Popper`, and `Arrow` component's `tag` prop to `component` to allow custom components to be passed in. 63 | 64 | Upgrade PopperJS 1.10.2 65 | 66 | ### 0.6.6 67 | 68 | Upgrade PopperJS to 1.9.9 69 | 70 | ### 0.6.5 71 | 72 | Call `innerRef` in _all_ component child functions 73 | 74 | ### 0.6.4 75 | 76 | Call `innerRef` in child function as well 77 | 78 | ### 0.6.3 79 | 80 | Upgrade PopperJS to 1.9.5 81 | 82 | ### 0.6.2 83 | 84 | Replace `lodash.isequal` with `is-equal-shallow` 85 | 86 | ### 0.6.1 87 | 88 | Pass down `scheduleUpdate` to `Popper` child function to allow programatic updates 89 | 90 | Upgrade to Popper.js 1.9.4 91 | 92 | Fix `modifier.function` is deprecated, use `modifier.fn` [#22](https://github.com/souporserious/react-popper/pull/22) 93 | 94 | ### 0.6.0 95 | 96 | Make sure to pass props from above down to child function, fixes [#13](https://github.com/souporserious/react-popper/issues/13) 97 | 98 | Recalculate size of `Popper` when children change, fixes [#15](https://github.com/souporserious/react-popper/issues/15) 99 | 100 | ### 0.5.0 101 | 102 | Use `prop-types` package instead of React PropTypes [#9](https://github.com/souporserious/react-popper/pull/9) 103 | 104 | Make updateState modifier return data object [#11](https://github.com/souporserious/react-popper/pull/11) 105 | 106 | Removed `findDOMNode` 🎉 107 | 108 | Moved back to `tag` instead of `component`. Use a child function now for custom components and pass down the provided ref to the proper component. 109 | 110 | Removed default classNames for `popper` and `popper__arrow` so we can be unopinionated about styling. 111 | 112 | ### 0.4.3 113 | 114 | Allow passing children through to components 115 | 116 | ### 0.4.2 117 | 118 | Move back to `translate3d` and round values since half pixel placement was the culprit for causing blurry text 119 | 120 | ### 0.4.1 121 | 122 | Don't use `translate3d` since it causes blurry text on lower res displays 123 | 124 | ### 0.4.0 125 | 126 | Remove `getRef` function since it seems to be causing problems. 127 | 128 | Move functional components to classes so we can get nodes more reliably. 129 | 130 | Spread modifier styles inside `_getPopperStyle` [#6](https://github.com/souporserious/react-popper/pull/6) 131 | 132 | ### 0.3.0 133 | 134 | Renamed `PopperManager` -> `Manager` 135 | 136 | Added `getRef` prop to `Target`, `Popper`, and `Arrow` components 137 | 138 | ### 0.2.2 139 | 140 | Bundle popper.js with dist build 141 | 142 | ### 0.2.1 143 | 144 | Remove React ARIA from demos for now 145 | 146 | ### 0.2.0 147 | 148 | New API see README for full docs 149 | 150 | ### 0.1.0 151 | 152 | Initial release 153 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 React Popper authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Popper 2 | 3 | [![Unit Tests](https://github.com/popperjs/react-popper/workflows/Unit%20Tests/badge.svg)](https://github.com/popperjs/react-popper/actions?query=workflow%3A%22Unit+Tests%22) 4 | [![npm version](https://img.shields.io/npm/v/react-popper.svg)](https://www.npmjs.com/package/react-popper) 5 | [![npm downloads](https://img.shields.io/npm/dm/react-popper.svg)](https://www.npmjs.com/package/react-popper) 6 | [![Dependency Status](https://david-dm.org/souporserious/react-popper.svg)](https://david-dm.org/souporserious/react-popper) 7 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 8 | [![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) 9 | 10 | React wrapper around [Popper](https://popper.js.org). 11 | 12 | --- 13 | 14 | ## ⚠️ This library is in maintenance mode! 15 | 16 | This library wraps `@popperjs/core`, not `@floating-ui/dom`. 17 | 18 | To use the new Floating UI package with React, instead visit https://floating-ui.com/docs/react-dom. 19 | 20 | --- 21 | 22 | ## Install 23 | 24 | Via package managers: 25 | 26 | ```bash 27 | # With npm 28 | npm i react-popper @popperjs/core 29 | 30 | # With Yarn 31 | yarn add react-popper @popperjs/core 32 | ``` 33 | 34 | **Note:** `@popperjs/core` must be installed in your project in order for 35 | `react-popper` to work. 36 | 37 | Via `script` tag (UMD library exposed as `ReactPopper`): 38 | 39 | ```html 40 | 41 | ``` 42 | 43 | ## Documentation 44 | 45 | The full documentation can be found on the official Popper website: 46 | 47 | http://popper.js.org/react-popper 48 | 49 | 50 | ## Running Locally 51 | 52 | #### clone repo 53 | 54 | `git clone git@github.com:popperjs/react-popper.git` 55 | 56 | #### move into folder 57 | 58 | `cd ~/react-popper` 59 | 60 | #### install dependencies 61 | 62 | `npm install` or `yarn` 63 | 64 | #### run dev mode 65 | 66 | `npm run demo:dev` or `yarn demo:dev` 67 | 68 | #### open your browser and visit: 69 | 70 | `http://localhost:1234/` 71 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | react-popper 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import React, { useState } from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import { Global, css } from '@emotion/core'; 5 | import { Transition } from 'react-spring/renderprops'; 6 | import { Manager, Reference, Popper } from '../src'; 7 | import { 8 | Main, 9 | ReferenceBox, 10 | ClickableReferenceBox, 11 | PoppersContainer, 12 | TransitionedPopperBox, 13 | PopperBox, 14 | Arrow, 15 | PopperDot, 16 | } from './styles'; 17 | 18 | const Null = () => null; 19 | 20 | const placements = ['top', 'right', 'bottom', 'left']; 21 | 22 | const modifiers = [ 23 | { 24 | name: 'flip', 25 | enabled: false, 26 | }, 27 | { 28 | name: 'hide', 29 | enabled: false, 30 | }, 31 | ]; 32 | 33 | const popperModifiers = [ 34 | ...modifiers, 35 | { 36 | name: 'arrow', 37 | options: { 38 | padding: 5, 39 | }, 40 | }, 41 | { 42 | name: 'offset', 43 | options: { 44 | offset: [0, 14], 45 | }, 46 | }, 47 | ]; 48 | 49 | const dotModifiers = [ 50 | ...modifiers, 51 | { 52 | name: 'offset', 53 | options: { 54 | offset: [0, 56], 55 | }, 56 | }, 57 | ]; 58 | 59 | const mainModifiers = [ 60 | ...popperModifiers, 61 | // We can't use adaptive styles with CSS transitions 62 | { 63 | name: 'computeStyles', 64 | options: { 65 | adaptive: false, 66 | }, 67 | }, 68 | ]; 69 | 70 | const animatedModifiers = [ 71 | ...popperModifiers, 72 | // We disable the built-in gpuAcceleration so that 73 | // Popper.js will return us easy to interpolate values 74 | // (top, left instead of transform: translate3d) 75 | // We'll then use these values to generate the needed 76 | // css transform values blended with the react-spring values 77 | { 78 | name: 'computeStyles', 79 | options: { 80 | gpuAcceleration: false, 81 | }, 82 | }, 83 | ]; 84 | 85 | const Demo = () => { 86 | const [activePlacement, setActivePlacement] = useState( 87 | placements[Math.floor(Math.random() * placements.length)] 88 | ); 89 | const [isPopper2Open, togglePopper2] = useState(false); 90 | 91 | return ( 92 | <> 93 | 115 | 116 |
117 | 118 | 119 | {({ ref }) => ( 120 | 121 | 125 | react-popper 126 | 127 | 128 | )} 129 | 130 | 131 | 132 | {({ ref, style, placement, arrowProps }) => ( 133 | 134 | {placement} 135 | 140 | 141 | )} 142 | 143 | {placements 144 | .filter((p) => p !== activePlacement) 145 | .map((p) => ( 146 | 147 | {({ ref, style }) => ( 148 | setActivePlacement(p)} 152 | title={p} 153 | /> 154 | )} 155 | 156 | ))} 157 | 158 | 159 |
160 |
161 | 162 | 163 | {({ ref }) => ( 164 | togglePopper2(!isPopper2Open)} 168 | > 169 | Click to toggle 170 | 171 | )} 172 | 173 | 179 | {(show) => 180 | show 181 | ? ({ rotation, scale, opacity, top: topOffset }) => ( 182 | 183 | {({ 184 | ref, 185 | style: { top, left, position }, 186 | placement, 187 | arrowProps, 188 | }) => ( 189 | 204 | 208 | react-spring 209 | 210 | animated 211 | 216 | 217 | )} 218 | 219 | ) 220 | : Null 221 | } 222 | 223 | 224 |
225 | 226 | ); 227 | }; 228 | 229 | const rootNode = document.querySelector('#root'); 230 | 231 | rootNode && ReactDOM.render(, rootNode); 232 | -------------------------------------------------------------------------------- /demo/styles.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { keyframes } from '@emotion/core'; 3 | 4 | const gradients = { 5 | purple: 'linear-gradient(to right, #9d50bb, #6e48aa)', 6 | orange: 'linear-gradient(to right, #ff4e50, #f9d423)', 7 | pink: 'linear-gradient(to right, #f857a6, #ff5858)', 8 | blue: 'linear-gradient(to right, #4b6cb7, #182848)', 9 | green: 'linear-gradient(to right, #134E5E, #71B280)', 10 | }; 11 | 12 | export const Main = styled('main')` 13 | overflow: hidden; 14 | min-height: 30em; 15 | display: flex; 16 | flex-direction: column; 17 | justify-content: center; 18 | align-items: center; 19 | height: 100vh; 20 | background-image: ${(props) => gradients[props.gradient]}; 21 | color: #ffffff; 22 | clip-path: polygon(99% 1%, 99% 95%, 50% 99%, 1% 95%, 1% 1%, 50% 5%); 23 | &:first-of-type { 24 | clip-path: polygon(99% 2%, 99% 97%, 50% 100%, 1% 97%, 1% 2%); 25 | } 26 | &:last-of-type { 27 | height: calc(100vh - 0.5em); 28 | clip-path: polygon(99% 0%, 99% 98%, 50% 98%, 1% 98%, 1% 0%, 50% 3%); 29 | } 30 | `; 31 | 32 | export const ReferenceBox = styled('div')` 33 | display: flex; 34 | flex-direction: column; 35 | justify-content: center; 36 | align-items: center; 37 | width: 10em; 38 | height: 6em; 39 | background-color: #ffffff; 40 | color: #000000; 41 | border-radius: 4px; 42 | z-index: 1; 43 | position: relative; 44 | 45 | a { 46 | color: #000000; 47 | } 48 | `; 49 | 50 | export const ClickableReferenceBox = styled(ReferenceBox)` 51 | cursor: pointer; 52 | `; 53 | 54 | export const PopperBox = styled('div')` 55 | display: flex; 56 | flex-direction: column; 57 | justify-content: center; 58 | align-items: center; 59 | width: 6em; 60 | height: 6em; 61 | background-color: #232323; 62 | color: #ffffff; 63 | border-radius: 10px; 64 | padding: 0.5em; 65 | text-align: center; 66 | ${(props) => props.popperStyle}; 67 | `; 68 | 69 | export const TransitionedPopperBox = styled(PopperBox)` 70 | transition: all 0.2s ease-out; 71 | `; 72 | 73 | export const fadeIn = keyframes` 74 | from { opacity: 0; } 75 | to { opacity: 1; } 76 | `; 77 | export const PoppersContainer = styled('div')` 78 | opacity: 0; 79 | animation: ${fadeIn} 0.3s ease-in 0.5s forwards; 80 | `; 81 | 82 | export const pulse = keyframes` 83 | 0% { box-shadow: 0 0 0 rgba(0, 0, 0, .2); } 84 | 50% { box-shadow: 0 0 0 4px rgba(0, 0, 0, .2); } 85 | 100% { box-shadow: 0 0 0 rgba(0, 0, 0, .2); } 86 | `; 87 | 88 | export const PopperDot = styled('button')` 89 | cursor: pointer; 90 | border: 0; 91 | font-size: inherit; 92 | width: 1em; 93 | height: 1em; 94 | border-radius: 50%; 95 | background-color: #232323; 96 | animation: ${pulse} 2s ease infinite; 97 | `; 98 | 99 | export const Arrow = styled('div')` 100 | position: absolute; 101 | width: 3em; 102 | height: 3em; 103 | &[data-placement*='bottom'] { 104 | top: 0; 105 | left: 0; 106 | margin-top: -0.9em; 107 | &::before { 108 | border-width: 0 1.5em 1em 1.5em; 109 | border-color: transparent transparent #232323 transparent; 110 | } 111 | } 112 | &[data-placement*='top'] { 113 | bottom: 0; 114 | left: 0; 115 | margin-bottom: -2.9em; 116 | &::before { 117 | border-width: 1em 1.5em 0 1.5em; 118 | border-color: #232323 transparent transparent transparent; 119 | } 120 | } 121 | &[data-placement*='right'] { 122 | left: 0; 123 | margin-left: -1.9em; 124 | &::before { 125 | border-width: 1.5em 1em 1.5em 0; 126 | border-color: transparent #232323 transparent transparent; 127 | } 128 | } 129 | &[data-placement*='left'] { 130 | right: 0; 131 | margin-right: -1.9em; 132 | &::before { 133 | border-width: 1.5em 0 1.5em 1em; 134 | border-color: transparent transparent transparent#232323; 135 | } 136 | } 137 | &::before { 138 | content: ''; 139 | margin: auto; 140 | display: block; 141 | width: 0; 142 | height: 0; 143 | border-style: solid; 144 | } 145 | `; 146 | -------------------------------------------------------------------------------- /flow-typed/npm/jest_v25.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: b074a60bb87f6f6d496e019aec4703b2 2 | // flow-typed version: 7223a8293e/jest_v25.x.x/flow_>=v0.104.x 3 | 4 | type JestMockFn, TReturn> = { 5 | (...args: TArguments): TReturn, 6 | /** 7 | * An object for introspecting mock calls 8 | */ 9 | mock: { 10 | /** 11 | * An array that represents all calls that have been made into this mock 12 | * function. Each call is represented by an array of arguments that were 13 | * passed during the call. 14 | */ 15 | calls: Array, 16 | /** 17 | * An array that contains all the object instances that have been 18 | * instantiated from this mock function. 19 | */ 20 | instances: Array, 21 | /** 22 | * An array that contains all the object results that have been 23 | * returned by this mock function call 24 | */ 25 | results: Array<{ 26 | isThrow: boolean, 27 | value: TReturn, 28 | ... 29 | }>, 30 | ... 31 | }, 32 | /** 33 | * Resets all information stored in the mockFn.mock.calls and 34 | * mockFn.mock.instances arrays. Often this is useful when you want to clean 35 | * up a mock's usage data between two assertions. 36 | */ 37 | mockClear(): void, 38 | /** 39 | * Resets all information stored in the mock. This is useful when you want to 40 | * completely restore a mock back to its initial state. 41 | */ 42 | mockReset(): void, 43 | /** 44 | * Removes the mock and restores the initial implementation. This is useful 45 | * when you want to mock functions in certain test cases and restore the 46 | * original implementation in others. Beware that mockFn.mockRestore only 47 | * works when mock was created with jest.spyOn. Thus you have to take care of 48 | * restoration yourself when manually assigning jest.fn(). 49 | */ 50 | mockRestore(): void, 51 | /** 52 | * Accepts a function that should be used as the implementation of the mock. 53 | * The mock itself will still record all calls that go into and instances 54 | * that come from itself -- the only difference is that the implementation 55 | * will also be executed when the mock is called. 56 | */ 57 | mockImplementation( 58 | fn: (...args: TArguments) => TReturn 59 | ): JestMockFn, 60 | /** 61 | * Accepts a function that will be used as an implementation of the mock for 62 | * one call to the mocked function. Can be chained so that multiple function 63 | * calls produce different results. 64 | */ 65 | mockImplementationOnce( 66 | fn: (...args: TArguments) => TReturn 67 | ): JestMockFn, 68 | /** 69 | * Accepts a string to use in test result output in place of "jest.fn()" to 70 | * indicate which mock function is being referenced. 71 | */ 72 | mockName(name: string): JestMockFn, 73 | /** 74 | * Just a simple sugar function for returning `this` 75 | */ 76 | mockReturnThis(): void, 77 | /** 78 | * Accepts a value that will be returned whenever the mock function is called. 79 | */ 80 | mockReturnValue(value: TReturn): JestMockFn, 81 | /** 82 | * Sugar for only returning a value once inside your mock 83 | */ 84 | mockReturnValueOnce(value: TReturn): JestMockFn, 85 | /** 86 | * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value)) 87 | */ 88 | mockResolvedValue(value: TReturn): JestMockFn>, 89 | /** 90 | * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) 91 | */ 92 | mockResolvedValueOnce( 93 | value: TReturn 94 | ): JestMockFn>, 95 | /** 96 | * Sugar for jest.fn().mockImplementation(() => Promise.reject(value)) 97 | */ 98 | mockRejectedValue(value: TReturn): JestMockFn>, 99 | /** 100 | * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value)) 101 | */ 102 | mockRejectedValueOnce(value: TReturn): JestMockFn>, 103 | ... 104 | }; 105 | 106 | type JestAsymmetricEqualityType = { /** 107 | * A custom Jasmine equality tester 108 | */ 109 | asymmetricMatch(value: mixed): boolean, ... }; 110 | 111 | type JestCallsType = { 112 | allArgs(): mixed, 113 | all(): mixed, 114 | any(): boolean, 115 | count(): number, 116 | first(): mixed, 117 | mostRecent(): mixed, 118 | reset(): void, 119 | ... 120 | }; 121 | 122 | type JestClockType = { 123 | install(): void, 124 | mockDate(date: Date): void, 125 | tick(milliseconds?: number): void, 126 | uninstall(): void, 127 | ... 128 | }; 129 | 130 | type JestMatcherResult = { 131 | message?: string | (() => string), 132 | pass: boolean, 133 | ... 134 | }; 135 | 136 | type JestMatcher = ( 137 | received: any, 138 | ...actual: Array 139 | ) => JestMatcherResult | Promise; 140 | 141 | type JestPromiseType = { 142 | /** 143 | * Use rejects to unwrap the reason of a rejected promise so any other 144 | * matcher can be chained. If the promise is fulfilled the assertion fails. 145 | */ 146 | rejects: JestExpectType, 147 | /** 148 | * Use resolves to unwrap the value of a fulfilled promise so any other 149 | * matcher can be chained. If the promise is rejected the assertion fails. 150 | */ 151 | resolves: JestExpectType, 152 | ... 153 | }; 154 | 155 | /** 156 | * Jest allows functions and classes to be used as test names in test() and 157 | * describe() 158 | */ 159 | type JestTestName = string | Function; 160 | 161 | /** 162 | * Plugin: jest-styled-components 163 | */ 164 | 165 | type JestStyledComponentsMatcherValue = 166 | | string 167 | | JestAsymmetricEqualityType 168 | | RegExp 169 | | typeof undefined; 170 | 171 | type JestStyledComponentsMatcherOptions = { 172 | media?: string, 173 | modifier?: string, 174 | supports?: string, 175 | ... 176 | }; 177 | 178 | type JestStyledComponentsMatchersType = { toHaveStyleRule( 179 | property: string, 180 | value: JestStyledComponentsMatcherValue, 181 | options?: JestStyledComponentsMatcherOptions 182 | ): void, ... }; 183 | 184 | /** 185 | * Plugin: jest-enzyme 186 | */ 187 | type EnzymeMatchersType = { 188 | // 5.x 189 | toBeEmpty(): void, 190 | toBePresent(): void, 191 | // 6.x 192 | toBeChecked(): void, 193 | toBeDisabled(): void, 194 | toBeEmptyRender(): void, 195 | toContainMatchingElement(selector: string): void, 196 | toContainMatchingElements(n: number, selector: string): void, 197 | toContainExactlyOneMatchingElement(selector: string): void, 198 | toContainReact(element: React$Element): void, 199 | toExist(): void, 200 | toHaveClassName(className: string): void, 201 | toHaveHTML(html: string): void, 202 | toHaveProp: ((propKey: string, propValue?: any) => void) & 203 | ((props: {...}) => void), 204 | toHaveRef(refName: string): void, 205 | toHaveState: ((stateKey: string, stateValue?: any) => void) & 206 | ((state: {...}) => void), 207 | toHaveStyle: ((styleKey: string, styleValue?: any) => void) & 208 | ((style: {...}) => void), 209 | toHaveTagName(tagName: string): void, 210 | toHaveText(text: string): void, 211 | toHaveValue(value: any): void, 212 | toIncludeText(text: string): void, 213 | toMatchElement( 214 | element: React$Element, 215 | options?: {| ignoreProps?: boolean, verbose?: boolean |} 216 | ): void, 217 | toMatchSelector(selector: string): void, 218 | // 7.x 219 | toHaveDisplayName(name: string): void, 220 | ... 221 | }; 222 | 223 | // DOM testing library extensions (jest-dom) 224 | // https://github.com/testing-library/jest-dom 225 | type DomTestingLibraryType = { 226 | /** 227 | * @deprecated 228 | */ 229 | toBeInTheDOM(container?: HTMLElement): void, 230 | toBeInTheDocument(): void, 231 | toBeVisible(): void, 232 | toBeEmpty(): void, 233 | toBeDisabled(): void, 234 | toBeEnabled(): void, 235 | toBeInvalid(): void, 236 | toBeRequired(): void, 237 | toBeValid(): void, 238 | toContainElement(element: HTMLElement | null): void, 239 | toContainHTML(htmlText: string): void, 240 | toHaveAttribute(attr: string, value?: any): void, 241 | toHaveClass(...classNames: string[]): void, 242 | toHaveFocus(): void, 243 | toHaveFormValues(expectedValues: { [name: string]: any, ... }): void, 244 | toHaveStyle(css: string): void, 245 | toHaveTextContent( 246 | text: string | RegExp, 247 | options?: { normalizeWhitespace: boolean, ... } 248 | ): void, 249 | toHaveValue(value?: string | string[] | number): void, 250 | ... 251 | }; 252 | 253 | // Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers 254 | type JestJQueryMatchersType = { 255 | toExist(): void, 256 | toHaveLength(len: number): void, 257 | toHaveId(id: string): void, 258 | toHaveClass(className: string): void, 259 | toHaveTag(tag: string): void, 260 | toHaveAttr(key: string, val?: any): void, 261 | toHaveProp(key: string, val?: any): void, 262 | toHaveText(text: string | RegExp): void, 263 | toHaveData(key: string, val?: any): void, 264 | toHaveValue(val: any): void, 265 | toHaveCss(css: { [key: string]: any, ... }): void, 266 | toBeChecked(): void, 267 | toBeDisabled(): void, 268 | toBeEmpty(): void, 269 | toBeHidden(): void, 270 | toBeSelected(): void, 271 | toBeVisible(): void, 272 | toBeFocused(): void, 273 | toBeInDom(): void, 274 | toBeMatchedBy(sel: string): void, 275 | toHaveDescendant(sel: string): void, 276 | toHaveDescendantWithText(sel: string, text: string | RegExp): void, 277 | ... 278 | }; 279 | 280 | // Jest Extended Matchers: https://github.com/jest-community/jest-extended 281 | type JestExtendedMatchersType = { 282 | /** 283 | * Note: Currently unimplemented 284 | * Passing assertion 285 | * 286 | * @param {String} message 287 | */ 288 | // pass(message: string): void; 289 | 290 | /** 291 | * Note: Currently unimplemented 292 | * Failing assertion 293 | * 294 | * @param {String} message 295 | */ 296 | // fail(message: string): void; 297 | 298 | /** 299 | * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. 300 | */ 301 | toBeEmpty(): void, 302 | /** 303 | * Use .toBeOneOf when checking if a value is a member of a given Array. 304 | * @param {Array.<*>} members 305 | */ 306 | toBeOneOf(members: any[]): void, 307 | /** 308 | * Use `.toBeNil` when checking a value is `null` or `undefined`. 309 | */ 310 | toBeNil(): void, 311 | /** 312 | * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. 313 | * @param {Function} predicate 314 | */ 315 | toSatisfy(predicate: (n: any) => boolean): void, 316 | /** 317 | * Use `.toBeArray` when checking if a value is an `Array`. 318 | */ 319 | toBeArray(): void, 320 | /** 321 | * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. 322 | * @param {Number} x 323 | */ 324 | toBeArrayOfSize(x: number): void, 325 | /** 326 | * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. 327 | * @param {Array.<*>} members 328 | */ 329 | toIncludeAllMembers(members: any[]): void, 330 | /** 331 | * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. 332 | * @param {Array.<*>} members 333 | */ 334 | toIncludeAnyMembers(members: any[]): void, 335 | /** 336 | * 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. 337 | * @param {Function} predicate 338 | */ 339 | toSatisfyAll(predicate: (n: any) => boolean): void, 340 | /** 341 | * Use `.toBeBoolean` when checking if a value is a `Boolean`. 342 | */ 343 | toBeBoolean(): void, 344 | /** 345 | * Use `.toBeTrue` when checking a value is equal (===) to `true`. 346 | */ 347 | toBeTrue(): void, 348 | /** 349 | * Use `.toBeFalse` when checking a value is equal (===) to `false`. 350 | */ 351 | toBeFalse(): void, 352 | /** 353 | * Use .toBeDate when checking if a value is a Date. 354 | */ 355 | toBeDate(): void, 356 | /** 357 | * Use `.toBeFunction` when checking if a value is a `Function`. 358 | */ 359 | toBeFunction(): void, 360 | /** 361 | * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. 362 | * 363 | * Note: Required Jest version >22 364 | * 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 365 | * 366 | * @param {Mock} mock 367 | */ 368 | toHaveBeenCalledBefore(mock: JestMockFn): void, 369 | /** 370 | * Use `.toBeNumber` when checking if a value is a `Number`. 371 | */ 372 | toBeNumber(): void, 373 | /** 374 | * Use `.toBeNaN` when checking a value is `NaN`. 375 | */ 376 | toBeNaN(): void, 377 | /** 378 | * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. 379 | */ 380 | toBeFinite(): void, 381 | /** 382 | * Use `.toBePositive` when checking if a value is a positive `Number`. 383 | */ 384 | toBePositive(): void, 385 | /** 386 | * Use `.toBeNegative` when checking if a value is a negative `Number`. 387 | */ 388 | toBeNegative(): void, 389 | /** 390 | * Use `.toBeEven` when checking if a value is an even `Number`. 391 | */ 392 | toBeEven(): void, 393 | /** 394 | * Use `.toBeOdd` when checking if a value is an odd `Number`. 395 | */ 396 | toBeOdd(): void, 397 | /** 398 | * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). 399 | * 400 | * @param {Number} start 401 | * @param {Number} end 402 | */ 403 | toBeWithin(start: number, end: number): void, 404 | /** 405 | * Use `.toBeObject` when checking if a value is an `Object`. 406 | */ 407 | toBeObject(): void, 408 | /** 409 | * Use `.toContainKey` when checking if an object contains the provided key. 410 | * 411 | * @param {String} key 412 | */ 413 | toContainKey(key: string): void, 414 | /** 415 | * Use `.toContainKeys` when checking if an object has all of the provided keys. 416 | * 417 | * @param {Array.} keys 418 | */ 419 | toContainKeys(keys: string[]): void, 420 | /** 421 | * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. 422 | * 423 | * @param {Array.} keys 424 | */ 425 | toContainAllKeys(keys: string[]): void, 426 | /** 427 | * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. 428 | * 429 | * @param {Array.} keys 430 | */ 431 | toContainAnyKeys(keys: string[]): void, 432 | /** 433 | * Use `.toContainValue` when checking if an object contains the provided value. 434 | * 435 | * @param {*} value 436 | */ 437 | toContainValue(value: any): void, 438 | /** 439 | * Use `.toContainValues` when checking if an object contains all of the provided values. 440 | * 441 | * @param {Array.<*>} values 442 | */ 443 | toContainValues(values: any[]): void, 444 | /** 445 | * Use `.toContainAllValues` when checking if an object only contains all of the provided values. 446 | * 447 | * @param {Array.<*>} values 448 | */ 449 | toContainAllValues(values: any[]): void, 450 | /** 451 | * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. 452 | * 453 | * @param {Array.<*>} values 454 | */ 455 | toContainAnyValues(values: any[]): void, 456 | /** 457 | * Use `.toContainEntry` when checking if an object contains the provided entry. 458 | * 459 | * @param {Array.} entry 460 | */ 461 | toContainEntry(entry: [string, string]): void, 462 | /** 463 | * Use `.toContainEntries` when checking if an object contains all of the provided entries. 464 | * 465 | * @param {Array.>} entries 466 | */ 467 | toContainEntries(entries: [string, string][]): void, 468 | /** 469 | * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. 470 | * 471 | * @param {Array.>} entries 472 | */ 473 | toContainAllEntries(entries: [string, string][]): void, 474 | /** 475 | * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. 476 | * 477 | * @param {Array.>} entries 478 | */ 479 | toContainAnyEntries(entries: [string, string][]): void, 480 | /** 481 | * Use `.toBeExtensible` when checking if an object is extensible. 482 | */ 483 | toBeExtensible(): void, 484 | /** 485 | * Use `.toBeFrozen` when checking if an object is frozen. 486 | */ 487 | toBeFrozen(): void, 488 | /** 489 | * Use `.toBeSealed` when checking if an object is sealed. 490 | */ 491 | toBeSealed(): void, 492 | /** 493 | * Use `.toBeString` when checking if a value is a `String`. 494 | */ 495 | toBeString(): void, 496 | /** 497 | * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. 498 | * 499 | * @param {String} string 500 | */ 501 | toEqualCaseInsensitive(string: string): void, 502 | /** 503 | * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. 504 | * 505 | * @param {String} prefix 506 | */ 507 | toStartWith(prefix: string): void, 508 | /** 509 | * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. 510 | * 511 | * @param {String} suffix 512 | */ 513 | toEndWith(suffix: string): void, 514 | /** 515 | * Use `.toInclude` when checking if a `String` includes the given `String` substring. 516 | * 517 | * @param {String} substring 518 | */ 519 | toInclude(substring: string): void, 520 | /** 521 | * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. 522 | * 523 | * @param {String} substring 524 | * @param {Number} times 525 | */ 526 | toIncludeRepeated(substring: string, times: number): void, 527 | /** 528 | * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. 529 | * 530 | * @param {Array.} substring 531 | */ 532 | toIncludeMultiple(substring: string[]): void, 533 | ... 534 | }; 535 | 536 | interface JestExpectType { 537 | not: JestExpectType & 538 | EnzymeMatchersType & 539 | DomTestingLibraryType & 540 | JestJQueryMatchersType & 541 | JestStyledComponentsMatchersType & 542 | JestExtendedMatchersType; 543 | /** 544 | * If you have a mock function, you can use .lastCalledWith to test what 545 | * arguments it was last called with. 546 | */ 547 | lastCalledWith(...args: Array): void; 548 | /** 549 | * toBe just checks that a value is what you expect. It uses === to check 550 | * strict equality. 551 | */ 552 | toBe(value: any): void; 553 | /** 554 | * Use .toBeCalledWith to ensure that a mock function was called with 555 | * specific arguments. 556 | */ 557 | toBeCalledWith(...args: Array): void; 558 | /** 559 | * Using exact equality with floating point numbers is a bad idea. Rounding 560 | * means that intuitive things fail. 561 | */ 562 | toBeCloseTo(num: number, delta: any): void; 563 | /** 564 | * Use .toBeDefined to check that a variable is not undefined. 565 | */ 566 | toBeDefined(): void; 567 | /** 568 | * Use .toBeFalsy when you don't care what a value is, you just want to 569 | * ensure a value is false in a boolean context. 570 | */ 571 | toBeFalsy(): void; 572 | /** 573 | * To compare floating point numbers, you can use toBeGreaterThan. 574 | */ 575 | toBeGreaterThan(number: number): void; 576 | /** 577 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 578 | */ 579 | toBeGreaterThanOrEqual(number: number): void; 580 | /** 581 | * To compare floating point numbers, you can use toBeLessThan. 582 | */ 583 | toBeLessThan(number: number): void; 584 | /** 585 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 586 | */ 587 | toBeLessThanOrEqual(number: number): void; 588 | /** 589 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 590 | * class. 591 | */ 592 | toBeInstanceOf(cls: Class<*>): void; 593 | /** 594 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 595 | * nicer. 596 | */ 597 | toBeNull(): void; 598 | /** 599 | * Use .toBeTruthy when you don't care what a value is, you just want to 600 | * ensure a value is true in a boolean context. 601 | */ 602 | toBeTruthy(): void; 603 | /** 604 | * Use .toBeUndefined to check that a variable is undefined. 605 | */ 606 | toBeUndefined(): void; 607 | /** 608 | * Use .toContain when you want to check that an item is in a list. For 609 | * testing the items in the list, this uses ===, a strict equality check. 610 | */ 611 | toContain(item: any): void; 612 | /** 613 | * Use .toContainEqual when you want to check that an item is in a list. For 614 | * testing the items in the list, this matcher recursively checks the 615 | * equality of all fields, rather than checking for object identity. 616 | */ 617 | toContainEqual(item: any): void; 618 | /** 619 | * Use .toEqual when you want to check that two objects have the same value. 620 | * This matcher recursively checks the equality of all fields, rather than 621 | * checking for object identity. 622 | */ 623 | toEqual(value: any): void; 624 | /** 625 | * Use .toHaveBeenCalled to ensure that a mock function got called. 626 | */ 627 | toHaveBeenCalled(): void; 628 | toBeCalled(): void; 629 | /** 630 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 631 | * number of times. 632 | */ 633 | toHaveBeenCalledTimes(number: number): void; 634 | toBeCalledTimes(number: number): void; 635 | /** 636 | * 637 | */ 638 | toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; 639 | nthCalledWith(nthCall: number, ...args: Array): void; 640 | /** 641 | * 642 | */ 643 | toHaveReturned(): void; 644 | toReturn(): void; 645 | /** 646 | * 647 | */ 648 | toHaveReturnedTimes(number: number): void; 649 | toReturnTimes(number: number): void; 650 | /** 651 | * 652 | */ 653 | toHaveReturnedWith(value: any): void; 654 | toReturnWith(value: any): void; 655 | /** 656 | * 657 | */ 658 | toHaveLastReturnedWith(value: any): void; 659 | lastReturnedWith(value: any): void; 660 | /** 661 | * 662 | */ 663 | toHaveNthReturnedWith(nthCall: number, value: any): void; 664 | nthReturnedWith(nthCall: number, value: any): void; 665 | /** 666 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 667 | * specific arguments. 668 | */ 669 | toHaveBeenCalledWith(...args: Array): void; 670 | toBeCalledWith(...args: Array): void; 671 | /** 672 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 673 | * with specific arguments. 674 | */ 675 | toHaveBeenLastCalledWith(...args: Array): void; 676 | lastCalledWith(...args: Array): void; 677 | /** 678 | * Check that an object has a .length property and it is set to a certain 679 | * numeric value. 680 | */ 681 | toHaveLength(number: number): void; 682 | /** 683 | * 684 | */ 685 | toHaveProperty(propPath: string | $ReadOnlyArray, value?: any): void; 686 | /** 687 | * Use .toMatch to check that a string matches a regular expression or string. 688 | */ 689 | toMatch(regexpOrString: RegExp | string): void; 690 | /** 691 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 692 | */ 693 | toMatchObject(object: Object | Array): void; 694 | /** 695 | * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. 696 | */ 697 | toStrictEqual(value: any): void; 698 | /** 699 | * This ensures that an Object matches the most recent snapshot. 700 | */ 701 | toMatchSnapshot(propertyMatchers?: any, name?: string): void; 702 | /** 703 | * This ensures that an Object matches the most recent snapshot. 704 | */ 705 | toMatchSnapshot(name: string): void; 706 | 707 | toMatchInlineSnapshot(snapshot?: string): void; 708 | toMatchInlineSnapshot(propertyMatchers?: any, snapshot?: string): void; 709 | /** 710 | * Use .toThrow to test that a function throws when it is called. 711 | * If you want to test that a specific error gets thrown, you can provide an 712 | * argument to toThrow. The argument can be a string for the error message, 713 | * a class for the error, or a regex that should match the error. 714 | * 715 | * Alias: .toThrowError 716 | */ 717 | toThrow(message?: string | Error | Class | RegExp): void; 718 | toThrowError(message?: string | Error | Class | RegExp): void; 719 | /** 720 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 721 | * matching the most recent snapshot when it is called. 722 | */ 723 | toThrowErrorMatchingSnapshot(): void; 724 | toThrowErrorMatchingInlineSnapshot(snapshot?: string): void; 725 | } 726 | 727 | type JestObjectType = { 728 | /** 729 | * Disables automatic mocking in the module loader. 730 | * 731 | * After this method is called, all `require()`s will return the real 732 | * versions of each module (rather than a mocked version). 733 | */ 734 | disableAutomock(): JestObjectType, 735 | /** 736 | * An un-hoisted version of disableAutomock 737 | */ 738 | autoMockOff(): JestObjectType, 739 | /** 740 | * Enables automatic mocking in the module loader. 741 | */ 742 | enableAutomock(): JestObjectType, 743 | /** 744 | * An un-hoisted version of enableAutomock 745 | */ 746 | autoMockOn(): JestObjectType, 747 | /** 748 | * Clears the mock.calls and mock.instances properties of all mocks. 749 | * Equivalent to calling .mockClear() on every mocked function. 750 | */ 751 | clearAllMocks(): JestObjectType, 752 | /** 753 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 754 | * mocked function. 755 | */ 756 | resetAllMocks(): JestObjectType, 757 | /** 758 | * Restores all mocks back to their original value. 759 | */ 760 | restoreAllMocks(): JestObjectType, 761 | /** 762 | * Removes any pending timers from the timer system. 763 | */ 764 | clearAllTimers(): void, 765 | /** 766 | * Returns the number of fake timers still left to run. 767 | */ 768 | getTimerCount(): number, 769 | /** 770 | * The same as `mock` but not moved to the top of the expectation by 771 | * babel-jest. 772 | */ 773 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 774 | /** 775 | * The same as `unmock` but not moved to the top of the expectation by 776 | * babel-jest. 777 | */ 778 | dontMock(moduleName: string): JestObjectType, 779 | /** 780 | * Returns a new, unused mock function. Optionally takes a mock 781 | * implementation. 782 | */ 783 | fn, TReturn>( 784 | implementation?: (...args: TArguments) => TReturn 785 | ): JestMockFn, 786 | /** 787 | * Determines if the given function is a mocked function. 788 | */ 789 | isMockFunction(fn: Function): boolean, 790 | /** 791 | * Given the name of a module, use the automatic mocking system to generate a 792 | * mocked version of the module for you. 793 | */ 794 | genMockFromModule(moduleName: string): any, 795 | /** 796 | * Mocks a module with an auto-mocked version when it is being required. 797 | * 798 | * The second argument can be used to specify an explicit module factory that 799 | * is being run instead of using Jest's automocking feature. 800 | * 801 | * The third argument can be used to create virtual mocks -- mocks of modules 802 | * that don't exist anywhere in the system. 803 | */ 804 | mock( 805 | moduleName: string, 806 | moduleFactory?: any, 807 | options?: Object 808 | ): JestObjectType, 809 | /** 810 | * Returns the actual module instead of a mock, bypassing all checks on 811 | * whether the module should receive a mock implementation or not. 812 | */ 813 | requireActual(moduleName: string): any, 814 | /** 815 | * Returns a mock module instead of the actual module, bypassing all checks 816 | * on whether the module should be required normally or not. 817 | */ 818 | requireMock(moduleName: string): any, 819 | /** 820 | * Resets the module registry - the cache of all required modules. This is 821 | * useful to isolate modules where local state might conflict between tests. 822 | */ 823 | resetModules(): JestObjectType, 824 | /** 825 | * Creates a sandbox registry for the modules that are loaded inside the 826 | * callback function. This is useful to isolate specific modules for every 827 | * test so that local module state doesn't conflict between tests. 828 | */ 829 | isolateModules(fn: () => void): JestObjectType, 830 | /** 831 | * Exhausts the micro-task queue (usually interfaced in node via 832 | * process.nextTick). 833 | */ 834 | runAllTicks(): void, 835 | /** 836 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 837 | * setInterval(), and setImmediate()). 838 | */ 839 | runAllTimers(): void, 840 | /** 841 | * Exhausts all tasks queued by setImmediate(). 842 | */ 843 | runAllImmediates(): void, 844 | /** 845 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 846 | * or setInterval() and setImmediate()). 847 | */ 848 | advanceTimersByTime(msToRun: number): void, 849 | /** 850 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 851 | * or setInterval() and setImmediate()). 852 | * 853 | * Renamed to `advanceTimersByTime`. 854 | */ 855 | runTimersToTime(msToRun: number): void, 856 | /** 857 | * Executes only the macro-tasks that are currently pending (i.e., only the 858 | * tasks that have been queued by setTimeout() or setInterval() up to this 859 | * point) 860 | */ 861 | runOnlyPendingTimers(): void, 862 | /** 863 | * Explicitly supplies the mock object that the module system should return 864 | * for the specified module. Note: It is recommended to use jest.mock() 865 | * instead. 866 | */ 867 | setMock(moduleName: string, moduleExports: any): JestObjectType, 868 | /** 869 | * Indicates that the module system should never return a mocked version of 870 | * the specified module from require() (e.g. that it should always return the 871 | * real module). 872 | */ 873 | unmock(moduleName: string): JestObjectType, 874 | /** 875 | * Instructs Jest to use fake versions of the standard timer functions 876 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 877 | * setImmediate and clearImmediate). 878 | */ 879 | useFakeTimers(): JestObjectType, 880 | /** 881 | * Instructs Jest to use the real versions of the standard timer functions. 882 | */ 883 | useRealTimers(): JestObjectType, 884 | /** 885 | * Creates a mock function similar to jest.fn but also tracks calls to 886 | * object[methodName]. 887 | */ 888 | spyOn( 889 | object: Object, 890 | methodName: string, 891 | accessType?: 'get' | 'set' 892 | ): JestMockFn, 893 | /** 894 | * Set the default timeout interval for tests and before/after hooks in milliseconds. 895 | * Note: The default timeout interval is 5 seconds if this method is not called. 896 | */ 897 | setTimeout(timeout: number): JestObjectType, 898 | ... 899 | }; 900 | 901 | type JestSpyType = { calls: JestCallsType, ... }; 902 | 903 | type JestDoneFn = {| 904 | (): void, 905 | fail: (error: Error) => void, 906 | |}; 907 | 908 | /** Runs this function after every test inside this context */ 909 | declare function afterEach( 910 | fn: (done: JestDoneFn) => ?Promise, 911 | timeout?: number 912 | ): void; 913 | /** Runs this function before every test inside this context */ 914 | declare function beforeEach( 915 | fn: (done: JestDoneFn) => ?Promise, 916 | timeout?: number 917 | ): void; 918 | /** Runs this function after all tests have finished inside this context */ 919 | declare function afterAll( 920 | fn: (done: JestDoneFn) => ?Promise, 921 | timeout?: number 922 | ): void; 923 | /** Runs this function before any tests have started inside this context */ 924 | declare function beforeAll( 925 | fn: (done: JestDoneFn) => ?Promise, 926 | timeout?: number 927 | ): void; 928 | 929 | /** A context for grouping tests together */ 930 | declare var describe: { 931 | /** 932 | * Creates a block that groups together several related tests in one "test suite" 933 | */ 934 | (name: JestTestName, fn: () => void): void, 935 | /** 936 | * Only run this describe block 937 | */ 938 | only(name: JestTestName, fn: () => void): void, 939 | /** 940 | * Skip running this describe block 941 | */ 942 | skip(name: JestTestName, fn: () => void): void, 943 | /** 944 | * each runs this test against array of argument arrays per each run 945 | * 946 | * @param {table} table of Test 947 | */ 948 | each( 949 | ...table: Array | mixed> | [Array, string] 950 | ): ( 951 | name: JestTestName, 952 | fn?: (...args: Array) => ?Promise, 953 | timeout?: number 954 | ) => void, 955 | ... 956 | }; 957 | 958 | /** An individual test unit */ 959 | declare var it: { 960 | /** 961 | * An individual test unit 962 | * 963 | * @param {JestTestName} Name of Test 964 | * @param {Function} Test 965 | * @param {number} Timeout for the test, in milliseconds. 966 | */ 967 | ( 968 | name: JestTestName, 969 | fn?: (done: JestDoneFn) => ?Promise, 970 | timeout?: number 971 | ): void, 972 | /** 973 | * Only run this test 974 | * 975 | * @param {JestTestName} Name of Test 976 | * @param {Function} Test 977 | * @param {number} Timeout for the test, in milliseconds. 978 | */ 979 | only: {| 980 | ( 981 | name: JestTestName, 982 | fn?: (done: JestDoneFn) => ?Promise, 983 | timeout?: number 984 | ): void, 985 | each( 986 | ...table: Array | mixed> | [Array, string] 987 | ): ( 988 | name: JestTestName, 989 | fn?: (...args: Array) => ?Promise, 990 | timeout?: number 991 | ) => void 992 | |}, 993 | /** 994 | * Skip running this test 995 | * 996 | * @param {JestTestName} Name of Test 997 | * @param {Function} Test 998 | * @param {number} Timeout for the test, in milliseconds. 999 | */ 1000 | skip( 1001 | name: JestTestName, 1002 | fn?: (done: JestDoneFn) => ?Promise, 1003 | timeout?: number 1004 | ): void, 1005 | /** 1006 | * Highlight planned tests in the summary output 1007 | * 1008 | * @param {String} Name of Test to do 1009 | */ 1010 | todo(name: string): void, 1011 | /** 1012 | * Run the test concurrently 1013 | * 1014 | * @param {JestTestName} Name of Test 1015 | * @param {Function} Test 1016 | * @param {number} Timeout for the test, in milliseconds. 1017 | */ 1018 | concurrent( 1019 | name: JestTestName, 1020 | fn?: (done: JestDoneFn) => ?Promise, 1021 | timeout?: number 1022 | ): void, 1023 | /** 1024 | * each runs this test against array of argument arrays per each run 1025 | * 1026 | * @param {table} table of Test 1027 | */ 1028 | each( 1029 | ...table: Array | mixed> | [Array, string] 1030 | ): ( 1031 | name: JestTestName, 1032 | fn?: (...args: Array) => ?Promise, 1033 | timeout?: number 1034 | ) => void, 1035 | ... 1036 | }; 1037 | 1038 | declare function fit( 1039 | name: JestTestName, 1040 | fn: (done: JestDoneFn) => ?Promise, 1041 | timeout?: number 1042 | ): void; 1043 | /** An individual test unit */ 1044 | declare var test: typeof it; 1045 | /** A disabled group of tests */ 1046 | declare var xdescribe: typeof describe; 1047 | /** A focused group of tests */ 1048 | declare var fdescribe: typeof describe; 1049 | /** A disabled individual test */ 1050 | declare var xit: typeof it; 1051 | /** A disabled individual test */ 1052 | declare var xtest: typeof it; 1053 | 1054 | type JestPrettyFormatColors = { 1055 | comment: { 1056 | close: string, 1057 | open: string, 1058 | ... 1059 | }, 1060 | content: { 1061 | close: string, 1062 | open: string, 1063 | ... 1064 | }, 1065 | prop: { 1066 | close: string, 1067 | open: string, 1068 | ... 1069 | }, 1070 | tag: { 1071 | close: string, 1072 | open: string, 1073 | ... 1074 | }, 1075 | value: { 1076 | close: string, 1077 | open: string, 1078 | ... 1079 | }, 1080 | ... 1081 | }; 1082 | 1083 | type JestPrettyFormatIndent = string => string; 1084 | type JestPrettyFormatRefs = Array; 1085 | type JestPrettyFormatPrint = any => string; 1086 | type JestPrettyFormatStringOrNull = string | null; 1087 | 1088 | type JestPrettyFormatOptions = {| 1089 | callToJSON: boolean, 1090 | edgeSpacing: string, 1091 | escapeRegex: boolean, 1092 | highlight: boolean, 1093 | indent: number, 1094 | maxDepth: number, 1095 | min: boolean, 1096 | plugins: JestPrettyFormatPlugins, 1097 | printFunctionName: boolean, 1098 | spacing: string, 1099 | theme: {| 1100 | comment: string, 1101 | content: string, 1102 | prop: string, 1103 | tag: string, 1104 | value: string, 1105 | |}, 1106 | |}; 1107 | 1108 | type JestPrettyFormatPlugin = { 1109 | print: ( 1110 | val: any, 1111 | serialize: JestPrettyFormatPrint, 1112 | indent: JestPrettyFormatIndent, 1113 | opts: JestPrettyFormatOptions, 1114 | colors: JestPrettyFormatColors 1115 | ) => string, 1116 | test: any => boolean, 1117 | ... 1118 | }; 1119 | 1120 | type JestPrettyFormatPlugins = Array; 1121 | 1122 | /** The expect function is used every time you want to test a value */ 1123 | declare var expect: { 1124 | /** The object that you want to make assertions against */ 1125 | ( 1126 | value: any 1127 | ): JestExpectType & 1128 | JestPromiseType & 1129 | EnzymeMatchersType & 1130 | DomTestingLibraryType & 1131 | JestJQueryMatchersType & 1132 | JestStyledComponentsMatchersType & 1133 | JestExtendedMatchersType, 1134 | /** Add additional Jasmine matchers to Jest's roster */ 1135 | extend(matchers: { [name: string]: JestMatcher, ... }): void, 1136 | /** Add a module that formats application-specific data structures. */ 1137 | addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, 1138 | assertions(expectedAssertions: number): void, 1139 | hasAssertions(): void, 1140 | any(value: mixed): JestAsymmetricEqualityType, 1141 | anything(): any, 1142 | arrayContaining(value: Array): Array, 1143 | objectContaining(value: Object): Object, 1144 | /** Matches any received string that contains the exact expected string. */ 1145 | stringContaining(value: string): string, 1146 | stringMatching(value: string | RegExp): string, 1147 | not: { 1148 | arrayContaining: (value: $ReadOnlyArray) => Array, 1149 | objectContaining: (value: {...}) => Object, 1150 | stringContaining: (value: string) => string, 1151 | stringMatching: (value: string | RegExp) => string, 1152 | ... 1153 | }, 1154 | ... 1155 | }; 1156 | 1157 | // TODO handle return type 1158 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 1159 | declare function spyOn(value: mixed, method: string): Object; 1160 | 1161 | /** Holds all functions related to manipulating test runner */ 1162 | declare var jest: JestObjectType; 1163 | 1164 | /** 1165 | * The global Jasmine object, this is generally not exposed as the public API, 1166 | * using features inside here could break in later versions of Jest. 1167 | */ 1168 | declare var jasmine: { 1169 | DEFAULT_TIMEOUT_INTERVAL: number, 1170 | any(value: mixed): JestAsymmetricEqualityType, 1171 | anything(): any, 1172 | arrayContaining(value: Array): Array, 1173 | clock(): JestClockType, 1174 | createSpy(name: string): JestSpyType, 1175 | createSpyObj( 1176 | baseName: string, 1177 | methodNames: Array 1178 | ): { [methodName: string]: JestSpyType, ... }, 1179 | objectContaining(value: Object): Object, 1180 | stringMatching(value: string): string, 1181 | ... 1182 | }; 1183 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import '@babel/polyfill'; 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-popper", 3 | "version": "2.3.0", 4 | "description": "Official library to use Popper on React projects", 5 | "license": "MIT", 6 | "author": "Travis Arnold (http://souporserious.com)", 7 | "contributors": [ 8 | "Federico Zivolo (https://fezvrasta.github.io)" 9 | ], 10 | "homepage": "https://popper.js.org/react-popper", 11 | "main": "lib/cjs/index.js", 12 | "module": "lib/esm/index.js", 13 | "typings": "typings/react-popper.d.ts", 14 | "sideEffects": false, 15 | "files": [ 16 | "/dist", 17 | "/lib", 18 | "/typings/react-popper.d.ts" 19 | ], 20 | "scripts": { 21 | "build": "yarn build:clean && yarn build:esm && yarn build:cjs && yarn build:umd && yarn build:flow", 22 | "build:clean": "rimraf dist/ && rimraf lib/", 23 | "build:umd": "rollup -c && rimraf dist/index.esm.js", 24 | "build:esm": "cross-env BABEL_ENV=esm babel src --out-dir lib/esm", 25 | "build:cjs": "cross-env BABEL_ENV=cjs babel src --out-dir lib/cjs", 26 | "build:flow": "flow-copy-source --ignore '{__typings__/*,*.test}.js' src lib/cjs", 27 | "demo:dev": "parcel --out-dir demo/dist demo/index.html", 28 | "demo:build": "parcel build --out-dir demo/dist demo/index.html --public-url=/react-popper", 29 | "demo:deploy": "yarn demo:build && gh-pages -d demo/dist", 30 | "test": "yarn test:eslint && yarn test:flow && yarn test:ts && yarn test:jest", 31 | "test:ts": "tsc --project ./typings/tests", 32 | "test:flow": "flow check", 33 | "test:jest": "jest", 34 | "test:eslint": "eslint src", 35 | "prepare": "yarn build", 36 | "precommit": "pretty-quick --staged && test", 37 | "prepublishOnly": "git-branch-is master" 38 | }, 39 | "jest": { 40 | "setupFilesAfterEnv": [ 41 | "jest.setup.js" 42 | ] 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "https://github.com/popperjs/react-popper" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/popperjs/react-popper/issues" 50 | }, 51 | "keywords": [ 52 | "react", 53 | "react-popper", 54 | "popperjs", 55 | "component", 56 | "drop", 57 | "tooltip", 58 | "popover" 59 | ], 60 | "peerDependencies": { 61 | "@popperjs/core": "^2.0.0", 62 | "react": "^16.8.0 || ^17 || ^18", 63 | "react-dom": "^16.8.0 || ^17 || ^18" 64 | }, 65 | "dependencies": { 66 | "react-fast-compare": "^3.0.1", 67 | "warning": "^4.0.2" 68 | }, 69 | "devDependencies": { 70 | "@atomico/rollup-plugin-sizes": "^1.1.3", 71 | "@babel/cli": "^7.8.4", 72 | "@babel/core": "^7.9.0", 73 | "@babel/plugin-transform-modules-commonjs": "^7.9.0", 74 | "@babel/polyfill": "^7.8.7", 75 | "@babel/preset-env": "^7.9.0", 76 | "@babel/preset-flow": "^7.9.0", 77 | "@babel/preset-react": "^7.9.4", 78 | "@emotion/core": "^10.0.28", 79 | "@emotion/styled": "^10.0.27", 80 | "@popperjs/core": "^2.3.3", 81 | "@rollup/plugin-commonjs": "^11.0.2", 82 | "@rollup/plugin-node-resolve": "^7.1.1", 83 | "@rollup/plugin-replace": "^2.3.1", 84 | "@testing-library/react": "^13.1.1", 85 | "@testing-library/react-hooks": "^8.0.0", 86 | "@types/react": "^16.9.29", 87 | "babel-eslint": "^10.1.0", 88 | "babel-jest": "^25.2.4", 89 | "cross-env": "^7.0.2", 90 | "eslint": "^6.8.0", 91 | "eslint-config-prettier": "^6.10.1", 92 | "eslint-plugin-flowtype": "^4.7.0", 93 | "eslint-plugin-jest": "^23.8.2", 94 | "eslint-plugin-promise": "^4.2.1", 95 | "eslint-plugin-react": "^7.19.0", 96 | "eslint-plugin-react-hooks": "^3.0.0", 97 | "flow-bin": "^0.176.2", 98 | "flow-copy-source": "^2.0.9", 99 | "gh-pages": "^2.2.0", 100 | "git-branch-is": "^3.1.0", 101 | "jest": "^25.2.4", 102 | "parcel-bundler": "^1.12.4", 103 | "prettier": "^2.0.2", 104 | "pretty-quick": "^2.0.1", 105 | "react": "18.0.0", 106 | "react-dom": "^18.0.0", 107 | "react-spring": "^8.0.27", 108 | "react-test-renderer": "^18.0.0", 109 | "rimraf": "^3.0.2", 110 | "rollup": "^2.3.1", 111 | "rollup-plugin-babel": "^4.4.0", 112 | "rollup-plugin-terser": "^5.3.0", 113 | "typescript": "^3.8.3" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import babel from 'rollup-plugin-babel'; 4 | import replace from '@rollup/plugin-replace'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import bundleSize from '@atomico/rollup-plugin-sizes'; 7 | 8 | const input = './src/index.js'; 9 | 10 | const umdGlobals = { 11 | react: 'React', 12 | 'react-dom': 'ReactDOM', 13 | '@popperjs/core': 'Popper', 14 | }; 15 | 16 | const getBabelOptions = () => ({ 17 | exclude: '**/node_modules/**', 18 | }); 19 | 20 | export default [ 21 | { 22 | input, 23 | output: { 24 | file: 'dist/index.umd.js', 25 | format: 'umd', 26 | name: 'ReactPopper', 27 | globals: umdGlobals, 28 | }, 29 | external: Object.keys(umdGlobals), 30 | plugins: [ 31 | nodeResolve(), 32 | commonjs({ include: '**/node_modules/**' }), 33 | babel(getBabelOptions()), 34 | replace({ 'process.env.NODE_ENV': JSON.stringify('development') }), 35 | bundleSize(), 36 | ], 37 | }, 38 | 39 | { 40 | input, 41 | output: { 42 | file: 'dist/index.umd.min.js', 43 | format: 'umd', 44 | name: 'ReactPopper', 45 | globals: umdGlobals, 46 | }, 47 | external: Object.keys(umdGlobals), 48 | plugins: [ 49 | nodeResolve(), 50 | commonjs({ include: '**/node_modules/**' }), 51 | babel(getBabelOptions()), 52 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), 53 | terser(), 54 | ], 55 | }, 56 | 57 | { 58 | input, 59 | output: { file: 'dist/index.esm.js', format: 'esm' }, 60 | external: (id) => 61 | !id.startsWith('\0') && !id.startsWith('.') && !id.startsWith('/'), 62 | plugins: [babel(getBabelOptions())], 63 | }, 64 | ]; 65 | -------------------------------------------------------------------------------- /src/Manager.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import * as React from 'react'; 3 | 4 | export const ManagerReferenceNodeContext: React.Context = React.createContext(); 5 | export const ManagerReferenceNodeSetterContext: React.Context< 6 | void | ((?Element) => void) 7 | > = React.createContext(); 8 | 9 | export type ManagerProps = $ReadOnly<{ 10 | children: React.Node, 11 | }>; 12 | 13 | export function Manager({ children }: ManagerProps): React.Node { 14 | const [referenceNode, setReferenceNode] = React.useState(null); 15 | 16 | const hasUnmounted = React.useRef(false); 17 | React.useEffect(() => { 18 | hasUnmounted.current = false; 19 | return () => { 20 | hasUnmounted.current = true; 21 | }; 22 | }, []); 23 | 24 | const handleSetReferenceNode = React.useCallback((node) => { 25 | if (!hasUnmounted.current) { 26 | setReferenceNode(node); 27 | } 28 | }, []); 29 | 30 | return ( 31 | 32 | 35 | {children} 36 | 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/Manager.test.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import React from 'react'; 3 | import { render, waitFor } from '@testing-library/react'; 4 | import * as Popperjs from '@popperjs/core'; 5 | 6 | // Public API 7 | import { Manager, Reference, Popper } from '.'; 8 | 9 | describe('Manager component', () => { 10 | it('renders the expected markup', () => { 11 | const { asFragment } = render( 12 | 13 |
14 |
15 | 16 | ); 17 | expect(asFragment()).toMatchSnapshot(); 18 | }); 19 | 20 | it('connects Popper and Reference', async () => { 21 | const spy = jest.spyOn(Popperjs, 'createPopper'); 22 | 23 | render( 24 | 25 | {({ ref }) =>
} 26 | {({ ref }) =>
} 27 | 28 | ); 29 | 30 | await waitFor(() => { 31 | expect(spy.mock.calls[0].slice(0, 2)).toMatchInlineSnapshot(` 32 | Array [ 33 |
, 34 |
, 35 | ] 36 | `); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/Popper.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import * as React from 'react'; 3 | import { 4 | type State, 5 | type Placement, 6 | type PositioningStrategy, 7 | type VirtualElement, 8 | type StrictModifiers, 9 | type Modifier, 10 | } from '@popperjs/core/lib'; 11 | import { ManagerReferenceNodeContext } from './Manager'; 12 | import type { Ref } from './RefTypes'; 13 | import { unwrapArray, setRef } from './utils'; 14 | import { usePopper } from './usePopper'; 15 | 16 | type ReferenceElement = ?(VirtualElement | HTMLElement); 17 | type Modifiers = Array>>; 18 | 19 | export type PopperArrowProps = {| 20 | ref: Ref, 21 | style: CSSStyleDeclaration, 22 | |}; 23 | export type PopperChildrenProps = {| 24 | ref: Ref, 25 | style: CSSStyleDeclaration, 26 | 27 | placement: Placement, 28 | isReferenceHidden: ?boolean, 29 | hasPopperEscaped: ?boolean, 30 | 31 | update: () => Promise>, 32 | forceUpdate: () => void, 33 | arrowProps: PopperArrowProps, 34 | |}; 35 | export type PopperChildren = (PopperChildrenProps) => React.Node; 36 | 37 | export type PopperProps = $ReadOnly<{| 38 | children: PopperChildren, 39 | innerRef?: Ref, 40 | modifiers?: Modifiers, 41 | placement?: Placement, 42 | strategy?: PositioningStrategy, 43 | referenceElement?: ReferenceElement, 44 | onFirstUpdate?: ($Shape) => void, 45 | |}>; 46 | 47 | const NOOP = () => void 0; 48 | const NOOP_PROMISE = () => Promise.resolve(null); 49 | const EMPTY_MODIFIERS = []; 50 | 51 | export function Popper({ 52 | placement = 'bottom', 53 | strategy = 'absolute', 54 | modifiers = EMPTY_MODIFIERS, 55 | referenceElement, 56 | onFirstUpdate, 57 | innerRef, 58 | children, 59 | }: PopperProps): React.Node { 60 | const referenceNode = React.useContext(ManagerReferenceNodeContext); 61 | 62 | const [popperElement, setPopperElement] = React.useState(null); 63 | const [arrowElement, setArrowElement] = React.useState(null); 64 | 65 | React.useEffect(() => { 66 | setRef(innerRef, popperElement) 67 | }, [innerRef, popperElement]); 68 | 69 | const options = React.useMemo( 70 | () => ({ 71 | placement, 72 | strategy, 73 | onFirstUpdate, 74 | modifiers: [ 75 | ...modifiers, 76 | { 77 | name: 'arrow', 78 | enabled: arrowElement != null, 79 | options: { element: arrowElement }, 80 | }, 81 | ], 82 | }), 83 | [placement, strategy, onFirstUpdate, modifiers, arrowElement] 84 | ); 85 | 86 | const { state, styles, forceUpdate, update } = usePopper( 87 | referenceElement || referenceNode, 88 | popperElement, 89 | options 90 | ); 91 | 92 | const childrenProps = React.useMemo( 93 | () => ({ 94 | ref: setPopperElement, 95 | style: styles.popper, 96 | placement: state ? state.placement : placement, 97 | hasPopperEscaped: 98 | state && state.modifiersData.hide 99 | ? state.modifiersData.hide.hasPopperEscaped 100 | : null, 101 | isReferenceHidden: 102 | state && state.modifiersData.hide 103 | ? state.modifiersData.hide.isReferenceHidden 104 | : null, 105 | arrowProps: { 106 | style: styles.arrow, 107 | ref: setArrowElement, 108 | }, 109 | forceUpdate: forceUpdate || NOOP, 110 | update: update || NOOP_PROMISE, 111 | }), 112 | [ 113 | setPopperElement, 114 | setArrowElement, 115 | placement, 116 | state, 117 | styles, 118 | update, 119 | forceUpdate, 120 | ] 121 | ); 122 | 123 | return unwrapArray(children)(childrenProps); 124 | } 125 | -------------------------------------------------------------------------------- /src/Popper.test.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import React from 'react'; 3 | import { render, waitFor, act } from '@testing-library/react'; 4 | import * as PopperJs from '@popperjs/core'; 5 | import type { Ref } from './RefTypes'; 6 | 7 | // Public API 8 | import { Popper } from '.'; 9 | 10 | const renderPopper = async (props): any => { 11 | let result; 12 | await act(async () => { 13 | result = await render( 14 | 15 | {({ ref, style, placement, arrowProps }) => ( 16 |
17 |
18 |
19 | )} 20 | 21 | ); 22 | }); 23 | return result; 24 | }; 25 | 26 | const handleRef = (ref: Ref) => (node: ?HTMLElement) => { 27 | if (typeof ref === 'function') { 28 | ref(node); 29 | } else if (typeof ref === 'function') { 30 | ref.current = node; 31 | } 32 | }; 33 | 34 | describe('Popper component', () => { 35 | it('renders the expected markup', async () => { 36 | const referenceElement = document.createElement('div'); 37 | 38 | const { asFragment } = await renderPopper({ referenceElement }); 39 | 40 | await waitFor(() => { 41 | expect(asFragment()).toMatchSnapshot(); 42 | }); 43 | }); 44 | 45 | it('handles changing refs gracefully', async () => { 46 | const referenceElement = document.createElement('div'); 47 | 48 | expect(() => 49 | render( 50 | 51 | {({ ref, style, placement, arrowProps }) => ( 52 |
57 |
58 |
59 | )} 60 | 61 | ) 62 | ).not.toThrow(); 63 | 64 | await waitFor(() => {}); 65 | }); 66 | 67 | it('accepts a ref function', async () => { 68 | const myRef = jest.fn(); 69 | const referenceElement = document.createElement('div'); 70 | 71 | render( 72 | 73 | {({ ref, style, placement }) => ( 74 |
75 | )} 76 | 77 | ); 78 | 79 | await waitFor(() => { 80 | expect(myRef).toBeCalled(); 81 | }); 82 | }); 83 | 84 | it('accepts a ref object', async () => { 85 | const myRef = React.createRef(); 86 | const referenceElement = document.createElement('div'); 87 | 88 | render( 89 | 90 | {({ ref, style, placement }) => ( 91 |
92 | )} 93 | 94 | ); 95 | 96 | await waitFor(() => { 97 | expect(myRef.current).toBeDefined(); 98 | }); 99 | }); 100 | 101 | it('accepts a `referenceElement` property', async () => { 102 | const spy = jest.spyOn(PopperJs, 'createPopper'); 103 | const virtualReferenceElement = { 104 | getBoundingClientRect(): any { 105 | return { 106 | top: 10, 107 | left: 10, 108 | bottom: 20, 109 | right: 100, 110 | width: 90, 111 | height: 10, 112 | }; 113 | }, 114 | }; 115 | await renderPopper({ 116 | referenceElement: virtualReferenceElement, 117 | }); 118 | await waitFor(() => { 119 | expect(spy.mock.calls[0][0]).toBe(virtualReferenceElement); 120 | }); 121 | }); 122 | 123 | it(`should update placement when property is changed`, async () => { 124 | const referenceElement = document.createElement('div'); 125 | 126 | const Component = ({ placement }) => ( 127 | 128 | {({ ref, style, placement }) => ( 129 |
135 | {placement} 136 |
137 | )} 138 |
139 | ); 140 | 141 | const { rerender, getByTestId } = render(); 142 | 143 | expect(getByTestId('placement').textContent).toBe('top'); 144 | 145 | await waitFor(() => rerender()); 146 | 147 | expect(getByTestId('placement').textContent).toBe('bottom'); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /src/RefTypes.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | type RefHandler = (?HTMLElement) => void; 3 | type RefObject = { current?: ?HTMLElement}; 4 | 5 | export type Ref = RefHandler | RefObject; 6 | -------------------------------------------------------------------------------- /src/Reference.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import * as React from 'react'; 3 | import warning from 'warning'; 4 | import { ManagerReferenceNodeSetterContext } from './Manager'; 5 | import { safeInvoke, unwrapArray, setRef } from './utils'; 6 | import { type Ref } from './RefTypes'; 7 | 8 | export type ReferenceChildrenProps = $ReadOnly<{ ref: Ref }>; 9 | export type ReferenceProps = $ReadOnly<{| 10 | children: (ReferenceChildrenProps) => React.Node, 11 | innerRef?: Ref, 12 | |}>; 13 | 14 | export function Reference({ children, innerRef }: ReferenceProps): React.Node { 15 | const setReferenceNode = React.useContext(ManagerReferenceNodeSetterContext); 16 | 17 | const refHandler = React.useCallback( 18 | (node: ?HTMLElement) => { 19 | setRef(innerRef, node); 20 | safeInvoke(setReferenceNode, node); 21 | }, 22 | [innerRef, setReferenceNode] 23 | ); 24 | 25 | React.useEffect(() => { 26 | warning( 27 | Boolean(setReferenceNode), 28 | '`Reference` should not be used outside of a `Manager` component.' 29 | ); 30 | }, [setReferenceNode]); 31 | 32 | return unwrapArray(children)({ ref: refHandler }); 33 | } 34 | -------------------------------------------------------------------------------- /src/Reference.test.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import React from 'react'; 3 | import warning from 'warning'; 4 | import { render } from '@testing-library/react'; 5 | 6 | // Public API 7 | import { Reference } from '.'; 8 | 9 | // Private API 10 | import { ManagerReferenceNodeSetterContext } from './Manager'; 11 | 12 | jest.mock('warning'); 13 | 14 | describe('Arrow component', () => { 15 | beforeEach(() => { 16 | jest.clearAllMocks(); 17 | }); 18 | 19 | it('renders the expected markup', () => { 20 | const setReferenceNode = jest.fn(); 21 | 22 | const { asFragment } = render( 23 | 24 | {({ ref }) =>
} 25 | 26 | ); 27 | expect(asFragment()).toMatchSnapshot(); 28 | }); 29 | 30 | it('consumes the ManagerReferenceNodeSetterContext from Manager', () => { 31 | const setReferenceNode = jest.fn(); 32 | 33 | render( 34 | 35 | {({ ref }) =>
} 36 | 37 | ); 38 | expect(setReferenceNode).toHaveBeenCalled(); 39 | }); 40 | 41 | it('warns when setReferenceNode is present', () => { 42 | const setReferenceNode = jest.fn(); 43 | 44 | render( 45 | 46 | {({ ref }) =>
} 47 | 48 | ); 49 | expect(warning).toHaveBeenCalledWith( 50 | true, 51 | '`Reference` should not be used outside of a `Manager` component.' 52 | ); 53 | }); 54 | 55 | it('does not warn when setReferenceNode is not present', () => { 56 | render( 57 | 58 | {({ ref }) =>
} 59 | 60 | ); 61 | expect(warning).toHaveBeenCalledWith( 62 | false, 63 | '`Reference` should not be used outside of a `Manager` component.' 64 | ); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/__snapshots__/Manager.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Manager component renders the expected markup 1`] = ` 4 | 5 |
8 |
11 | 12 | `; 13 | -------------------------------------------------------------------------------- /src/__snapshots__/Popper.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Popper component renders the expected markup 1`] = ` 4 | 5 |
9 |
12 |
13 | 14 | `; 15 | -------------------------------------------------------------------------------- /src/__snapshots__/Reference.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Arrow component renders the expected markup 1`] = ` 4 | 5 |
6 | 7 | `; 8 | -------------------------------------------------------------------------------- /src/__typings__/main-test.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | 3 | // Please remember to update also the TypeScript test files that can 4 | // be found under `/typings/tests` please. Thanks! 🤗 5 | 6 | import * as React from 'react'; 7 | import { Manager, Reference, Popper } from '..'; 8 | 9 | export const Test = (): React.Node => ( 10 | 11 | {/* $FlowExpectedError: empty children */} 12 | 13 | {({ ref }) =>
} 14 | 25 | {({ 26 | ref, 27 | style, 28 | placement, 29 | isReferenceHidden, 30 | hasPopperEscaped, 31 | update, 32 | arrowProps, 33 | }) => ( 34 |
update()} 39 | > 40 | Popper 41 |
42 |
43 | )} 44 | 45 | 46 | {({ ref, style, placement }) => ( 47 |
48 | Popper 49 |
50 | )} 51 |
52 | 53 | ); 54 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | 3 | // Public components 4 | import { Popper } from './Popper'; 5 | import { Manager } from './Manager'; 6 | import { Reference } from './Reference'; 7 | import { usePopper } from './usePopper'; 8 | export { Popper, Manager, Reference, usePopper }; 9 | 10 | // Public types 11 | import type { ManagerProps } from './Manager'; 12 | import type { ReferenceProps, ReferenceChildrenProps } from './Reference'; 13 | import type { 14 | PopperChildrenProps, 15 | PopperArrowProps, 16 | PopperProps, 17 | } from './Popper'; 18 | export type { 19 | ManagerProps, 20 | ReferenceProps, 21 | ReferenceChildrenProps, 22 | PopperChildrenProps, 23 | PopperArrowProps, 24 | PopperProps, 25 | }; 26 | -------------------------------------------------------------------------------- /src/usePopper.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import * as React from 'react'; 3 | import * as ReactDOM from 'react-dom'; 4 | import { 5 | createPopper as defaultCreatePopper, 6 | type Options as PopperOptions, 7 | type VirtualElement, 8 | type State as PopperState, 9 | type Instance as PopperInstance, 10 | } from '@popperjs/core'; 11 | import isEqual from 'react-fast-compare'; 12 | import { fromEntries, useIsomorphicLayoutEffect } from './utils'; 13 | 14 | type Options = $Shape<{ 15 | ...PopperOptions, 16 | createPopper: typeof defaultCreatePopper, 17 | }>; 18 | 19 | type Styles = { 20 | [key: string]: $Shape, 21 | }; 22 | 23 | type Attributes = { 24 | [key: string]: { [key: string]: string }, 25 | }; 26 | 27 | type State = { 28 | styles: Styles, 29 | attributes: Attributes, 30 | }; 31 | 32 | const EMPTY_MODIFIERS = []; 33 | 34 | type UsePopperResult = $ReadOnly<{ 35 | state: ?PopperState, 36 | styles: Styles, 37 | attributes: Attributes, 38 | update: ?$PropertyType, 39 | forceUpdate: ?$PropertyType, 40 | }>; 41 | 42 | export const usePopper = ( 43 | referenceElement: ?(Element | VirtualElement), 44 | popperElement: ?HTMLElement, 45 | options: Options = {} 46 | ): UsePopperResult => { 47 | const prevOptions = React.useRef(null); 48 | 49 | const optionsWithDefaults = { 50 | onFirstUpdate: options.onFirstUpdate, 51 | placement: options.placement || 'bottom', 52 | strategy: options.strategy || 'absolute', 53 | modifiers: options.modifiers || EMPTY_MODIFIERS, 54 | }; 55 | 56 | const [state, setState] = React.useState({ 57 | styles: { 58 | popper: { 59 | position: optionsWithDefaults.strategy, 60 | left: '0', 61 | top: '0', 62 | }, 63 | arrow: { 64 | position: 'absolute', 65 | }, 66 | }, 67 | attributes: {}, 68 | }); 69 | 70 | const updateStateModifier = React.useMemo( 71 | () => ({ 72 | name: 'updateState', 73 | enabled: true, 74 | phase: 'write', 75 | fn: ({ state }) => { 76 | const elements = Object.keys(state.elements); 77 | 78 | ReactDOM.flushSync(() => { 79 | setState({ 80 | styles: fromEntries( 81 | elements.map((element) => [element, state.styles[element] || {}]) 82 | ), 83 | attributes: fromEntries( 84 | elements.map((element) => [element, state.attributes[element]]) 85 | ), 86 | }); 87 | }); 88 | }, 89 | requires: ['computeStyles'], 90 | }), 91 | [] 92 | ); 93 | 94 | const popperOptions = React.useMemo(() => { 95 | const newOptions = { 96 | onFirstUpdate: optionsWithDefaults.onFirstUpdate, 97 | placement: optionsWithDefaults.placement, 98 | strategy: optionsWithDefaults.strategy, 99 | modifiers: [ 100 | ...optionsWithDefaults.modifiers, 101 | updateStateModifier, 102 | { name: 'applyStyles', enabled: false }, 103 | ], 104 | }; 105 | 106 | if (isEqual(prevOptions.current, newOptions)) { 107 | return prevOptions.current || newOptions; 108 | } else { 109 | prevOptions.current = newOptions; 110 | return newOptions; 111 | } 112 | }, [ 113 | optionsWithDefaults.onFirstUpdate, 114 | optionsWithDefaults.placement, 115 | optionsWithDefaults.strategy, 116 | optionsWithDefaults.modifiers, 117 | updateStateModifier, 118 | ]); 119 | 120 | const popperInstanceRef = React.useRef(); 121 | 122 | useIsomorphicLayoutEffect(() => { 123 | if (popperInstanceRef.current) { 124 | popperInstanceRef.current.setOptions(popperOptions); 125 | } 126 | }, [popperOptions]); 127 | 128 | useIsomorphicLayoutEffect(() => { 129 | if (referenceElement == null || popperElement == null) { 130 | return; 131 | } 132 | 133 | const createPopper = options.createPopper || defaultCreatePopper; 134 | const popperInstance = createPopper( 135 | referenceElement, 136 | popperElement, 137 | popperOptions 138 | ); 139 | 140 | popperInstanceRef.current = popperInstance; 141 | 142 | return () => { 143 | popperInstance.destroy(); 144 | popperInstanceRef.current = null; 145 | }; 146 | }, [referenceElement, popperElement, options.createPopper]); 147 | 148 | return { 149 | state: popperInstanceRef.current ? popperInstanceRef.current.state : null, 150 | styles: state.styles, 151 | attributes: state.attributes, 152 | update: popperInstanceRef.current ? popperInstanceRef.current.update : null, 153 | forceUpdate: popperInstanceRef.current 154 | ? popperInstanceRef.current.forceUpdate 155 | : null, 156 | }; 157 | }; 158 | -------------------------------------------------------------------------------- /src/usePopper.test.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import { renderHook, act } from '@testing-library/react-hooks'; 3 | import * as PopperJs from '@popperjs/core'; 4 | 5 | // Public API 6 | import { usePopper } from '.'; 7 | 8 | const referenceElement = document.createElement('div'); 9 | const popperElement = document.createElement('div'); 10 | 11 | describe('userPopper', () => { 12 | afterEach(() => { 13 | jest.clearAllMocks(); 14 | }); 15 | 16 | it('initializes the Popper instance', async () => { 17 | const { result, waitFor } = renderHook(() => 18 | usePopper(referenceElement, popperElement) 19 | ); 20 | 21 | await waitFor(() => { 22 | expect(result.current.state).not.toBe(null); 23 | }); 24 | }); 25 | 26 | it("doesn't update Popper instance on props update if not needed by Popper", async () => { 27 | const spy = jest.spyOn(PopperJs, 'createPopper'); 28 | const { waitFor, rerender } = renderHook( 29 | ({ referenceElement, popperElement }) => 30 | usePopper(referenceElement, popperElement), 31 | { initialProps: { referenceElement, popperElement } } 32 | ); 33 | 34 | await act(async () => { 35 | await rerender({ referenceElement, popperElement }); 36 | }); 37 | 38 | await waitFor(() => { 39 | expect(spy).toHaveBeenCalledTimes(1); 40 | }); 41 | }); 42 | 43 | it('updates Popper on explicitly listed props change', async () => { 44 | const spy = jest.spyOn(PopperJs, 'createPopper'); 45 | 46 | const { waitForNextUpdate, rerender } = renderHook( 47 | ({ referenceElement, popperElement }) => 48 | usePopper(referenceElement, popperElement), 49 | { initialProps: { referenceElement, popperElement } } 50 | ); 51 | 52 | rerender({ 53 | referenceElement, 54 | popperElement: document.createElement('div'), 55 | }); 56 | 57 | await waitForNextUpdate(); 58 | expect(spy).toHaveBeenCalledTimes(2); 59 | }); 60 | 61 | it('does not update Popper on generic props change', async () => { 62 | const spy = jest.spyOn(PopperJs, 'createPopper'); 63 | const { waitForNextUpdate, rerender } = renderHook( 64 | ({ referenceElement, popperElement, options }) => 65 | usePopper(referenceElement, popperElement, options), 66 | { initialProps: { referenceElement, popperElement } } 67 | ); 68 | 69 | rerender({ 70 | referenceElement, 71 | popperElement, 72 | options: { foo: 'bar' }, 73 | }); 74 | 75 | await waitForNextUpdate(); 76 | 77 | expect(spy).not.toHaveBeenCalledTimes(2); 78 | }); 79 | 80 | it('destroys Popper on instance on unmount', async () => { 81 | const spy = jest.spyOn(PopperJs, 'createPopper'); 82 | const { waitForNextUpdate, unmount } = renderHook(() => 83 | usePopper(referenceElement, popperElement) 84 | ); 85 | 86 | await waitForNextUpdate(); 87 | const popperInstance = spy.mock.results[0].value; 88 | const destroy = jest.spyOn(popperInstance, 'destroy'); 89 | 90 | await unmount(); 91 | 92 | expect(destroy).toHaveBeenCalled(); 93 | }); 94 | 95 | it('Initializes the arrow positioning', async () => { 96 | const arrowElement = document.createElement('div'); 97 | const popperElementWithArrow = document.createElement('div'); 98 | popperElementWithArrow.appendChild(arrowElement); 99 | 100 | const { result, waitForNextUpdate } = renderHook(() => 101 | usePopper(referenceElement, popperElementWithArrow, { 102 | placement: 'bottom', 103 | modifiers: [{ name: 'arrow', options: { element: arrowElement } }], 104 | }) 105 | ); 106 | 107 | expect(result.current.styles.arrow.position).toBe('absolute'); 108 | expect(result.current.styles.arrow.transform).toBeUndefined(); 109 | 110 | await waitForNextUpdate(); 111 | 112 | expect(result.current.styles.arrow.transform).toBeDefined(); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | import * as React from 'react'; 3 | import { type Ref } from './RefTypes'; 4 | 5 | /** 6 | * Takes an argument and if it's an array, returns the first item in the array, 7 | * otherwise returns the argument. Used for Preact compatibility. 8 | */ 9 | export const unwrapArray = (arg: *): * => (Array.isArray(arg) ? arg[0] : arg); 10 | 11 | /** 12 | * Takes a maybe-undefined function and arbitrary args and invokes the function 13 | * only if it is defined. 14 | */ 15 | export const safeInvoke = ( 16 | fn: ?F, 17 | ...args: Array 18 | ): $Call => { 19 | if (typeof fn === 'function') { 20 | return fn(...args); 21 | } 22 | }; 23 | 24 | /** 25 | * Sets a ref using either a ref callback or a ref object 26 | */ 27 | export const setRef = (ref: ?Ref, node: ?HTMLElement): void => { 28 | // if its a function call it 29 | if (typeof ref === 'function') { 30 | return safeInvoke(ref, node); 31 | } 32 | // otherwise we should treat it as a ref object 33 | else if (ref != null) { 34 | ref.current = node; 35 | } 36 | }; 37 | 38 | /** 39 | * Simple ponyfill for Object.fromEntries 40 | */ 41 | export const fromEntries = ( 42 | entries: Array<[string, any]> 43 | ): { [key: string]: any } => 44 | entries.reduce((acc, [key, value]) => { 45 | acc[key] = value; 46 | return acc; 47 | }, {}); 48 | 49 | /** 50 | * Small wrapper around `useLayoutEffect` to get rid of the warning on SSR envs 51 | */ 52 | export const useIsomorphicLayoutEffect: 53 | | typeof React.useEffect 54 | | typeof React.useLayoutEffect = 55 | typeof window !== 'undefined' && 56 | window.document && 57 | window.document.createElement 58 | ? React.useLayoutEffect 59 | : React.useEffect; 60 | -------------------------------------------------------------------------------- /typings/react-popper.d.ts: -------------------------------------------------------------------------------- 1 | import * as PopperJS from '@popperjs/core'; 2 | import * as React from 'react'; 3 | 4 | // Utility type 5 | type UnionWhere = U extends M ? U : never; 6 | 7 | interface ManagerProps { 8 | children: React.ReactNode; 9 | } 10 | export class Manager extends React.Component {} 11 | 12 | export type RefHandler = (ref: HTMLElement | null) => void; 13 | 14 | interface ReferenceChildrenProps { 15 | // React refs are supposed to be contravariant (allows a more general type to be passed rather than a more specific one) 16 | // However, Typescript currently can't infer that fact for refs 17 | // See https://github.com/microsoft/TypeScript/issues/30748 for more information 18 | ref: React.Ref; 19 | } 20 | 21 | interface ReferenceProps { 22 | children: (props: ReferenceChildrenProps) => React.ReactNode; 23 | innerRef?: React.Ref; 24 | } 25 | export class Reference extends React.Component {} 26 | 27 | export interface PopperArrowProps { 28 | ref: React.Ref; 29 | style: React.CSSProperties; 30 | } 31 | 32 | export interface PopperChildrenProps { 33 | ref: React.Ref; 34 | style: React.CSSProperties; 35 | 36 | placement: PopperJS.Placement; 37 | isReferenceHidden?: boolean; 38 | hasPopperEscaped?: boolean; 39 | 40 | update: () => Promise>; 41 | forceUpdate: () => Partial; 42 | arrowProps: PopperArrowProps; 43 | } 44 | 45 | type StrictModifierNames = NonNullable; 46 | 47 | export type StrictModifier< 48 | Name extends StrictModifierNames = StrictModifierNames 49 | > = UnionWhere; 50 | 51 | export type Modifier< 52 | Name, 53 | Options extends object = object 54 | > = Name extends StrictModifierNames 55 | ? StrictModifier 56 | : Partial>; 57 | 58 | export interface PopperProps { 59 | children: (props: PopperChildrenProps) => React.ReactNode; 60 | innerRef?: React.Ref; 61 | modifiers?: ReadonlyArray>; 62 | placement?: PopperJS.Placement; 63 | strategy?: PopperJS.PositioningStrategy; 64 | referenceElement?: HTMLElement | PopperJS.VirtualElement; 65 | onFirstUpdate?: (state: Partial) => void; 66 | } 67 | export class Popper extends React.Component< 68 | PopperProps, 69 | {} 70 | > {} 71 | 72 | export function usePopper( 73 | referenceElement?: Element | PopperJS.VirtualElement | null, 74 | popperElement?: HTMLElement | null, 75 | options?: Omit, 'modifiers'> & { 76 | createPopper?: typeof PopperJS.createPopper; 77 | modifiers?: ReadonlyArray>; 78 | } 79 | ): { 80 | styles: { [key: string]: React.CSSProperties }; 81 | attributes: { [key: string]: { [key: string]: string } | undefined }; 82 | state: PopperJS.State | null; 83 | update: PopperJS.Instance['update'] | null; 84 | forceUpdate: PopperJS.Instance['forceUpdate'] | null; 85 | }; 86 | -------------------------------------------------------------------------------- /typings/tests/main-test.tsx: -------------------------------------------------------------------------------- 1 | // Please remember to update also the Flow test files that can 2 | // be found under `/src/__typings__` please. Thanks! 🤗 3 | 4 | import * as React from 'react'; 5 | import { Manager, Reference, Popper, usePopper } from '../..'; 6 | 7 | export const Test = () => ( 8 | 9 | {({ ref }) =>
} 10 | 15 | {({ 16 | ref, 17 | style, 18 | placement, 19 | isReferenceHidden, 20 | hasPopperEscaped, 21 | update, 22 | arrowProps, 23 | }) => ( 24 |
update()} 32 | > 33 | Popper 34 |
35 |
36 | )} 37 | 38 | 39 | {({ ref, style, placement }) => ( 40 |
41 | Popper 42 |
43 | )} 44 |
45 | 46 | ); 47 | 48 | const HookTest = () => { 49 | const [ 50 | referenceElement, 51 | setReferenceElement, 52 | ] = React.useState(null); 53 | const [popperElement, setPopperElement] = React.useState( 54 | null 55 | ); 56 | const [arrowElement, setArrowElement] = React.useState( 57 | null 58 | ); 59 | const { styles, attributes, update } = usePopper( 60 | referenceElement, 61 | popperElement, 62 | { 63 | modifiers: [{ name: 'arrow', options: { element: arrowElement } }], 64 | } 65 | ); 66 | 67 | return ( 68 | <> 69 | 78 | 79 |
80 | Popper element 81 |
82 |
83 | 84 | ); 85 | }; 86 | -------------------------------------------------------------------------------- /typings/tests/svg-test.tsx: -------------------------------------------------------------------------------- 1 | // Please remember to update also the Flow test files that can 2 | // be found under `/src/__typings__` please. Thanks! 🤗 3 | 4 | import * as React from 'react'; 5 | import { Manager, Reference, Popper } from '../..'; 6 | 7 | export const Test = () => ( 8 | 9 | 10 | {({ ref }) => } 11 | 12 | 17 | {({ 18 | ref, 19 | style, 20 | placement, 21 | isReferenceHidden, 22 | hasPopperEscaped, 23 | update, 24 | arrowProps, 25 | }) => ( 26 |
update()} 31 | > 32 | Popper 33 |
34 |
35 | )} 36 | 37 | 38 | {({ ref, style, placement }) => ( 39 |
40 | Popper 41 |
42 | )} 43 |
44 | 45 | ); 46 | -------------------------------------------------------------------------------- /typings/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es2015", 5 | "jsx": "react", 6 | "noEmit": true, 7 | "strict": true, 8 | "moduleResolution": "node", 9 | "typeRoots": ["./node_modules/@types"], 10 | "types": ["react"] 11 | } 12 | } 13 | --------------------------------------------------------------------------------