├── README.md ├── .prettierignore ├── lerna.json ├── www ├── public │ ├── favicon.ico │ ├── index.html │ └── bundle.html ├── src │ ├── tsconfig.json │ ├── react-app-env.d.ts │ └── index.tsx ├── tsconfig.json ├── package.json └── .kktrc.ts ├── .lintstagedrc ├── .husky └── pre-commit ├── .gitpod.yml ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── core ├── tsconfig.json ├── .kktrc.ts ├── package.json ├── src │ ├── style │ │ └── index.less │ └── index.tsx └── README.md ├── renovate.json ├── .gitignore ├── .prettierrc ├── tsconfig.json ├── LICENSE ├── test └── index.test.tsx ├── package.json └── README-zh.md /README.md: -------------------------------------------------------------------------------- 1 | ./core/README.md -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | coverage 3 | dist 4 | build 5 | cjs 6 | esm 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5.9.3", 3 | "packages": ["core", "www"] 4 | } 5 | -------------------------------------------------------------------------------- /www/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiwjs/react-split/HEAD/www/public/favicon.ico -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx,html,less,md,json}": [ 3 | "prettier --write" 4 | ] 5 | } -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | ports: 2 | - port: 3000 3 | onOpen: open-preview 4 | tasks: 5 | - init: npm install 6 | command: npm run build && npm run start -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: jaywcjlove 2 | buy_me_a_coffee: jaywcjlove 3 | custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"] 4 | -------------------------------------------------------------------------------- /core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "outDir": "./cjs", 6 | "baseUrl": ".", 7 | "noEmit": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /www/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "include": ["./"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "emitDeclarationOnly": true, 7 | "noEmit": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "emitDeclarationOnly": true, 7 | "noEmit": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "packageRules": [ 5 | { 6 | "matchPackagePatterns": ["*"], 7 | "rangeStrategy": "replace" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cjs 2 | esm 3 | coverage 4 | build 5 | dist 6 | node_modules 7 | npm-debug.log* 8 | package-lock.json 9 | dist.css 10 | 11 | .eslintcache 12 | .DS_Store 13 | .cache 14 | .rdoc-dist 15 | .vscode 16 | 17 | *.bak 18 | *.tem 19 | *.temp 20 | #.swp 21 | *.*~ 22 | ~*.* 23 | -------------------------------------------------------------------------------- /www/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare var VERSION: string; 4 | 5 | declare module '*.md' { 6 | import { CodeBlockData } from 'markdown-react-code-preview-loader'; 7 | const src: CodeBlockData; 8 | export default src; 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | }, 10 | { 11 | "files": ".lintstagedrc", 12 | "options": { "parser": "json" } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /core/.kktrc.ts: -------------------------------------------------------------------------------- 1 | import { LoaderConfOptions, WebpackConfiguration } from 'kkt'; 2 | import lessModules from '@kkt/less-modules'; 3 | 4 | export default (conf: WebpackConfiguration, env: 'production' | 'development', options: LoaderConfOptions) => { 5 | conf = lessModules(conf, env, options); 6 | if (options.bundle) { 7 | conf.output!.library = '@uiw/react-split'; 8 | conf.externals = { 9 | react: { 10 | root: 'React', 11 | commonjs2: 'react', 12 | commonjs: 'react', 13 | amd: 'react', 14 | }, 15 | }; 16 | } 17 | 18 | return conf; 19 | }; 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "declaration": true, 16 | "baseUrl": ".", 17 | "jsx": "react-jsx", 18 | "noFallthroughCasesInSwitch": true, 19 | "noEmit": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /www/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React Split 9 | 10 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /www/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import MarkdownPreviewExample from '@uiw/react-markdown-preview-example'; 3 | import data from '@uiw/react-split/README.md'; 4 | 5 | const Github = MarkdownPreviewExample.Github; 6 | 7 | const container = document.getElementById('root'); 8 | const root = createRoot(container!); 9 | root.render( 10 | 18 | 19 | , 20 | ); 21 | -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uiw/react-split", 3 | "version": "5.9.3", 4 | "description": "A piece of content can be divided into areas that can be dragged to adjust the width or height.", 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "homepage": "https://uiwjs.github.io/react-split", 8 | "funding": "https://jaywcjlove.github.io/#/sponsor", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/uiwjs/react-split.git" 12 | }, 13 | "author": "Kenny Wong ", 14 | "license": "MIT", 15 | "files": [ 16 | "src/**/*.{ts,tsx,less}", 17 | "README-zh.md", 18 | "dist.css", 19 | "dist", 20 | "esm", 21 | "cjs" 22 | ], 23 | "peerDependencies": { 24 | "react": ">=16.8.0", 25 | "react-dom": ">=16.8.0" 26 | }, 27 | "devDependencies": { 28 | "react": "^18.2.0", 29 | "react-dom": "^18.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "www", 3 | "private": true, 4 | "version": "5.9.3", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "kkt build", 8 | "start": "kkt start" 9 | }, 10 | "dependencies": { 11 | "@uiw/react-markdown-preview-example": "^2.0.0", 12 | "@uiw/react-split": "5.9.3" 13 | }, 14 | "devDependencies": { 15 | "@types/react": "^18.2.14", 16 | "@types/react-dom": "^18.2.6", 17 | "kkt": "^7.4.9", 18 | "markdown-react-code-preview-loader": "^2.1.2", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "uiw": "^4.21.14" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 uiw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/index.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import { render, screen } from '@testing-library/react'; 5 | import '@testing-library/jest-dom'; 6 | import React from 'react'; 7 | import Split from '../core/src/'; 8 | 9 | test('renders react-split', () => { 10 | render( 11 | 12 |
Header
13 | 14 |
Sider
15 |
Content
16 |
17 |
Footer
18 |
, 19 | ); 20 | const element = screen.getByTestId('split'); 21 | expect(element.className).toEqual('w-split w-split-vertical'); 22 | expect(element).toBeInTheDocument(); 23 | }); 24 | 25 | test('lineBar props', () => { 26 | render( 27 | 28 |
Header
29 |
, 30 | ); 31 | const element = screen.getByTestId('split'); 32 | expect(element.className).toEqual('w-split w-split-vertical'); 33 | expect(element.childNodes.length).toEqual(1); 34 | expect(element).toBeInTheDocument(); 35 | }); 36 | 37 | test('disable props', () => { 38 | render( 39 | 40 |
Header
41 |
Header
42 |
, 43 | ); 44 | const element = screen.getByTestId('split'); 45 | expect(element.className).toEqual('w-split w-split-horizontal'); 46 | expect((element.childNodes[1] as any).className).toEqual('w-split-bar w-split-large-bar disable'); 47 | expect(element.childNodes.length).toEqual(3); 48 | expect(element).toBeInTheDocument(); 49 | }); 50 | -------------------------------------------------------------------------------- /www/.kktrc.ts: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import { WebpackConfiguration } from 'kkt'; 3 | import { mdCodeModulesLoader } from 'markdown-react-code-preview-loader'; 4 | import pkg from './package.json'; 5 | 6 | export default (conf: WebpackConfiguration, env: 'production' | 'development') => { 7 | conf = mdCodeModulesLoader(conf); 8 | // Get the project version. 9 | conf.plugins!.push( 10 | new webpack.DefinePlugin({ 11 | VERSION: JSON.stringify(pkg.version), 12 | }), 13 | ); 14 | conf.module!.exprContextCritical = false; 15 | if (env === 'production') { 16 | conf.module!.exprContextCritical = false; 17 | conf.output = { ...conf.output, publicPath: './' }; 18 | conf.optimization = { 19 | ...conf.optimization, 20 | splitChunks: { 21 | cacheGroups: { 22 | reactvendor: { 23 | test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, 24 | name: 'react-vendor', 25 | chunks: 'all', 26 | }, 27 | babelstandalone: { 28 | test: /[\\/]node_modules[\\/](@babel[\\/]standalone)[\\/]/, 29 | name: 'babel-standalone-vendor', 30 | chunks: 'all', 31 | }, 32 | prismjs: { 33 | test: /[\\/]node_modules[\\/](refractor)[\\/]/, 34 | name: 'refractor-vendor', 35 | chunks: 'all', 36 | }, 37 | codemirror: { 38 | test: /[\\/]node_modules[\\/](@codemirror)[\\/]/, 39 | name: 'codemirror-vendor', 40 | chunks: 'all', 41 | }, 42 | uiw: { 43 | test: /[\\/]node_modules[\\/](@uiw)[\\/]/, 44 | name: 'uiw-vendor', 45 | chunks: 'all', 46 | }, 47 | parse5: { 48 | test: /[\\/]node_modules[\\/](parse5)[\\/]/, 49 | name: 'parse5-vendor', 50 | chunks: 'all', 51 | }, 52 | }, 53 | }, 54 | }; 55 | } 56 | 57 | return conf; 58 | }; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "doc": "lerna exec --scope www -- npm run build", 5 | "start": "lerna exec --scope www -- npm run start", 6 | "⬇️⬇️⬇️⬇️⬇️ package ⬇️⬇️⬇️⬇️⬇️": "▼▼▼▼▼ package ▼▼▼▼▼", 7 | "build": "lerna exec --scope @uiw/* -- tsbb build src/*.tsx --use-babel --cjs cjs --bail && npm run css:build && npm run css:build:dist", 8 | "watch": "lerna exec \"tsbb watch src/*.tsx --use-babel --cjs cjs\" --scope @uiw/* & npm run css:watch", 9 | "css:build": "lerna exec --scope @uiw/* -- compile-less -d src -o esm", 10 | "css:watch": "lerna exec --scope @uiw/* -- compile-less -d src -o esm --watch", 11 | "css:build:dist": "lerna exec --scope @uiw/* -- compile-less -d src -o lib --combine=dist.css", 12 | "bundle": "lerna exec --scope @uiw/* -- ncc build src/index.tsx --target web --filename dist", 13 | "bundle:min": "lerna exec --scope @uiw/* -- ncc build src/index.tsx --target web --filename dist --minify", 14 | "⬆️⬆️⬆️⬆️⬆️ package ⬆️⬆️⬆️⬆️⬆️": "▲▲▲▲▲ package ▲▲▲▲▲", 15 | "type-check": "tsc --noEmit", 16 | "test": "tsbb test", 17 | "coverage": "tsbb test --coverage --bail", 18 | "prepare": "husky install", 19 | "version": "lerna version --exact --force-publish --no-push --no-git-tag-version", 20 | "prettier": "prettier --write '**/*.{js,jsx,ts,tsx,html,less,md,json}'", 21 | "remove": "npm run clean && lerna exec \"rm -rf package-lock.json\" --scope react-code-preview-layout --scope www", 22 | "clean": "lerna clean --yes" 23 | }, 24 | "workspaces": [ 25 | "core", 26 | "www" 27 | ], 28 | "engines": { 29 | "node": ">=16.0.0" 30 | }, 31 | "lint-staged": { 32 | "*.{js,jsx,ts,tsx,html,less,md,json}": [ 33 | "prettier --write" 34 | ] 35 | }, 36 | "jest": { 37 | "collectCoverageFrom": [ 38 | "/core/src/*.{tsx,ts}" 39 | ], 40 | "coverageReporters": [ 41 | "lcov", 42 | "json-summary" 43 | ] 44 | }, 45 | "devDependencies": { 46 | "@kkt/ncc": "^1.0.15", 47 | "@kkt/less-modules": "^7.5.2", 48 | "compile-less-cli": "^1.9.0", 49 | "jest": "^29.5.0", 50 | "jest-environment-jsdom": "^29.6.0", 51 | "jest-environment-node": "^29.5.0", 52 | "jest-watch-typeahead": "^2.2.2", 53 | "husky": "~8.0.3", 54 | "kkt": "^7.5.2", 55 | "lerna": "^8.0.0", 56 | "lint-staged": "^15.1.0", 57 | "prettier": "^3.0.0", 58 | "tsbb": "^4.2.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths-ignore: 7 | - '.github/**/*.yml' 8 | 9 | jobs: 10 | build-deploy: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | id-token: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: 20 20 | registry-url: 'https://registry.npmjs.org' 21 | 22 | - run: npm install 23 | - run: npm run build 24 | - run: npm run doc 25 | - run: npm run bundle 26 | - run: npm run bundle:min 27 | - run: npm run coverage 28 | - run: npm run type-check 29 | - run: cp -rp coverage www/build 30 | - run: cp -rp README-zh.md core 31 | 32 | - name: Generate Contributors Images 33 | uses: jaywcjlove/github-action-contributors@main 34 | with: 35 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) 36 | output: www/build/CONTRIBUTORS.svg 37 | avatarSize: 42 38 | 39 | - name: Create Coverage Badges 40 | uses: jaywcjlove/coverage-badges-cli@main 41 | with: 42 | output: www/build/badges.svg 43 | 44 | - name: Deploy 45 | uses: peaceiris/actions-gh-pages@v3 46 | with: 47 | github_token: ${{ secrets.GITHUB_TOKEN }} 48 | publish_dir: ./www/build 49 | 50 | - name: Create Tag 51 | id: create_tag 52 | uses: jaywcjlove/create-tag-action@main 53 | with: 54 | package-path: ./core/package.json 55 | 56 | - name: Generate Changelog 57 | id: changelog 58 | uses: jaywcjlove/changelog-generator@main 59 | with: 60 | head-ref: ${{steps.create_tag.outputs.version}} 61 | filter-author: (renovate-bot|Renovate Bot) 62 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 63 | 64 | - name: Create Release 65 | uses: ncipollo/release-action@v1 66 | if: steps.create_tag.outputs.successful 67 | with: 68 | allowUpdates: true 69 | token: ${{ secrets.GITHUB_TOKEN }} 70 | name: ${{ steps.create_tag.outputs.version }} 71 | tag: ${{ steps.create_tag.outputs.version }} 72 | body: | 73 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) [![](https://img.shields.io/badge/Open%20in-unpkg-blue)](https://uiwjs.github.io/npm-unpkg/#/pkg/@uiw/react-split@${{steps.create_tag.outputs.versionNumber}}/file/README.md) 74 | 75 | Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/uiwjs/react-split/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html 76 | Comparing Changes: ${{ steps.changelog.outputs.compareurl }} 77 | 78 | ```bash 79 | npm i @uiw/react-split@${{steps.create_tag.outputs.versionNumber}} 80 | ``` 81 | 82 | ${{ steps.changelog.outputs.changelog }} 83 | 84 | Bundle Example: https://uiwjs.github.io/react-split/bundle.html 85 | 86 | - run: npm publish --access public --provenance 87 | name: 📦 @uiw/react-split publish to NPM 88 | continue-on-error: true 89 | working-directory: core 90 | env: 91 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 92 | 93 | 94 | # outputs: 95 | # successful: ${{steps.create_tag.outputs.successful }} 96 | 97 | # github-package: 98 | # runs-on: ubuntu-latest 99 | # needs: build-deploy 100 | # if: needs.build-deploy.outputs.successful 101 | # steps: 102 | # - uses: actions/checkout@v3 103 | # - uses: actions/setup-node@v3 104 | # with: 105 | # node-version: 16 106 | # registry-url: https://npm.pkg.github.com 107 | # scope: '@uiwjs' 108 | 109 | # - run: npm install 110 | # - run: npm run build 111 | # - run: npm run bundle 112 | # - run: npm run bundle:min 113 | 114 | # - name: "Modify @uiw/react-split => @uiwjs/react-split" 115 | # uses: jaywcjlove/github-action-package@main 116 | # with: 117 | # path: package.json 118 | # data: | 119 | # { "name": "@uiwjs/react-split" } 120 | 121 | # - run: npm publish 122 | # env: 123 | # NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /core/src/style/index.less: -------------------------------------------------------------------------------- 1 | @split-prefix: ~'w-split'; 2 | 3 | .@{split-prefix} { 4 | height: 100%; 5 | display: flex; 6 | flex-direction: row; 7 | --w-split-bar-shadow: #d5d5d5; 8 | --w-split-bar-shadow-disable: #d5d5d5; 9 | &-bar { 10 | z-index: 10; 11 | position: relative; 12 | justify-content: center; 13 | display: flex; 14 | background: var(--w-split-bar-background, rgb(248, 248, 249)); 15 | user-select: none; 16 | &::before, 17 | &::after { 18 | content: ''; 19 | display: block; 20 | } 21 | &.disable { 22 | pointer-events: none; 23 | cursor: not-allowed !important; 24 | } 25 | &:hover:not(.disable) { 26 | transition: background-color 0.3s; 27 | background: var(--w-split-bar-hover-background, #ecf7ff); 28 | } 29 | } 30 | &.dragging &-pane { 31 | position: relative; 32 | &::before { 33 | content: ''; 34 | display: block; 35 | position: absolute; 36 | height: 100%; 37 | width: 100%; 38 | z-index: 10; 39 | } 40 | } 41 | & > &-pane { 42 | transform: none; 43 | } 44 | &:not(.dragging) > &-pane { 45 | transition: all 0.3s; 46 | } 47 | &-horizontal > &-bar { 48 | cursor: col-resize; 49 | width: 5px; 50 | flex-direction: column; 51 | box-shadow: 52 | inset 1px 0 0 0 var(--w-split-bar-shadow), 53 | 1px 0 0 0 var(--w-split-bar-shadow); 54 | &::before, 55 | &::after { 56 | height: 3px; 57 | width: 100%; 58 | box-shadow: 59 | inset 0 1px 0 0 var(--w-split-bar-shadow), 60 | 0 1px 0 0 var(--w-split-bar-shadow); 61 | } 62 | &::before { 63 | margin-top: -1px; 64 | } 65 | &::after { 66 | margin-top: 3px; 67 | } 68 | &.disable { 69 | box-shadow: 70 | inset 1px 0 0 0 var(--w-split-bar-shadow-disable), 71 | 1px 0 0 0 var(--w-split-bar-shadow-disable) !important; 72 | } 73 | } 74 | &-vertical { 75 | flex-direction: column; 76 | } 77 | &-vertical > &-bar { 78 | cursor: row-resize; 79 | width: 100%; 80 | height: 5px; 81 | align-items: center; 82 | flex-direction: row; 83 | box-shadow: 84 | inset 0 1px 0 0 var(--w-split-bar-shadow), 85 | 0 1px 0 0 var(--w-split-bar-shadow); 86 | &.disable { 87 | box-shadow: 88 | inset 0 1px 0 0 var(--w-split-bar-shadow-disable), 89 | 0 1px 0 0 var(--w-split-bar-shadow-disable) !important; 90 | } 91 | &::before, 92 | &::after { 93 | height: 100%; 94 | width: 3px; 95 | box-shadow: 96 | inset 1px 0 0 0 var(--w-split-bar-shadow), 97 | 1px 0 0 0 var(--w-split-bar-shadow); 98 | } 99 | &::before { 100 | margin-left: -1px; 101 | } 102 | &::after { 103 | margin-left: 3px; 104 | } 105 | } 106 | &-large-bar { 107 | div { 108 | position: absolute; 109 | left: 0; 110 | right: 0; 111 | bottom: 0; 112 | top: 0; 113 | } 114 | } 115 | &-vertical > &-line-bar:hover, 116 | &-horizontal > &-line-bar:hover { 117 | &::before { 118 | background: var(--w-split-line-bar-hover-border-color, #008ef0); 119 | } 120 | } 121 | &-vertical > &-line-bar { 122 | height: 1px; 123 | div { 124 | cursor: row-resize; 125 | width: 100%; 126 | height: 8px; 127 | margin-top: -2px; 128 | margin-bottom: -2px; 129 | &:hover, 130 | &:active, 131 | &:focus { 132 | margin-top: -6px; 133 | margin-bottom: -6px; 134 | height: 6px; 135 | &::after { 136 | background: var(--w-split-line-bar-active-background, #008ef0); 137 | } 138 | } 139 | &::after { 140 | height: 1px; 141 | width: 100%; 142 | } 143 | } 144 | } 145 | &-horizontal > &-line-bar { 146 | width: 1px; 147 | div { 148 | cursor: col-resize; 149 | height: 100%; 150 | width: 8px; 151 | margin-left: -2px; 152 | margin-right: -2px; 153 | &:hover, 154 | &:active, 155 | &:focus { 156 | margin-left: -6px; 157 | margin-right: -6px; 158 | width: 6px; 159 | &::after { 160 | background: var(--w-split-line-bar-active-background, #008ef0); 161 | } 162 | } 163 | &::after { 164 | width: 1px; 165 | height: 100%; 166 | } 167 | } 168 | } 169 | &-vertical > &-line-bar, 170 | &-horizontal > &-line-bar { 171 | flex-direction: inherit; 172 | &::before { 173 | display: none; 174 | } 175 | div { 176 | position: relative; 177 | display: flex; 178 | align-items: center; 179 | justify-content: center; 180 | &::after { 181 | content: ''; 182 | display: block; 183 | background-color: var(--w-split-line-bar-background, #d5d5d5); 184 | } 185 | } 186 | } 187 | &-line-bar { 188 | box-shadow: inset 0 0 0 0 !important; 189 | background: transparent; 190 | &::before { 191 | box-shadow: inset 0 0 0 0 !important; 192 | } 193 | &::after { 194 | display: none; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /www/public/bundle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | @uiw/react-split 13 |
14 | 15 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /core/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect, useCallback } from 'react'; 2 | import './style/index.less'; 3 | 4 | export interface SplitProps extends Omit, 'onDragEnd'> { 5 | style?: React.CSSProperties; 6 | className?: string; 7 | prefixCls?: string; 8 | /** 9 | * Drag width/height change callback function, 10 | * the width or height is determined according to the mode parameter 11 | */ 12 | onDragging?: (preSize: number, nextSize: number, paneNumber: number) => void; 13 | /** Callback function for dragging end */ 14 | onDragEnd?: (preSize: number, nextSize: number, paneNumber: number) => void; 15 | /** Support custom drag and drop toolbar */ 16 | renderBar?: (props: React.HTMLAttributes) => React.ReactElement; 17 | /** Set the drag and drop toolbar as a line style. */ 18 | lineBar?: boolean; 19 | /** Set the dragged toolbar, whether it is visible or not */ 20 | visible?: boolean | number[]; 21 | /** 22 | * @deprecated Use `visible` instead 23 | */ 24 | visiable?: boolean | number[]; 25 | /** 26 | * Set the drag and drop toolbar, disable 27 | */ 28 | disable?: boolean | number[]; 29 | /** 30 | * type, optional `horizontal` or `vertical` 31 | */ 32 | mode?: 'horizontal' | 'vertical'; 33 | } 34 | 35 | const Split: React.FC = (props: SplitProps) => { 36 | const { 37 | prefixCls = 'w-split', 38 | visiable = true, 39 | mode = 'horizontal', 40 | className, 41 | children, 42 | visible = props.visible ?? props.visiable, 43 | renderBar, 44 | lineBar, 45 | disable, 46 | onDragEnd, 47 | onDragging, 48 | ...other 49 | } = props; 50 | const [dragging, setDragging] = useState(false); 51 | 52 | const wrapperRef = useRef(null); 53 | const paneNumberRef = useRef(0); 54 | const startXRef = useRef(0); 55 | const startYRef = useRef(0); 56 | const moveRef = useRef(false); 57 | const targetRef = useRef(null); 58 | 59 | const boxWidthRef = useRef(0); 60 | const boxHeightRef = useRef(0); 61 | const preWidthRef = useRef(0); 62 | const nextWidthRef = useRef(0); 63 | const preHeightRef = useRef(0); 64 | const nextHeightRef = useRef(0); 65 | 66 | const preSizeRef = useRef(0); 67 | const nextSizeRef = useRef(0); 68 | 69 | const removeEvent = useCallback(() => { 70 | window.removeEventListener('mousemove', onDraggingHandler, false); 71 | window.removeEventListener('mouseup', onDragEndHandler, false); 72 | }, []); 73 | 74 | const onDraggingHandler = useCallback( 75 | (env: Event) => { 76 | if (!moveRef.current) { 77 | return; 78 | } 79 | if (!dragging) { 80 | setDragging(true); 81 | } 82 | const nextTarget = targetRef.current?.nextElementSibling as HTMLDivElement; 83 | const prevTarget = targetRef.current?.previousElementSibling as HTMLDivElement; 84 | const x = (env as MouseEvent).clientX - startXRef.current; 85 | const y = (env as MouseEvent).clientY - startYRef.current; 86 | preSizeRef.current = 0; 87 | nextSizeRef.current = 0; 88 | if (mode === 'horizontal') { 89 | preSizeRef.current = preWidthRef.current + x > -1 ? preWidthRef.current + x : 0; 90 | nextSizeRef.current = nextWidthRef.current - x > -1 ? nextWidthRef.current - x : 0; 91 | if (preSizeRef.current === 0 || nextSizeRef.current === 0) { 92 | return; 93 | } 94 | preSizeRef.current = 95 | (preSizeRef.current / boxWidthRef.current >= 1 ? 1 : preSizeRef.current / boxWidthRef.current) * 100; 96 | nextSizeRef.current = 97 | (nextSizeRef.current / boxWidthRef.current >= 1 ? 1 : nextSizeRef.current / boxWidthRef.current) * 100; 98 | if (prevTarget && nextTarget) { 99 | prevTarget.style.width = `${preSizeRef.current}%`; 100 | nextTarget.style.width = `${nextSizeRef.current}%`; 101 | } 102 | } 103 | if (mode === 'vertical' && preHeightRef.current + y > -1 && nextHeightRef.current - y > -1) { 104 | preSizeRef.current = preHeightRef.current + y > -1 ? preHeightRef.current + y : 0; 105 | nextSizeRef.current = nextHeightRef.current - y > -1 ? nextHeightRef.current - y : 0; 106 | preSizeRef.current = 107 | (preSizeRef.current / boxHeightRef.current >= 1 ? 1 : preSizeRef.current / boxHeightRef.current) * 100; 108 | nextSizeRef.current = 109 | (nextSizeRef.current / boxHeightRef.current >= 1 ? 1 : nextSizeRef.current / boxHeightRef.current) * 100; 110 | if (preSizeRef.current === 0 || nextSizeRef.current === 0) { 111 | return; 112 | } 113 | if (prevTarget && nextTarget) { 114 | prevTarget.style.height = `${preSizeRef.current}%`; 115 | nextTarget.style.height = `${nextSizeRef.current}%`; 116 | } 117 | } 118 | onDragging && onDragging(preSizeRef.current, nextSizeRef.current, paneNumberRef.current); 119 | }, 120 | [mode, onDragging, dragging], 121 | ); 122 | 123 | const onDragEndHandler = useCallback(() => { 124 | moveRef.current = false; 125 | onDragEnd && onDragEnd(preSizeRef.current, nextSizeRef.current, paneNumberRef.current); 126 | removeEvent(); 127 | setDragging(false); 128 | }, [onDragEnd, removeEvent]); 129 | 130 | const onMouseDown = useCallback( 131 | (paneNumber: number, env: React.MouseEvent) => { 132 | if (!env.target || !wrapperRef.current) { 133 | return; 134 | } 135 | paneNumberRef.current = paneNumber; 136 | startXRef.current = env.clientX; 137 | startYRef.current = env.clientY; 138 | moveRef.current = true; 139 | targetRef.current = (env.target as HTMLDivElement).parentNode as HTMLDivElement; 140 | const prevTarget = targetRef.current.previousElementSibling; 141 | const nextTarget = targetRef.current.nextElementSibling; 142 | boxWidthRef.current = wrapperRef.current.clientWidth; 143 | boxHeightRef.current = wrapperRef.current.clientHeight; 144 | if (prevTarget) { 145 | preWidthRef.current = prevTarget.clientWidth; 146 | preHeightRef.current = prevTarget.clientHeight; 147 | } 148 | if (nextTarget) { 149 | nextWidthRef.current = nextTarget.clientWidth; 150 | nextHeightRef.current = nextTarget.clientHeight; 151 | } 152 | window.addEventListener('mousemove', onDraggingHandler); 153 | window.addEventListener('mouseup', onDragEndHandler, false); 154 | setDragging(true); 155 | }, 156 | [onDraggingHandler, onDragEndHandler], 157 | ); 158 | 159 | useEffect(() => { 160 | return () => { 161 | removeEvent(); 162 | }; 163 | }, [removeEvent]); 164 | 165 | const cls = [prefixCls, className, `${prefixCls}-${mode}`, dragging ? 'dragging' : null] 166 | .filter(Boolean) 167 | .join(' ') 168 | .trim(); 169 | const child = React.Children.toArray(children); 170 | 171 | return ( 172 |
173 | {React.Children.map(child, (element: any, idx: number) => { 174 | const props = Object.assign({}, element.props, { 175 | className: [`${prefixCls}-pane`, element.props.className].filter(Boolean).join(' ').trim(), 176 | style: { ...element.props.style }, 177 | }); 178 | const visibleBar = visible === true || (visible && visible.includes((idx + 1) as never)) || false; 179 | const barProps = { 180 | className: [ 181 | `${prefixCls}-bar`, 182 | lineBar ? `${prefixCls}-line-bar` : null, 183 | !lineBar ? `${prefixCls}-large-bar` : null, 184 | ] 185 | .filter(Boolean) 186 | .join(' ') 187 | .trim(), 188 | }; 189 | if (disable === true || (disable && disable.includes((idx + 1) as never))) { 190 | barProps.className = [barProps.className, disable ? 'disable' : null].filter(Boolean).join(' ').trim(); 191 | } 192 | let BarCom = null; 193 | if (idx !== 0 && visibleBar && renderBar) { 194 | BarCom = renderBar({ 195 | ...barProps, 196 | onMouseDown: (e: React.MouseEvent) => onMouseDown(idx + 1, e), 197 | }); 198 | } else if (idx !== 0 && visibleBar) { 199 | BarCom = React.createElement( 200 | 'div', 201 | { ...barProps }, 202 |
) => onMouseDown(idx + 1, e)} />, 203 | ); 204 | } 205 | return ( 206 | 207 | {BarCom} 208 | {React.cloneElement(element, { ...props })} 209 | 210 | ); 211 | })} 212 |
213 | ); 214 | }; 215 | 216 | export default Split; 217 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 |

2 | English 3 |

4 | 5 |
6 | 使用我的应用,也是对我的支持 7 |
8 | Deskmark 9 | Keyzer 10 | Vidwall Hub 11 | VidCrop 12 | Vidwall 13 | Mousio Hint 14 | Mousio 15 | Musicer 16 | Audioer 17 | FileSentinel 18 | FocusCursor 19 | Videoer 20 | KeyClicker 21 | DayBar 22 | Iconed 23 | Mousio 24 | Quick RSS 25 | Quick RSS 26 | Web Serve 27 | Copybook Generator 28 | DevTutor for SwiftUI 29 | RegexMate 30 | Time Passage 31 | Iconize Folder 32 | Textsound Saver 33 | Create Custom Symbols 34 | DevHub 35 | Resume Revise 36 | Palette Genius 37 | Symbol Scribe 38 |
39 |
40 | 41 | # Split 面板分割 42 | 43 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) 44 | [![NPM Downloads](https://img.shields.io/npm/dm/@uiw/react-split.svg?style=flat)](https://www.npmjs.com/package/@uiw/react-split) 45 | [![Build & Deploy](https://github.com/uiwjs/react-split/actions/workflows/ci.yml/badge.svg)](https://github.com/uiwjs/react-split/actions/workflows/ci.yml) 46 | [![Open in unpkg](https://img.shields.io/badge/Open%20in-unpkg-blue)](https://uiwjs.github.io/npm-unpkg/#/pkg/@uiw/react-split/file/README.md) 47 | [![npm version](https://img.shields.io/npm/v/@uiw/react-split.svg)](https://www.npmjs.com/package/@uiw/react-split) 48 | [![Coverage Status](https://uiwjs.github.io/react-split/badges.svg)](https://uiwjs.github.io/react-split/coverage/lcov-report/) 49 | [![Open in Gitpod](https://shields.io/badge/Open%20in-Gitpod-green?logo=Gitpod)](https://gitpod.io/#https://github.com/uiwjs/react-split) 50 | [![Repo Dependents](https://badgen.net/github/dependents-repo/uiwjs/react-split)](https://github.com/uiwjs/react-split/network/dependents) 51 | 52 | 可将一块内容,分割为可以拖拽调整宽度或高度的区域。 53 | 54 | ```jsx 55 | import { Split } from 'uiw'; 56 | ``` 57 | 58 | 从组件库 `uiw` 中抽离出来的 `@uiw/react-split`,可以单独使用。 59 | 60 | ```jsx 61 | import Split from '@uiw/react-split'; 62 | ``` 63 | 64 | ### 基础用法 65 | 66 | > ~~通过设置子节点的 `minWidth` 样式,即可设置拖拽最小宽度值。通过设置子节点样式 `flexBasis` 样式即可设置默认分割内容的占比宽度。~~ 67 | 68 | - 固定初始宽度或者高度,可通过设置子节点,样式 `width: '80%'` 宽度百分百来计算。 69 | - 拖拽至最小宽度,可通过设置子节点样式 `minWidth: 30`,来达到效果 70 | - 默认情况下,不设置样式 `width`,需要将某个子节点样式设为 `flex: 1`,来自适应 71 | 72 | ```jsx mdx:preview&background=#fff&codeSandbox=true&codePen=true 73 | import React from 'react'; 74 | import Split from '@uiw/react-split'; 75 | 76 | const Demo = () => ( 77 |
78 | 79 |
80 |