` to render children in first.
715 |
716 | #### v7.4.2 (unpublished)
717 |
718 | fix: `isMounted` and `(!this.state.isMounted) return
` line #356
719 |
720 | #### v7.4.1
721 |
722 | - fix: Fixed Array.from error in IE11
723 |
724 | #### v7.4.0
725 |
726 | - fix: add `enableUserSelectHack?: boolean;`.
727 |
728 | #### v7.3.1
729 |
730 | - chore(deps): upgrade deps
731 | - chore(deps): upgrade lint and remove unused state
732 | - chore(deps): install prettier
733 |
734 | #### v7.3.0
735 |
736 | - chore(deps): upgrade re-resizable
737 |
738 | #### v7.2.0
739 |
740 | - Support for cancel feature of react-draggable #206
741 |
742 | #### v7.1.5
743 |
744 | - Fixed a issue #199 Add enableUserSelectHack props to react-draggable
745 |
746 | #### v7.1.4
747 |
748 | - Fixed a issue #188 maxWidth and maxHeight props don't respect after resize
749 |
750 | #### v7.1.3
751 |
752 | - Fixed a bug, `extendProps` is not passed correctly.
753 | - Fixed a bug, `bounds` is not work correctly. (#162)
754 |
755 | #### v7.1.1 / v7.1.2
756 |
757 | - Add internal props.
758 |
759 | #### v7.1.0
760 |
761 | - Add `size`.
762 | - Add `position`.
763 |
764 | #### v7.0.0
765 |
766 | - Add `default` instead of `x`, `y`, `width`, `height`.
767 | - Add `resizeHandleWrapperClass` and `resizeHandleWrapperStyle`.
768 |
769 | #### v6.0.1
770 |
771 | - Remove unnecessary types.
772 |
773 | #### v6.0.0
774 |
775 | - Use rollup.
776 | - Support % min/max size.
777 | - Change props, remove `default` and add `x`, `y`, `width`, `height`.
778 | - Rename `dragHandlersXXXX` and `resizeHandlersXXXX` props to `dragHandleXXXX` and `resizeHandleXXXX`.
779 |
780 | #### v5.1.3
781 |
782 | - Fix cursor style, set `normal` to cursor style when `dragHandlerClassName` is not empty.
783 |
784 | #### v5.1.2
785 |
786 | - Add position `relative` when component will update.
787 |
788 | #### v5.1.1
789 |
790 | - Add `top: 0`, `left: 0`.
791 | - Add position `relative` when parent position equals `static`.
792 |
793 | #### v5.1.0
794 |
795 | - Update dependencies(`react-draggable v3`, `flow-bin v0.53`, and other...)
796 |
797 | #### v5.0.9
798 |
799 | - Fix bug new `z` props is not applied to state.
800 |
801 | #### v5.0.8
802 |
803 | - Add `extendsProps`. #129
804 |
805 | #### v5.0.7
806 |
807 | - Add `disableDragging` props.
808 |
809 | #### v5.0.6
810 |
811 | - Fix flow error.
812 |
813 | #### v5.0.5
814 |
815 | - Add index.js.flow
816 |
817 | #### v5.0.4
818 |
819 | - Fix Issue #117.
820 |
821 | #### v5.0.3
822 |
823 | - Fix `updateZIndex`.
824 | - Fix `updateSize`.
825 | - Fix left and top bounds.
826 |
827 | #### v5.0.2
828 |
829 | - Fix argument events #100
830 |
831 | #### v5.0.1
832 |
833 | - Fix example
834 | - Update README
835 |
836 | #### v5.0.0
837 |
838 | - Fix resize bounds.
839 | - Modify API.
840 | - Use original `react-draggable`.
841 |
842 |
843 | #### v4.2.1
844 |
845 | - Added `updateZIndex`, method to updated component `zIndex` state.
846 |
847 | #### v4.2.0
848 |
849 | - Pass the new position in the onResizeStop callback #60
850 |
851 |
852 | #### v4.1.0
853 |
854 | - Pass the new position along in the resize callback #55
855 |
856 |
857 | #### v4.0.1
858 |
859 | - Fix style props to applt zIndex chaned.
860 |
861 | #### v4.0.0
862 |
863 | - Rename `react-rnd`.
864 | - Remove `canUpdatePositionByParent` property.
865 | - Remove `canUpdateSizeByParent` property.
866 | - Remove `initiAsResizing` property.
867 | - Change `x`, `y`, `width` and `height` property to `initial`.
868 | - Add `updateSize`, `updatePosition`, method to updated conponent state.
869 | - Add `lockAspectRatio` property to lock aspect ratio when resizing.
870 |
871 | #### v3.0.0
872 |
873 | - Add `canUpdatePositionByParent` property.
874 |
875 | #### v2.0.0
876 |
877 | - Fix bug, resize and grid not work properly.
878 |
879 | #### v1.2.0
880 |
881 | - Add `grid` props to snap grid. (thanks @paulyoung)
882 | - Fix bug, moveAxis not work properly.
883 |
884 |
885 | #### v1.1.3
886 |
887 | - Fix situations when on dragStop you wanted to revert to 0,0 position #39
888 | - Add `canUpdateSizeByParent` props #38
889 |
890 | #### v1.1.2
891 |
892 | - Add object.assign transform
893 |
894 | #### v1.1.0
895 |
896 | - Add add module exports plugin for `require`
897 |
898 | #### v1.0.1
899 |
900 | - Bug fix
901 |
902 | #### v1.0.0
903 |
904 | - Support react v15.x
905 | - Support left, top resizer
906 | - Remove start props, use width, height, x, and y.
907 |
908 | #### v0.5.3
909 |
910 | - Add handle selector
911 |
912 | ## License
913 |
914 | The MIT License (MIT)
915 |
916 | Copyright (c) 2018 bokuweb
917 |
918 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
919 |
920 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
921 |
922 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
923 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | "@babel/preset-typescript",
4 | '@babel/preset-env',
5 | "@babel/preset-react"
6 | ],
7 | plugins: [
8 | "@babel/plugin-transform-modules-commonjs",
9 | ["@babel/plugin-proposal-class-properties", { "loose": true }]
10 | ],
11 | };
12 |
--------------------------------------------------------------------------------
/docs/stories/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/examples/vite/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/examples/vite/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/vite/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | export default {
18 | // other rules...
19 | parserOptions: {
20 | ecmaVersion: 'latest',
21 | sourceType: 'module',
22 | project: ['./tsconfig.json', './tsconfig.node.json', './tsconfig.app.json'],
23 | tsconfigRootDir: __dirname,
24 | },
25 | }
26 | ```
27 |
28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
31 |
--------------------------------------------------------------------------------
/examples/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-example",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc -b && vite build",
9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "react": "^18.3.1",
14 | "react-dom": "^18.3.1"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^18.3.3",
18 | "@types/react-dom": "^18.3.0",
19 | "@typescript-eslint/eslint-plugin": "^7.15.0",
20 | "@typescript-eslint/parser": "^7.15.0",
21 | "@vitejs/plugin-react-swc": "^3.5.0",
22 | "eslint": "^8.57.0",
23 | "eslint-plugin-react-hooks": "^4.6.2",
24 | "eslint-plugin-react-refresh": "^0.4.7",
25 | "typescript": "^5.2.2",
26 | "vite": "^5.3.4"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/vite/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/vite/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/examples/vite/src/App.tsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import { useState } from "react";
3 |
4 | import { Rnd } from "../../../src";
5 |
6 | const style = {
7 | display: "flex",
8 | alignItems: "center",
9 | justifyContent: "center",
10 | border: "solid 1px #ddd",
11 | background: "#f0f0f0",
12 | } as const;
13 |
14 | const App = () => {
15 | const [size, updateSize] = useState({ width: 200, height: 200 });
16 | const [position, updatePosition] = useState({ x: 0, y: 0 });
17 | return (
18 |
{
23 | updatePosition({ x: d.x, y: d.y });
24 | }}
25 | onResizeStop={(e, direction, ref, delta, position) => {
26 | updateSize({
27 | width: Number(ref.style.width),
28 | height: Number(ref.style.height),
29 | });
30 | updatePosition(position)
31 | }}
32 | >
33 | Rnd
34 |
35 | );
36 | };
37 |
38 | export default App;
39 |
--------------------------------------------------------------------------------
/examples/vite/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/vite/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | a {
17 | font-weight: 500;
18 | color: #646cff;
19 | text-decoration: inherit;
20 | }
21 | a:hover {
22 | color: #535bf2;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | display: flex;
28 | place-items: center;
29 | min-width: 320px;
30 | min-height: 100vh;
31 | }
32 |
33 | h1 {
34 | font-size: 3.2em;
35 | line-height: 1.1;
36 | }
37 |
38 | button {
39 | border-radius: 8px;
40 | border: 1px solid transparent;
41 | padding: 0.6em 1.2em;
42 | font-size: 1em;
43 | font-weight: 500;
44 | font-family: inherit;
45 | background-color: #1a1a1a;
46 | cursor: pointer;
47 | transition: border-color 0.25s;
48 | }
49 | button:hover {
50 | border-color: #646cff;
51 | }
52 | button:focus,
53 | button:focus-visible {
54 | outline: 4px auto -webkit-focus-ring-color;
55 | }
56 |
57 | @media (prefers-color-scheme: light) {
58 | :root {
59 | color: #213547;
60 | background-color: #ffffff;
61 | }
62 | a:hover {
63 | color: #747bff;
64 | }
65 | button {
66 | background-color: #f9f9f9;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/examples/vite/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.tsx'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/examples/vite/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/vite/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5 | "target": "ES2020",
6 | "useDefineForClassFields": true,
7 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
8 | "module": "ESNext",
9 | "skipLibCheck": true,
10 |
11 | /* Bundler mode */
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "moduleDetection": "force",
17 | "noEmit": true,
18 | "jsx": "react-jsx",
19 |
20 | /* Linting */
21 | "strict": true,
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": true,
24 | "noFallthroughCasesInSwitch": true
25 | },
26 | "include": ["src"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/vite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.app.json"
6 | },
7 | {
8 | "path": "./tsconfig.node.json"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/examples/vite/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5 | "skipLibCheck": true,
6 | "module": "ESNext",
7 | "moduleResolution": "bundler",
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "noEmit": true
11 | },
12 | "include": ["vite.config.ts"]
13 | }
14 |
--------------------------------------------------------------------------------
/examples/vite/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/fixture.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bokuweb/react-rnd/34ccbfa162775b305b0c84836c8082e8caee9500/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-rnd",
3 | "version": "10.5.2",
4 | "description": "A draggable and resizable React Component",
5 | "title": "react-rnd",
6 | "main": "./lib/index.es5.js",
7 | "module": "./lib/index.js",
8 | "jsnext:main": "./lib/index.js",
9 | "types": "./lib/index.d.ts",
10 | "keywords": [
11 | "react",
12 | "resize",
13 | "resizable",
14 | "draggable",
15 | "component"
16 | ],
17 | "scripts": {
18 | "lint": "tslint -c tslint.json 'src/index.tsx'",
19 | "flow": "flow",
20 | "tsc": "tsc -p tsconfig.json --skipLibCheck",
21 | "build:prod:main": "rollup -c scripts/prod.js",
22 | "build:prod:es5": "rollup -c scripts/prod.es5.js",
23 | "build": "npm-run-all --serial build:prod:* copy:flow",
24 | "test": "cross-env NODE_ENV='test' npm run tsc && avaron lib/index.test.js --renderer",
25 | "copy:flow": "cpy src/index.js.flow lib && cpy src/index.js.flow lib --rename index.es5.js.flow",
26 | "test:ci": "npm run flow && npm run build",
27 | "prepare": "npm run build",
28 | "storybook": "start-storybook -p 6016",
29 | "build-storybook": "build-storybook -o docs/stories",
30 | "deploy": "npm run build-storybook && gh-pages -d docs"
31 | },
32 | "repository": {
33 | "type": "git",
34 | "url": "https://github.com/bokuweb/react-rnd.git"
35 | },
36 | "author": "bokuweb",
37 | "license": "MIT",
38 | "bugs": {
39 | "url": "https://github.com/bokuweb/react-rnd/issues"
40 | },
41 | "homepage": "https://github.com/bokuweb/react-rnd",
42 | "devDependencies": {
43 | "@babel/cli": "7.17.6",
44 | "@babel/core": "7.17.5",
45 | "@babel/plugin-proposal-class-properties": "7.16.7",
46 | "@babel/plugin-transform-modules-commonjs": "7.16.8",
47 | "@babel/preset-react": "7.16.7",
48 | "@babel/preset-typescript": "7.16.7",
49 | "@babel/traverse": "7.17.3",
50 | "@babel/types": "7.17.0",
51 | "@emotion/core": "11.0.0",
52 | "@storybook/addon-actions": "6.5.16",
53 | "@storybook/addon-info": "5.3.21",
54 | "@storybook/addon-links": "6.5.16",
55 | "@storybook/addon-options": "5.3.21",
56 | "@storybook/react": "6.5.16",
57 | "@types/enzyme": "3.1.16",
58 | "@types/enzyme-adapter-react-16": "1.0.6",
59 | "@types/node": "16.18.96",
60 | "@types/react": "17.0.80",
61 | "@types/react-dom": "17.0.25",
62 | "@types/sinon": "10.0.11",
63 | "@types/storybook__addon-actions": "5.2.1",
64 | "@types/storybook__react": "5.2.1",
65 | "babel-core": "7.0.0-bridge.0",
66 | "babel-eslint": "10.1.0",
67 | "babel-loader": "8.2.3",
68 | "cpy-cli": "4.2.0",
69 | "cross-env": "7.0.3",
70 | "enzyme": "3.11.0",
71 | "enzyme-adapter-react-16": "1.9.1",
72 | "gh-pages": "3.2.3",
73 | "light-ts-loader": "1.1.2",
74 | "npm-run-all2": "5.0.2",
75 | "prettier": "2.6.2",
76 | "react": "16.14.0",
77 | "react-dom": "16.14.0",
78 | "react-test-renderer": "16.14.0",
79 | "rollup": "1.32.1",
80 | "@rollup/plugin-babel": "5.0.0",
81 | "rollup-plugin-commonjs": "10.1.0",
82 | "rollup-plugin-node-globals": "1.4.0",
83 | "@rollup/plugin-node-resolve": "6.0.0",
84 | "rollup-plugin-replace": "2.2.0",
85 | "rollup-plugin-typescript2": "0.22.0",
86 | "rollup-watch": "4.3.1",
87 | "sinon": "13.0.1",
88 | "tslint": "6.1.3",
89 | "tslint-eslint-rules": "5.4.0",
90 | "tslint-plugin-prettier": "2.3.0",
91 | "tslint-react": "5.0.0",
92 | "typescript": "5.4.5"
93 | },
94 | "files": [
95 | "lib"
96 | ],
97 | "avaron": {
98 | "fixture": "./fixture.html"
99 | },
100 | "dependencies": {
101 | "re-resizable": "6.11.2",
102 | "react-draggable": "4.4.6",
103 | "tslib": "2.6.2"
104 | },
105 | "peerDependencies": {
106 | "react": ">=16.3.0",
107 | "react-dom": ">=16.3.0"
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bokuweb/react-rnd/34ccbfa162775b305b0c84836c8082e8caee9500/screenshot.gif
--------------------------------------------------------------------------------
/scripts/deploy-minor.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git config --global -l
4 | git config --global user.email bokuweb12@gmail.com
5 | git config --global user.name bokuweb
6 | git remote --v
7 | git reset --hard HEAD
8 | npm version minor
9 | git push origin master
10 | git push --tags
11 | npm publish
12 |
--------------------------------------------------------------------------------
/scripts/deploy-patch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git config --global -l
4 | git config --global user.email bokuweb12@gmail.com
5 | git config --global user.name bokuweb
6 | git remote --v
7 | git reset --hard HEAD
8 | npm version patch
9 | git push origin master
10 | git push --tags
11 | npm publish
12 |
--------------------------------------------------------------------------------
/scripts/prod.common.js:
--------------------------------------------------------------------------------
1 | import replace from 'rollup-plugin-replace';
2 | import typescript from 'rollup-plugin-typescript2';
3 |
4 | export default {
5 | input: 'src/index.tsx',
6 | plugins: [
7 | typescript({
8 | tsconfig: 'tsconfig.json',
9 | exclude: ['*.d.ts', 'stories'],
10 | }),
11 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
12 | ],
13 | output: {
14 | sourcemap: true,
15 | exports: 'named',
16 | name: 'react-sortable-pane',
17 | globals: {
18 | react: 'React',
19 | 're-resizable': 'Resizable',
20 | 'react-draggable': 'Draggable',
21 | },
22 | },
23 | external: ['react', 're-resizable', 'react-draggable'],
24 | };
25 |
--------------------------------------------------------------------------------
/scripts/prod.es5.js:
--------------------------------------------------------------------------------
1 | import common from './prod.common';
2 |
3 | export default Object.assign({}, common, {
4 | output: {
5 | file: 'lib/index.es5.js',
6 | format: 'cjs',
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/scripts/prod.js:
--------------------------------------------------------------------------------
1 | import common from './prod.common';
2 |
3 | export default Object.assign({}, common, {
4 | output: {
5 | file: 'lib/index.js',
6 | format: 'es',
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/src/index.js.flow:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import * as React from 'react';
4 | import type { ResizeDirection, ResizeCallback, ResizeStartCallback } from 're-resizable';
5 |
6 | export type Grid = [number, number];
7 |
8 | export type Position = {
9 | x: number,
10 | y: number,
11 | };
12 |
13 | export type DraggableData = {
14 | node: HTMLElement,
15 | deltaX: number,
16 | deltaY: number,
17 | lastX: number,
18 | lastY: number,
19 | } & Position;
20 |
21 | export type RndDragCallback = (e: Event, data: DraggableData) => void | false;
22 |
23 | export type RndResizeStartCallback = (
24 | e: SyntheticMouseEvent
| SyntheticTouchEvent,
25 | dir: ResizeDirection,
26 | refToElement: React.ElementRef<'div'>,
27 | ) => void;
28 |
29 | export type ResizableDelta = {
30 | width: number,
31 | height: number,
32 | };
33 |
34 | export type RndResizeCallback = (
35 | e: MouseEvent | TouchEvent,
36 | dir: ResizeDirection,
37 | refToElement: React.ElementRef<'div'>,
38 | delta: ResizableDelta,
39 | position: Position,
40 | ) => void;
41 |
42 | type Size = {
43 | width: string | number,
44 | height: string | number,
45 | };
46 |
47 | type State = {
48 | original: Position,
49 | bounds: {
50 | top: number,
51 | right: number,
52 | bottom: number,
53 | left: number,
54 | },
55 | maxWidth?: number | string,
56 | maxHeight?: number | string,
57 | };
58 |
59 | export type ResizeEnable = {
60 | bottom?: boolean,
61 | bottomLeft?: boolean,
62 | bottomRight?: boolean,
63 | left?: boolean,
64 | right?: boolean,
65 | top?: boolean,
66 | topLeft?: boolean,
67 | topRight?: boolean,
68 | };
69 |
70 | export type HandleClasses = {
71 | bottom?: string,
72 | bottomLeft?: string,
73 | bottomRight?: string,
74 | left?: string,
75 | right?: string,
76 | top?: string,
77 | topLeft?: string,
78 | topRight?: string,
79 | };
80 |
81 | type Style = {
82 | [key: string]: string | number,
83 | };
84 |
85 | export type HandleStyles = {
86 | bottom?: Style,
87 | bottomLeft?: Style,
88 | bottomRight?: Style,
89 | left?: Style,
90 | right?: Style,
91 | top?: Style,
92 | topLeft?: Style,
93 | topRight?: Style,
94 | };
95 |
96 | export type HandleComponent = {
97 | top?: React.ReactElement;
98 | right?: React.ReactElement;
99 | bottom?: React.ReactElement;
100 | left?: React.ReactElement;
101 | topRight?: React.ReactElement;
102 | bottomRight?: React.ReactElement;
103 | bottomLeft?: React.ReactElement;
104 | topLeft?: React.ReactElement;
105 | }
106 |
107 | export type Props = {
108 | dragGrid?: Grid,
109 | default?: {
110 | x: number,
111 | y: number,
112 | } & Size,
113 | position?: {
114 | x: number,
115 | y: number,
116 | },
117 | size?: Size,
118 | resizeGrid?: Grid,
119 | bounds?: string,
120 | onResizeStart?: RndResizeStartCallback,
121 | onResize?: RndResizeCallback,
122 | onResizeStop?: RndResizeCallback,
123 | onDragStart?: RndDragCallback,
124 | onDrag?: RndDragCallback,
125 | onDragStop?: RndDragCallback,
126 | className?: string,
127 | style?: Style,
128 | children?: React.Node,
129 | enableResizing?: ResizeEnable,
130 | extendsProps?: { [key: string]: any },
131 | resizeHandleClasses?: HandleClasses,
132 | resizeHandleStyles?: HandleStyles,
133 | resizeHandleComponent?: HandleComponent,
134 | resizeHandleWrapperClass?: string,
135 | resizeHandleWrapperStyle?: Style,
136 | lockAspectRatio?: boolean | number,
137 | lockAspectRatioExtraWidth?: number,
138 | lockAspectRatioExtraHeight?: number,
139 | maxHeight?: number | string,
140 | maxWidth?: number | string,
141 | minHeight?: number | string,
142 | minWidth?: number | string,
143 | dragAxis?: 'x' | 'y' | 'both' | 'none',
144 | dragHandleClassName?: string,
145 | disableDragging?: boolean,
146 | cancel?: boolean,
147 | enableUserSelectHack?: boolean,
148 | scale?: number,
149 | };
150 |
151 | export class Rnd extends React.Component {
152 | static defaultProps = {
153 | maxWidth: Number.MAX_SAFE_INTEGER,
154 | maxHeight: Number.MAX_SAFE_INTEGER,
155 | onResizeStart: () => {},
156 | onResize: () => {},
157 | onResizeStop: () => {},
158 | onDragStart: () => {},
159 | onDrag: () => {},
160 | onDragStop: () => {},
161 | scale: 1,
162 | };
163 |
164 | updateSize(size: { width: number | string, height: number | string }) {
165 | }
166 |
167 | updatePosition(position: Position) {
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/index.test.tsx:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 |
3 | /* TODO: mask for now
4 | import test from "ava";
5 | import * as React from "react";
6 | import { spy } from "sinon";
7 | import Enzyme, { mount } from "enzyme";
8 | import Adapter from "enzyme-adapter-react-16";
9 | import { Rnd } from "./";
10 |
11 | Enzyme.configure({ adapter: new Adapter() });
12 |
13 | const mouseMove = (x: number, y: number) => {
14 | const event = document.createEvent("MouseEvents");
15 | event.initMouseEvent("mousemove", true, true, window, 0, 0, 0, x, y, false, false, false, false, 0, null);
16 | document.dispatchEvent(event);
17 | return event;
18 | };
19 |
20 | const mouseUp = (x: number, y: number) => {
21 | const event = document.createEvent("MouseEvents");
22 | event.initMouseEvent("mouseup", true, true, window, 0, 0, 0, x, y, false, false, false, false, 0, null);
23 | document.dispatchEvent(event);
24 | return event;
25 | };
26 |
27 | test.beforeEach(async (t) => {
28 | const div = document.createElement("div");
29 | document.body.appendChild(div);
30 | });
31 |
32 | test("should mount without error", async (t) => {
33 | const rnd = mount();
34 | t.truthy(!!rnd);
35 | });
36 |
37 | test("Should custom class name be applied to box", async (t) => {
38 | const rnd = mount();
39 | t.truthy(rnd.getDOMNode().classList.contains("custom-class-name"));
40 | });
41 |
42 | test("Should render custom components", async (t) => {
43 | const CustomComponent = () => ;
44 |
45 | const rnd = mount(
46 | ,
50 | right: ,
51 | bottom: ,
52 | left: ,
53 | topRight: ,
54 | bottomRight: ,
55 | bottomLeft: ,
56 | topLeft: ,
57 | }}
58 | />,
59 | );
60 | const customComponents = rnd.find(".custom-component");
61 | t.is(customComponents.length, 8);
62 | });
63 |
64 | test("Should set handler className", async (t) => {
65 | const rnd = mount(
66 | ,
79 | );
80 | const handlers = rnd.find(".handler");
81 | // FIXME: Is it a enzyme 3.x bug ? I can not understand why handlers.length equals 16.
82 | // When use enzyme v2.x this test is passed...
83 | // t.is(handlers.length, 8);
84 | t.is(handlers.length, 16);
85 | });
86 |
87 | test("Should not render resizer when enable props all false", async (t) => {
88 | const rnd = mount(
89 | ,
112 | );
113 | const handlers = rnd.find(".handler");
114 | t.is(handlers.length, 0);
115 | });
116 |
117 | test("should call onMouseDown when mouse downed", async (t) => {
118 | const onMouseDown = spy();
119 | const rnd = mount();
120 | rnd.find("div").at(0).simulate("mousedown");
121 | t.is(onMouseDown.callCount, 1);
122 | t.is(onMouseDown.firstCall.args[0].type, "mousedown");
123 | });
124 |
125 | test("should call onDragStart when start dragging", async (t) => {
126 | const onDragStart = spy();
127 | const rnd = mount();
128 | rnd.find("div").at(0).simulate("mousedown");
129 | t.is(onDragStart.callCount, 1);
130 | t.is(onDragStart.firstCall.args[0].type, "mousedown");
131 | t.is(onDragStart.firstCall.args[1].x, 200);
132 | t.is(onDragStart.firstCall.args[1].y, 200);
133 | });
134 |
135 | test("should call onDrag when dragging", async (t) => {
136 | const onDrag = spy();
137 | const rnd = mount();
138 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
139 | mouseMove(200, 220);
140 | t.is(onDrag.callCount, 1);
141 | t.is(onDrag.firstCall.args[1].x, 500);
142 | t.is(onDrag.firstCall.args[1].y, 520);
143 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(400px, 420px)"), -1);
144 | });
145 |
146 | test("should call onDragStop when drag stop", async (t) => {
147 | const onDragStop = spy();
148 | const rnd = mount();
149 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
150 | mouseMove(200, 220);
151 | mouseUp(100, 120);
152 | t.is(onDragStop.callCount, 1);
153 | t.is(onDragStop.firstCall.args[1].x, 200);
154 | t.is(onDragStop.firstCall.args[1].y, 220);
155 | });
156 |
157 | test("should dragging disabled when axis equals none", async (t) => {
158 | const onDrag = spy();
159 | const rnd = mount(, {
160 | attachTo: document.querySelector("div"),
161 | });
162 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
163 | mouseMove(200, 220);
164 | t.is(onDrag.callCount, 0);
165 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(100px, 100px)"), -1);
166 | });
167 |
168 | test("should enable dragging only x when axis equals x", async (t) => {
169 | const onDrag = spy();
170 | const rnd = mount(, {
171 | attachTo: document.querySelector("div"),
172 | });
173 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
174 | mouseMove(200, 220);
175 | t.is(onDrag.callCount, 1);
176 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(300px, 100px)"), -1);
177 | });
178 |
179 | test("xdragging only y when axis equals y", async (t) => {
180 | const onDrag = spy();
181 | const rnd = mount(, {
182 | attachTo: document.querySelector("div"),
183 | });
184 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
185 | mouseMove(200, 220);
186 | t.is(onDrag.callCount, 1);
187 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(100px, 320px)"), -1);
188 | });
189 |
190 | test("should enable dragging both x & y when axis equals both", async (t) => {
191 | const onDrag = spy();
192 | const rnd = mount(, {
193 | attachTo: document.querySelector("div"),
194 | });
195 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
196 | mouseMove(200, 220);
197 | t.is(onDrag.callCount, 1);
198 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(300px, 320px)"), -1);
199 | });
200 |
201 | test("should snap when dragging smaller than threshold", async (t) => {
202 | const rnd = mount(, {
203 | attachTo: document.querySelector("div"),
204 | });
205 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
206 | mouseMove(14, 49);
207 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(100px, 100px)"), -1);
208 | });
209 |
210 | test("should snap when dragging larger than threshold", async (t) => {
211 | const rnd = mount(, {
212 | attachTo: document.querySelector("div"),
213 | });
214 | rnd.find("div").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
215 | mouseMove(15, 50);
216 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(130px, 200px)"), -1);
217 | });
218 |
219 | test("should limit position by parent bounds", async (t) => {
220 | const rnd = mount(
221 |
222 |
223 |
,
224 | { attachTo: document.querySelector("div") },
225 | );
226 | rnd.find("div").at(0).childAt(0).simulate("mousedown", { clientX: 50, clientY: 50 });
227 | mouseMove(1000, 1000);
228 | t.not(
229 | (rnd.find("div").at(0).childAt(0).getDOMNode().getAttribute("style") || "").indexOf(
230 | "transform: translate(700px, 500px)",
231 | ),
232 | -1,
233 | );
234 | });
235 |
236 | test("should limit position by selector bounds", async (t) => {
237 | const rnd = mount(
238 | ,
243 | { attachTo: document.querySelector("div") },
244 | );
245 |
246 | rnd.find("div").at(0).childAt(0).childAt(0).simulate("mousedown", { clientX: 0, clientY: 0 });
247 | mouseMove(2000, 2000);
248 | t.not(
249 | (rnd.find("div").at(0).childAt(0).childAt(0).getDOMNode().getAttribute("style") || "").indexOf(
250 | "translate(900px, 700px)",
251 | ),
252 | -1,
253 | );
254 | });
255 |
256 | test("Should box width and height equal 100px", async (t) => {
257 | const rnd = mount(
258 | ,
271 | { attachTo: document.querySelector("div") },
272 | );
273 | t.is((rnd.getDOMNode() as HTMLElement).style.width, "100px");
274 | t.is((rnd.getDOMNode() as HTMLElement).style.height, "100px");
275 | });
276 |
277 | test("Should call onResizeStart when mousedown", async (t) => {
278 | const onResizeStart = spy();
279 | const rnd = mount(
280 | ,
304 | { attachTo: document.querySelector("div") },
305 | );
306 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
307 | t.is(onResizeStart.callCount, 1);
308 | t.is(onResizeStart.getCall(0).args[1], "right");
309 | });
310 |
311 | test("should call onResize with expected args when resize direction right", async (t) => {
312 | const onResize = spy();
313 | const onResizeStart = spy();
314 | const rnd = mount(
315 | ,
340 | { attachTo: document.querySelector("div") },
341 | );
342 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
343 | mouseMove(200, 220);
344 | t.is(onResize.callCount, 1);
345 | t.truthy(onResize.getCall(0).args[0] instanceof Event);
346 | t.is(onResize.getCall(0).args[1], "right");
347 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 300);
348 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 100);
349 | t.deepEqual(onResize.getCall(0).args[3], { width: 200, height: 0 });
350 | });
351 |
352 | test("should call onResizeStop with expected args when resize direction right", async (t) => {
353 | const onResize = spy();
354 | const onResizeStop = spy();
355 | const rnd = mount(
356 | ,
381 | { attachTo: document.querySelector("div") },
382 | );
383 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
384 | mouseMove(200, 220);
385 | mouseUp(200, 220);
386 | t.is(onResizeStop.callCount, 1);
387 | t.truthy(onResize.getCall(0).args[0] instanceof Event);
388 | t.is(onResize.getCall(0).args[1], "right");
389 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 300);
390 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 100);
391 | t.deepEqual(onResize.getCall(0).args[3], { width: 200, height: 0 });
392 | });
393 |
394 | test("should move x when resizing left", async (t) => {
395 | const onResize = spy();
396 | const onResizeStart = spy();
397 | const rnd = mount(
398 | ,
423 | { attachTo: document.querySelector("div") },
424 | );
425 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
426 | mouseMove(-50, 0);
427 | t.is(onResize.callCount, 1);
428 | t.is(onResize.getCall(0).args[1], "left");
429 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 150);
430 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 100);
431 | t.deepEqual(onResize.getCall(0).args[3], { width: 50, height: 0 });
432 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(50px, 100px)"), -1);
433 | });
434 |
435 | test("should move y when resizing top", async (t) => {
436 | const onResize = spy();
437 | const onResizeStart = spy();
438 | const rnd = mount(
439 | ,
464 | { attachTo: document.querySelector("div") },
465 | );
466 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
467 | mouseMove(0, -50);
468 | t.is(onResize.callCount, 1);
469 | t.is(onResize.getCall(0).args[1], "top");
470 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 100);
471 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 150);
472 | t.deepEqual(onResize.getCall(0).args[3], { width: 0, height: 50 });
473 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(100px, 50px)"), -1);
474 | });
475 |
476 | test("should snapped by original grid when x axis resizing smaller then threshold", async (t) => {
477 | const onResize = spy();
478 | const rnd = mount(
479 | ,
504 | { attachTo: document.querySelector("div") },
505 | );
506 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
507 | mouseMove(9, 0);
508 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 100);
509 | });
510 |
511 | test("should snapped by original grid when x axis resizing larger then threshold", async (t) => {
512 | const onResize = spy();
513 | const rnd = mount(
514 | ,
539 | { attachTo: document.querySelector("div") },
540 | );
541 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
542 | mouseMove(10, 0);
543 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 120);
544 | });
545 |
546 | test("should snapped by original grid when y axis resizing smaller then threshold", async (t) => {
547 | const onResize = spy();
548 | const rnd = mount(
549 | ,
574 | { attachTo: document.querySelector("div") },
575 | );
576 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
577 | mouseMove(0, 9);
578 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 100);
579 | });
580 |
581 | test("should snapped by original grid when y axis resizing larger then threshold", async (t) => {
582 | const onResize = spy();
583 | const rnd = mount(
584 | ,
609 | { attachTo: document.querySelector("div") },
610 | );
611 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
612 | mouseMove(0, 10);
613 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 120);
614 | });
615 |
616 | test("should snapped by original grid when y axis resizing larger then threshold", async (t) => {
617 | const onResize = spy();
618 | const rnd = mount(
619 | ,
644 | { attachTo: document.querySelector("div") },
645 | );
646 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
647 | mouseMove(20, 10);
648 | // TODO: It'a resizable-box grid bug??
649 | t.deepEqual(onResize.getCall(0).args[2].clientWidth, 120);
650 | t.deepEqual(onResize.getCall(0).args[2].clientHeight, 120);
651 | });
652 |
653 | test("should clamped by parent size", async (t) => {
654 | const rnd = mount(
655 |
656 |
680 |
,
681 | { attachTo: document.querySelector("div") },
682 | );
683 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
684 | mouseMove(1200, 1200);
685 | t.is((rnd.childAt(0).getDOMNode() as HTMLElement).style.width, "800px");
686 | t.is((rnd.childAt(0).getDOMNode() as HTMLElement).style.height, "600px");
687 | });
688 |
689 | test("should clamped by selector size", async (t) => {
690 | const rnd = mount(
691 | ,
719 | { attachTo: document.querySelector("div") },
720 | );
721 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
722 | mouseMove(2000, 2000);
723 | t.is((rnd.childAt(0).childAt(0).getDOMNode() as HTMLElement).style.width, "1000px");
724 | t.is((rnd.childAt(0).childAt(0).getDOMNode() as HTMLElement).style.height, "800px");
725 | });
726 |
727 | test("should clamped by boundary element size", async (t) => {
728 | const rnd = mount(
729 | ,
757 | { attachTo: document.querySelector("div") },
758 | );
759 | rnd.find("div.handler").at(0).simulate("mousedown", { clientX: 0, clientY: 0 });
760 | mouseMove(1200, 1200);
761 | t.is((rnd.childAt(0).getDOMNode() as HTMLElement).style.width, "800px");
762 | t.is((rnd.childAt(0).getDOMNode() as HTMLElement).style.height, "600px");
763 | });
764 |
765 | test("should get rnd updated when updatePosition invoked", async (t) => {
766 | const rnd = mount();
767 | rnd.instance().updatePosition({ x: 200, y: 300 });
768 | t.not((rnd.getDOMNode().getAttribute("style") || "").indexOf("transform: translate(200px, 300px)"), -1);
769 | });
770 |
771 | test("should get rnd updated when updateSize invoked", async (t) => {
772 | const rnd = mount();
773 | rnd.instance().updateSize({ width: 200, height: 300 });
774 | t.is((rnd.getDOMNode() as HTMLElement).style.width, "200px");
775 | t.is((rnd.getDOMNode() as HTMLElement).style.height, "300px");
776 | });
777 |
778 | test("should find drag handle class when dragHandleClassName props passed", async (t) => {
779 | const onDrag = spy();
780 | const rnd = mount(
781 |
782 | Test
783 | ,
784 | );
785 | rnd.find("div.handle").simulate("mousedown", { clientX: 0, clientY: 0 });
786 | mouseMove(200, 220);
787 | t.is(onDrag.callCount, 1);
788 | });
789 |
790 | test("should pass data- attribute", async (t) => {
791 | const rnd = mount(Test);
792 | t.is(!!rnd.find("[data-foo]"), true);
793 | });
794 |
795 | */
796 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Draggable, { DraggableEventHandler, DraggableProps } from "react-draggable";
3 | import { Enable, Resizable, ResizeDirection } from "re-resizable";
4 | import { flushSync } from "react-dom";
5 |
6 | export type Grid = [number, number];
7 |
8 | export type Position = {
9 | x: number;
10 | y: number;
11 | };
12 |
13 | export type DraggableData = {
14 | node: HTMLElement;
15 | deltaX: number;
16 | deltaY: number;
17 | lastX: number;
18 | lastY: number;
19 | } & Position;
20 |
21 | export type RndDragCallback = DraggableEventHandler;
22 |
23 | export type RndDragEvent =
24 | | React.MouseEvent
25 | | React.TouchEvent
26 | | MouseEvent
27 | | TouchEvent;
28 |
29 | export type RndResizeStartCallback = (
30 | e: React.MouseEvent | React.TouchEvent,
31 | dir: ResizeDirection,
32 | elementRef: HTMLElement,
33 | ) => void | boolean;
34 |
35 | export type ResizableDelta = {
36 | width: number;
37 | height: number;
38 | };
39 |
40 | export type RndResizeCallback = (
41 | e: MouseEvent | TouchEvent,
42 | dir: ResizeDirection,
43 | elementRef: HTMLElement,
44 | delta: ResizableDelta,
45 | position: Position,
46 | ) => void;
47 |
48 | type Size = {
49 | width: string | number;
50 | height: string | number;
51 | };
52 |
53 | type State = {
54 | resizing: boolean;
55 | bounds: {
56 | top: number;
57 | right: number;
58 | bottom: number;
59 | left: number;
60 | };
61 | maxWidth?: number | string;
62 | maxHeight?: number | string;
63 | };
64 |
65 | type MaxSize = {
66 | maxWidth: number | string;
67 | maxHeight: number | string;
68 | };
69 |
70 | export type ResizeEnable =
71 | | {
72 | bottom?: boolean;
73 | bottomLeft?: boolean;
74 | bottomRight?: boolean;
75 | left?: boolean;
76 | right?: boolean;
77 | top?: boolean;
78 | topLeft?: boolean;
79 | topRight?: boolean;
80 | }
81 | | boolean;
82 |
83 | export type HandleClasses = {
84 | bottom?: string;
85 | bottomLeft?: string;
86 | bottomRight?: string;
87 | left?: string;
88 | right?: string;
89 | top?: string;
90 | topLeft?: string;
91 | topRight?: string;
92 | };
93 |
94 | export type HandleStyles = {
95 | bottom?: React.CSSProperties;
96 | bottomLeft?: React.CSSProperties;
97 | bottomRight?: React.CSSProperties;
98 | left?: React.CSSProperties;
99 | right?: React.CSSProperties;
100 | top?: React.CSSProperties;
101 | topLeft?: React.CSSProperties;
102 | topRight?: React.CSSProperties;
103 | };
104 |
105 | export type HandleComponent = {
106 | top?: React.ReactElement;
107 | right?: React.ReactElement;
108 | bottom?: React.ReactElement;
109 | left?: React.ReactElement;
110 | topRight?: React.ReactElement;
111 | bottomRight?: React.ReactElement;
112 | bottomLeft?: React.ReactElement;
113 | topLeft?: React.ReactElement;
114 | };
115 |
116 | export interface Props {
117 | dragGrid?: Grid;
118 | default?: {
119 | x: number;
120 | y: number;
121 | } & Size;
122 | position?: {
123 | x: number;
124 | y: number;
125 | };
126 | size?: Size;
127 | resizeGrid?: Grid;
128 | bounds?: string | Element;
129 | onMouseDown?: (e: MouseEvent) => void;
130 | onMouseUp?: (e: MouseEvent) => void;
131 | onResizeStart?: RndResizeStartCallback;
132 | onResize?: RndResizeCallback;
133 | onResizeStop?: RndResizeCallback;
134 | onDragStart?: RndDragCallback;
135 | onDrag?: RndDragCallback;
136 | onDragStop?: RndDragCallback;
137 | className?: string;
138 | style?: React.CSSProperties;
139 | children?: React.ReactNode;
140 | enableResizing?: ResizeEnable;
141 | resizeHandleClasses?: HandleClasses;
142 | resizeHandleStyles?: HandleStyles;
143 | resizeHandleWrapperClass?: string;
144 | resizeHandleWrapperStyle?: React.CSSProperties;
145 | resizeHandleComponent?: HandleComponent;
146 | lockAspectRatio?: boolean | number;
147 | lockAspectRatioExtraWidth?: number;
148 | lockAspectRatioExtraHeight?: number;
149 | maxHeight?: number | string;
150 | maxWidth?: number | string;
151 | minHeight?: number | string;
152 | minWidth?: number | string;
153 | dragAxis?: "x" | "y" | "both" | "none";
154 | dragHandleClassName?: string;
155 | disableDragging?: boolean;
156 | cancel?: string;
157 | enableUserSelectHack?: boolean;
158 | dragPositionOffset?: DraggableProps["positionOffset"];
159 | allowAnyClick?: boolean;
160 | scale?: number;
161 | [key: string]: any;
162 | }
163 |
164 | const resizableStyle = {
165 | width: "auto" as "auto",
166 | height: "auto" as "auto",
167 | display: "inline-block" as "inline-block",
168 | position: "absolute" as "absolute",
169 | top: 0,
170 | left: 0,
171 | };
172 |
173 | const getEnableResizingByFlag = (flag: boolean): Enable => ({
174 | bottom: flag,
175 | bottomLeft: flag,
176 | bottomRight: flag,
177 | left: flag,
178 | right: flag,
179 | top: flag,
180 | topLeft: flag,
181 | topRight: flag,
182 | });
183 |
184 | interface DefaultProps {
185 | maxWidth: number;
186 | maxHeight: number;
187 | onResizeStart: RndResizeStartCallback;
188 | onResize: RndResizeCallback;
189 | onResizeStop: RndResizeCallback;
190 | onDragStart: RndDragCallback;
191 | onDrag: RndDragCallback;
192 | onDragStop: RndDragCallback;
193 | scale: number;
194 | }
195 |
196 | export class Rnd extends React.PureComponent {
197 | public static defaultProps: DefaultProps = {
198 | maxWidth: Number.MAX_SAFE_INTEGER,
199 | maxHeight: Number.MAX_SAFE_INTEGER,
200 | scale: 1,
201 | onResizeStart: () => {},
202 | onResize: () => {},
203 | onResizeStop: () => {},
204 | onDragStart: () => {},
205 | onDrag: () => {},
206 | onDragStop: () => {},
207 | };
208 | resizable!: Resizable;
209 | draggable!: Draggable;
210 | resizingPosition = { x: 0, y: 0 };
211 | offsetFromParent = { left: 0, top: 0 };
212 | resizableElement: { current: HTMLElement | null } = { current: null };
213 | originalPosition = { x: 0, y: 0 };
214 |
215 | constructor(props: Props) {
216 | super(props);
217 | this.state = {
218 | resizing: false,
219 | bounds: {
220 | top: 0,
221 | right: 0,
222 | bottom: 0,
223 | left: 0,
224 | },
225 | maxWidth: props.maxWidth,
226 | maxHeight: props.maxHeight,
227 | };
228 |
229 | this.onResizeStart = this.onResizeStart.bind(this);
230 | this.onResize = this.onResize.bind(this);
231 | this.onResizeStop = this.onResizeStop.bind(this);
232 | this.onDragStart = this.onDragStart.bind(this);
233 | this.onDrag = this.onDrag.bind(this);
234 | this.onDragStop = this.onDragStop.bind(this);
235 | this.getMaxSizesFromProps = this.getMaxSizesFromProps.bind(this);
236 | }
237 |
238 | componentDidMount() {
239 | this.updateOffsetFromParent();
240 | const { left, top } = this.offsetFromParent;
241 | const { x, y } = this.getDraggablePosition();
242 | this.draggable.setState({
243 | x: x - left,
244 | y: y - top,
245 | });
246 | // HACK: Apply position adjustment
247 | this.forceUpdate();
248 | }
249 |
250 | // HACK: To get `react-draggable` state x and y.
251 | getDraggablePosition(): { x: number; y: number } {
252 | const { x, y } = (this.draggable as any).state;
253 | return { x, y };
254 | }
255 |
256 | getParent() {
257 | return this.resizable && (this.resizable as any).parentNode;
258 | }
259 |
260 | getParentSize(): { width: number; height: number } {
261 | return (this.resizable as any).getParentSize();
262 | }
263 |
264 | getMaxSizesFromProps(): MaxSize {
265 | const maxWidth = typeof this.props.maxWidth === "undefined" ? Number.MAX_SAFE_INTEGER : this.props.maxWidth;
266 | const maxHeight = typeof this.props.maxHeight === "undefined" ? Number.MAX_SAFE_INTEGER : this.props.maxHeight;
267 | return { maxWidth, maxHeight };
268 | }
269 |
270 | getSelfElement(): HTMLElement | null {
271 | return this.resizable && this.resizable.resizable;
272 | }
273 |
274 | getOffsetHeight(boundary: HTMLElement) {
275 | const scale = this.props.scale as number;
276 | switch (this.props.bounds) {
277 | case "window":
278 | return window.innerHeight / scale;
279 | case "body":
280 | return document.body.offsetHeight / scale;
281 | default:
282 | return boundary.offsetHeight;
283 | }
284 | }
285 |
286 | getOffsetWidth(boundary: HTMLElement) {
287 | const scale = this.props.scale as number;
288 | switch (this.props.bounds) {
289 | case "window":
290 | return window.innerWidth / scale;
291 | case "body":
292 | return document.body.offsetWidth / scale;
293 | default:
294 | return boundary.offsetWidth;
295 | }
296 | }
297 |
298 | onDragStart(e: RndDragEvent, data: DraggableData) {
299 | if (this.props.onDragStart) {
300 | this.props.onDragStart(e, data);
301 | }
302 | const pos = this.getDraggablePosition();
303 | this.originalPosition = pos;
304 | if (!this.props.bounds) return;
305 | const parent = this.getParent();
306 | const scale = this.props.scale as number;
307 | let boundary;
308 | if (this.props.bounds === "parent") {
309 | boundary = parent;
310 | } else if (this.props.bounds === "body") {
311 | const parentRect = parent.getBoundingClientRect();
312 | const parentLeft = parentRect.left;
313 | const parentTop = parentRect.top;
314 | const bodyRect = document.body.getBoundingClientRect();
315 | const left = -(parentLeft - parent.offsetLeft * scale - bodyRect.left) / scale;
316 | const top = -(parentTop - parent.offsetTop * scale - bodyRect.top) / scale;
317 | const right = (document.body.offsetWidth - this.resizable.size.width * scale) / scale + left;
318 | const bottom = (document.body.offsetHeight - this.resizable.size.height * scale) / scale + top;
319 | return this.setState({ bounds: { top, right, bottom, left } });
320 | } else if (this.props.bounds === "window") {
321 | if (!this.resizable) return;
322 | const parentRect = parent.getBoundingClientRect();
323 | const parentLeft = parentRect.left;
324 | const parentTop = parentRect.top;
325 | const left = -(parentLeft - parent.offsetLeft * scale) / scale;
326 | const top = -(parentTop - parent.offsetTop * scale) / scale;
327 | const right = (window.innerWidth - this.resizable.size.width * scale) / scale + left;
328 | const bottom = (window.innerHeight - this.resizable.size.height * scale) / scale + top;
329 | return this.setState({ bounds: { top, right, bottom, left } });
330 | } else if (typeof this.props.bounds === "string") {
331 | boundary = document.querySelector(this.props.bounds);
332 | } else if (this.props.bounds instanceof HTMLElement) {
333 | boundary = this.props.bounds;
334 | }
335 | if (!(boundary instanceof HTMLElement) || !(parent instanceof HTMLElement)) {
336 | return;
337 | }
338 | const boundaryRect = boundary.getBoundingClientRect();
339 | const boundaryLeft = boundaryRect.left;
340 | const boundaryTop = boundaryRect.top;
341 | const parentRect = parent.getBoundingClientRect();
342 | const parentLeft = parentRect.left;
343 | const parentTop = parentRect.top;
344 | const left = (boundaryLeft - parentLeft) / scale;
345 | const top = boundaryTop - parentTop;
346 | if (!this.resizable) return;
347 | this.updateOffsetFromParent();
348 | const offset = this.offsetFromParent;
349 | this.setState({
350 | bounds: {
351 | top: top - offset.top,
352 | right: left + (boundary.offsetWidth - this.resizable.size.width) - offset.left / scale,
353 | bottom: top + (boundary.offsetHeight - this.resizable.size.height) - offset.top,
354 | left: left - offset.left / scale,
355 | },
356 | });
357 | }
358 |
359 | onDrag(e: RndDragEvent, data: DraggableData) {
360 | if (!this.props.onDrag) return;
361 | const { left, top } = this.offsetFromParent;
362 | if (!this.props.dragAxis || this.props.dragAxis === "both") {
363 | return this.props.onDrag(e, { ...data, x: data.x + left, y: data.y + top });
364 | } else if (this.props.dragAxis === "x") {
365 | return this.props.onDrag(e, { ...data, x: data.x + left, y: this.originalPosition.y + top, deltaY: 0 });
366 | } else if (this.props.dragAxis === "y") {
367 | return this.props.onDrag(e, { ...data, x: this.originalPosition.x + left, y: data.y + top, deltaX: 0 });
368 | }
369 | }
370 |
371 | onDragStop(e: RndDragEvent, data: DraggableData) {
372 | if (!this.props.onDragStop) return;
373 | const { left, top } = this.offsetFromParent;
374 | if (!this.props.dragAxis || this.props.dragAxis === "both") {
375 | return this.props.onDragStop(e, { ...data, x: data.x + left, y: data.y + top });
376 | } else if (this.props.dragAxis === "x") {
377 | return this.props.onDragStop(e, { ...data, x: data.x + left, y: this.originalPosition.y + top, deltaY: 0 });
378 | } else if (this.props.dragAxis === "y") {
379 | return this.props.onDragStop(e, { ...data, x: this.originalPosition.x + left, y: data.y + top, deltaX: 0 });
380 | }
381 | }
382 |
383 | onResizeStart(
384 | e: React.MouseEvent | React.TouchEvent,
385 | dir: ResizeDirection,
386 | elementRef: HTMLElement,
387 | ) {
388 | e.stopPropagation();
389 | this.setState({
390 | resizing: true,
391 | });
392 | const scale = this.props.scale as number;
393 | const offset = this.offsetFromParent;
394 | const pos = this.getDraggablePosition();
395 | this.resizingPosition = { x: pos.x + offset.left, y: pos.y + offset.top };
396 | this.originalPosition = pos;
397 |
398 | if (this.props.bounds) {
399 | const parent = this.getParent();
400 | let boundary;
401 | if (this.props.bounds === "parent") {
402 | boundary = parent;
403 | } else if (this.props.bounds === "body") {
404 | boundary = document.body;
405 | } else if (this.props.bounds === "window") {
406 | boundary = window;
407 | } else if (typeof this.props.bounds === "string") {
408 | boundary = document.querySelector(this.props.bounds);
409 | } else if (this.props.bounds instanceof HTMLElement) {
410 | boundary = this.props.bounds;
411 | }
412 |
413 | const self = this.getSelfElement();
414 | if (
415 | self instanceof Element &&
416 | (boundary instanceof HTMLElement || boundary === window) &&
417 | parent instanceof HTMLElement
418 | ) {
419 | let { maxWidth, maxHeight } = this.getMaxSizesFromProps();
420 | const parentSize = this.getParentSize();
421 | if (maxWidth && typeof maxWidth === "string") {
422 | if (maxWidth.endsWith("%")) {
423 | const ratio = Number(maxWidth.replace("%", "")) / 100;
424 | maxWidth = parentSize.width * ratio;
425 | } else if (maxWidth.endsWith("px")) {
426 | maxWidth = Number(maxWidth.replace("px", ""));
427 | }
428 | }
429 | if (maxHeight && typeof maxHeight === "string") {
430 | if (maxHeight.endsWith("%")) {
431 | const ratio = Number(maxHeight.replace("%", "")) / 100;
432 | maxHeight = parentSize.height * ratio;
433 | } else if (maxHeight.endsWith("px")) {
434 | maxHeight = Number(maxHeight.replace("px", ""));
435 | }
436 | }
437 | const selfRect = self.getBoundingClientRect();
438 | const selfLeft = selfRect.left;
439 | const selfTop = selfRect.top;
440 | const boundaryRect = this.props.bounds === "window" ? { left: 0, top: 0 } : boundary.getBoundingClientRect();
441 | const boundaryLeft = boundaryRect.left;
442 | const boundaryTop = boundaryRect.top;
443 | const offsetWidth = this.getOffsetWidth(boundary);
444 | const offsetHeight = this.getOffsetHeight(boundary);
445 | const hasLeft = dir.toLowerCase().endsWith("left");
446 | const hasRight = dir.toLowerCase().endsWith("right");
447 | const hasTop = dir.startsWith("top");
448 | const hasBottom = dir.startsWith("bottom");
449 |
450 | if ((hasLeft || hasTop) && this.resizable) {
451 | const max = (selfLeft - boundaryLeft) / scale + this.resizable.size.width;
452 | this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max });
453 | }
454 | // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story.
455 | if (hasRight || (this.props.lockAspectRatio && !hasLeft && !hasTop)) {
456 | const max = offsetWidth + (boundaryLeft - selfLeft) / scale;
457 | this.setState({ maxWidth: max > Number(maxWidth) ? maxWidth : max });
458 | }
459 | if ((hasTop || hasLeft) && this.resizable) {
460 | const max = (selfTop - boundaryTop) / scale + this.resizable.size.height;
461 | this.setState({
462 | maxHeight: max > Number(maxHeight) ? maxHeight : max,
463 | });
464 | }
465 | // INFO: To set bounds in `lock aspect ratio with bounds` case. See also that story.
466 | if (hasBottom || (this.props.lockAspectRatio && !hasTop && !hasLeft)) {
467 | const max = offsetHeight + (boundaryTop - selfTop) / scale;
468 | this.setState({
469 | maxHeight: max > Number(maxHeight) ? maxHeight : max,
470 | });
471 | }
472 | }
473 | } else {
474 | this.setState({
475 | maxWidth: this.props.maxWidth,
476 | maxHeight: this.props.maxHeight,
477 | });
478 | }
479 | if (this.props.onResizeStart) {
480 | this.props.onResizeStart(e, dir, elementRef);
481 | }
482 | }
483 |
484 | onResize(
485 | e: MouseEvent | TouchEvent,
486 | direction: ResizeDirection,
487 | elementRef: HTMLElement,
488 | delta: { height: number; width: number },
489 | ) {
490 | // INFO: Apply x and y position adjustments caused by resizing to draggable
491 | const newPos = { x: this.originalPosition.x, y: this.originalPosition.y };
492 | const left = -delta.width;
493 | const top = -delta.height;
494 | const directions: ResizeDirection[] = ["top", "left", "topLeft", "bottomLeft", "topRight"];
495 |
496 | if (directions.includes(direction)) {
497 | if (direction === "bottomLeft") {
498 | newPos.x += left;
499 | } else if (direction === "topRight") {
500 | newPos.y += top;
501 | } else {
502 | newPos.x += left;
503 | newPos.y += top;
504 | }
505 | }
506 |
507 | const draggableState = this.draggable.state as unknown as { x: number; y: number };
508 | if (newPos.x !== draggableState.x || newPos.y !== draggableState.y) {
509 | flushSync(() => {
510 | this.draggable.setState(newPos);
511 | });
512 | }
513 |
514 | this.updateOffsetFromParent();
515 | const offset = this.offsetFromParent;
516 | const x = this.getDraggablePosition().x + offset.left;
517 | const y = this.getDraggablePosition().y + offset.top;
518 |
519 | this.resizingPosition = { x, y };
520 | if (!this.props.onResize) return;
521 | this.props.onResize(e, direction, elementRef, delta, {
522 | x,
523 | y,
524 | });
525 | }
526 |
527 | onResizeStop(
528 | e: MouseEvent | TouchEvent,
529 | direction: ResizeDirection,
530 | elementRef: HTMLElement,
531 | delta: { height: number; width: number },
532 | ) {
533 | this.setState({
534 | resizing: false,
535 | });
536 | const { maxWidth, maxHeight } = this.getMaxSizesFromProps();
537 | this.setState({ maxWidth, maxHeight });
538 | if (this.props.onResizeStop) {
539 | this.props.onResizeStop(e, direction, elementRef, delta, this.resizingPosition);
540 | }
541 | }
542 |
543 | updateSize(size: { width: number | string; height: number | string }) {
544 | if (!this.resizable) return;
545 | this.resizable.updateSize({ width: size.width, height: size.height });
546 | }
547 |
548 | updatePosition(position: Position) {
549 | this.draggable.setState(position);
550 | }
551 |
552 | updateOffsetFromParent() {
553 | const scale = this.props.scale as number;
554 | const parent = this.getParent();
555 | const self = this.getSelfElement();
556 | if (!parent || self === null) {
557 | return {
558 | top: 0,
559 | left: 0,
560 | };
561 | }
562 | const parentRect = parent.getBoundingClientRect();
563 | const parentLeft = parentRect.left;
564 | const parentTop = parentRect.top;
565 | const selfRect = self.getBoundingClientRect();
566 | const position = this.getDraggablePosition();
567 | const scrollLeft = parent.scrollLeft;
568 | const scrollTop = parent.scrollTop;
569 | this.offsetFromParent = {
570 | left: selfRect.left - parentLeft + scrollLeft - position.x * scale,
571 | top: selfRect.top - parentTop + scrollTop - position.y * scale,
572 | };
573 | }
574 |
575 | render() {
576 | const {
577 | disableDragging,
578 | style,
579 | dragHandleClassName,
580 | position,
581 | onMouseDown,
582 | onMouseUp,
583 | dragAxis,
584 | dragGrid,
585 | bounds,
586 | enableUserSelectHack,
587 | cancel,
588 | children,
589 | onResizeStart,
590 | onResize,
591 | onResizeStop,
592 | onDragStart,
593 | onDrag,
594 | onDragStop,
595 | resizeHandleStyles,
596 | resizeHandleClasses,
597 | resizeHandleComponent,
598 | enableResizing,
599 | resizeGrid,
600 | resizeHandleWrapperClass,
601 | resizeHandleWrapperStyle,
602 | scale,
603 | allowAnyClick,
604 | dragPositionOffset,
605 | ...resizableProps
606 | } = this.props;
607 | const defaultValue = this.props.default ? { ...this.props.default } : undefined;
608 | // Remove unknown props, see also https://reactjs.org/warnings/unknown-prop.html
609 | delete resizableProps.default;
610 |
611 | const cursorStyle = disableDragging || dragHandleClassName ? { cursor: "auto" } : { cursor: "move" };
612 | const innerStyle = {
613 | ...resizableStyle,
614 | ...cursorStyle,
615 | ...style,
616 | };
617 | const { left, top } = this.offsetFromParent;
618 | let draggablePosition;
619 | if (position) {
620 | draggablePosition = {
621 | x: position.x - left,
622 | y: position.y - top,
623 | };
624 | }
625 | // INFO: Make uncontorolled component when resizing to control position by setPostion.
626 | const pos = this.state.resizing ? undefined : draggablePosition;
627 | const dragAxisOrUndefined = this.state.resizing ? "both" : dragAxis;
628 |
629 | return (
630 | {
632 | if (!c) return;
633 | this.draggable = c;
634 | }}
635 | handle={dragHandleClassName ? `.${dragHandleClassName}` : undefined}
636 | defaultPosition={defaultValue}
637 | onMouseDown={onMouseDown}
638 | // @ts-expect-error
639 | onMouseUp={onMouseUp}
640 | onStart={this.onDragStart}
641 | onDrag={this.onDrag}
642 | onStop={this.onDragStop}
643 | axis={dragAxisOrUndefined}
644 | disabled={disableDragging}
645 | grid={dragGrid}
646 | bounds={bounds ? this.state.bounds : undefined}
647 | position={pos}
648 | enableUserSelectHack={enableUserSelectHack}
649 | cancel={cancel}
650 | scale={scale}
651 | allowAnyClick={allowAnyClick}
652 | nodeRef={this.resizableElement}
653 | positionOffset={dragPositionOffset}
654 | >
655 | {
658 | if (!c) return;
659 | this.resizable = c;
660 | this.resizableElement.current = c.resizable;
661 | }}
662 | defaultSize={defaultValue}
663 | size={this.props.size}
664 | enable={typeof enableResizing === "boolean" ? getEnableResizingByFlag(enableResizing) : enableResizing}
665 | onResizeStart={this.onResizeStart}
666 | onResize={this.onResize}
667 | onResizeStop={this.onResizeStop}
668 | style={innerStyle}
669 | minWidth={this.props.minWidth}
670 | minHeight={this.props.minHeight}
671 | maxWidth={this.state.resizing ? this.state.maxWidth : this.props.maxWidth}
672 | maxHeight={this.state.resizing ? this.state.maxHeight : this.props.maxHeight}
673 | grid={resizeGrid}
674 | handleWrapperClass={resizeHandleWrapperClass}
675 | handleWrapperStyle={resizeHandleWrapperStyle}
676 | lockAspectRatio={this.props.lockAspectRatio}
677 | lockAspectRatioExtraWidth={this.props.lockAspectRatioExtraWidth}
678 | lockAspectRatioExtraHeight={this.props.lockAspectRatioExtraHeight}
679 | handleStyles={resizeHandleStyles}
680 | handleClasses={resizeHandleClasses}
681 | handleComponent={resizeHandleComponent}
682 | scale={this.props.scale}
683 | >
684 | {children}
685 |
686 |
687 | );
688 | }
689 | }
690 |
--------------------------------------------------------------------------------
/stories/bare/bare.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => 001;
6 |
--------------------------------------------------------------------------------
/stories/basic/controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number;
9 | height: number;
10 | };
11 |
12 | export default class Example extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 200,
17 | height: 200,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 | {
36 | this.setState({ x: d.x, y: d.y });
37 | }}
38 | onResizeStop={(e, direction, ref, delta, position) => {
39 | this.setState({
40 | width: ref.offsetWidth,
41 | height: ref.offsetHeight,
42 | ...position,
43 | });
44 | }}
45 | >
46 | 001
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/stories/basic/multi-controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | type State = {
6 | rnds: {
7 | x: number;
8 | y: number;
9 | width: number;
10 | height: number;
11 | }[];
12 | };
13 |
14 | export default class Example extends React.Component<{}, State> {
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | rnds: [0, 1, 2].map((i) => ({
19 | width: 200,
20 | height: 200,
21 | x: i * 100,
22 | y: i * 100,
23 | })),
24 | };
25 | }
26 |
27 | render() {
28 | return (
29 | <>
30 | {[0, 1, 2].map((i) => (
31 | {
43 | const rnds = [...this.state.rnds];
44 | rnds[i] = { ...rnds[i], x: d.x, y: d.y };
45 | this.setState({ rnds });
46 | }}
47 | onResize={(e, direction, ref, delta, position) => {
48 | const rnds = [...this.state.rnds];
49 | rnds[i] = {
50 | ...rnds[i],
51 | width: ref.offsetWidth,
52 | height: ref.offsetHeight,
53 | ...position,
54 | };
55 | this.setState({
56 | rnds,
57 | });
58 | }}
59 | >
60 | 00{i}
61 |
62 | ))}
63 | >
64 | );
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/stories/basic/multi-uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 | <>
7 | {[0, 1, 2].map((i) => (
8 |
21 | 00{i}
22 |
23 | ))}
24 | >
25 | );
26 |
--------------------------------------------------------------------------------
/stories/basic/uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
15 | 001
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/stories/bounds-and-offset.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import React from 'react';
4 | import Rnd from '../src';
5 |
6 | const style = {
7 | display: 'flex',
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | border: 'solid 1px #ddd',
11 | background: '#f0f0f0',
12 | };
13 |
14 | const handleClick = () => {
15 | console.log('click work.')
16 | }
17 |
18 | const handleDragStart = (e, data) => {
19 | console.log(data.x, data.y)
20 | }
21 |
22 | const handleDrag = (e, data) => {
23 | console.log(data.x, data.y)
24 | }
25 |
26 | const handleDragStop = (e, data) => {
27 | console.log(data.x, data.y)
28 | }
29 |
30 | const handleResizeStart = (_, __, ele) => {
31 | console.log(ele.clientWidth, ele.clientHeight)
32 | }
33 |
34 | const handleResize = (_, __, ele, ___, pos) => {
35 | console.log(ele.clientWidth, ele.clientHeight, pos.x, pos.y)
36 | }
37 |
38 | const handleResizeStop = (_, __, ele, ___, pos) => {
39 | console.log(ele.clientWidth, ele.clientHeight, pos.x, pos.y)
40 | }
41 |
42 | export default () => (
43 |
44 |
45 |
46 |
58 | 001
59 |
60 |
61 |
62 |
63 | );
64 |
--------------------------------------------------------------------------------
/stories/bounds/body-controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number;
9 | height: number;
10 | };
11 |
12 | export default class Example extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 200,
17 | height: 200,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 |
26 | {
38 | this.setState({ x: d.x, y: d.y });
39 | }}
40 | onResize={(e, direction, ref, delta, position) => {
41 | this.setState({
42 | width: ref.offsetWidth,
43 | height: ref.offsetHeight,
44 | ...position,
45 | });
46 | }}
47 | >
48 | 001
49 |
50 |
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/stories/bounds/element-controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number;
9 | height: number;
10 | };
11 |
12 | const Example: React.FC = () => {
13 | const [state, setState] = React.useState({
14 | width: 200,
15 | height: 200,
16 | x: 0,
17 | y: 0,
18 | });
19 | const [boundaryElm, setBoundaryElm] = React.useState();
20 | return (
21 | setBoundaryElm(elm!)}>
22 | {
34 | setState({ ...state, x: d.x, y: d.y });
35 | }}
36 | onResize={(e, direction, ref, delta, position) => {
37 | setState({
38 | width: ref.offsetWidth,
39 | height: ref.offsetHeight,
40 | ...position,
41 | });
42 | }}
43 | >
44 | 001
45 |
46 |
47 | );
48 | };
49 |
50 | export default Example;
51 |
--------------------------------------------------------------------------------
/stories/bounds/element-uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary } from "../styles";
4 |
5 | export default () => {
6 | const [boundaryElm, setBoundaryElm] = React.useState();
7 | return (
8 | setBoundaryElm(ref!)}>
9 | {boundaryElm && (
10 |
20 | 001
21 |
22 | )}
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/stories/bounds/parent-controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number;
9 | height: number;
10 | };
11 |
12 | export default class Example extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 200,
17 | height: 200,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 |
26 | {
38 | this.setState({ x: d.x, y: d.y });
39 | }}
40 | onResize={(e, direction, ref, delta, position) => {
41 | this.setState({
42 | width: ref.offsetWidth,
43 | height: ref.offsetHeight,
44 | ...position,
45 | });
46 | }}
47 | >
48 | 001
49 |
50 |
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/stories/bounds/parent-uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary } from "../styles";
4 |
5 | export default () => (
6 |
7 |
17 | 001
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/stories/bounds/selector-controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary, selectorBoundary } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number;
9 | height: number;
10 | };
11 |
12 | export default class Example extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 200,
17 | height: 200,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 |
26 |
27 | {
39 | this.setState({ x: d.x, y: d.y });
40 | }}
41 | onResize={(e, direction, ref, delta, position) => {
42 | this.setState({
43 | width: ref.offsetWidth,
44 | height: ref.offsetHeight,
45 | ...position,
46 | });
47 | }}
48 | >
49 | 001
50 |
51 |
52 |
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/stories/bounds/selector-uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary, selectorBoundary } from "../styles";
4 |
5 | export default () => (
6 |
7 |
8 |
18 | 001
19 |
20 |
21 |
22 | );
23 |
--------------------------------------------------------------------------------
/stories/bounds/window-controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number;
9 | height: number;
10 | };
11 |
12 | export default class Example extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 200,
17 | height: 200,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 |
26 |
27 | {
41 | this.setState({ x: d.x, y: d.y });
42 | }}
43 | onResize={(e, direction, ref, delta, position) => {
44 | this.setState({
45 | width: ref.offsetWidth,
46 | height: ref.offsetHeight,
47 | ...position,
48 | });
49 | }}
50 | >
51 | 001
52 |
53 |
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/stories/callback/callbacks.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { action } from "@storybook/addon-actions";
3 | import { Rnd } from "../../src";
4 | import { style } from "../styles";
5 |
6 | export default () => (
7 |
25 | 001
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/stories/cancel/cancel.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
16 |
20 | 001
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/stories/customization/resizeHandleComponent.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | const SouthEastArrow = () => (
6 |
9 | );
10 |
11 | const CustomHandle = (props) => (
12 |
24 | );
25 | const BottomRightHandle = () => (
26 |
27 |
28 |
29 | );
30 |
31 | export default () => (
32 | }}
41 | >
42 | 001
43 |
44 | );
45 |
--------------------------------------------------------------------------------
/stories/dragAxis/dragAxisNone.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number | string;
9 | height: number | string;
10 | };
11 |
12 | export default class dragAxisX extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 100,
17 | height: 100,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 | {
30 | console.log("onDrag", d);
31 | }}
32 | onDragStop={(e, d) => {
33 | console.log(d);
34 | this.setState({ ...this.state, ...d });
35 | }}
36 | onResizeStop={(e, direction, ref, delta, position) => {
37 | this.setState({
38 | ...this.state,
39 | width: ref.style.width,
40 | height: ref.style.height,
41 | ...position,
42 | });
43 | }}
44 | dragAxis="none"
45 | >
46 | 001
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/stories/dragAxis/dragAxisX.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number | string;
9 | height: number | string;
10 | };
11 |
12 | export default class dragAxisY extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 100,
17 | height: 100,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 | {
30 | console.log("onDrag", d);
31 | }}
32 | onDragStop={(e, d) => {
33 | console.log(d);
34 | this.setState({ ...this.state, ...d });
35 | }}
36 | onResizeStop={(e, direction, ref, delta, position) => {
37 | this.setState({
38 | ...this.state,
39 | width: ref.style.width,
40 | height: ref.style.height,
41 | ...position,
42 | });
43 | }}
44 | dragAxis="x"
45 | >
46 | 001
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/stories/dragAxis/dragAxisY.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number | string;
9 | height: number | string;
10 | };
11 |
12 | export default class dragAxisX extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 100,
17 | height: 100,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 | {
30 | console.log("onDrag", d);
31 | }}
32 | onDragStop={(e, d) => {
33 | console.log(d);
34 | this.setState({ ...this.state, ...d });
35 | }}
36 | onResizeStop={(e, direction, ref, delta, position) => {
37 | this.setState({
38 | ...this.state,
39 | width: ref.style.width,
40 | height: ref.style.height,
41 | ...position,
42 | });
43 | }}
44 | dragAxis="y"
45 | >
46 | 001
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/stories/grid/both.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
17 | 001
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/stories/grid/drag.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
16 | 001
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/stories/grid/resize.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
16 | 001
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/stories/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { storiesOf } from "@storybook/react";
3 | import "./styles.css";
4 |
5 | import Bare from "./bare/bare";
6 |
7 | import BasicUncontrolled from "./basic/uncontrolled";
8 | import BasicControlled from "./basic/controlled";
9 |
10 | import MinUncontrolled from "./min/uncontrolled";
11 |
12 | import ScaleParentUnControlled from "./scale/parent-uncontrolled";
13 | import ScaleWindowUnControlled from "./scale/window-uncontrolled";
14 | import ScaleBodyX05UnControlled from "./scale/body-uncontrolled-x0-5";
15 | import ScaleBodyX15UnControlled from "./scale/body-uncontrolled-x1-5";
16 | import ScaleSelectorUnControlled from "./scale/selector-uncontrolled";
17 | import ScaleSelectorControlled from "./scale/selector-controlled";
18 |
19 | import BasicMultiUncontrolled from "./basic/multi-uncontrolled";
20 | import BasicMultiControlled from "./basic/multi-controlled";
21 |
22 | import BoundsParentUncontrolled from "./bounds/parent-uncontrolled";
23 | import BoundsParentControlled from "./bounds/parent-controlled";
24 | import BoundsSelectorUncontrolled from "./bounds/selector-uncontrolled";
25 | import BoundsSelectorControlled from "./bounds/selector-controlled";
26 | import BoundsWindowControlled from "./bounds/window-controlled";
27 | import BoundsBodyControlled from "./bounds/body-controlled";
28 | import BoundsElementControlled from "./bounds/element-controlled";
29 | import BoundsElementUncontrolled from "./bounds/element-uncontrolled";
30 |
31 | import SizePercentUncontrolled from "./size/size-percent-uncontrolled";
32 | import SizePercentControlled from "./size/size-percent-controlled";
33 |
34 | import Callbacks from "./callback/callbacks";
35 |
36 | import Cancel from "./cancel/cancel";
37 |
38 | import ResizeHandleComponent from "./customization/resizeHandleComponent";
39 |
40 | import DragAxisX from "./dragAxis/dragAxisX";
41 | import DragAxisY from "./dragAxis/dragAxisY";
42 | import DragAxisNone from "./dragAxis/dragAxisNone";
43 |
44 | import GridResize from "./grid/resize";
45 | import GridDrag from "./grid/drag";
46 | import GridBoth from "./grid/both";
47 |
48 | import SandboxBodySizeToMaxWidth from "./sandbox/bodysize-to-maxwidth";
49 | import SandboxLockAspectRatioWithBounds from "./sandbox/lock-aspect-ratio-with-bounds";
50 |
51 | import LockAspectRatioBasic from "./lock-aspect-ratio/basic";
52 | import Issue622 from "./sandbox/issue-#622";
53 |
54 | storiesOf("bare", module).add("bare", () => );
55 |
56 | storiesOf("basic", module)
57 | .add("uncontrolled", () => )
58 | .add("controlled", () => )
59 | .add("multi uncontrolled", () => )
60 | .add("multi controlled", () => );
61 |
62 | storiesOf("bounds", module)
63 | .add("parent uncontrolled", () => )
64 | .add("parent controlled", () => )
65 | .add("selector uncontrolled", () => )
66 | .add("selector controlled", () => )
67 | .add("window controlled", () => )
68 | .add("body controlled", () => )
69 | .add("element controlled", () => )
70 | .add("element uncontrolled", () => )
71 |
72 | storiesOf("scale", module)
73 | .add("with parent boundary", () => )
74 | .add("x0.5 with body boundary", () => )
75 | .add("x1.5 with body boundary", () => )
76 | .add("with window boundary", () => )
77 | .add("with selector boundary uncontrolled", () => )
78 | .add("with selector boundary controlled", () => )
79 | .add("with selector boundary", () => );
80 |
81 | storiesOf("size", module)
82 | .add("percent uncontrolled", () => )
83 | .add("percent controlled", () => );
84 |
85 | storiesOf("callbacks", module).add("callback", () => );
86 |
87 | storiesOf("cancel", module).add("cancel", () => );
88 |
89 | storiesOf("customization", module).add("resizeHandleComponent", () => );
90 |
91 | storiesOf("dragAxis", module)
92 | .add("dragAxisX", () => )
93 | .add("dragAxisY", () => )
94 | .add("dragAxisNone", () => );
95 |
96 | storiesOf("grid", module)
97 | .add("resize", () => )
98 | .add("drag", () => )
99 | .add("both", () => );
100 |
101 | storiesOf("sandbox", module)
102 | .add("body size apply to maxwidth", () => )
103 | .add("lock aspect ratio with bounds", () => )
104 | .add("issue622", () => );
105 |
106 | storiesOf("ratio", module).add("lock aspect ratio", () => );
107 |
108 | storiesOf("min", module).add("min uncontrolled", () => );
109 |
--------------------------------------------------------------------------------
/stories/lock-aspect-ratio/basic.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
16 | 001
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/stories/max-size-with-percent.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import React from 'react';
4 | import Rnd from '../src';
5 |
6 | const style = {
7 | display: 'flex',
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | border: 'solid 1px #ddd',
11 | background: '#f0f0f0',
12 | };
13 |
14 | export default () => (
15 |
22 |
34 | 001
35 |
36 |
37 | );
38 |
--------------------------------------------------------------------------------
/stories/min/uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
17 | Rnd
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/stories/multiple.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import React from 'react';
4 | import Rnd from '../src';
5 |
6 | const style = {
7 | display: 'flex',
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | border: 'solid 1px #ddd',
11 | background: '#f0f0f0',
12 | };
13 |
14 | export default () => (
15 |
23 | {[...Array(3).keys()].map((_, i) => {
24 | return
34 | 00{i}
35 |
36 | })}
37 |
38 | );
39 |
--------------------------------------------------------------------------------
/stories/sandbox.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import React from 'react';
4 | import Rnd from '../src';
5 |
6 | const style = {
7 | display: 'flex',
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | border: 'solid 1px #ddd',
11 | background: '#f0f0f0',
12 | };
13 |
14 | let i = 100;
15 |
16 | export default class SandBox extends React.Component {
17 |
18 | constructor(p) {
19 | super(p);
20 | this.state = {
21 | x: 100,
22 | }
23 | }
24 |
25 | componentDidMount() {
26 | setInterval(() => {
27 | i += 100;
28 | this.setState({
29 | x: i,
30 | })
31 | }, 1000);
32 | }
33 |
34 | render() {
35 | return
43 | 001
44 |
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/stories/sandbox/bodysize-to-maxwidth.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default class App extends React.Component {
6 | state = {
7 | maxWidth: Number.MAX_SAFE_INTEGER,
8 | };
9 | constructor(props) {
10 | super(props);
11 | }
12 | componentDidMount() {
13 | window.addEventListener("resize", () => {
14 | this.setState({ maxWidth: document.body.clientWidth - 100 });
15 | });
16 | }
17 |
18 | render() {
19 | return (
20 |
30 | Rnd
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/stories/sandbox/issue-#622.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 |
4 | const style = {
5 | display: "flex",
6 | alignItems: "center",
7 | justifyContent: "center",
8 | borderLeft: "solid 5px #ddd",
9 | borderRight: "solid 5px #ddd",
10 | borderTop: "solid 1px #ddd",
11 | borderBottom: "solid 1px #ddd",
12 | background: "#f0f0f0",
13 | height: "100%",
14 | };
15 |
16 | export default class App extends React.Component {
17 | state = {
18 | width: 200,
19 | height: 52,
20 | x: 100,
21 | y: 0,
22 | };
23 |
24 | render() {
25 | return (
26 |
34 | {
50 | this.setState({ x: d.x, y: d.y });
51 | }}
52 | onResizeStop={(e, direction, ref, delta, position) => {
53 | this.setState({
54 | width: ref.style.width,
55 | height: ref.style.height,
56 | ...position,
57 | });
58 | }}
59 | >
60 | Rnd
61 |
62 |
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/stories/sandbox/lock-aspect-ratio-with-bounds.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary } from "../styles";
4 |
5 | export default () => (
6 |
7 |
18 | 001
19 |
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/stories/scale/body-uncontrolled-x0-5.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
7 |
8 |
19 | 001
20 |
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/stories/scale/body-uncontrolled-x1-5.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
7 |
8 |
19 | 001
20 |
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/stories/scale/parent-uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary } from "../styles";
4 |
5 | export default () => (
6 |
7 |
8 |
19 | 001
20 |
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/stories/scale/selector-controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary, selectorBoundary } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number;
9 | height: number;
10 | };
11 |
12 | export default class Example extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: 200,
17 | height: 200,
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 |
26 |
27 | {
40 | this.setState({ x: d.x, y: d.y });
41 | }}
42 | onResize={(e, direction, ref, delta, position) => {
43 | this.setState({
44 | width: ref.offsetWidth,
45 | height: ref.offsetHeight,
46 | ...position,
47 | });
48 | }}
49 | >
50 | 001
51 |
52 |
53 |
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/stories/scale/selector-uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style, parentBoundary, selectorBoundary } from "../styles";
4 |
5 | export default () => (
6 |
7 |
8 |
19 | 001
20 |
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/stories/scale/window-uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
7 |
8 |
19 | 001
20 |
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/stories/size-and-position.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import React from 'react';
4 | import Rnd from '../src';
5 |
6 | const style = {
7 | display: 'flex',
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | border: 'solid 1px #ddd',
11 | background: '#f0f0f0',
12 | };
13 |
14 | export default class Example extends React.Component {
15 |
16 | constructor() {
17 | super();
18 | this.state = {
19 | width: 100,
20 | height: 120,
21 | x: 0,
22 | y: 0,
23 | };
24 | }
25 |
26 | render() {
27 | return (
28 | { this.setState({ x: d.x, y: d.y }) }}
40 | onResize={(e, direction, ref, delta, position) => {
41 | this.setState({
42 | width: ref.offsetWidth,
43 | height: ref.offsetHeight,
44 | ...position,
45 | });
46 | }}
47 | >
48 | 001
49 |
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/stories/size-percentage.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import React from 'react';
4 | import Rnd from '../src';
5 |
6 | const style = {
7 | display: 'flex',
8 | alignItems: 'center',
9 | justifyContent: 'center',
10 | border: 'solid 1px #ddd',
11 | background: '#f0f0f0',
12 | };
13 |
14 | export default () => (
15 |
23 |
33 | 001
34 |
35 |
36 | );
37 |
--------------------------------------------------------------------------------
/stories/size/size-percent-controlled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | type State = {
6 | x: number;
7 | y: number;
8 | width: number | string;
9 | height: number | string;
10 | };
11 |
12 | export default class Example extends React.Component<{}, State> {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | width: "30%",
17 | height: "30%",
18 | x: 0,
19 | y: 0,
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 | {
36 | this.setState({ x: d.x, y: d.y });
37 | }}
38 | onResize={(e, direction, ref, delta, position) => {
39 | this.setState({
40 | width: ref.style.width,
41 | height: ref.style.height,
42 | ...position,
43 | });
44 | }}
45 | >
46 | 001
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/stories/size/size-percent-uncontrolled.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Rnd } from "../../src";
3 | import { style } from "../styles";
4 |
5 | export default () => (
6 |
15 | 001
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/stories/styles.css:
--------------------------------------------------------------------------------
1 | html, body, #root, #root > div {
2 | height: 100%;
3 | margin: 0;
4 | padding: 10px;
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | background: #fffefe;
10 | border: solid 1px #f0f0f0;
11 | }
12 |
13 | html {
14 | overflow: hidden;
15 | }
16 |
17 | #root > div {
18 | padding: 0px;
19 | }
--------------------------------------------------------------------------------
/stories/styles.ts:
--------------------------------------------------------------------------------
1 | export const style = {
2 | display: "flex",
3 | alignItems: "center",
4 | justifyContent: "center",
5 | border: "solid 1px #ddd",
6 | background: "#f0f0f0",
7 | };
8 |
9 | export const parentBoundary = {
10 | background: "#eee",
11 | width: "100%",
12 | height: "100%",
13 | };
14 |
15 | export const selectorBoundary = {
16 | background: "#d1d8ff",
17 | padding: "20px",
18 | width: "100%",
19 | height: "100%",
20 | };
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "ES5",
5 | // "module": "ES2015",
6 | "lib": ["es5", "es2015", "dom"],
7 | // "allowJs": true, /* Allow javascript files to be compiled. */
8 | // "checkJs": true, /* Report errors in .js files. */
9 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
10 | "declaration": true /* Generates corresponding '.d.ts' file. */,
11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
12 | // "sourceMap": true, /* Generates corresponding '.map' file. */
13 | // "outFile": "./", /* Concatenate and emit output to single file. */
14 | "outDir": "./lib", /* Redirect output structure to the directory. */
15 | // "rootDir": "./src/components", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
16 | // "composite": true, /* Enable project compilation */
17 | // "removeComments": true, /* Do not emit comments to output. */
18 | // "noEmit": true, /* Do not emit outputs. */
19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
22 |
23 | /* Strict Type-Checking Options */
24 | "strict": true /* Enable all strict type-checking options. */,
25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
26 | // "strictNullChecks": true, /* Enable strict null checks. */
27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
31 |
32 | /* Additional Checks */
33 | // "noUnusedLocals": true, /* Report errors on unused locals. */
34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
37 |
38 | /* Module Resolution Options */
39 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
43 | // "typeRoots": [], /* List of folders to include type definitions from. */
44 | // "types": [], /* Type declaration files to be included in compilation. */
45 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
48 |
49 | /* Source Map Options */
50 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
51 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
54 |
55 | /* Experimental Options */
56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
58 | },
59 | "exclude": ["stories", "lib", "node_modules"],
60 | "include": [
61 | "src"
62 | ]
63 | }
64 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "extends": ["tslint-eslint-rules"],
4 | "linterOptions": { "exclude": ["src/index.test.tsx"] },
5 | "rules": {
6 | "class-name": true,
7 | "comment-format": [true, "check-space"],
8 | "indent": [true, "spaces"],
9 | "import-blacklist": [true, "lodash", "date-fns", "d3"],
10 | "import-spacing": true,
11 | "object-curly-spacing": [true, "always"],
12 | "no-duplicate-variable": true,
13 | "no-eval": true,
14 | "no-debugger": true,
15 | "no-console": [true, "log"],
16 | "no-internal-module": true,
17 | "no-trailing-whitespace": [true, "ignore-comments"],
18 | "no-unsafe-finally": true,
19 | "no-var-keyword": true,
20 | "no-unused-variable": [true],
21 | "no-reference": true,
22 | "one-line": [true, "check-whitespace"],
23 | "quotemark": [true, "double"],
24 | "semicolon": [false, "always"],
25 | "triple-equals": [true, "allow-null-check"],
26 | "typedef-whitespace": [
27 | true,
28 | {
29 | "call-signature": "nospace",
30 | "index-signature": "nospace",
31 | "parameter": "nospace",
32 | "property-declaration": "nospace",
33 | "variable-declaration": "nospace"
34 | }
35 | ],
36 | "variable-name": [true, "ban-keywords"],
37 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"],
38 | "prefer-const": true
39 | }
40 | }
41 |
--------------------------------------------------------------------------------