├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .vscode └── settings.json ├── README.md ├── demo.gif ├── package.json ├── packages └── react-drag-sizing │ ├── .gitignore │ ├── .npmrc │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── DragHandler.tsx │ ├── DragSizing.tsx │ ├── index.ts │ ├── module.d.ts │ ├── types.ts │ └── util.ts │ └── tsconfig.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.tsx ├── Home.module.css ├── Home.tsx ├── __tests__ │ └── react-drag-sizing │ │ └── basic.tsx ├── index.css ├── index.tsx ├── logo.svg ├── react-app-env.d.ts └── serviceWorker.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'react-app', 5 | // 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react 6 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 7 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 8 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 12 | sourceType: 'module', // Allows for the use of imports 13 | ecmaFeatures: { 14 | jsx: true, // Allows for the parsing of JSX 15 | }, 16 | }, 17 | rules: { 18 | 'prefer-const': 'error', 19 | // 'prefer-const': 'off', 20 | 'react/display-name': 'off', 21 | 'react/prop-types': 'off', 22 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | '@typescript-eslint/prefer-interface': 'off', 26 | }, 27 | settings: { 28 | react: { 29 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 30 | }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | engine-strict = true 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'es5', 4 | singleQuote: true, 5 | // printWidth: 120, 6 | tabWidth: 2, 7 | }; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "node_modules": true 4 | }, 5 | "files.associations": { 6 | ".npmrc": "dotenv" 7 | }, 8 | // "editor.formatOnSave": true, 9 | "editor.codeActionsOnSave": { 10 | "source.organizeImports": true, 11 | "source.fixAll.eslint": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-drag-sizing 2 | 3 | 4 | 5 | 6 | 7 | - [x] "Drag to resize" (sizing) as React Component 8 | - [x] Rewritten with TS & React-hooks 9 | - [ ] Polyfill workaround with React < 16.8 10 | - [x] Support both mouse & touch 11 | - [x] Rollup bundle for both esm & umd 12 | - [x] Default handlerWidth=16, handlerOffset=-w/2, handlerZIndex=10 13 | - [x] Legacy branch: https://github.com/fritx/react-drag-sizing/tree/legacy 14 | - [x] Live Demo: https://fritx.github.io/react-drag-sizing 15 | 16 | ```sh 17 | npm i -S react-drag-sizing 18 | ``` 19 | 20 | ```tsx 21 | import DragSizing from 'react-drag-sizing' 22 | 23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 |
36 |
37 | ``` 38 | 39 | #### Props 40 | 41 | ```tsx 42 | export type MEvent = MouseEvent | TouchEvent; 43 | export type RdsMEvent = 44 | | MouseEvent 45 | | (TouchEvent & { 46 | clientX: number; 47 | clientY: number; 48 | }); 49 | 50 | export interface DragSizingProps { 51 | border: 'top' | 'bottom' | 'left' | 'right'; 52 | onStart?: (e: RdsMEvent) => void; 53 | onEnd?: (e: RdsMEvent) => void; 54 | onUpdate?: (e: RdsMEvent) => void; 55 | id?: string; 56 | className?: string; 57 | style?: React.CSSProperties; 58 | handlerClassName?: string; 59 | handlerStyle?: React.CSSProperties; 60 | handlerWidth?: number; 61 | handlerOffset?: number; 62 | handlerZIndex?: number; 63 | } 64 | ``` 65 | 66 | **hooking event listeners** 67 | 68 | ```tsx 69 | handleEditorSizingStart = () => { 70 | // const nearBottom = scrollTop > ... 71 | setShouldStickToBottom(nearBottom); 72 | }; 73 | handleEditorSizingEnd = () => { 74 | if (shouldStickToBottom) { 75 | scrollToBottom(); 76 | } 77 | }; 78 | 79 | 84 | 85 | ; 86 | ``` 87 | 88 | **for umd / \ 92 | 93 | 94 | 95 | ``` 96 | 97 | ```js 98 | // myapp.js 99 | let React = window.React; 100 | let ReactDOM = window.ReactDOM; 101 | let { DragSizing } = window.ReactDragSizing; 102 | 103 | ReactDOM.render( 104 |
105 | Left bar 106 | 111 |
Main content
112 |
113 |
, 114 | mountNode 115 | ); 116 | ``` 117 | 118 | **for react < 16.8 we need hooks polyfill workaround** 119 | 120 | ```tsx 121 | // todo 122 | ``` 123 | 124 | --- 125 | 126 | This project was bootstrapped with [create-react-library](https://github.com/transitive-bullshit/create-react-library) & [react-ts-demo](https://github.com/fritx/react-ts-demo). 127 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fritx/react-drag-sizing/152732df9f9c945fbad017250cd054965f7d2da2/demo.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-drag-sizing-repo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "engines": { 6 | "pnpm": "please-use-yarn", 7 | "npm": "please-use-yarn", 8 | "yarn": "*" 9 | }, 10 | "scripts": { 11 | "postinstall": "cd ./packages/react-drag-sizing && yarn && npm run build", 12 | "lib:build": "cd ./packages/react-drag-sizing && npm run build", 13 | "lib:watch": "cd ./packages/react-drag-sizing && npm run watch", 14 | "lib:publish": "cd ./packages/react-drag-sizing && npm publish", 15 | "deploy": "npm run build && gh-pages -d ./build", 16 | "start": "react-scripts start", 17 | "build": "npm run lib:build && PUBLIC_URL=. react-scripts build", 18 | "coverage": "CI=true npm test -- --coverage", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "dependencies": { 23 | "react": "^16.8.6", 24 | "react-dom": "^16.8.6", 25 | "react-drag-sizing": "file:packages/react-drag-sizing", 26 | "react-editor": "^2.0.0-alpha.5", 27 | "react-router-dom": "^5.1.2" 28 | }, 29 | "devDependencies": { 30 | "@types/jest": "^25.1.3", 31 | "@types/node": "^13.7.4", 32 | "@types/react": "^16.9.22", 33 | "@types/react-dom": "^16.9.5", 34 | "@types/react-router-dom": "^5.1.3", 35 | "eslint-config-prettier": "^6.10.0", 36 | "eslint-plugin-prettier": "^3.1.2", 37 | "eslint-plugin-react": "^7.18.3", 38 | "gh-pages": "^2.2.0", 39 | "prettier": "^1.19.1", 40 | "react-scripts": "^3.4.0", 41 | "typescript": "^3.8.2" 42 | }, 43 | "jest": { 44 | "collectCoverageFrom": [ 45 | "/packages/react-drag-sizing/dist/*.{js,jsx,ts,tsx}", 46 | "!**/node_modules/**" 47 | ] 48 | }, 49 | "eslintConfig": { 50 | "extends": "react-app" 51 | }, 52 | "browserslist": { 53 | "production": [ 54 | ">0.2%", 55 | "not dead", 56 | "not op_mini all" 57 | ], 58 | "development": [ 59 | "last 1 chrome version", 60 | "last 1 firefox version", 61 | "last 1 safari version" 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-drag-sizing", 3 | "version": "0.2.1", 4 | "description": "", 5 | "author": "fritx", 6 | "license": "MIT", 7 | "repository": "fritx/react-drag-sizing", 8 | "main": "dist/index.js", 9 | "module": "dist/index.es.js", 10 | "jsnext:main": "dist/index.es.js", 11 | "scripts": { 12 | "prepublishOnly": "git stash -u && cp ../../README.md ./ && npm run build", 13 | "postpublish": "rm README.md && git stash apply", 14 | "build": "rollup -c", 15 | "watch": "rollup -c -w" 16 | }, 17 | "peerDependencies": { 18 | "react": ">= 16.8", 19 | "react-dom": ">= 16.8" 20 | }, 21 | "devDependencies": { 22 | "@rollup/plugin-commonjs": "^11.0.2", 23 | "@rollup/plugin-node-resolve": "^7.1.1", 24 | "@rollup/plugin-url": "^4.0.2", 25 | "rollup": "^1.31.1", 26 | "rollup-plugin-peer-deps-external": "^2.2.2", 27 | "rollup-plugin-postcss": "^2.1.1", 28 | "rollup-plugin-typescript2": "^0.26.0" 29 | }, 30 | "dependencies": {}, 31 | "files": [ 32 | "dist" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import external from 'rollup-plugin-peer-deps-external'; 4 | // import postcss from 'rollup-plugin-postcss-modules' 5 | import postcss from 'rollup-plugin-postcss'; 6 | import typescript from 'rollup-plugin-typescript2'; 7 | import pkg from './package.json'; 8 | 9 | const outputOptions = { 10 | exports: 'named', 11 | sourcemap: true, 12 | }; 13 | 14 | export default { 15 | input: 'src/index.ts', 16 | output: [ 17 | { 18 | ...outputOptions, 19 | file: pkg.module, 20 | format: 'es', 21 | }, 22 | { 23 | ...outputOptions, 24 | file: pkg.main, 25 | format: 'umd', 26 | name: 'ReactDragSizing', 27 | globals: { 28 | react: 'React', 29 | }, 30 | }, 31 | ], 32 | plugins: [ 33 | external(), 34 | postcss({ 35 | modules: true, 36 | }), 37 | resolve(), 38 | typescript({ 39 | rollupCommonJSResolveHack: true, 40 | clean: true, 41 | }), 42 | commonjs(), 43 | ], 44 | }; 45 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/src/DragHandler.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useRef, useState } from 'react'; 2 | import { DragHandlerData, DragHandlerProps } from './types'; 3 | 4 | export const DragHandler: React.FC = props => { 5 | const { dir, onStart, onEnd, onUpdate, className, style, children } = props; 6 | 7 | const [isDragging, setIsDragging] = useState(false); 8 | const listenersRef = useRef(null); 9 | 10 | const handleMouseMove = useCallback( 11 | e => { 12 | onUpdate(e); 13 | }, 14 | [onUpdate] 15 | ); 16 | 17 | const cleanMouseListeners = useCallback(() => { 18 | const oldRef = listenersRef.current; 19 | if (oldRef) { 20 | window.removeEventListener('mousemove', oldRef.handleMouseMove); 21 | window.removeEventListener('touchmove', oldRef.handleMouseMove); 22 | window.removeEventListener('mouseup', oldRef.handleMouseUp); 23 | window.removeEventListener('touchend', oldRef.handleMouseUp); 24 | } 25 | }, []); 26 | 27 | const handleMouseUp = useCallback( 28 | e => { 29 | setIsDragging(false); 30 | cleanMouseListeners(); 31 | onEnd(e); 32 | }, 33 | [cleanMouseListeners, onEnd] 34 | ); 35 | 36 | const handleMouseDown = useCallback( 37 | e => { 38 | setIsDragging(true); 39 | cleanMouseListeners(); 40 | 41 | listenersRef.current = { 42 | handleMouseMove, 43 | handleMouseUp, 44 | }; 45 | window.addEventListener('mousemove', handleMouseMove); 46 | window.addEventListener('touchmove', handleMouseMove); 47 | window.addEventListener('mouseup', handleMouseUp); 48 | window.addEventListener('touchend', handleMouseUp); 49 | 50 | onStart(e); 51 | }, 52 | [cleanMouseListeners, handleMouseMove, handleMouseUp, onStart] 53 | ); 54 | 55 | useEffect(() => { 56 | return () => { 57 | cleanMouseListeners(); 58 | }; 59 | // eslint-disable-next-line react-hooks/exhaustive-deps 60 | }, []); 61 | 62 | return ( 63 |
72 | {isDragging && ( 73 | 79 | )} 80 | {children} 81 |
82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/src/DragSizing.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useRef, useState } from 'react'; 2 | import { DragHandler } from './DragHandler'; 3 | import { DragSizingData, DragSizingProps, MEvent } from './types'; 4 | import { 5 | getContainerInfo, 6 | getContainerMeta, 7 | getHandlerInfo, 8 | isNil, 9 | normalizeMEvent, 10 | } from './util'; 11 | 12 | export const DragSizing: React.FC = props => { 13 | const { 14 | border, 15 | onStart, 16 | onEnd, 17 | onUpdate, 18 | id, 19 | className, 20 | style, 21 | handlerClassName, 22 | handlerStyle: _handlerStyle, 23 | handlerWidth: _handlerWidth, 24 | handlerOffset: _handlerOffset, 25 | handlerZIndex: _handlerZIndex, 26 | children, 27 | } = props; 28 | 29 | const handlerWidth = isNil(_handlerWidth) ? 16 : (_handlerWidth as number); 30 | const handlerOffset = (isNil(_handlerOffset) 31 | ? -handlerWidth / 2 32 | : _handlerOffset) as number; 33 | const handlerZIndex = (isNil(_handlerZIndex) ? 10 : _handlerZIndex) as number; 34 | 35 | const [diffCoord, setDiffCoord] = useState(0); 36 | const [oldSize, setOldSize] = useState(null); 37 | const oldCoordRef = useRef(null); 38 | const boxRef = useRef(null); 39 | 40 | const containerMeta = getContainerMeta({ border }); 41 | 42 | const { style: containerStyle } = getContainerInfo({ 43 | style, 44 | containerMeta, 45 | diffCoord, 46 | oldSize, 47 | }); 48 | 49 | const { dir, style: handlerStyle } = getHandlerInfo({ 50 | border, 51 | handlerWidth, 52 | handlerOffset, 53 | handlerStyle: _handlerStyle, 54 | }); 55 | 56 | const handleStart = useCallback( 57 | (_e: MEvent) => { 58 | const e = normalizeMEvent(_e); 59 | 60 | const { wh, xy } = containerMeta; 61 | const el = boxRef.current; 62 | if (!el) return; 63 | 64 | const px = window.getComputedStyle(el)[wh] as string; 65 | 66 | setDiffCoord(0); 67 | setOldSize(parseInt(px, 10)); 68 | oldCoordRef.current = e[xy]; 69 | 70 | if (onStart) onStart(e); 71 | }, 72 | [containerMeta, onStart] 73 | ); 74 | 75 | const handleEnd = useCallback( 76 | (_e: MEvent) => { 77 | const e = normalizeMEvent(_e); 78 | if (onEnd) onEnd(e); 79 | }, 80 | [onEnd] 81 | ); 82 | 83 | const handleUpdate = useCallback( 84 | (_e: MEvent) => { 85 | const e = normalizeMEvent(_e); 86 | 87 | const { xy } = containerMeta; 88 | if (oldCoordRef.current === null) return; 89 | 90 | setDiffCoord(e[xy] - oldCoordRef.current); 91 | 92 | if (onUpdate) onUpdate(e); 93 | }, 94 | [containerMeta, onUpdate] 95 | ); 96 | 97 | return ( 98 |
107 | 119 | {children} 120 |
121 | ); 122 | }; 123 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DragSizing'; 2 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/src/module.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default CSS definition for typescript, 3 | * will be overridden with file-specific definitions by rollup 4 | */ 5 | declare module '*.css' { 6 | const content: { [className: string]: string }; 7 | export default content; 8 | } 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 11 | interface SvgrComponent 12 | extends React.StatelessComponent> {} 13 | 14 | declare module '*.svg' { 15 | const svgUrl: string; 16 | const svgComponent: SvgrComponent; 17 | export default svgUrl; 18 | export { svgComponent as ReactComponent }; 19 | } 20 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/src/types.ts: -------------------------------------------------------------------------------- 1 | export type MEvent = MouseEvent | TouchEvent; 2 | 3 | export type RdsMEvent = 4 | | MouseEvent 5 | | (TouchEvent & { 6 | clientX: number; 7 | clientY: number; 8 | }); 9 | 10 | export interface DragHandlerProps { 11 | dir: 'ew' | 'ns'; 12 | onStart: (e: MEvent) => void; 13 | onEnd: (e: MEvent) => void; 14 | onUpdate: (e: MEvent) => void; 15 | className?: string; 16 | style?: React.CSSProperties; 17 | children?: React.ReactNode | undefined; 18 | } 19 | 20 | export interface DragHandlerData { 21 | listenersRef: { 22 | handleMouseMove: (e: MEvent) => void; 23 | handleMouseUp: (e: MEvent) => void; 24 | } | null; 25 | } 26 | 27 | export interface DragSizingProps { 28 | border: 'top' | 'bottom' | 'left' | 'right'; 29 | onStart?: DragHandlerProps['onStart']; 30 | onEnd?: DragHandlerProps['onEnd']; 31 | onUpdate?: DragHandlerProps['onUpdate']; 32 | id?: string; 33 | className?: string; 34 | style?: React.CSSProperties; 35 | handlerClassName?: string; 36 | handlerStyle?: React.CSSProperties; 37 | handlerWidth?: number; 38 | handlerOffset?: number; 39 | handlerZIndex?: number; 40 | children?: React.ReactNode | undefined; 41 | } 42 | 43 | export interface DragSizingData { 44 | diffCoord: number; 45 | oldCorrd: number | null; 46 | oldSize: number | null; 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/src/util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DragHandlerProps, 3 | DragSizingData, 4 | DragSizingProps, 5 | MEvent, 6 | RdsMEvent, 7 | } from './types'; 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | export const isNil = (v: any) => v === null || v === undefined; 11 | 12 | export const normalizeMEvent = (e: MEvent): RdsMEvent => { 13 | if ((e as TouchEvent).touches && (e as TouchEvent).touches[0]) { 14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 15 | (e as any).clientX = Math.round((e as TouchEvent).touches[0].clientX); 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | (e as any).clientY = Math.round((e as TouchEvent).touches[0].clientY); 18 | } 19 | return e as RdsMEvent; 20 | }; 21 | 22 | export const getContainerMeta = ({ 23 | border, 24 | }: { 25 | border: DragSizingProps['border']; 26 | }) => { 27 | let wh: 'width' | 'height'; 28 | let xy: 'clientX' | 'clientY'; 29 | let sn: 1 | -1; 30 | 31 | if (/^(left|right)$/.test(border)) { 32 | wh = 'width'; 33 | xy = 'clientX'; 34 | sn = border === 'right' ? 1 : -1; 35 | } else { 36 | wh = 'height'; 37 | xy = 'clientY'; 38 | sn = border === 'bottom' ? 1 : -1; 39 | } 40 | return { wh, xy, sn }; 41 | }; 42 | 43 | export const getContainerInfo = ({ 44 | style, 45 | containerMeta, 46 | diffCoord, 47 | oldSize, 48 | }: { 49 | style: DragSizingProps['style']; 50 | containerMeta: ReturnType; 51 | diffCoord: DragSizingData['diffCoord']; 52 | oldSize: DragSizingData['oldSize']; 53 | }) => { 54 | const { wh, sn } = containerMeta; 55 | let retStyle: React.CSSProperties = {}; 56 | 57 | if (oldSize != null) { 58 | retStyle[wh] = oldSize + diffCoord * sn; 59 | } 60 | retStyle = { 61 | ...style, 62 | ...retStyle, 63 | }; 64 | return { style: retStyle }; 65 | }; 66 | 67 | export const getHandlerInfo = ({ 68 | border, 69 | handlerWidth, 70 | handlerOffset, 71 | handlerStyle, 72 | }: { 73 | border: DragSizingProps['border']; 74 | handlerWidth: DragSizingProps['handlerWidth']; 75 | handlerOffset: DragSizingProps['handlerOffset']; 76 | handlerStyle: DragSizingProps['handlerStyle']; 77 | }) => { 78 | let dir: DragHandlerProps['dir']; 79 | let style: React.CSSProperties = {}; 80 | 81 | if (/^(left|right)$/.test(border)) { 82 | dir = 'ew'; 83 | style.width = handlerWidth; 84 | style.top = 0; 85 | style.bottom = 0; 86 | } else { 87 | dir = 'ns'; 88 | style.height = handlerWidth; 89 | style.left = 0; 90 | style.right = 0; 91 | } 92 | style[border] = handlerOffset; 93 | 94 | style = { ...style, ...handlerStyle }; 95 | return { dir, style }; 96 | }; 97 | -------------------------------------------------------------------------------- /packages/react-drag-sizing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build", 4 | "moduleResolution": "node", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": [ 8 | "es6", 9 | "dom", 10 | "es2016", 11 | "es2017" 12 | ], 13 | "sourceMap": true, 14 | "declaration": true, 15 | "allowJs": false, 16 | "allowSyntheticDefaultImports": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "jsx": "react" 19 | }, 20 | "include": [ 21 | "src" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fritx/react-drag-sizing/152732df9f9c945fbad017250cd054965f7d2da2/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | // import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 2 | import React from 'react'; 3 | import { HashRouter as Router, Route, Switch } from 'react-router-dom'; 4 | import { Home } from './Home'; 5 | 6 | export const App: React.FC = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/Home.module.css: -------------------------------------------------------------------------------- 1 | .Home { 2 | padding: 0 0 60px; 3 | min-height: 100vh; 4 | background-color: #282c34; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | color: white; 9 | font-size: calc(10px + 2vmin); 10 | } 11 | 12 | .HomeLogo { 13 | animation: Home-logo-spin infinite 20s linear; 14 | height: 20vmin; 15 | /* pointer-events: none; */ 16 | } 17 | 18 | .HomeHeader { 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | text-align: center; 23 | } 24 | 25 | .HomeLink { 26 | color: #61dafb; 27 | z-index: 1; 28 | /* fix covered by rotating logo */ 29 | } 30 | 31 | .Editor { 32 | padding: 8px 16px; 33 | } 34 | 35 | .InputControl { 36 | box-sizing: border-box; 37 | width: 70px; 38 | height: 31px; 39 | border: none; 40 | vertical-align: bottom; 41 | font-size: 50%; 42 | padding: 0 12px; 43 | } 44 | 45 | .Editor img { 46 | height: 40px; 47 | vertical-align: bottom; 48 | } 49 | 50 | .EditorButton { 51 | padding: 6px 12px; 52 | font-size: 50%; 53 | } 54 | 55 | .EditorBlock3 { 56 | margin-top: 40px; 57 | width: 70vw; 58 | display: flex; 59 | flex-direction: column; 60 | } 61 | 62 | .EditorWrap3 { 63 | border: solid 1px #666; 64 | height: 160px; 65 | overflow: auto; 66 | } 67 | 68 | @keyframes Home-logo-spin { 69 | from { 70 | transform: rotate(0deg); 71 | } 72 | 73 | to { 74 | transform: rotate(360deg); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useRef, useState } from 'react'; 2 | import { DragSizing } from 'react-drag-sizing'; 3 | import { Editor, EditorRefAttrs } from 'react-editor'; 4 | import styles from './Home.module.css'; 5 | import logo from './logo.svg'; 6 | 7 | const processText = (s: string) => s; // noop 8 | const processHTML = (s: string) => s; // todo limit 9 | 10 | export const Home: React.FC = () => { 11 | const [value, setValue] = useState(''); 12 | const [imgHeight, setImgHeight] = useState('40px'); 13 | const ref = useRef(null); 14 | 15 | const setRandomValue = useCallback(() => { 16 | setValue(String(Math.random())); 17 | 18 | // wait for editor render 19 | setTimeout(() => { 20 | const editor = ref.current; 21 | if (editor) editor.focus(); 22 | }); 23 | }, []); 24 | 25 | const insertEmoji = useCallback(() => { 26 | const editor = ref.current; 27 | if (editor) editor.insertText('😁'); 28 | }, []); 29 | 30 | const insertHTML = useCallback(() => { 31 | const editor = ref.current; 32 | if (editor) 33 | editor.insertHTML( 34 | // ' @fritx ' 35 | '' 36 | ); 37 | }, []); 38 | 39 | const handleImgHeight = useCallback(e => { 40 | setImgHeight(e.target.value); 41 | }, []); 42 | 43 | useEffect(() => { 44 | document.title = 'react-drag-sizing'; 45 | }, []); 46 | 47 | return ( 48 |
49 | 50 | 51 |
52 | logo 53 | 59 | react-drag-sizing 60 | 61 |
62 | 63 |
64 |
65 | 68 | 71 | 74 | 80 |
81 |
82 |
86 | 91 |
92 | 102 |
103 |
104 |
105 |
106 |
107 | ); 108 | }; 109 | -------------------------------------------------------------------------------- /src/__tests__/react-drag-sizing/basic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { act } from 'react-dom/test-utils'; 4 | // import { DragSizing } from 'react-drag-sizing'; 5 | import { DragSizing } from '../../../packages/react-drag-sizing'; 6 | 7 | let container: HTMLDivElement; 8 | 9 | beforeEach(() => { 10 | container = document.createElement('div'); 11 | document.body.appendChild(container); 12 | }); 13 | 14 | afterEach(() => { 15 | document.body.removeChild(container as HTMLDivElement); 16 | }); 17 | 18 | it('border left', () => { 19 | act(() => { 20 | ReactDOM.render( 21 | 22 | content 23 | , 24 | container 25 | ); 26 | }); 27 | const sizing = container.querySelector('#sizing') as HTMLDivElement; 28 | expect(sizing.innerHTML).toBe( 29 | '
content' 30 | ); 31 | }); 32 | 33 | it('border top', () => { 34 | act(() => { 35 | ReactDOM.render( 36 | 37 | content 38 | , 39 | container 40 | ); 41 | }); 42 | const sizing = container.querySelector('#sizing') as HTMLDivElement; 43 | expect(sizing.innerHTML).toBe( 44 | '
content' 45 | ); 46 | }); 47 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | 15 | .atwho-item { 16 | color: #61dafb; 17 | } 18 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { App } from './App'; 4 | import './index.css'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl) 112 | .then(response => { 113 | // Ensure service worker exists, and that we really are getting a JS file. 114 | const contentType = response.headers.get('content-type'); 115 | if ( 116 | response.status === 404 || 117 | (contentType != null && contentType.indexOf('javascript') === -1) 118 | ) { 119 | // No service worker found. Probably a different app. Reload the page. 120 | navigator.serviceWorker.ready.then(registration => { 121 | registration.unregister().then(() => { 122 | window.location.reload(); 123 | }); 124 | }); 125 | } else { 126 | // Service worker found. Proceed as normal. 127 | registerValidSW(swUrl, config); 128 | } 129 | }) 130 | .catch(() => { 131 | console.log( 132 | 'No internet connection found. App is running in offline mode.' 133 | ); 134 | }); 135 | } 136 | 137 | export function unregister() { 138 | if ('serviceWorker' in navigator) { 139 | navigator.serviceWorker.ready.then(registration => { 140 | registration.unregister(); 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": [ 7 | "dom", 8 | "dom.iterable", 9 | "esnext" 10 | ], 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "preserve" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------