├── .babelrc ├── .browserslistrc ├── .eslintrc ├── .flowconfig ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── gh-pages.yml │ ├── lint.yml │ ├── packj.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .packj.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── Resizable.test.js ├── ResizableBox.test.js └── __snapshots__ │ ├── Resizable.test.js.snap │ └── ResizableBox.test.js.snap ├── build.sh ├── css └── styles.css ├── examples ├── ExampleLayout.js ├── example.css ├── example.js └── index.html ├── flow-typed └── npm │ └── jest_v26.x.x.js ├── index.js ├── jest.config.js ├── lib ├── Resizable.js ├── ResizableBox.js ├── propTypes.js └── utils.js ├── package.json ├── setupTests └── enzyme.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "loose": true 7 | } 8 | ], 9 | "@babel/preset-react", 10 | "@babel/preset-flow" 11 | ], 12 | "plugins": [ 13 | ["@babel/plugin-proposal-class-properties", {"loose": true}], 14 | "@babel/plugin-proposal-object-rest-spread" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 0.25% 2 | ie 11 3 | not dead -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@babel/eslint-parser", 4 | "plugins": [ 5 | "react" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:jest/recommended" 10 | ], 11 | "rules": { 12 | "strict": 0, 13 | "quotes": [0, "single"], 14 | "curly": [1, "multi-line"], 15 | "camelcase": 0, 16 | "comma-dangle": 0, 17 | "dot-notation": 0, 18 | "no-console": 0, 19 | "no-use-before-define": [1, "nofunc"], 20 | "no-underscore-dangle": 0, 21 | "no-unused-vars": 0, 22 | "new-cap": 0, 23 | "react/jsx-uses-vars": 1, 24 | "semi": [1, "always"] 25 | }, 26 | "env": { 27 | "browser": true, 28 | "node": true, 29 | "jest": true 30 | }, 31 | "globals": { 32 | // For Flow 33 | "ReactElement": false, 34 | "ReactClass": false, 35 | "SyntheticEvent": false, 36 | "ClientRect": false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [version] 2 | ^0.153.0 3 | 4 | [ignore] 5 | .*/node_modules/@babel.* 6 | .*/node_modules/.*/malformed_package_json/.* 7 | /build/.* 8 | 9 | [include] 10 | 11 | [libs] 12 | flow-typed 13 | 14 | [lints] 15 | all=warn 16 | implicit-inexact-object=error 17 | 18 | [options] 19 | suppress_type=$FlowFixMe 20 | experimental.strict_call_arity=true 21 | module.system.node.allow_root_relative=true 22 | module.use_strict=true 23 | server.max_workers=6 24 | exact_by_default=true 25 | sharedmemory.heap_size=3221225472 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Thanks for opening an issue! 2 | 3 | Please select the type of issue you're reporting. For questions. 4 | 5 | - [ ] Bug 6 | - [ ] Feature Request 7 | - [ ] Question 8 | 9 | ### Problem Report 10 | 11 | > Please describe the problem here. 12 | 13 | #### System Info 14 | 15 | Node Version: 16 | Browser: 17 | OS: 18 | 19 | #### Reproduction 20 | 21 | If this is a bug report, please provide a reproduction of the issue by going to 22 | https://codesandbox.io/s/9229wz40yo?fontsize=14. 23 | Paste a link here to your working reproduction. 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy to GitHub Pages 2 | on: 3 | push: 4 | tags: 5 | - "*" 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 🛎️ 11 | uses: actions/checkout@v2.3.1 12 | 13 | - name: Install and Build 14 | run: | 15 | yarn 16 | yarn build-example 17 | 18 | - name: Deploy 🚀 19 | uses: JamesIves/github-pages-deploy-action@4.0.0 20 | with: 21 | branch: gh-pages # The branch the action should deploy to. 22 | folder: examples # The folder the action should deploy. 23 | target-folder: examples # The destination. Shouldn't touch other folders. 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the master branch 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | run-linters: 15 | name: Run linters 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Check out Git repository 20 | uses: actions/checkout@v2 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: 14 26 | 27 | - name: Install Node.js dependencies 28 | run: yarn 29 | 30 | - name: Run ESLint/Flow 31 | run: yarn lint 32 | -------------------------------------------------------------------------------- /.github/workflows/packj.yml: -------------------------------------------------------------------------------- 1 | name: Packj Security Audit 2 | 3 | # Controls when the workflow will run 4 | on: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 10 | jobs: 11 | 12 | # This workflow contains a single job called "packj-audit" 13 | packj-security-audit: 14 | 15 | # The type of runner that the job will run on 16 | runs-on: ubuntu-latest 17 | 18 | # Steps represent a sequence of tasks that will be executed as part of the job 19 | steps: 20 | 21 | # Audit 22 | - name: Audit dependencies 23 | uses: ossillate-inc/packj-github-action@v0.0.7-beta 24 | 25 | with: 26 | DEPENDENCY_FILES: npm:package.json 27 | REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | # Trigger the workflow on push or pull request, 4 | # but only for the master branch 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out Git repository 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up Node.js 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: 14 22 | 23 | - name: Install Node.js dependencies 24 | run: yarn 25 | 26 | - name: Run tests 27 | run: yarn test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | dist 4 | build 5 | 6 | # dependencies 7 | /node_modules 8 | package-lock.json 9 | 10 | # testing 11 | /coverage/ 12 | 13 | # misc 14 | .DS_Store 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # ide 20 | .vscode 21 | .idea 22 | 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/ 2 | lib/ 3 | build.sh 4 | *.config.js 5 | yarn.lock 6 | .github 7 | coverage/ 8 | setupTests/ 9 | flow-typed/ 10 | __tests__/ -------------------------------------------------------------------------------- /.packj.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Audit policies 3 | # 4 | audit: 5 | alerts: 6 | # 7 | # category: malicious packages (publicly known and unknown) 8 | # 9 | malicious: 10 | contains known malware: 11 | - reason: package is known to contain a dangerous malware 12 | - enabled: true 13 | typo-squatting or repo-jacking package: 14 | - reason: package impersonates another popular package to propagate malware 15 | - enabled: true 16 | 17 | # 18 | # alert category: suspicious packages (potentially malicious) 19 | # 20 | suspicious: 21 | inconsistent with repo source: 22 | - reason: package code inconsistent with the public repo source code 23 | - enabled: false # WIP 24 | overwrites system binaries: 25 | - reason: package code inconsistent with the public repo source code 26 | - enabled: false # WIP 27 | 28 | # 29 | # alert category: packages vulnerable to code exploits 30 | # 31 | vulnerable: 32 | contains known vulnerabilities: 33 | - reason: known vulnerabilities (CVEs) in package code could be exploited 34 | - enabled: true 35 | insecure network communication: 36 | - reason: package code uses insecure network communication (not https) 37 | - enabled: false # WIP 38 | 39 | # 40 | # packages with undesirable or "risky" attributes 41 | # 42 | undesirable: 43 | package is old or abandoned: 44 | - reason: old or abandoned packages receive no security updates and are risky 45 | - enabled: true 46 | 47 | invalid or no author email: 48 | - reason: a package with lack of or invalid author email suggests 2FA not enabled 49 | - enabled: true 50 | 51 | invalid or no homepage: 52 | - reason: a package with no or invalid homepage may not be preferable 53 | - enabled: false 54 | 55 | no source repo: 56 | - reason: lack of public source repo may suggest malicious intention 57 | - enabled: true 58 | 59 | fewer downloads: 60 | - reason: a package with few downloads may not be preferable 61 | - enabled: true 62 | 63 | no or insufficient readme: 64 | - reason: a package with lack of documentation may not be preferable 65 | - enabled: false 66 | 67 | fewer versions or releases: 68 | - reason: few versions suggest unstable or inactive project 69 | - enabled: true 70 | 71 | too many dependencies: 72 | - reason: too many dependencies increase attack surface 73 | - enabled: false 74 | 75 | version release after a long gap: 76 | - reason: a release after a long time may indicate account hijacking 77 | - enabled: false 78 | 79 | contains custom installation hooks: 80 | - reason: custom installation hooks may download or execute malicious code 81 | - enabled: false # WIP 82 | 83 | # 84 | # type: repo stats 85 | # 86 | few source repo stars: 87 | - reason: a package with few repo stars may not be preferable 88 | - enabled: false 89 | 90 | few source repo forks: 91 | - reason: a package with few repo forks may not be preferable 92 | - enabled: false 93 | 94 | forked source repo: 95 | - reason: a forked copy of a popular package may contain malicious code 96 | - enabled: true 97 | 98 | # 99 | # type: APIs and permissions 100 | # 101 | generates new code: 102 | - reason: package generates new code at runtime, which could be malicious 103 | - enabled: false 104 | forks or exits OS processes: 105 | - reason: package spawns new operating system processes, which could be malicious 106 | - enabled: false 107 | accesses obfuscated (hidden) code: 108 | - enabled: true 109 | accesses environment variables: 110 | - enabled: false 111 | changes system/environment variables: 112 | - enabled: false 113 | accesses files and dirs: 114 | - enabled: false 115 | communicates with external network: 116 | - enabled: false 117 | reads user input: 118 | - enabled: false 119 | 120 | # 121 | # Sandboxing policies 122 | # 123 | sandbox: 124 | rules: 125 | # 126 | # File system (allow or block accesses to file/dirs) 127 | # 128 | # ~/ represents home dir 129 | # . represents cwd dir 130 | # 131 | # NOTE: only ONE 'allow' and 'block' lines are allowed 132 | # 133 | fs: 134 | # TODO: customize as per your threat model 135 | 136 | # block access to home dir and all other locations (except the ones below) 137 | block: ~/, / 138 | allow: ., ~/.cache, ~/.npm, ~/.local, ~/.ruby, /tmp, /proc, /etc, /var, /bin, /usr/include, /usr/local, /usr/bin, /usr/lib, /usr/share, /lib 139 | 140 | # 141 | # Network (allow or block domains/ports) 142 | # 143 | # NOTE: only ONE 'allow' and 'block' lines are allowed 144 | # 145 | network: 146 | 147 | # TODO: customize as per your threat model 148 | 149 | # block all external network communication (except the ones below) 150 | block: 0.0.0.0 151 | 152 | # For NPM packages 153 | allow: registry.yarnpkg.com:0, npmjs.org:0, npmjs.com:0 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 3.0.5 (Mar 21, 2023) 4 | 5 | - 🐛 Bugfix: Make `width` and `height` conditionally required if an `axis` is set. See [#196](https://github.com/react-grid-layout/react-resizable/issues/196) 6 | - ✏ Chore: Minor dependency upgrades. 7 | - ✏ Chore: Fix documentation of `onResize` callback arity. 8 | 9 | ### 3.0.4 (Jun 15, 2021) 10 | 11 | - 🐛 Bugfix: Fix incorrect fix for `handleAxis` on DOM elements. [#175](https://github.com/react-grid-layout/react-resizable/issues/175) 12 | - ✏ Chore:Upgrade dependencies. 13 | 14 | ### 3.0.3 (Jun 14, 2021) 15 | 16 | - 🐛 Bugfix: Remove unknown prop `handleAxis` making it to DOM elements, causing a warning in dev. 17 | - ✏ Chore: Rewrote `lockAspectRatio` logic to be more accurate and shorter. 18 | 19 | ### 3.0.2 (Jun 8, 2021) 20 | 21 | - ✏ Chore: Add documentation for resize handles and fix a mistake where the `handleAxis` prop was not being passed to custom components. 22 | - See [Resize Handles](README.md#resize-handle) 23 | 24 | ### 3.0.1 (May 10, 2021) 25 | 26 | - ✏ Chore: Reduce package size through `.npmignore`. 27 | 28 | ### 3.0.0 (May 10, 2021) 29 | 30 | #### Breaking 31 | 32 | - 🐛 Bugfix: Fixed handling of the `nodeRef` that needs to be passed to `` to avoid use of ReactDOM. This means that vanilla usage of `react-resizable` no longer requires ReactDOM. No code changes are needed in the usual case, except: 33 | * React `>= 16.3` is required due to use of `React.createRef()`, and 34 | * The `handle` prop now sends a `ReactRef` as its second argument and expects it to be used on your returned component. 35 | * If you do not attach the `ref`, you will receive the following error: `" not mounted on DragStart!"` This is due to the ref being present but not attached to any node. 36 | 37 | ### 1.11.1 (Mar 5, 2021) 38 | 39 | - ✏ Chore: Added React 17 to peerDependencies. 40 | 41 | ### 1.11.0 (Sep 3, 2020) 42 | 43 | - ⚠ Important Notice! 44 | - React-Resizable 2.0.0 was published due to a breaking change in `props` handling. This change ended up actually completely breaking certain workflows, for the dubious benefit of making the code slightly simpler to add to. The breaking change has been reverted, 2.0.0 is now deprecated, and we will continue on the 1.x branch. Future breaking changes to `react-resizable` will upgrade the major version to `3`. 45 | - ➕ Feature: `` now takes a `style` prop which will be applied on the wrapping `
`. `width` and `height` in this prop are ignored. 46 | - 🐛 Bugfix: remove unknown Prop `handle` from div children in Resizable `React.cloneElement`. [#124](https://github.com/STRML/react-resizable/issues/124) 47 | - 🐛 Bugfix: Fix top and left resizing jerkiness. Thanks @conor-kelleher. [#136](https://github.com/STRML/react-resizable/pull/136) 48 | - ✏ Chore: Improved test suite. Please contribute tests for your use cases if you have the time, I would really appreciate it! Thanks very much, @Danielecina 49 | - ✏ Chore: Minor internal refactors and additional tests. 50 | - ✏ Chore: Additional examples. 51 | 52 | ### 1.10.1 (Nov 25, 2019) 53 | 54 | > Note: 1.10.0 was a mis-publish. 55 | 56 | - ➕ Feature: Add `transformScale` prop [#115](https://github.com/STRML/react-resizable/pull/115) 57 | - 🐛 Bugfix: Resolve `getDerivedStateFromProps` dev warning [#117](https://github.com/STRML/react-resizable/pull/117) 58 | 59 | ### 1.9.0 (Oct 24, 2019) 60 | 61 | - 🐛 Bugfix: Fix resize with north/south handles when `lockAspectRatio=true` [#106](https://github.com/STRML/react-resizable/pull/106) 62 | - ✏ Chore: Remove deprecated React 16.9 lifecycle methods (`componentWillReceiveProps`) (https://github.com/STRML/react-resizable/pull/112/commits/541dee69b8e45d91a533855609472b481634edee) 63 | - ✏ Chore: Upgrade to babel 7 64 | - ✏ Chore: [Remove unused state inside ``](https://github.com/STRML/react-resizable/pull/112/commits/05693f63d6d221ad652f0f28af024cfb46a5f2df). This has not been needed for quite some time, fixes [some bugs](https://github.com/STRML/react-resizable/issues/99) and improves performance. 65 | 66 | ### 1.8.0 (May 15, 2019) 67 | 68 | - ➕ Feature: Added support for custom resize handles [#79](https://github.com/STRML/react-resizable/pull/79) 69 | - ➕ Feature: Added support for resize handles on all corners [#191](https://github.com/STRML/react-resizable/pull/191) 70 | 71 | ### 1.7.5 (Sep 26, 2017) 72 | 73 | - ✏ Chore: Support for React 16 (no changes required, updated `peerDependencies`) 74 | - ✏ Chore: Minor dep updates. 75 | 76 | ### 1.7.4 (Sep 5, 2017) 77 | 78 | - ✏ Chore: Minor Flow & dependency updates. 79 | 80 | ### 1.7.3 (Aug 31, 2017) 81 | 82 | - 🐛 Bugfix: React deprecation warnings from `import *` 83 | - https://github.com/facebook/react/issues/10583 84 | 85 | ### 1.7.2 (Aug 21, 2017) 86 | 87 | - ✏ Chore: Pkg: Add `react-draggable@3.0.0` to version range. 88 | - This package is compatible with both `@2` and `@3` versions. 89 | 90 | ### 1.7.1 (May 23, 2017) 91 | 92 | - 🐛 Bugfix: Some flow types were improperly specified. 93 | 94 | ### 1.7.0 (May 1, 2017) 95 | 96 | - ⚠ Deprecation: `React.PropTypes` now deprecated in React 15.5, moved to `prop-types` package 97 | - ✏ Chore: Update devDeps, upgrade to webpack 2 98 | - ✏ Chore: Remove babel `stage-1` and `transform-flow-comments`, bring in only selected plugins, makes for leaner dev/build. 99 | 100 | ### 1.6.0 (Jan 23, 2017) 101 | 102 | - ➕ Feature: Allow restricting by axis. (#40, thanks @dnissley-al) 103 | 104 | ### 1.5.0 (Jan 23, 2017) 105 | 106 | - 🐛 Bugfix: Persist SyntheticEvents when needed (#45, #46) 107 | - ➕ Feature: Add componentWillReceiveProps to `` (#44, thanks @JoaoMosmann) 108 | 109 | ### 1.4.6 (Dec 30, 2016) 110 | 111 | - ✏ Chore: Removed unused ref from ``. 112 | - ✏ Chore: Added development lockfile. 113 | 114 | ### 1.4.5 (Sep 30, 2016) 115 | 116 | - 🐛 Bugfix: Fix bad publish 117 | 118 | ### 1.4.4 (Sep 30, 2016) 119 | 120 | - 🐛 Bugfix: Minor flow errors 121 | 122 | ### 1.4.3 (Sep 27, 2016) 123 | 124 | - 🐛 Bugfix: Don't pass `onResize` in ``. 125 | - 🐛 Bugfix: Fix new Flow errors (type parameters no longer optional). 126 | 127 | ### 1.4.2 (July 1, 2016) 128 | 129 | - 🐛 Bugfix: Don't pass unknown props to underlying DOM element. Fixes React 15.2.0 warnings. 130 | 131 | ### 1.4.1 (May 23, 2016) 132 | 133 | - 🐛 Bugfix: Resizable handle should have a `key` when injected. Fixes React warnings on custom components. 134 | 135 | ### 1.4.0 (May 20, 2016) 136 | 137 | - ✏ Chore: Update to React-Draggable v2, which changed callback data structure. 138 | 139 | ### 1.3.4 (May 17, 2016) 140 | 141 | - 🐛 Bugfix: Slack was not being reset on resizeStop. Fixes #34, #36. 142 | - ✏ Chore: Added `flow-bin` to devDeps. 143 | 144 | ### 1.3.3 (Apr 19, 2016) 145 | 146 | - ➕ Feature: Add Flow comments. 147 | 148 | ### 1.3.2 (Apr 8, 2016) 149 | 150 | - 🐛 Bugfix: Prevent `width` and `height` from leaking to the underlying DOM element and being written. 151 | 152 | ### 1.3.1 (Apr 8, 2016) 153 | 154 | - ✏ Chore: Allow React v15 in peerdeps. 155 | 156 | ### 1.3.0 (Mar 11, 2016) 157 | 158 | - 🐛 Bugfix: Switch to ES2015 Loose Mode to fix IE9/10 issues. 159 | - 🐛 Bugfix: Flow typing fixes. 160 | - 🐛 Bugfix: Styling fixes to the demo page. 161 | 162 | > Changes before 1.3.0 were not logged. Please see the git commit history. 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 Samuel Reed 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-Resizable 2 | 3 | [View the Demo](https://react-grid-layout.github.io/react-resizable/index.html) 4 | 5 | A simple widget that can be resized via one or more handles. 6 | 7 | You can either use the `` element directly, or use the much simpler `` element. 8 | 9 | See the example and associated code in [ExampleLayout](/examples/ExampleLayout.js) and 10 | [ResizableBox](/lib/ResizableBox.js) for more details. 11 | 12 | Make sure you use the associated styles in [/css/styles.css](/css/styles.css), as without them, you will have 13 | problems with handle placement and visibility. 14 | 15 | You can pass options directly to the underlying `DraggableCore` instance by using the prop `draggableOpts`. 16 | See the [demo](/examples/TestLayout.js) for more on this. 17 | 18 | ### Installation 19 | 20 | $ npm install --save react-resizable 21 | 22 | ### Compatibility 23 | 24 | [React-Resizable 3.x](/CHANGELOG.md#3.0.0) is compatible with React `>= 16.3`. 25 | React-Resizable 2.x has been skipped. 26 | [React-Resizable 1.x](/CHANGELOG.md#1.11.1) is compatible with React `14-17`. 27 | 28 | ### Usage 29 | 30 | This package has two major exports: 31 | 32 | * [``](/lib/Resizable.js): A raw component that does not have state. Use as a building block for larger components, by listening to its 33 | callbacks and setting its props. 34 | * [``](/lib/ResizableBox.js): A simple `
` element that manages basic state. Convenient for simple use-cases. 35 | 36 | 37 | #### `` 38 | ```js 39 | const {Resizable} = require('react-resizable'); 40 | 41 | // ES6 42 | import { Resizable } from 'react-resizable'; 43 | 44 | // ... 45 | class Example extends React.Component { 46 | state = { 47 | width: 200, 48 | height: 200, 49 | }; 50 | 51 | // On top layout 52 | onResize = (event, {node, size, handle}) => { 53 | this.setState({width: size.width, height: size.height}); 54 | }; 55 | 56 | render() { 57 | return ( 58 | 59 |
60 | Contents 61 |
62 |
63 | ); 64 | } 65 | } 66 | 67 | ``` 68 | 69 | 70 | #### `` 71 | ```js 72 | const {ResizableBox} = require('react-resizable'); 73 | 74 | // ES6 75 | import { ResizableBox } from 'react-resizable'; 76 | 77 | class Example extends React.Component { 78 | render() { 79 | return ( 80 | 82 | Contents 83 | 84 | ); 85 | } 86 | } 87 | ``` 88 | 89 | ### Props 90 | 91 | These props apply to both `` and ``. Unknown props that are not in the list below will be passed to the child component. 92 | 93 | ```js 94 | type ResizeCallbackData = { 95 | node: HTMLElement, 96 | size: {width: number, height: number}, 97 | handle: ResizeHandleAxis 98 | }; 99 | type ResizeHandleAxis = 's' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'; 100 | 101 | type ResizableProps = 102 | { 103 | children: React.Element, 104 | width: number, 105 | height: number, 106 | // Either a ReactElement to be used as handle, or a function returning an element that is fed the handle's location as its first argument. 107 | handle: ReactElement | (resizeHandle: ResizeHandleAxis, ref: ReactRef) => ReactElement, 108 | // If you change this, be sure to update your css 109 | handleSize: [number, number] = [10, 10], 110 | lockAspectRatio: boolean = false, 111 | axis: 'both' | 'x' | 'y' | 'none' = 'both', 112 | minConstraints: [number, number] = [10, 10], 113 | maxConstraints: [number, number] = [Infinity, Infinity], 114 | onResizeStop?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any, 115 | onResizeStart?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any, 116 | onResize?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any, 117 | draggableOpts?: ?Object, 118 | resizeHandles?: ?Array = ['se'] 119 | }; 120 | ``` 121 | 122 | The following props can also be used on ``: 123 | 124 | ```js 125 | { 126 | style?: Object // styles the returned
127 | } 128 | ``` 129 | 130 | If a `width` or `height` is passed to ``'s `style` prop, it will be ignored as it is required for internal function. 131 | 132 | #### Resize Handle 133 | 134 | If you override the resize handle, we expect that any `ref` passed to your new handle with represent the underlying DOM element. 135 | 136 | This is required, as `react-resizable` must be able to access the underlying DOM node to attach handlers and measure position deltas. 137 | 138 | There are a few ways to do this: 139 | 140 | ##### Native DOM Element 141 | 142 | This requires no special treatment. 143 | 144 | ```js 145 | } /> 146 | ``` 147 | 148 | ##### Custom React Component 149 | 150 | You must [forward the ref](https://reactjs.org/docs/forwarding-refs.html) and props to the underlying DOM element. 151 | 152 | ###### Class Components 153 | 154 | ```js 155 | class MyHandleComponent extends React.Component { 156 | render() { 157 | const {handleAxis, innerRef, ...props} = this.props; 158 | return
159 | } 160 | } 161 | const MyHandle = React.forwardRef((props, ref) => ); 162 | 163 | } /> 164 | ``` 165 | 166 | ###### Functional Components 167 | 168 | ```js 169 | const MyHandle = React.forwardRef((props, ref) => { 170 | const {handleAxis, ...restProps} = props; 171 | return
; 172 | }); 173 | 174 | } /> 175 | ``` 176 | 177 | ##### Custom Function 178 | 179 | You can define a function as a handle, which will simply receive an axis (see above `ResizeHandleAxis` type) and ref. This may be more clear to read, depending on your coding style. 180 | 181 | ```js 182 | const MyHandle = (props) => { 183 | return
; 184 | }; 185 | 186 | } /> 187 | ``` -------------------------------------------------------------------------------- /__tests__/Resizable.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import renderer from 'react-test-renderer'; 4 | import {shallow, mount} from 'enzyme'; 5 | import {DraggableCore} from "react-draggable"; 6 | 7 | import Resizable from '../lib/Resizable'; 8 | 9 | describe('render Resizable', () => { 10 | const props = { 11 | axis: 'both', 12 | className: 'test-classname', 13 | draggableOpts: {}, 14 | handleSize: [20, 20], 15 | height: 50, 16 | lockAspectRatio: false, 17 | maxConstraints: [Infinity, Infinity], 18 | minConstraints: [20, 20], 19 | onResize: jest.fn(), 20 | onResizeStart: jest.fn(), 21 | onResizeStop: jest.fn(), 22 | resizeHandles: ['se', 'e'], 23 | transformScale: 1, 24 | width: 50, 25 | }; 26 | const userChildren = ; 27 | const resizableBoxChildren =
{userChildren}
; 28 | 29 | beforeEach(() => { 30 | jest.clearAllMocks(); 31 | }); 32 | 33 | test('snapshot default props', () => { 34 | const tree = renderer.create({resizableBoxChildren}).toJSON(); 35 | expect(tree).toMatchSnapshot(); 36 | }); 37 | 38 | test('with correct props', () => { 39 | const element = shallow({resizableBoxChildren}); 40 | expect(element.find('.test-classname').find('.children')).toHaveLength(1); 41 | expect(element.find(DraggableCore)).toHaveLength(2); 42 | const cursorSe = element.find('.react-resizable-handle-se'); 43 | const cursorE = element.find('.react-resizable-handle-e'); 44 | expect(cursorSe).toHaveLength(1); 45 | expect(cursorE).toHaveLength(1); 46 | }); 47 | 48 | describe('Handles', () => { 49 | test('with handle function', () => { 50 | const handleFn = (axis, ref) => { 51 | expect(axis).toMatch(/(se|e)/); 52 | expect(ref).toMatchObject({current: null}); // ReactRef 53 | return ; 54 | }; 55 | const element = shallow({resizableBoxChildren}); 56 | 57 | expect(element.find('.test-classname').find('.children')).toHaveLength(1); 58 | expect(element.find(DraggableCore)).toHaveLength(2); 59 | const cursorSe = element.find('.custom-handle-se'); 60 | const cursorE = element.find('.custom-handle-e'); 61 | expect(cursorSe).toHaveLength(1); 62 | expect(cursorE).toHaveLength(1); 63 | }); 64 | 65 | test('with handle component', () => { 66 | const ResizeHandle = React.forwardRef((props, ref) => { 67 | // $FlowIgnore doens't know this is cloned and has handleAxis 68 | const {handleAxis, ...restProps} = props; 69 | return ( 70 |
75 | ); 76 | }); 77 | const element = mount(}>{resizableBoxChildren}); 78 | 79 | expect(element.find('.test-classname').find('.children')).toHaveLength(1); 80 | expect(element.find(DraggableCore)).toHaveLength(2); 81 | const cursorSe = element.find('.element-handle-se'); 82 | const cursorE = element.find('.element-handle-e'); 83 | expect(cursorSe).toHaveLength(1); 84 | expect(cursorE).toHaveLength(1); 85 | }); 86 | 87 | describe('and pass handle props', () => { 88 | test('as component', () => { 89 | const customProps = { 90 | ...props, 91 | resizeHandles: ['se'], 92 | handle: 93 | }; 94 | const element = shallow({resizableBoxChildren}); 95 | expect(element.find('.react-resizable-handle-se')).toHaveLength(0); 96 | expect(element.find('.custom-component')).toHaveLength(1); 97 | }); 98 | test('as function', () => { 99 | const customProps = { 100 | ...props, 101 | resizeHandles: ['se'], 102 | handle: (h) => 103 | }; 104 | const element = shallow({resizableBoxChildren}); 105 | expect(element.find('.custom-component-se')).toHaveLength(1); 106 | }); 107 | }); 108 | }); 109 | 110 | describe(' props filtering', () => { 111 | const allProps = { 112 | ...props, 113 | draggableOpts: {}, 114 | handle:
, 115 | }; 116 | 117 | // Ensure everything in propTypes is represented here. Otherwise the next two tests are not valid 118 | test('all intended props are in our allProps object', () => { 119 | expect(['children', ...Object.keys(allProps)].sort()).toEqual(Object.keys(Resizable.propTypes).sort()); 120 | }); 121 | 122 | test('none of these props leak down to the child', () => { 123 | const element = shallow(
); 124 | expect(Object.keys(element.find('.foo').props())).toEqual(['className', 'children']); 125 | }); 126 | 127 | test('className is constructed properly', () => { 128 | const element = shallow(
); 129 | expect(element.find('.foo').props().className).toEqual(`foo ${allProps.className} react-resizable`); 130 | }); 131 | }); 132 | 133 | describe('onResize callback with modified position', () => { 134 | const customProps = { 135 | ...props, 136 | resizeHandles: ['nw', 'sw' ,'ne', 'se', 'n', 's', 'w', 'e'], 137 | }; 138 | const mockClientRect = { 139 | left: 0, 140 | top: 0, 141 | }; 142 | const node = document.createElement('div'); 143 | // $FlowIgnore need to override to have control over dummy dom element 144 | node.getBoundingClientRect = () => ({ ...mockClientRect }); 145 | const mockEvent = { }; 146 | const element = shallow({resizableBoxChildren}); 147 | function findHandle(element, axis) { 148 | return element.find(`.react-resizable-handle-${axis}`).parent(); 149 | } 150 | 151 | test('Gradual resizing without movement between does not modify callback', () => { 152 | expect(props.onResize).not.toHaveBeenCalled(); 153 | const seHandle = element.find('.react-resizable-handle-se').parent(); 154 | seHandle.prop('onDrag')(mockEvent, { node, deltaX: 5, deltaY: 10 }); 155 | expect(props.onResize).toHaveBeenLastCalledWith( 156 | mockEvent, 157 | expect.objectContaining({ 158 | size: { 159 | height: 60, 160 | width: 55, 161 | }, 162 | }) 163 | ); 164 | }); 165 | 166 | test('Movement between callbacks modifies response values', () => { 167 | expect(props.onResize).not.toHaveBeenCalled(); 168 | 169 | const nwHandle = findHandle(element, 'nw'); 170 | mockClientRect.top = -10; // Object moves between callbacks 171 | nwHandle.prop('onDrag')(mockEvent, { node, deltaX: 5, deltaY: 10 }); 172 | expect(props.onResize).toHaveBeenLastCalledWith( 173 | mockEvent, 174 | expect.objectContaining({ 175 | size: { 176 | height: 50, // No height change since deltaY is caused by clientRect moving vertically 177 | width: 45, 178 | }, 179 | }) 180 | ); 181 | 182 | mockClientRect.left = 20; // Object moves between callbacks 183 | nwHandle.prop('onDrag')(mockEvent, { node, deltaX: 5, deltaY: 10 }); 184 | expect(props.onResize).toHaveBeenLastCalledWith( 185 | mockEvent, 186 | expect.objectContaining({ 187 | size: { 188 | height: 40, // Height decreased as deltaY increases - no further top position change since last 189 | width: 25, // Width decreased 25 - 5 from deltaX and 20 from changing position 190 | }, 191 | }) 192 | ); 193 | 194 | props.onResize.mockClear(); 195 | mockClientRect.left -= 10; // Object moves between callbacks 196 | mockClientRect.top -= 10; // Object moves between callbacks 197 | nwHandle.prop('onDrag')(mockEvent, { node, deltaX: 10, deltaY: 10 }); 198 | expect(props.onResize).not.toHaveBeenCalled(); 199 | 200 | mockClientRect.left -= 10; // Object moves between callbacks 201 | mockClientRect.top -= 10; // Object moves between callbacks 202 | const swHandle = findHandle(element, 'sw'); 203 | swHandle.prop('onDrag')(mockEvent, { node, deltaX: 10, deltaY: 10 }); 204 | expect(props.onResize).toHaveBeenLastCalledWith( 205 | mockEvent, 206 | expect.objectContaining({ 207 | size: { 208 | height: 60, // Changed since resizing from bottom doesn't cause position change 209 | width: 50, // No change - movement has caused entire delta 210 | }, 211 | }) 212 | ); 213 | 214 | mockClientRect.left -= 10; // Object moves between callbacks 215 | mockClientRect.top -= 10; // Object moves between callbacks 216 | const neHandle = findHandle(element, 'ne'); 217 | neHandle.prop('onDrag')(mockEvent, { node, deltaX: 10, deltaY: 10 }); 218 | expect(props.onResize).toHaveBeenLastCalledWith( 219 | mockEvent, 220 | expect.objectContaining({ 221 | size: { 222 | height: 50, // No change - movement has caused entire delta 223 | width: 60, // Changed since resizing from right doesn't cause position change 224 | }, 225 | }) 226 | ); 227 | 228 | mockClientRect.left -= 10; // Object moves between callbacks 229 | mockClientRect.top -= 10; // Object moves between callbacks 230 | const seHandle = element.find('DraggableCore').at(3); 231 | seHandle.prop('onDrag')(mockEvent, { node, deltaX: 10, deltaY: 10 }); 232 | expect(props.onResize).toHaveBeenLastCalledWith( 233 | mockEvent, 234 | expect.objectContaining({ 235 | size: { 236 | height: 60, // Changed since resizing from right doesn't cause position change 237 | width: 60, // Changed since resizing from right doesn't cause position change 238 | }, 239 | }) 240 | ); 241 | }); 242 | 243 | test('use of < 1 transformScale', () => { 244 | const element = shallow({resizableBoxChildren}); 245 | expect(props.onResize).not.toHaveBeenCalled(); 246 | const nwHandle = findHandle(element, 'nw'); 247 | nwHandle.prop('onDrag')(mockEvent, { node, deltaX: 5, deltaY: 10 }); 248 | expect(props.onResize).toHaveBeenLastCalledWith( 249 | mockEvent, 250 | expect.objectContaining({ 251 | size: { 252 | // Should be doubled 253 | height: 30, 254 | width: 40, 255 | }, 256 | }) 257 | ); 258 | 259 | mockClientRect.left = 20; // Object moves between callbacks 260 | nwHandle.prop('onDrag')(mockEvent, { node, deltaX: 5, deltaY: 10 }); 261 | expect(props.onResize).toHaveBeenLastCalledWith( 262 | mockEvent, 263 | expect.objectContaining({ 264 | size: { 265 | height: 30, // Height decreased as deltaY increases - no further top position change since last 266 | width: 20, // Width decreased 10 from deltaX and 20 from changing position 267 | }, 268 | }) 269 | ); 270 | }); 271 | 272 | test('use of > 1 transformScale', () => { 273 | const element = shallow({resizableBoxChildren}); 274 | expect(props.onResize).not.toHaveBeenCalled(); 275 | const nwHandle = findHandle(element, 'nw'); 276 | nwHandle.prop('onDrag')(mockEvent, { node, deltaX: 5, deltaY: 10 }); 277 | expect(props.onResize).toHaveBeenLastCalledWith( 278 | mockEvent, 279 | expect.objectContaining({ 280 | size: { 281 | // Should be halved 282 | height: 45, 283 | width: 47.5, 284 | }, 285 | }) 286 | ); 287 | }); 288 | 289 | describe('lockAspectRatio', () => { 290 | 291 | [[5, 0], [0, 5], [10, 5], [5, 10], [50, 51]].forEach(([w, h]) => { 292 | test(`drags with aspect ratio preserved w:${w} h:${h}`, () => { 293 | const element = shallow({resizableBoxChildren}); 294 | expect(props.onResize).not.toHaveBeenCalled(); 295 | const seHandle = findHandle(element, 'se'); 296 | seHandle.prop('onDrag')(mockEvent, { node, deltaX: w, deltaY: h }); 297 | expect(props.onResize).toHaveBeenLastCalledWith( 298 | mockEvent, 299 | expect.objectContaining({ 300 | size: { 301 | height: 50 + Math.max(w, h), 302 | width: 50 + Math.max(w, h), 303 | }, 304 | }) 305 | ); 306 | }); 307 | }); 308 | }); 309 | }); 310 | }); 311 | -------------------------------------------------------------------------------- /__tests__/ResizableBox.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import renderer from 'react-test-renderer'; 4 | import {shallow} from 'enzyme'; 5 | 6 | import ResizableBox from '../lib/ResizableBox'; 7 | import Resizable from "../lib/Resizable"; 8 | 9 | describe('render ResizableBox', () => { 10 | const props = { 11 | axis: 'x', 12 | draggableOpts: {}, 13 | handle: (jest.fn((resizeHandle, ref) => ): Function), 14 | handleSize: [20, 20], 15 | height: 50, 16 | lockAspectRatio: false, 17 | maxConstraints: [30, 30], 18 | minConstraints: [10, 10], 19 | onResize: jest.fn(), 20 | onResizeStart: jest.fn(), 21 | onResizeStop: jest.fn(), 22 | resizeHandles: ['w'], 23 | transformScale: 1, 24 | width: 50, 25 | }; 26 | const children = ; 27 | 28 | beforeEach(() => { 29 | jest.clearAllMocks(); 30 | }); 31 | 32 | test('snapshot default props', () => { 33 | const tree = renderer.create({children}).toJSON(); 34 | expect(tree).toMatchSnapshot(); 35 | }); 36 | 37 | test('with correct props', () => { 38 | const element = shallow({children}); 39 | expect(element.state()).toEqual({ 40 | height: 50, 41 | propsHeight: 50, 42 | propsWidth: 50, 43 | width: 50, 44 | }); 45 | const resizable = element.find(Resizable); 46 | const fakeEvent = {persist: jest.fn()}; 47 | const data = {node: children, size: {width: 30, height: 30}, handle: 'w'}; 48 | resizable.simulate('resize', fakeEvent, data); 49 | expect(element.state()).toEqual({ 50 | height: 30, 51 | propsHeight: 50, 52 | propsWidth: 50, 53 | width: 30, 54 | }); 55 | expect(element.find('.children')).toHaveLength(1); 56 | expect(fakeEvent.persist).toHaveBeenCalledTimes(1); 57 | expect(props.onResize).toHaveBeenCalledWith(fakeEvent, data); 58 | 59 | resizable.simulate('resizeStart', fakeEvent, data); 60 | expect(props.onResizeStart).toHaveBeenCalledWith(fakeEvent, data); 61 | 62 | resizable.simulate('resizeStop', fakeEvent, data); 63 | expect(props.onResizeStop).toHaveBeenCalledWith(fakeEvent, data); 64 | }); 65 | 66 | describe(' props filtering', () => { 67 | // Ensure everything in propTypes is represented here. Otherwise the next two tests are not valid 68 | test('all intended props are in our props object', () => { 69 | expect(['children', 'className', ...Object.keys(props)].sort()).toEqual(Object.keys(Resizable.propTypes).sort()); 70 | }); 71 | 72 | test('none of these props leak down to the child', () => { 73 | const element = shallow(); 74 | expect(Object.keys(element.find('div').props())).toEqual(['style']); 75 | }); 76 | 77 | test('className is constructed properly', () => { 78 | const element = shallow(); 79 | expect(element.find('div').props().className).toEqual(`foo`); 80 | }); 81 | }); 82 | 83 | test('style prop', () => { 84 | const element = shallow({children}); 85 | expect(element.find('div').at(0).prop('style')).toEqual({ 86 | width: '50px', 87 | height: '50px', 88 | backgroundColor: 'red' 89 | }); 90 | }); 91 | 92 | test('style prop width and height ignored', () => { 93 | const element = shallow({children}); 94 | expect(element.find('div').at(0).prop('style')).toEqual({ 95 | width: '50px', 96 | height: '50px', 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/Resizable.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`render Resizable snapshot default props 1`] = ` 4 |
13 | 16 | 22 | 28 |
29 | `; 30 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/ResizableBox.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`render ResizableBox snapshot default props 1`] = ` 4 |
13 | 16 | 22 |
23 | `; 24 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | rm -rf ./build 3 | 4 | # Simple babel run 5 | ./node_modules/.bin/babel --out-dir ./build ./lib 6 | 7 | # Gen flow configs 8 | # When https://github.com/facebook/flow/issues/2830 et al are fixed we can use this, but not until then 9 | # find ./lib -type f -name '*.js' -exec sh -c "$BIN/flow gen-flow-files \$0 --out-dir build" {} \; 10 | 11 | # Copy original source as js.flow; flow will pick them up 12 | find ./lib -type f -name '*.js' -exec sh -c 'cp $0 build/$(basename $0).flow' {} \; 13 | -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | .react-resizable { 2 | position: relative; 3 | } 4 | .react-resizable-handle { 5 | position: absolute; 6 | width: 20px; 7 | height: 20px; 8 | background-repeat: no-repeat; 9 | background-origin: content-box; 10 | box-sizing: border-box; 11 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+'); 12 | background-position: bottom right; 13 | padding: 0 3px 3px 0; 14 | } 15 | .react-resizable-handle-sw { 16 | bottom: 0; 17 | left: 0; 18 | cursor: sw-resize; 19 | transform: rotate(90deg); 20 | } 21 | .react-resizable-handle-se { 22 | bottom: 0; 23 | right: 0; 24 | cursor: se-resize; 25 | } 26 | .react-resizable-handle-nw { 27 | top: 0; 28 | left: 0; 29 | cursor: nw-resize; 30 | transform: rotate(180deg); 31 | } 32 | .react-resizable-handle-ne { 33 | top: 0; 34 | right: 0; 35 | cursor: ne-resize; 36 | transform: rotate(270deg); 37 | } 38 | .react-resizable-handle-w, 39 | .react-resizable-handle-e { 40 | top: 50%; 41 | margin-top: -10px; 42 | cursor: ew-resize; 43 | } 44 | .react-resizable-handle-w { 45 | left: 0; 46 | transform: rotate(135deg); 47 | } 48 | .react-resizable-handle-e { 49 | right: 0; 50 | transform: rotate(315deg); 51 | } 52 | .react-resizable-handle-n, 53 | .react-resizable-handle-s { 54 | left: 50%; 55 | margin-left: -10px; 56 | cursor: ns-resize; 57 | } 58 | .react-resizable-handle-n { 59 | top: 0; 60 | transform: rotate(225deg); 61 | } 62 | .react-resizable-handle-s { 63 | bottom: 0; 64 | transform: rotate(45deg); 65 | } -------------------------------------------------------------------------------- /examples/ExampleLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Resizable from '../lib/Resizable'; 3 | import ResizableBox from '../lib/ResizableBox'; 4 | import 'style-loader!css-loader!../css/styles.css'; 5 | import 'style-loader!css-loader!./example.css'; 6 | 7 | const CustomResizeHandle = React.forwardRef((props, ref) => { 8 | const {handleAxis, ...restProps} = props; 9 | return ( 10 |
15 | ); 16 | }); 17 | 18 | export default class ExampleLayout extends React.Component<{}, {width: number, height: number}> { 19 | state = { 20 | width: 200, 21 | height: 200, 22 | absoluteWidth: 200, 23 | absoluteHeight: 200, 24 | absoluteLeft: 0, 25 | absoluteTop: 0, 26 | }; 27 | 28 | onResetClick = () => { 29 | this.setState({ width: 200, height: 200, absoluteWidth: 200, absoluteHeight: 200 }); 30 | }; 31 | 32 | // On top layout 33 | onFirstBoxResize = (event, {element, size, handle}) => { 34 | this.setState({width: size.width, height: size.height}); 35 | }; 36 | 37 | // On bottom layout. Used to resize the center element around its flex parent. 38 | onResizeAbsolute = (event, {element, size, handle}) => { 39 | this.setState((state) => { 40 | let newLeft = state.absoluteLeft; 41 | let newTop = state.absoluteTop; 42 | const deltaHeight = size.height - state.absoluteHeight; 43 | const deltaWidth = size.width - state.absoluteWidth; 44 | if (handle[0] === 'n') { 45 | newTop -= deltaHeight; 46 | } else if (handle[0] === 's') { 47 | newTop += deltaHeight; 48 | } 49 | if (handle[handle.length - 1] === 'w') { 50 | newLeft -= deltaWidth; 51 | } else if (handle[handle.length - 1] === 'e') { 52 | newLeft += deltaWidth; 53 | } 54 | 55 | return { 56 | absoluteWidth: size.width, 57 | absoluteHeight: size.height, 58 | absoluteLeft: newLeft, 59 | absoluteTop: newTop, 60 | }; 61 | }); 62 | }; 63 | 64 | render() { 65 | return ( 66 |
67 | 68 |

Statically Positioned Layout

69 |
70 | 71 |
72 | {"Raw use of element. 200x200, all Resize Handles."} 73 | 74 |
75 |
76 | 77 | {""} 78 | 79 | } 84 | handleSize={[8, 8]}> 85 | {" with custom overflow style & handle in SE corner."} 86 | 87 | } 92 | handleSize={[8, 8]}> 93 | {" with a custom resize handle component."} 94 | 95 | } 100 | handleSize={[8, 8]} 101 | resizeHandles={['sw', 'se', 'nw', 'ne', 'w', 'e', 'n', 's']}> 102 | {" with custom handles in all locations."} 103 | 104 | 105 | Resizable box that snaps to even intervals of 25px. 106 | 107 | 108 | Resizable box, starting at 200x200. Min size is 150x150, max is 500x300. 109 | 110 | 111 | Resizable box with a handle that only appears on hover. 112 | 113 | 114 | Resizable square with a locked aspect ratio. 115 | 116 | 117 | Resizable rectangle with a locked aspect ratio. 118 | 119 | 120 | Only resizable by "x" axis. 121 | 122 | 123 | Only resizable by "y" axis. 124 | 125 | 126 | Resizable ("both" axis). 127 | 128 | 129 | Not resizable ("none" axis). 130 | 131 |
132 | 133 |

Absolutely Positioned Layout

134 |
135 | 136 | Top-left Aligned 137 | 138 | 139 | Bottom-left Aligned 140 | 141 | {/* See implementation of `onResizeAbsolute` for how this can be moved around its container */} 142 | 149 |
156 | {"Raw use of element with controlled position. Resize and reposition in all directions."} 157 |
158 |
159 | 160 | Top-right Aligned 161 | 162 | 163 | Bottom-right Aligned 164 | 165 |
166 | 167 |

Scaled Absolute Layout

168 |
169 | 170 | If you are nesting Resizables in an element with transform: scale(n), be sure to pass the same n  171 | as the transformScale property. 172 |
173 | This box has scale 0.75. 174 |
175 |
176 |
177 | 178 | {" with incorrect scale 1"} 179 | 180 | 181 | 182 | {" with correct scale 0.75"} 183 | 184 | 185 | {/* See implementation of `onResizeAbsolute` for how this can be moved around its container */} 186 | 194 |
201 | {"Raw use of element with controlled position. Resize and reposition in all directions."} 202 |
203 |
204 | 205 | 206 | {" with correct scale 0.75"} 207 | 208 | 209 | 210 | {" with correct scale 0.75"} 211 | 212 |
213 | 214 |
215 | ); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /examples/example.css: -------------------------------------------------------------------------------- 1 | .layoutRoot { 2 | display: flex; 3 | background: #eee; 4 | margin-bottom: 20px; 5 | flex-wrap: wrap; 6 | } 7 | .absoluteLayout { 8 | height: 600px; 9 | position: relative; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | .scaledLayout { 14 | width: 125%; 15 | left: -12.5vw; 16 | transform: scale(0.75); 17 | margin-top: -7.5vh; 18 | } 19 | 20 | .box { 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | flex-direction: column; 25 | background: #ccc; 26 | border: 1px solid black; 27 | text-align: center; 28 | padding: 10px; 29 | box-sizing: border-box; 30 | margin-bottom: 10px; 31 | overflow: hidden; 32 | position: relative; 33 | margin: 20px; 34 | } 35 | .box .text { 36 | text-align: center; 37 | } 38 | 39 | .hover-handles .react-resizable-handle { 40 | display: none; 41 | } 42 | .hover-handles:hover .react-resizable-handle { 43 | display: block; 44 | } 45 | .absolutely-positioned { 46 | position: absolute !important; 47 | } 48 | .left-aligned { 49 | left: 0; 50 | } 51 | .right-aligned { 52 | right: 0; 53 | } 54 | .top-aligned { 55 | top: 0; 56 | } 57 | .bottom-aligned { 58 | bottom: 0; 59 | } 60 | 61 | .custom-box { 62 | overflow: visible; 63 | } 64 | .custom-handle { 65 | position: absolute; 66 | width: 8px; 67 | height: 8px; 68 | background-color: #1153aa; 69 | opacity: 0.75; 70 | border-radius: 4px; 71 | } 72 | .custom-handle-sw { 73 | bottom: -4px; 74 | left: -4px; 75 | cursor: sw-resize; 76 | } 77 | .custom-handle-se { 78 | bottom: -4px; 79 | right: -4px; 80 | cursor: se-resize; 81 | } 82 | .custom-handle-nw { 83 | top: -4px; 84 | left: -4px; 85 | cursor: nw-resize; 86 | } 87 | .custom-handle-ne { 88 | top: -4px; 89 | right: -4px; 90 | cursor: ne-resize; 91 | } 92 | .custom-handle-w, 93 | .custom-handle-e { 94 | top: 50%; 95 | margin-top: -4px; 96 | cursor: ew-resize; 97 | } 98 | .custom-handle-w { 99 | left: -4px; 100 | } 101 | .custom-handle-e { 102 | right: -4px; 103 | } 104 | .custom-handle-n, 105 | .custom-handle-s { 106 | left: 50%; 107 | margin-left: -4px; 108 | cursor: ns-resize; 109 | } 110 | .custom-handle-n { 111 | top: -4px; 112 | } 113 | .custom-handle-s { 114 | bottom: -4px; 115 | } 116 | .custom-resize-handle-component { 117 | background-color: red; 118 | } -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import ExampleLayout from './ExampleLayout'; 5 | 6 | document.addEventListener("DOMContentLoaded", function(event) { 7 | var contentDiv = document.getElementById('content'); 8 | ReactDOM.render(React.createElement(ExampleLayout), contentDiv); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | 16 | React-Resizable 17 | 18 | 19 |

React-Resizable Demo

20 |

React-resizable is a simple component that you wrap your existing components inside. It 21 | gives those components resize handles. 22 |

23 |

View project on GitHub

24 |

View this page's source

25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /flow-typed/npm/jest_v26.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 9a1f9054d272cf6383233b8bfb639f84 2 | // flow-typed version: 4efeddffd8/jest_v26.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 | /** 108 | * A custom Jasmine equality tester 109 | */ 110 | asymmetricMatch(value: mixed): boolean, 111 | ... 112 | }; 113 | 114 | type JestCallsType = { 115 | allArgs(): mixed, 116 | all(): mixed, 117 | any(): boolean, 118 | count(): number, 119 | first(): mixed, 120 | mostRecent(): mixed, 121 | reset(): void, 122 | ... 123 | }; 124 | 125 | type JestClockType = { 126 | install(): void, 127 | mockDate(date: Date): void, 128 | tick(milliseconds?: number): void, 129 | uninstall(): void, 130 | ... 131 | }; 132 | 133 | type JestMatcherResult = { 134 | message?: string | (() => string), 135 | pass: boolean, 136 | ... 137 | }; 138 | 139 | type JestMatcher = ( 140 | received: any, 141 | ...actual: Array 142 | ) => JestMatcherResult | Promise; 143 | 144 | type JestPromiseType = { 145 | /** 146 | * Use rejects to unwrap the reason of a rejected promise so any other 147 | * matcher can be chained. If the promise is fulfilled the assertion fails. 148 | */ 149 | rejects: JestExpectType, 150 | /** 151 | * Use resolves to unwrap the value of a fulfilled promise so any other 152 | * matcher can be chained. If the promise is rejected the assertion fails. 153 | */ 154 | resolves: JestExpectType, 155 | ... 156 | }; 157 | 158 | /** 159 | * Jest allows functions and classes to be used as test names in test() and 160 | * describe() 161 | */ 162 | type JestTestName = string | Function; 163 | 164 | /** 165 | * Plugin: jest-styled-components 166 | */ 167 | 168 | type JestStyledComponentsMatcherValue = 169 | | string 170 | | JestAsymmetricEqualityType 171 | | RegExp 172 | | typeof undefined; 173 | 174 | type JestStyledComponentsMatcherOptions = { 175 | media?: string, 176 | modifier?: string, 177 | supports?: string, 178 | ... 179 | }; 180 | 181 | type JestStyledComponentsMatchersType = { 182 | toHaveStyleRule( 183 | property: string, 184 | value: JestStyledComponentsMatcherValue, 185 | options?: JestStyledComponentsMatcherOptions 186 | ): void, 187 | ... 188 | }; 189 | 190 | /** 191 | * Plugin: jest-enzyme 192 | */ 193 | type EnzymeMatchersType = { 194 | // 5.x 195 | toBeEmpty(): void, 196 | toBePresent(): void, 197 | // 6.x 198 | toBeChecked(): void, 199 | toBeDisabled(): void, 200 | toBeEmptyRender(): void, 201 | toContainMatchingElement(selector: string): void, 202 | toContainMatchingElements(n: number, selector: string): void, 203 | toContainExactlyOneMatchingElement(selector: string): void, 204 | toContainReact(element: React$Element): void, 205 | toExist(): void, 206 | toHaveClassName(className: string): void, 207 | toHaveHTML(html: string): void, 208 | toHaveProp: ((propKey: string, propValue?: any) => void) & 209 | ((props: { ... }) => void), 210 | toHaveRef(refName: string): void, 211 | toHaveState: ((stateKey: string, stateValue?: any) => void) & 212 | ((state: { ... }) => void), 213 | toHaveStyle: ((styleKey: string, styleValue?: any) => void) & 214 | ((style: { ... }) => void), 215 | toHaveTagName(tagName: string): void, 216 | toHaveText(text: string): void, 217 | toHaveValue(value: any): void, 218 | toIncludeText(text: string): void, 219 | toMatchElement( 220 | element: React$Element, 221 | options?: {| ignoreProps?: boolean, verbose?: boolean |} 222 | ): void, 223 | toMatchSelector(selector: string): void, 224 | // 7.x 225 | toHaveDisplayName(name: string): void, 226 | ... 227 | }; 228 | 229 | // DOM testing library extensions (jest-dom) 230 | // https://github.com/testing-library/jest-dom 231 | type DomTestingLibraryType = { 232 | /** 233 | * @deprecated 234 | */ 235 | toBeInTheDOM(container?: HTMLElement): void, 236 | 237 | // 4.x 238 | toBeInTheDocument(): void, 239 | toBeVisible(): void, 240 | toBeEmpty(): void, 241 | toBeDisabled(): void, 242 | toBeEnabled(): void, 243 | toBeInvalid(): void, 244 | toBeRequired(): void, 245 | toBeValid(): void, 246 | toContainElement(element: HTMLElement | null): void, 247 | toContainHTML(htmlText: string): void, 248 | toHaveAttribute(attr: string, value?: any): void, 249 | toHaveClass(...classNames: string[]): void, 250 | toHaveFocus(): void, 251 | toHaveFormValues(expectedValues: { [name: string]: any, ... }): void, 252 | toHaveStyle(css: string | { [name: string]: any, ... }): void, 253 | toHaveTextContent( 254 | text: string | RegExp, 255 | options?: {| normalizeWhitespace: boolean |} 256 | ): void, 257 | toHaveValue(value?: string | string[] | number): void, 258 | 259 | // 5.x 260 | toHaveDisplayValue(value: string | string[]): void, 261 | toBeChecked(): void, 262 | ... 263 | }; 264 | 265 | // Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers 266 | type JestJQueryMatchersType = { 267 | toExist(): void, 268 | toHaveLength(len: number): void, 269 | toHaveId(id: string): void, 270 | toHaveClass(className: string): void, 271 | toHaveTag(tag: string): void, 272 | toHaveAttr(key: string, val?: any): void, 273 | toHaveProp(key: string, val?: any): void, 274 | toHaveText(text: string | RegExp): void, 275 | toHaveData(key: string, val?: any): void, 276 | toHaveValue(val: any): void, 277 | toHaveCss(css: { [key: string]: any, ... }): void, 278 | toBeChecked(): void, 279 | toBeDisabled(): void, 280 | toBeEmpty(): void, 281 | toBeHidden(): void, 282 | toBeSelected(): void, 283 | toBeVisible(): void, 284 | toBeFocused(): void, 285 | toBeInDom(): void, 286 | toBeMatchedBy(sel: string): void, 287 | toHaveDescendant(sel: string): void, 288 | toHaveDescendantWithText(sel: string, text: string | RegExp): void, 289 | ... 290 | }; 291 | 292 | // Jest Extended Matchers: https://github.com/jest-community/jest-extended 293 | type JestExtendedMatchersType = { 294 | /** 295 | * Note: Currently unimplemented 296 | * Passing assertion 297 | * 298 | * @param {String} message 299 | */ 300 | // pass(message: string): void; 301 | 302 | /** 303 | * Note: Currently unimplemented 304 | * Failing assertion 305 | * 306 | * @param {String} message 307 | */ 308 | // fail(message: string): void; 309 | 310 | /** 311 | * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. 312 | */ 313 | toBeEmpty(): void, 314 | /** 315 | * Use .toBeOneOf when checking if a value is a member of a given Array. 316 | * @param {Array.<*>} members 317 | */ 318 | toBeOneOf(members: any[]): void, 319 | /** 320 | * Use `.toBeNil` when checking a value is `null` or `undefined`. 321 | */ 322 | toBeNil(): void, 323 | /** 324 | * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. 325 | * @param {Function} predicate 326 | */ 327 | toSatisfy(predicate: (n: any) => boolean): void, 328 | /** 329 | * Use `.toBeArray` when checking if a value is an `Array`. 330 | */ 331 | toBeArray(): void, 332 | /** 333 | * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. 334 | * @param {Number} x 335 | */ 336 | toBeArrayOfSize(x: number): void, 337 | /** 338 | * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. 339 | * @param {Array.<*>} members 340 | */ 341 | toIncludeAllMembers(members: any[]): void, 342 | /** 343 | * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. 344 | * @param {Array.<*>} members 345 | */ 346 | toIncludeAnyMembers(members: any[]): void, 347 | /** 348 | * 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. 349 | * @param {Function} predicate 350 | */ 351 | toSatisfyAll(predicate: (n: any) => boolean): void, 352 | /** 353 | * Use `.toBeBoolean` when checking if a value is a `Boolean`. 354 | */ 355 | toBeBoolean(): void, 356 | /** 357 | * Use `.toBeTrue` when checking a value is equal (===) to `true`. 358 | */ 359 | toBeTrue(): void, 360 | /** 361 | * Use `.toBeFalse` when checking a value is equal (===) to `false`. 362 | */ 363 | toBeFalse(): void, 364 | /** 365 | * Use .toBeDate when checking if a value is a Date. 366 | */ 367 | toBeDate(): void, 368 | /** 369 | * Use `.toBeFunction` when checking if a value is a `Function`. 370 | */ 371 | toBeFunction(): void, 372 | /** 373 | * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. 374 | * 375 | * Note: Required Jest version >22 376 | * 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 377 | * 378 | * @param {Mock} mock 379 | */ 380 | toHaveBeenCalledBefore(mock: JestMockFn): void, 381 | /** 382 | * Use `.toBeNumber` when checking if a value is a `Number`. 383 | */ 384 | toBeNumber(): void, 385 | /** 386 | * Use `.toBeNaN` when checking a value is `NaN`. 387 | */ 388 | toBeNaN(): void, 389 | /** 390 | * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. 391 | */ 392 | toBeFinite(): void, 393 | /** 394 | * Use `.toBePositive` when checking if a value is a positive `Number`. 395 | */ 396 | toBePositive(): void, 397 | /** 398 | * Use `.toBeNegative` when checking if a value is a negative `Number`. 399 | */ 400 | toBeNegative(): void, 401 | /** 402 | * Use `.toBeEven` when checking if a value is an even `Number`. 403 | */ 404 | toBeEven(): void, 405 | /** 406 | * Use `.toBeOdd` when checking if a value is an odd `Number`. 407 | */ 408 | toBeOdd(): void, 409 | /** 410 | * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). 411 | * 412 | * @param {Number} start 413 | * @param {Number} end 414 | */ 415 | toBeWithin(start: number, end: number): void, 416 | /** 417 | * Use `.toBeObject` when checking if a value is an `Object`. 418 | */ 419 | toBeObject(): void, 420 | /** 421 | * Use `.toContainKey` when checking if an object contains the provided key. 422 | * 423 | * @param {String} key 424 | */ 425 | toContainKey(key: string): void, 426 | /** 427 | * Use `.toContainKeys` when checking if an object has all of the provided keys. 428 | * 429 | * @param {Array.} keys 430 | */ 431 | toContainKeys(keys: string[]): void, 432 | /** 433 | * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. 434 | * 435 | * @param {Array.} keys 436 | */ 437 | toContainAllKeys(keys: string[]): void, 438 | /** 439 | * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. 440 | * 441 | * @param {Array.} keys 442 | */ 443 | toContainAnyKeys(keys: string[]): void, 444 | /** 445 | * Use `.toContainValue` when checking if an object contains the provided value. 446 | * 447 | * @param {*} value 448 | */ 449 | toContainValue(value: any): void, 450 | /** 451 | * Use `.toContainValues` when checking if an object contains all of the provided values. 452 | * 453 | * @param {Array.<*>} values 454 | */ 455 | toContainValues(values: any[]): void, 456 | /** 457 | * Use `.toContainAllValues` when checking if an object only contains all of the provided values. 458 | * 459 | * @param {Array.<*>} values 460 | */ 461 | toContainAllValues(values: any[]): void, 462 | /** 463 | * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. 464 | * 465 | * @param {Array.<*>} values 466 | */ 467 | toContainAnyValues(values: any[]): void, 468 | /** 469 | * Use `.toContainEntry` when checking if an object contains the provided entry. 470 | * 471 | * @param {Array.} entry 472 | */ 473 | toContainEntry(entry: [string, string]): void, 474 | /** 475 | * Use `.toContainEntries` when checking if an object contains all of the provided entries. 476 | * 477 | * @param {Array.>} entries 478 | */ 479 | toContainEntries(entries: [string, string][]): void, 480 | /** 481 | * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. 482 | * 483 | * @param {Array.>} entries 484 | */ 485 | toContainAllEntries(entries: [string, string][]): void, 486 | /** 487 | * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. 488 | * 489 | * @param {Array.>} entries 490 | */ 491 | toContainAnyEntries(entries: [string, string][]): void, 492 | /** 493 | * Use `.toBeExtensible` when checking if an object is extensible. 494 | */ 495 | toBeExtensible(): void, 496 | /** 497 | * Use `.toBeFrozen` when checking if an object is frozen. 498 | */ 499 | toBeFrozen(): void, 500 | /** 501 | * Use `.toBeSealed` when checking if an object is sealed. 502 | */ 503 | toBeSealed(): void, 504 | /** 505 | * Use `.toBeString` when checking if a value is a `String`. 506 | */ 507 | toBeString(): void, 508 | /** 509 | * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. 510 | * 511 | * @param {String} string 512 | */ 513 | toEqualCaseInsensitive(string: string): void, 514 | /** 515 | * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. 516 | * 517 | * @param {String} prefix 518 | */ 519 | toStartWith(prefix: string): void, 520 | /** 521 | * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. 522 | * 523 | * @param {String} suffix 524 | */ 525 | toEndWith(suffix: string): void, 526 | /** 527 | * Use `.toInclude` when checking if a `String` includes the given `String` substring. 528 | * 529 | * @param {String} substring 530 | */ 531 | toInclude(substring: string): void, 532 | /** 533 | * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. 534 | * 535 | * @param {String} substring 536 | * @param {Number} times 537 | */ 538 | toIncludeRepeated(substring: string, times: number): void, 539 | /** 540 | * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. 541 | * 542 | * @param {Array.} substring 543 | */ 544 | toIncludeMultiple(substring: string[]): void, 545 | ... 546 | }; 547 | 548 | // Diffing snapshot utility for Jest (snapshot-diff) 549 | // https://github.com/jest-community/snapshot-diff 550 | type SnapshotDiffType = { 551 | /** 552 | * Compare the difference between the actual in the `expect()` 553 | * vs the object inside `valueB` with some extra options. 554 | */ 555 | toMatchDiffSnapshot( 556 | valueB: any, 557 | options?: {| 558 | expand?: boolean, 559 | colors?: boolean, 560 | contextLines?: number, 561 | stablePatchmarks?: boolean, 562 | aAnnotation?: string, 563 | bAnnotation?: string, 564 | |}, 565 | testName?: string 566 | ): void, 567 | ... 568 | }; 569 | 570 | interface JestExpectType { 571 | not: JestExpectType & 572 | EnzymeMatchersType & 573 | DomTestingLibraryType & 574 | JestJQueryMatchersType & 575 | JestStyledComponentsMatchersType & 576 | JestExtendedMatchersType & 577 | SnapshotDiffType; 578 | /** 579 | * If you have a mock function, you can use .lastCalledWith to test what 580 | * arguments it was last called with. 581 | */ 582 | lastCalledWith(...args: Array): void; 583 | /** 584 | * toBe just checks that a value is what you expect. It uses === to check 585 | * strict equality. 586 | */ 587 | toBe(value: any): void; 588 | /** 589 | * Use .toBeCalledWith to ensure that a mock function was called with 590 | * specific arguments. 591 | */ 592 | toBeCalledWith(...args: Array): void; 593 | /** 594 | * Using exact equality with floating point numbers is a bad idea. Rounding 595 | * means that intuitive things fail. 596 | */ 597 | toBeCloseTo(num: number, delta: any): void; 598 | /** 599 | * Use .toBeDefined to check that a variable is not undefined. 600 | */ 601 | toBeDefined(): void; 602 | /** 603 | * Use .toBeFalsy when you don't care what a value is, you just want to 604 | * ensure a value is false in a boolean context. 605 | */ 606 | toBeFalsy(): void; 607 | /** 608 | * To compare floating point numbers, you can use toBeGreaterThan. 609 | */ 610 | toBeGreaterThan(number: number): void; 611 | /** 612 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 613 | */ 614 | toBeGreaterThanOrEqual(number: number): void; 615 | /** 616 | * To compare floating point numbers, you can use toBeLessThan. 617 | */ 618 | toBeLessThan(number: number): void; 619 | /** 620 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 621 | */ 622 | toBeLessThanOrEqual(number: number): void; 623 | /** 624 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 625 | * class. 626 | */ 627 | toBeInstanceOf(cls: Class<*>): void; 628 | /** 629 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 630 | * nicer. 631 | */ 632 | toBeNull(): void; 633 | /** 634 | * Use .toBeTruthy when you don't care what a value is, you just want to 635 | * ensure a value is true in a boolean context. 636 | */ 637 | toBeTruthy(): void; 638 | /** 639 | * Use .toBeUndefined to check that a variable is undefined. 640 | */ 641 | toBeUndefined(): void; 642 | /** 643 | * Use .toContain when you want to check that an item is in a list. For 644 | * testing the items in the list, this uses ===, a strict equality check. 645 | */ 646 | toContain(item: any): void; 647 | /** 648 | * Use .toContainEqual when you want to check that an item is in a list. For 649 | * testing the items in the list, this matcher recursively checks the 650 | * equality of all fields, rather than checking for object identity. 651 | */ 652 | toContainEqual(item: any): void; 653 | /** 654 | * Use .toEqual when you want to check that two objects have the same value. 655 | * This matcher recursively checks the equality of all fields, rather than 656 | * checking for object identity. 657 | */ 658 | toEqual(value: any): void; 659 | /** 660 | * Use .toHaveBeenCalled to ensure that a mock function got called. 661 | */ 662 | toHaveBeenCalled(): void; 663 | toBeCalled(): void; 664 | /** 665 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 666 | * number of times. 667 | */ 668 | toHaveBeenCalledTimes(number: number): void; 669 | toBeCalledTimes(number: number): void; 670 | /** 671 | * 672 | */ 673 | toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; 674 | nthCalledWith(nthCall: number, ...args: Array): void; 675 | /** 676 | * 677 | */ 678 | toHaveReturned(): void; 679 | toReturn(): void; 680 | /** 681 | * 682 | */ 683 | toHaveReturnedTimes(number: number): void; 684 | toReturnTimes(number: number): void; 685 | /** 686 | * 687 | */ 688 | toHaveReturnedWith(value: any): void; 689 | toReturnWith(value: any): void; 690 | /** 691 | * 692 | */ 693 | toHaveLastReturnedWith(value: any): void; 694 | lastReturnedWith(value: any): void; 695 | /** 696 | * 697 | */ 698 | toHaveNthReturnedWith(nthCall: number, value: any): void; 699 | nthReturnedWith(nthCall: number, value: any): void; 700 | /** 701 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 702 | * specific arguments. 703 | */ 704 | toHaveBeenCalledWith(...args: Array): void; 705 | toBeCalledWith(...args: Array): void; 706 | /** 707 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 708 | * with specific arguments. 709 | */ 710 | toHaveBeenLastCalledWith(...args: Array): void; 711 | lastCalledWith(...args: Array): void; 712 | /** 713 | * Check that an object has a .length property and it is set to a certain 714 | * numeric value. 715 | */ 716 | toHaveLength(number: number): void; 717 | /** 718 | * 719 | */ 720 | toHaveProperty(propPath: string | $ReadOnlyArray, value?: any): void; 721 | /** 722 | * Use .toMatch to check that a string matches a regular expression or string. 723 | */ 724 | toMatch(regexpOrString: RegExp | string): void; 725 | /** 726 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 727 | */ 728 | toMatchObject(object: Object | Array): void; 729 | /** 730 | * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. 731 | */ 732 | toStrictEqual(value: any): void; 733 | /** 734 | * This ensures that an Object matches the most recent snapshot. 735 | */ 736 | toMatchSnapshot(propertyMatchers?: any, name?: string): void; 737 | /** 738 | * This ensures that an Object matches the most recent snapshot. 739 | */ 740 | toMatchSnapshot(name: string): void; 741 | 742 | toMatchInlineSnapshot(snapshot?: string): void; 743 | toMatchInlineSnapshot(propertyMatchers?: any, snapshot?: string): void; 744 | /** 745 | * Use .toThrow to test that a function throws when it is called. 746 | * If you want to test that a specific error gets thrown, you can provide an 747 | * argument to toThrow. The argument can be a string for the error message, 748 | * a class for the error, or a regex that should match the error. 749 | * 750 | * Alias: .toThrowError 751 | */ 752 | toThrow(message?: string | Error | Class | RegExp): void; 753 | toThrowError(message?: string | Error | Class | RegExp): void; 754 | /** 755 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 756 | * matching the most recent snapshot when it is called. 757 | */ 758 | toThrowErrorMatchingSnapshot(): void; 759 | toThrowErrorMatchingInlineSnapshot(snapshot?: string): void; 760 | } 761 | 762 | type JestObjectType = { 763 | /** 764 | * Disables automatic mocking in the module loader. 765 | * 766 | * After this method is called, all `require()`s will return the real 767 | * versions of each module (rather than a mocked version). 768 | */ 769 | disableAutomock(): JestObjectType, 770 | /** 771 | * An un-hoisted version of disableAutomock 772 | */ 773 | autoMockOff(): JestObjectType, 774 | /** 775 | * Enables automatic mocking in the module loader. 776 | */ 777 | enableAutomock(): JestObjectType, 778 | /** 779 | * An un-hoisted version of enableAutomock 780 | */ 781 | autoMockOn(): JestObjectType, 782 | /** 783 | * Clears the mock.calls and mock.instances properties of all mocks. 784 | * Equivalent to calling .mockClear() on every mocked function. 785 | */ 786 | clearAllMocks(): JestObjectType, 787 | /** 788 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 789 | * mocked function. 790 | */ 791 | resetAllMocks(): JestObjectType, 792 | /** 793 | * Restores all mocks back to their original value. 794 | */ 795 | restoreAllMocks(): JestObjectType, 796 | /** 797 | * Removes any pending timers from the timer system. 798 | */ 799 | clearAllTimers(): void, 800 | /** 801 | * Returns the number of fake timers still left to run. 802 | */ 803 | getTimerCount(): number, 804 | /** 805 | * The same as `mock` but not moved to the top of the expectation by 806 | * babel-jest. 807 | */ 808 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 809 | /** 810 | * The same as `unmock` but not moved to the top of the expectation by 811 | * babel-jest. 812 | */ 813 | dontMock(moduleName: string): JestObjectType, 814 | /** 815 | * Returns a new, unused mock function. Optionally takes a mock 816 | * implementation. 817 | */ 818 | fn, TReturn>( 819 | implementation?: (...args: TArguments) => TReturn 820 | ): JestMockFn, 821 | /** 822 | * Determines if the given function is a mocked function. 823 | */ 824 | isMockFunction(fn: Function): boolean, 825 | /** 826 | * Given the name of a module, use the automatic mocking system to generate a 827 | * mocked version of the module for you. 828 | */ 829 | genMockFromModule(moduleName: string): any, 830 | /** 831 | * Mocks a module with an auto-mocked version when it is being required. 832 | * 833 | * The second argument can be used to specify an explicit module factory that 834 | * is being run instead of using Jest's automocking feature. 835 | * 836 | * The third argument can be used to create virtual mocks -- mocks of modules 837 | * that don't exist anywhere in the system. 838 | */ 839 | mock( 840 | moduleName: string, 841 | moduleFactory?: any, 842 | options?: Object 843 | ): JestObjectType, 844 | /** 845 | * Returns the actual module instead of a mock, bypassing all checks on 846 | * whether the module should receive a mock implementation or not. 847 | */ 848 | requireActual(moduleName: string): any, 849 | /** 850 | * Returns a mock module instead of the actual module, bypassing all checks 851 | * on whether the module should be required normally or not. 852 | */ 853 | requireMock(moduleName: string): any, 854 | /** 855 | * Resets the module registry - the cache of all required modules. This is 856 | * useful to isolate modules where local state might conflict between tests. 857 | */ 858 | resetModules(): JestObjectType, 859 | /** 860 | * Creates a sandbox registry for the modules that are loaded inside the 861 | * callback function. This is useful to isolate specific modules for every 862 | * test so that local module state doesn't conflict between tests. 863 | */ 864 | isolateModules(fn: () => void): JestObjectType, 865 | /** 866 | * Exhausts the micro-task queue (usually interfaced in node via 867 | * process.nextTick). 868 | */ 869 | runAllTicks(): void, 870 | /** 871 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 872 | * setInterval(), and setImmediate()). 873 | */ 874 | runAllTimers(): void, 875 | /** 876 | * Exhausts all tasks queued by setImmediate(). 877 | */ 878 | runAllImmediates(): void, 879 | /** 880 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 881 | * or setInterval() and setImmediate()). 882 | */ 883 | advanceTimersByTime(msToRun: number): void, 884 | /** 885 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 886 | * or setInterval() and setImmediate()). 887 | * 888 | * Renamed to `advanceTimersByTime`. 889 | */ 890 | runTimersToTime(msToRun: number): void, 891 | /** 892 | * Executes only the macro-tasks that are currently pending (i.e., only the 893 | * tasks that have been queued by setTimeout() or setInterval() up to this 894 | * point) 895 | */ 896 | runOnlyPendingTimers(): void, 897 | /** 898 | * Explicitly supplies the mock object that the module system should return 899 | * for the specified module. Note: It is recommended to use jest.mock() 900 | * instead. 901 | */ 902 | setMock(moduleName: string, moduleExports: any): JestObjectType, 903 | /** 904 | * Indicates that the module system should never return a mocked version of 905 | * the specified module from require() (e.g. that it should always return the 906 | * real module). 907 | */ 908 | unmock(moduleName: string): JestObjectType, 909 | /** 910 | * Instructs Jest to use fake versions of the standard timer functions 911 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 912 | * setImmediate and clearImmediate). 913 | */ 914 | useFakeTimers(mode?: 'modern' | 'legacy'): JestObjectType, 915 | /** 916 | * Instructs Jest to use the real versions of the standard timer functions. 917 | */ 918 | useRealTimers(): JestObjectType, 919 | /** 920 | * Creates a mock function similar to jest.fn but also tracks calls to 921 | * object[methodName]. 922 | */ 923 | spyOn( 924 | object: Object, 925 | methodName: string, 926 | accessType?: 'get' | 'set' 927 | ): JestMockFn, 928 | /** 929 | * Set the default timeout interval for tests and before/after hooks in milliseconds. 930 | * Note: The default timeout interval is 5 seconds if this method is not called. 931 | */ 932 | setTimeout(timeout: number): JestObjectType, 933 | ... 934 | }; 935 | 936 | type JestSpyType = { calls: JestCallsType, ... }; 937 | 938 | type JestDoneFn = {| 939 | (error?: Error): void, 940 | fail: (error: Error) => void, 941 | |}; 942 | 943 | /** Runs this function after every test inside this context */ 944 | declare function afterEach( 945 | fn: (done: JestDoneFn) => ?Promise, 946 | timeout?: number 947 | ): void; 948 | /** Runs this function before every test inside this context */ 949 | declare function beforeEach( 950 | fn: (done: JestDoneFn) => ?Promise, 951 | timeout?: number 952 | ): void; 953 | /** Runs this function after all tests have finished inside this context */ 954 | declare function afterAll( 955 | fn: (done: JestDoneFn) => ?Promise, 956 | timeout?: number 957 | ): void; 958 | /** Runs this function before any tests have started inside this context */ 959 | declare function beforeAll( 960 | fn: (done: JestDoneFn) => ?Promise, 961 | timeout?: number 962 | ): void; 963 | 964 | /** A context for grouping tests together */ 965 | declare var describe: { 966 | /** 967 | * Creates a block that groups together several related tests in one "test suite" 968 | */ 969 | (name: JestTestName, fn: () => void): void, 970 | /** 971 | * Only run this describe block 972 | */ 973 | only(name: JestTestName, fn: () => void): void, 974 | /** 975 | * Skip running this describe block 976 | */ 977 | skip(name: JestTestName, fn: () => void): void, 978 | /** 979 | * each runs this test against array of argument arrays per each run 980 | * 981 | * @param {table} table of Test 982 | */ 983 | each( 984 | ...table: Array | mixed> | [Array, string] 985 | ): ( 986 | name: JestTestName, 987 | fn?: (...args: Array) => ?Promise, 988 | timeout?: number 989 | ) => void, 990 | ... 991 | }; 992 | 993 | /** An individual test unit */ 994 | declare var it: { 995 | /** 996 | * An individual test unit 997 | * 998 | * @param {JestTestName} Name of Test 999 | * @param {Function} Test 1000 | * @param {number} Timeout for the test, in milliseconds. 1001 | */ 1002 | ( 1003 | name: JestTestName, 1004 | fn?: (done: JestDoneFn) => ?Promise, 1005 | timeout?: number 1006 | ): void, 1007 | /** 1008 | * Only run this test 1009 | * 1010 | * @param {JestTestName} Name of Test 1011 | * @param {Function} Test 1012 | * @param {number} Timeout for the test, in milliseconds. 1013 | */ 1014 | only: {| 1015 | ( 1016 | name: JestTestName, 1017 | fn?: (done: JestDoneFn) => ?Promise, 1018 | timeout?: number 1019 | ): void, 1020 | each( 1021 | ...table: Array | mixed> | [Array, string] 1022 | ): ( 1023 | name: JestTestName, 1024 | fn?: (...args: Array) => ?Promise, 1025 | timeout?: number 1026 | ) => void, 1027 | |}, 1028 | /** 1029 | * Skip running this test 1030 | * 1031 | * @param {JestTestName} Name of Test 1032 | * @param {Function} Test 1033 | * @param {number} Timeout for the test, in milliseconds. 1034 | */ 1035 | skip( 1036 | name: JestTestName, 1037 | fn?: (done: JestDoneFn) => ?Promise, 1038 | timeout?: number 1039 | ): void, 1040 | /** 1041 | * Highlight planned tests in the summary output 1042 | * 1043 | * @param {String} Name of Test to do 1044 | */ 1045 | todo(name: string): void, 1046 | /** 1047 | * Run the test concurrently 1048 | * 1049 | * @param {JestTestName} Name of Test 1050 | * @param {Function} Test 1051 | * @param {number} Timeout for the test, in milliseconds. 1052 | */ 1053 | concurrent( 1054 | name: JestTestName, 1055 | fn?: (done: JestDoneFn) => ?Promise, 1056 | timeout?: number 1057 | ): void, 1058 | /** 1059 | * each runs this test against array of argument arrays per each run 1060 | * 1061 | * @param {table} table of Test 1062 | */ 1063 | each( 1064 | ...table: Array | mixed> | [Array, string] 1065 | ): ( 1066 | name: JestTestName, 1067 | fn?: (...args: Array) => ?Promise, 1068 | timeout?: number 1069 | ) => void, 1070 | ... 1071 | }; 1072 | 1073 | declare function fit( 1074 | name: JestTestName, 1075 | fn: (done: JestDoneFn) => ?Promise, 1076 | timeout?: number 1077 | ): void; 1078 | /** An individual test unit */ 1079 | declare var test: typeof it; 1080 | /** A disabled group of tests */ 1081 | declare var xdescribe: typeof describe; 1082 | /** A focused group of tests */ 1083 | declare var fdescribe: typeof describe; 1084 | /** A disabled individual test */ 1085 | declare var xit: typeof it; 1086 | /** A disabled individual test */ 1087 | declare var xtest: typeof it; 1088 | 1089 | type JestPrettyFormatColors = { 1090 | comment: { 1091 | close: string, 1092 | open: string, 1093 | ... 1094 | }, 1095 | content: { 1096 | close: string, 1097 | open: string, 1098 | ... 1099 | }, 1100 | prop: { 1101 | close: string, 1102 | open: string, 1103 | ... 1104 | }, 1105 | tag: { 1106 | close: string, 1107 | open: string, 1108 | ... 1109 | }, 1110 | value: { 1111 | close: string, 1112 | open: string, 1113 | ... 1114 | }, 1115 | ... 1116 | }; 1117 | 1118 | type JestPrettyFormatIndent = (string) => string; 1119 | type JestPrettyFormatRefs = Array; 1120 | type JestPrettyFormatPrint = (any) => string; 1121 | type JestPrettyFormatStringOrNull = string | null; 1122 | 1123 | type JestPrettyFormatOptions = {| 1124 | callToJSON: boolean, 1125 | edgeSpacing: string, 1126 | escapeRegex: boolean, 1127 | highlight: boolean, 1128 | indent: number, 1129 | maxDepth: number, 1130 | min: boolean, 1131 | plugins: JestPrettyFormatPlugins, 1132 | printFunctionName: boolean, 1133 | spacing: string, 1134 | theme: {| 1135 | comment: string, 1136 | content: string, 1137 | prop: string, 1138 | tag: string, 1139 | value: string, 1140 | |}, 1141 | |}; 1142 | 1143 | type JestPrettyFormatPlugin = { 1144 | print: ( 1145 | val: any, 1146 | serialize: JestPrettyFormatPrint, 1147 | indent: JestPrettyFormatIndent, 1148 | opts: JestPrettyFormatOptions, 1149 | colors: JestPrettyFormatColors 1150 | ) => string, 1151 | test: (any) => boolean, 1152 | ... 1153 | }; 1154 | 1155 | type JestPrettyFormatPlugins = Array; 1156 | 1157 | /** The expect function is used every time you want to test a value */ 1158 | declare var expect: { 1159 | /** The object that you want to make assertions against */ 1160 | ( 1161 | value: any 1162 | ): JestExpectType & 1163 | JestPromiseType & 1164 | EnzymeMatchersType & 1165 | DomTestingLibraryType & 1166 | JestJQueryMatchersType & 1167 | JestStyledComponentsMatchersType & 1168 | JestExtendedMatchersType & 1169 | SnapshotDiffType, 1170 | /** Add additional Jasmine matchers to Jest's roster */ 1171 | extend(matchers: { [name: string]: JestMatcher, ... }): void, 1172 | /** Add a module that formats application-specific data structures. */ 1173 | addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, 1174 | assertions(expectedAssertions: number): void, 1175 | hasAssertions(): void, 1176 | any(value: mixed): JestAsymmetricEqualityType, 1177 | anything(): any, 1178 | arrayContaining(value: Array): Array, 1179 | objectContaining(value: Object): Object, 1180 | /** Matches any received string that contains the exact expected string. */ 1181 | stringContaining(value: string): string, 1182 | stringMatching(value: string | RegExp): string, 1183 | not: { 1184 | arrayContaining: (value: $ReadOnlyArray) => Array, 1185 | objectContaining: (value: { ... }) => Object, 1186 | stringContaining: (value: string) => string, 1187 | stringMatching: (value: string | RegExp) => string, 1188 | ... 1189 | }, 1190 | ... 1191 | }; 1192 | 1193 | // TODO handle return type 1194 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 1195 | declare function spyOn(value: mixed, method: string): Object; 1196 | 1197 | /** Holds all functions related to manipulating test runner */ 1198 | declare var jest: JestObjectType; 1199 | 1200 | /** 1201 | * The global Jasmine object, this is generally not exposed as the public API, 1202 | * using features inside here could break in later versions of Jest. 1203 | */ 1204 | declare var jasmine: { 1205 | DEFAULT_TIMEOUT_INTERVAL: number, 1206 | any(value: mixed): JestAsymmetricEqualityType, 1207 | anything(): any, 1208 | arrayContaining(value: Array): Array, 1209 | clock(): JestClockType, 1210 | createSpy(name: string): JestSpyType, 1211 | createSpyObj( 1212 | baseName: string, 1213 | methodNames: Array 1214 | ): { [methodName: string]: JestSpyType, ... }, 1215 | objectContaining(value: Object): Object, 1216 | stringMatching(value: string): string, 1217 | ... 1218 | }; 1219 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function() { 3 | throw new Error("Don't instantiate Resizable directly! Use require('react-resizable').Resizable"); 4 | }; 5 | 6 | module.exports.Resizable = require('./build/Resizable').default; 7 | module.exports.ResizableBox = require('./build/ResizableBox').default; 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | testEnvironment: 'jsdom', 5 | coverageThreshold: { 6 | global: { 7 | branches: 70, // TODO: > 80 8 | functions: 70, // TODO: > 80 9 | lines: 80, 10 | statements: 75 // TODO: > 80 11 | } 12 | }, 13 | setupFiles: [ 14 | path.join(__dirname, '/setupTests/enzyme') 15 | ], 16 | coveragePathIgnorePatterns: [ 17 | '/build/', 18 | '/dist/', 19 | '/flow-typed/', 20 | ], 21 | collectCoverageFrom: [ 22 | 'lib/*.{js,jsx}', 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /lib/Resizable.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import type {Node as ReactNode} from 'react'; 4 | import {DraggableCore} from 'react-draggable'; 5 | import {cloneElement} from './utils'; 6 | import {resizableProps} from "./propTypes"; 7 | import type {ResizeHandleAxis, DefaultProps, Props, ReactRef, DragCallbackData} from './propTypes'; 8 | 9 | // The base component. 10 | // This component does not have state and relies on the parent to set its props based on callback data. 11 | export default class Resizable extends React.Component { 12 | static propTypes = resizableProps; 13 | 14 | static defaultProps: DefaultProps = { 15 | axis: 'both', 16 | handleSize: [20, 20], 17 | lockAspectRatio: false, 18 | minConstraints: [20, 20], 19 | maxConstraints: [Infinity, Infinity], 20 | resizeHandles: ['se'], 21 | transformScale: 1 22 | }; 23 | 24 | handleRefs: {[key: ResizeHandleAxis]: ReactRef} = {}; 25 | lastHandleRect: ?ClientRect = null; 26 | slack: ?[number, number] = null; 27 | 28 | componentWillUnmount() { 29 | this.resetData(); 30 | } 31 | 32 | resetData() { 33 | this.lastHandleRect = this.slack = null; 34 | } 35 | 36 | // Clamp width and height within provided constraints 37 | runConstraints(width: number, height: number): [number, number] { 38 | const {minConstraints, maxConstraints, lockAspectRatio} = this.props; 39 | // short circuit 40 | if (!minConstraints && !maxConstraints && !lockAspectRatio) return [width, height]; 41 | 42 | // If constraining to min and max, we need to also fit width and height to aspect ratio. 43 | if (lockAspectRatio) { 44 | const ratio = this.props.width / this.props.height; 45 | const deltaW = width - this.props.width; 46 | const deltaH = height - this.props.height; 47 | 48 | // Find which coordinate was greater and should push the other toward it. 49 | // E.g.: 50 | // ratio = 1, deltaW = 10, deltaH = 5, deltaH should become 10. 51 | // ratio = 2, deltaW = 10, deltaH = 6, deltaW should become 12. 52 | if (Math.abs(deltaW) > Math.abs(deltaH * ratio)) { 53 | height = width / ratio; 54 | } else { 55 | width = height * ratio; 56 | } 57 | } 58 | 59 | const [oldW, oldH] = [width, height]; 60 | 61 | // Add slack to the values used to calculate bound position. This will ensure that if 62 | // we start removing slack, the element won't react to it right away until it's been 63 | // completely removed. 64 | let [slackW, slackH] = this.slack || [0, 0]; 65 | width += slackW; 66 | height += slackH; 67 | 68 | if (minConstraints) { 69 | width = Math.max(minConstraints[0], width); 70 | height = Math.max(minConstraints[1], height); 71 | } 72 | if (maxConstraints) { 73 | width = Math.min(maxConstraints[0], width); 74 | height = Math.min(maxConstraints[1], height); 75 | } 76 | 77 | // If the width or height changed, we must have introduced some slack. Record it for the next iteration. 78 | this.slack = [slackW + (oldW - width), slackH + (oldH - height)]; 79 | 80 | return [width, height]; 81 | } 82 | 83 | /** 84 | * Wrapper around drag events to provide more useful data. 85 | * 86 | * @param {String} handlerName Handler name to wrap. 87 | * @return {Function} Handler function. 88 | */ 89 | resizeHandler(handlerName: 'onResize' | 'onResizeStart' | 'onResizeStop', axis: ResizeHandleAxis): Function { 90 | return (e: SyntheticEvent<>, {node, deltaX, deltaY}: DragCallbackData) => { 91 | // Reset data in case it was left over somehow (should not be possible) 92 | if (handlerName === 'onResizeStart') this.resetData(); 93 | 94 | // Axis restrictions 95 | const canDragX = (this.props.axis === 'both' || this.props.axis === 'x') && axis !== 'n' && axis !== 's'; 96 | const canDragY = (this.props.axis === 'both' || this.props.axis === 'y') && axis !== 'e' && axis !== 'w'; 97 | // No dragging possible. 98 | if (!canDragX && !canDragY) return; 99 | 100 | // Decompose axis for later use 101 | const axisV = axis[0]; 102 | const axisH = axis[axis.length - 1]; // intentionally not axis[1], so that this catches axis === 'w' for example 103 | 104 | // Track the element being dragged to account for changes in position. 105 | // If a handle's position is changed between callbacks, we need to factor this in to the next callback. 106 | // Failure to do so will cause the element to "skip" when resized upwards or leftwards. 107 | const handleRect = node.getBoundingClientRect(); 108 | if (this.lastHandleRect != null) { 109 | // If the handle has repositioned on either axis since last render, 110 | // we need to increase our callback values by this much. 111 | // Only checking 'n', 'w' since resizing by 's', 'w' won't affect the overall position on page, 112 | if (axisH === 'w') { 113 | const deltaLeftSinceLast = handleRect.left - this.lastHandleRect.left; 114 | deltaX += deltaLeftSinceLast; 115 | } 116 | if (axisV === 'n') { 117 | const deltaTopSinceLast = handleRect.top - this.lastHandleRect.top; 118 | deltaY += deltaTopSinceLast; 119 | } 120 | } 121 | // Storage of last rect so we know how much it has really moved. 122 | this.lastHandleRect = handleRect; 123 | 124 | // Reverse delta if using top or left drag handles. 125 | if (axisH === 'w') deltaX = -deltaX; 126 | if (axisV === 'n') deltaY = -deltaY; 127 | 128 | // Update w/h by the deltas. Also factor in transformScale. 129 | let width = this.props.width + (canDragX ? deltaX / this.props.transformScale : 0); 130 | let height = this.props.height + (canDragY ? deltaY / this.props.transformScale : 0); 131 | 132 | // Run user-provided constraints. 133 | [width, height] = this.runConstraints(width, height); 134 | 135 | const dimensionsChanged = width !== this.props.width || height !== this.props.height; 136 | 137 | // Call user-supplied callback if present. 138 | const cb = typeof this.props[handlerName] === 'function' ? this.props[handlerName] : null; 139 | // Don't call 'onResize' if dimensions haven't changed. 140 | const shouldSkipCb = handlerName === 'onResize' && !dimensionsChanged; 141 | if (cb && !shouldSkipCb) { 142 | e.persist?.(); 143 | cb(e, {node, size: {width, height}, handle: axis}); 144 | } 145 | 146 | // Reset internal data 147 | if (handlerName === 'onResizeStop') this.resetData(); 148 | }; 149 | } 150 | 151 | // Render a resize handle given an axis & DOM ref. Ref *must* be attached for 152 | // the underlying draggable library to work properly. 153 | renderResizeHandle(handleAxis: ResizeHandleAxis, ref: ReactRef): ReactNode { 154 | const {handle} = this.props; 155 | // No handle provided, make the default 156 | if (!handle) { 157 | return ; 158 | } 159 | // Handle is a function, such as: 160 | // `handle={(handleAxis) => }` 161 | if (typeof handle === 'function') { 162 | return handle(handleAxis, ref); 163 | } 164 | // Handle is a React component (composite or DOM). 165 | const isDOMElement = typeof handle.type === 'string'; 166 | const props = { 167 | ref, 168 | // Add `handleAxis` prop iff this is not a DOM element, 169 | // otherwise we'll get an unknown property warning 170 | ...(isDOMElement ? {} : {handleAxis}) 171 | }; 172 | return React.cloneElement(handle, props); 173 | 174 | } 175 | 176 | render(): ReactNode { 177 | // Pass along only props not meant for the ``.` 178 | // eslint-disable-next-line no-unused-vars 179 | const {children, className, draggableOpts, width, height, handle, handleSize, 180 | lockAspectRatio, axis, minConstraints, maxConstraints, onResize, 181 | onResizeStop, onResizeStart, resizeHandles, transformScale, ...p} = this.props; 182 | 183 | // What we're doing here is getting the child of this element, and cloning it with this element's props. 184 | // We are then defining its children as: 185 | // 1. Its original children (resizable's child's children), and 186 | // 2. One or more draggable handles. 187 | return cloneElement(children, { 188 | ...p, 189 | className: `${className ? `${className} ` : ''}react-resizable`, 190 | children: [ 191 | ...children.props.children, 192 | ...resizeHandles.map((handleAxis) => { 193 | // Create a ref to the handle so that `` doesn't have to use ReactDOM.findDOMNode(). 194 | const ref = (this.handleRefs[handleAxis]) ?? (this.handleRefs[handleAxis] = React.createRef()); 195 | return ( 196 | 204 | {this.renderResizeHandle(handleAxis, ref)} 205 | 206 | ); 207 | }) 208 | ] 209 | }); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /lib/ResizableBox.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import type {Node as ReactNode, Element as ReactElement} from 'react'; 4 | import PropTypes from 'prop-types'; 5 | 6 | import Resizable from './Resizable'; 7 | import {resizableProps} from "./propTypes"; 8 | import type {ResizeCallbackData, ResizableBoxState} from './propTypes'; 9 | 10 | // ElementConfig gives us an object type where all items present in `defaultProps` are made optional. 11 | // does not have defaultProps, so we can use this type to tell Flow that we don't 12 | // care about that and will handle it in instead. 13 | // A can also have a `style` property. 14 | type ResizableBoxProps = {|...React.ElementConfig, style?: Object, children?: ReactElement|}; 15 | 16 | export default class ResizableBox extends React.Component { 17 | 18 | // PropTypes are identical to , except that children are not strictly required to be present. 19 | static propTypes = { 20 | ...resizableProps, 21 | children: PropTypes.element, 22 | }; 23 | 24 | state: ResizableBoxState = { 25 | width: this.props.width, 26 | height: this.props.height, 27 | propsWidth: this.props.width, 28 | propsHeight: this.props.height, 29 | }; 30 | 31 | static getDerivedStateFromProps(props: ResizableBoxProps, state: ResizableBoxState): ?ResizableBoxState { 32 | // If parent changes height/width, set that in our state. 33 | if (state.propsWidth !== props.width || state.propsHeight !== props.height) { 34 | return { 35 | width: props.width, 36 | height: props.height, 37 | propsWidth: props.width, 38 | propsHeight: props.height, 39 | }; 40 | } 41 | return null; 42 | } 43 | 44 | onResize: (e: SyntheticEvent<>, data: ResizeCallbackData) => void = (e, data) => { 45 | const {size} = data; 46 | if (this.props.onResize) { 47 | e.persist?.(); 48 | this.setState(size, () => this.props.onResize && this.props.onResize(e, data)); 49 | } else { 50 | this.setState(size); 51 | } 52 | }; 53 | 54 | render(): ReactNode { 55 | // Basic wrapper around a Resizable instance. 56 | // If you use Resizable directly, you are responsible for updating the child component 57 | // with a new width and height. 58 | const { 59 | handle, 60 | handleSize, 61 | onResize, 62 | onResizeStart, 63 | onResizeStop, 64 | draggableOpts, 65 | minConstraints, 66 | maxConstraints, 67 | lockAspectRatio, 68 | axis, 69 | width, 70 | height, 71 | resizeHandles, 72 | style, 73 | transformScale, 74 | ...props 75 | } = this.props; 76 | 77 | return ( 78 | 94 |
95 | 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/propTypes.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import PropTypes from 'prop-types'; 3 | import {DraggableCore} from "react-draggable"; 4 | import type {Element as ReactElement, ElementConfig} from 'react'; 5 | 6 | export type ReactRef = { 7 | current: T | null 8 | }; 9 | 10 | export type Axis = 'both' | 'x' | 'y' | 'none'; 11 | export type ResizeHandleAxis = 's' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'; 12 | export type ResizableState = void; 13 | export type ResizableBoxState = { 14 | width: number, height: number, 15 | propsWidth: number, propsHeight: number 16 | }; 17 | export type DragCallbackData = { 18 | node: HTMLElement, 19 | x: number, y: number, 20 | deltaX: number, deltaY: number, 21 | lastX: number, lastY: number 22 | }; 23 | export type ResizeCallbackData = { 24 | node: HTMLElement, 25 | size: {width: number, height: number}, 26 | handle: ResizeHandleAxis 27 | }; 28 | 29 | // 30 | export type DefaultProps = { 31 | axis: Axis, 32 | handleSize: [number, number], 33 | lockAspectRatio: boolean, 34 | minConstraints: [number, number], 35 | maxConstraints: [number, number], 36 | resizeHandles: ResizeHandleAxis[], 37 | transformScale: number, 38 | }; 39 | 40 | export type Props = { 41 | ...DefaultProps, 42 | children: ReactElement, 43 | className?: ?string, 44 | draggableOpts?: ?ElementConfig, 45 | height: number, 46 | handle?: ReactElement | (resizeHandleAxis: ResizeHandleAxis, ref: ReactRef) => ReactElement, 47 | onResizeStop?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any, 48 | onResizeStart?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any, 49 | onResize?: ?(e: SyntheticEvent<>, data: ResizeCallbackData) => any, 50 | width: number, 51 | }; 52 | 53 | 54 | 55 | export const resizableProps: Object = { 56 | /* 57 | * Restricts resizing to a particular axis (default: 'both') 58 | * 'both' - allows resizing by width or height 59 | * 'x' - only allows the width to be changed 60 | * 'y' - only allows the height to be changed 61 | * 'none' - disables resizing altogether 62 | * */ 63 | axis: PropTypes.oneOf(['both', 'x', 'y', 'none']), 64 | className: PropTypes.string, 65 | /* 66 | * Require that one and only one child be present. 67 | * */ 68 | children: PropTypes.element.isRequired, 69 | /* 70 | * These will be passed wholesale to react-draggable's DraggableCore 71 | * */ 72 | draggableOpts: PropTypes.shape({ 73 | allowAnyClick: PropTypes.bool, 74 | cancel: PropTypes.string, 75 | children: PropTypes.node, 76 | disabled: PropTypes.bool, 77 | enableUserSelectHack: PropTypes.bool, 78 | offsetParent: PropTypes.node, 79 | grid: PropTypes.arrayOf(PropTypes.number), 80 | handle: PropTypes.string, 81 | nodeRef: PropTypes.object, 82 | onStart: PropTypes.func, 83 | onDrag: PropTypes.func, 84 | onStop: PropTypes.func, 85 | onMouseDown: PropTypes.func, 86 | scale: PropTypes.number, 87 | }), 88 | /* 89 | * Initial height 90 | * */ 91 | height: (...args) => { 92 | const [props] = args; 93 | // Required if resizing height or both 94 | if (props.axis === 'both' || props.axis === 'y') { 95 | return PropTypes.number.isRequired(...args); 96 | } 97 | return PropTypes.number(...args); 98 | }, 99 | /* 100 | * Customize cursor resize handle 101 | * */ 102 | handle: PropTypes.oneOfType([ 103 | PropTypes.node, 104 | PropTypes.func 105 | ]), 106 | /* 107 | * If you change this, be sure to update your css 108 | * */ 109 | handleSize: PropTypes.arrayOf(PropTypes.number), 110 | lockAspectRatio: PropTypes.bool, 111 | /* 112 | * Max X & Y measure 113 | * */ 114 | maxConstraints: PropTypes.arrayOf(PropTypes.number), 115 | /* 116 | * Min X & Y measure 117 | * */ 118 | minConstraints: PropTypes.arrayOf(PropTypes.number), 119 | /* 120 | * Called on stop resize event 121 | * */ 122 | onResizeStop: PropTypes.func, 123 | /* 124 | * Called on start resize event 125 | * */ 126 | onResizeStart: PropTypes.func, 127 | /* 128 | * Called on resize event 129 | * */ 130 | onResize: PropTypes.func, 131 | /* 132 | * Defines which resize handles should be rendered (default: 'se') 133 | * 's' - South handle (bottom-center) 134 | * 'w' - West handle (left-center) 135 | * 'e' - East handle (right-center) 136 | * 'n' - North handle (top-center) 137 | * 'sw' - Southwest handle (bottom-left) 138 | * 'nw' - Northwest handle (top-left) 139 | * 'se' - Southeast handle (bottom-right) 140 | * 'ne' - Northeast handle (top-center) 141 | * */ 142 | resizeHandles: PropTypes.arrayOf(PropTypes.oneOf(['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne'])), 143 | 144 | /* 145 | * If `transform: scale(n)` is set on the parent, this should be set to `n`. 146 | * */ 147 | transformScale: PropTypes.number, 148 | /* 149 | * Initial width 150 | */ 151 | width: (...args) => { 152 | const [props] = args; 153 | // Required if resizing width or both 154 | if (props.axis === 'both' || props.axis === 'x') { 155 | return PropTypes.number.isRequired(...args); 156 | } 157 | return PropTypes.number(...args); 158 | }, 159 | }; 160 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import type {Element as ReactElement} from 'react'; 4 | 5 | // React.addons.cloneWithProps look-alike that merges style & className. 6 | export function cloneElement(element: ReactElement, props: Object): ReactElement { 7 | if (props.style && element.props.style) { 8 | props.style = {...element.props.style, ...props.style}; 9 | } 10 | if (props.className && element.props.className) { 11 | props.className = `${element.props.className} ${props.className}`; 12 | } 13 | return React.cloneElement(element, props); 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-resizable", 3 | "version": "3.0.5", 4 | "description": "A component that is resizable with handles.", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "eslint lib/ __tests__/ setupTests/; flow", 8 | "test": "jest --coverage", 9 | "unit": "jest --watch --verbose", 10 | "build": "bash build.sh", 11 | "build-example": "webpack", 12 | "dev": "webpack serve --open", 13 | "prepublishOnly": "npm run build", 14 | "validate": "yarn check", 15 | "preversion": "npm run lint", 16 | "version": "git add CHANGELOG.md", 17 | "postversion": "git push && git push --tags", 18 | "flow": "flow" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:react-grid-layout/react-resizable.git" 23 | }, 24 | "keywords": [ 25 | "react", 26 | "resize", 27 | "resizable" 28 | ], 29 | "author": "Samuel Reed (http://strml.net/)", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/react-grid-layout/react-resizable/issues" 33 | }, 34 | "homepage": "https://github.com/react-grid-layout/react-resizable", 35 | "devDependencies": { 36 | "@babel/cli": "^7.21.0", 37 | "@babel/core": "^7.21.3", 38 | "@babel/eslint-parser": "^7.21.3", 39 | "@babel/plugin-proposal-class-properties": "^7.18.6", 40 | "@babel/plugin-proposal-object-rest-spread": "^7.20.7", 41 | "@babel/preset-env": "^7.20.2", 42 | "@babel/preset-flow": "^7.18.6", 43 | "@babel/preset-react": "^7.18.6", 44 | "babel-loader": "^9.1.2", 45 | "cross-env": "^7.0.2", 46 | "css-loader": "^6.7.3", 47 | "enzyme": "^3.11.0", 48 | "enzyme-adapter-react-16": "^1.15.7", 49 | "eslint": "^8.36.0", 50 | "eslint-plugin-jest": "^27.2.1", 51 | "eslint-plugin-react": "^7.32.2", 52 | "flow-bin": "^0.153.0", 53 | "jest": "^29.5.0", 54 | "jest-environment-jsdom": "^29.5.0", 55 | "lodash": "^4.17.20", 56 | "pre-commit": "^1.1.2", 57 | "react": "^16.10.2", 58 | "react-dom": "^16.10.2", 59 | "react-test-renderer": "^16.11.0", 60 | "style-loader": "^3.3.2", 61 | "webpack": "^5.76.2", 62 | "webpack-cli": "^5.0.1", 63 | "webpack-dev-server": "^4.13.1" 64 | }, 65 | "dependencies": { 66 | "prop-types": "15.x", 67 | "react-draggable": "^4.0.3" 68 | }, 69 | "peerDependencies": { 70 | "react": ">= 16.3", 71 | "react-dom": ">= 16.3" 72 | }, 73 | "publishConfig": { 74 | "registry": "https://registry.npmjs.org" 75 | }, 76 | "pre-commit": [ 77 | "lint", 78 | "validate", 79 | "test" 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /setupTests/enzyme.js: -------------------------------------------------------------------------------- 1 | const Adapter = require('enzyme-adapter-react-16'); 2 | const enzyme = require('enzyme'); 3 | 4 | enzyme.configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const isProduction = process.env.NODE_ENV === 'production'; 5 | const isDevelopment = !isProduction; 6 | 7 | module.exports = { 8 | mode: isProduction ? 'production' : 'development', 9 | bail: isProduction, 10 | context: __dirname, 11 | entry: { 12 | test: "./examples/example.js", 13 | }, 14 | output: { 15 | path: path.join(__dirname, "dist"), 16 | filename: "bundle.js", 17 | sourceMapFilename: "[file].map", 18 | library: 'ReactResizable', 19 | libraryTarget: 'umd' 20 | }, 21 | target: 'web', // Work around https://github.com/webpack/webpack-dev-server/issues/2758 22 | externals: { 23 | 'react': { 24 | 'commonjs': 'react', 25 | 'commonjs2': 'react', 26 | 'amd': 'react', 27 | // React dep should be available as window.React, not window.react 28 | 'root': 'React' 29 | }, 30 | 'react-dom': { 31 | 'commonjs': 'react-dom', 32 | 'commonjs2': 'react-dom', 33 | 'amd': 'react-dom', 34 | 'root': 'ReactDOM' 35 | } 36 | }, 37 | module: { 38 | rules: [ 39 | {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader', options: {cacheDirectory: true}}, 40 | ] 41 | }, 42 | resolve: { 43 | extensions: [".js"] 44 | }, 45 | devServer: { 46 | static: path.join(__dirname, 'examples'), 47 | compress: true, 48 | port: 4003, 49 | hot: true, 50 | }, 51 | plugins: [ 52 | // Scope hoisting 53 | new webpack.optimize.ModuleConcatenationPlugin(), 54 | ] 55 | }; 56 | --------------------------------------------------------------------------------