├── demo ├── .gitignore ├── src │ ├── index.tsx │ ├── model │ │ ├── types.ts │ │ └── appModel.ts │ ├── stylesheet.css │ └── components │ │ ├── App.tsx │ │ ├── Main.tsx │ │ ├── LayoutIcons.tsx │ │ ├── CustomSplitters.tsx │ │ ├── SplitOptionsEditor.tsx │ │ └── DynamicPane.tsx ├── tsconfig.json ├── .vscode │ └── launch.json ├── package.json └── webpack.config.js ├── package ├── .gitignore ├── dist │ ├── 2.0.0 │ │ ├── index.d.ts │ │ ├── DefaultSplitter.d.ts │ │ ├── RenderSplitterProps.d.ts │ │ └── Split.d.ts │ ├── 2.0.1 │ │ ├── index.d.ts │ │ ├── DefaultSplitter.d.ts │ │ ├── RenderSplitterProps.d.ts │ │ └── Split.d.ts │ ├── 2.0.2 │ │ ├── index.d.ts │ │ ├── useDebounceHook.d.ts │ │ ├── DefaultSplitter.d.ts │ │ ├── RenderSplitterProps.d.ts │ │ └── Split.d.ts │ ├── 1.0.2 │ │ ├── index.d.ts │ │ ├── LeftRightSplit.d.ts │ │ └── TopBottomSplit.d.ts │ ├── 1.0.3 │ │ ├── index.d.ts │ │ ├── LeftRightSplit.d.ts │ │ └── TopBottomSplit.d.ts │ ├── 1.0.4 │ │ ├── index.d.ts │ │ ├── LeftRightSplit.d.ts │ │ └── TopBottomSplit.d.ts │ ├── 2.0.3 │ │ ├── useDebounceHook.d.ts │ │ ├── index.d.ts │ │ ├── DefaultSplitter.d.ts │ │ ├── RenderSplitterProps.d.ts │ │ └── Split.d.ts │ ├── 2.1.0 │ │ ├── useDebounceHook.d.ts │ │ ├── index.d.ts │ │ ├── DefaultSplitter.d.ts │ │ ├── RenderSplitterProps.d.ts │ │ ├── Split.d.ts │ │ └── index.js │ ├── 2.1.1 │ │ ├── index.d.ts │ │ ├── DefaultSplitter.d.ts │ │ ├── RenderSplitterProps.d.ts │ │ └── index.js │ └── 2.1.2 │ │ ├── index.d.ts │ │ ├── DefaultSplitter.d.ts │ │ ├── RenderSplitterProps.d.ts │ │ ├── Split.d.ts │ │ └── index.js ├── .npmignore ├── src │ ├── index.tsx │ ├── RenderSplitterProps.ts │ ├── DefaultSplitter.tsx │ ├── defaultSplitter.css │ ├── split.css │ └── Split.tsx ├── tsconfig.json ├── package.json ├── webpack.config.js └── README.md ├── .prettierrc ├── .editorconfig ├── .vscode └── launch.json ├── LICENSE ├── UsingEmotionStyles.md └── README.md /demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ -------------------------------------------------------------------------------- /package/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | build/ -------------------------------------------------------------------------------- /package/dist/2.0.0/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './Split'; 2 | -------------------------------------------------------------------------------- /package/dist/2.0.1/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './Split'; 2 | -------------------------------------------------------------------------------- /package/dist/2.0.2/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './Split'; 2 | -------------------------------------------------------------------------------- /package/.npmignore: -------------------------------------------------------------------------------- 1 | webpack.config.js 2 | tsconfig.json 3 | .eslintrc 4 | .gitignore -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "typescript", 3 | "singleQuote": true, 4 | "endOfLine": "auto" 5 | } -------------------------------------------------------------------------------- /package/dist/1.0.2/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './LeftRightSplit'; 2 | export * from './TopBottomSplit'; 3 | -------------------------------------------------------------------------------- /package/dist/1.0.3/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './LeftRightSplit'; 2 | export * from './TopBottomSplit'; 3 | -------------------------------------------------------------------------------- /package/dist/1.0.4/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './LeftRightSplit'; 2 | export * from './TopBottomSplit'; 3 | -------------------------------------------------------------------------------- /package/dist/2.0.2/useDebounceHook.d.ts: -------------------------------------------------------------------------------- 1 | export declare const useDebounce: (value: any, delay: number) => any; 2 | -------------------------------------------------------------------------------- /package/dist/2.0.3/useDebounceHook.d.ts: -------------------------------------------------------------------------------- 1 | export declare const useDebounce: (value: any, delay: number) => any; 2 | -------------------------------------------------------------------------------- /package/dist/2.1.0/useDebounceHook.d.ts: -------------------------------------------------------------------------------- 1 | export declare const useDebounce: (value: any, delay: number) => any; 2 | -------------------------------------------------------------------------------- /package/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './DefaultSplitter'; 2 | export * from './RenderSplitterProps'; 3 | export * from './Split'; 4 | -------------------------------------------------------------------------------- /package/dist/2.0.3/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './DefaultSplitter'; 2 | export * from './RenderSplitterProps'; 3 | export * from './Split'; 4 | -------------------------------------------------------------------------------- /package/dist/2.1.0/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './DefaultSplitter'; 2 | export * from './RenderSplitterProps'; 3 | export * from './Split'; 4 | -------------------------------------------------------------------------------- /package/dist/2.1.1/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './DefaultSplitter'; 2 | export * from './RenderSplitterProps'; 3 | export * from './Split'; 4 | -------------------------------------------------------------------------------- /package/dist/2.1.2/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './DefaultSplitter'; 2 | export * from './RenderSplitterProps'; 3 | export * from './Split'; 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | max_line_length = 120 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.cs] 12 | indent_size = 4 -------------------------------------------------------------------------------- /package/dist/1.0.2/LeftRightSplit.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | declare type Props = { 3 | initialLeftGridWidth?: string | number; 4 | minLeftPixels?: number; 5 | minRightPixels?: number; 6 | }; 7 | export declare const LeftRightSplit: (props: React.PropsWithChildren) => JSX.Element; 8 | export {}; 9 | -------------------------------------------------------------------------------- /package/dist/1.0.2/TopBottomSplit.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | declare type Props = { 3 | initialTopGridHeight?: string | number; 4 | minTopPixels?: number; 5 | minBottomPixels?: number; 6 | }; 7 | export declare const TopBottomSplit: (props: React.PropsWithChildren) => JSX.Element; 8 | export {}; 9 | -------------------------------------------------------------------------------- /package/dist/1.0.3/LeftRightSplit.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | declare type Props = { 3 | initialLeftGridWidth?: string | number; 4 | minLeftPixels?: number; 5 | minRightPixels?: number; 6 | }; 7 | export declare const LeftRightSplit: (props: React.PropsWithChildren) => JSX.Element; 8 | export {}; 9 | -------------------------------------------------------------------------------- /package/dist/1.0.3/TopBottomSplit.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | declare type Props = { 3 | initialTopGridHeight?: string | number; 4 | minTopPixels?: number; 5 | minBottomPixels?: number; 6 | }; 7 | export declare const TopBottomSplit: (props: React.PropsWithChildren) => JSX.Element; 8 | export {}; 9 | -------------------------------------------------------------------------------- /package/dist/1.0.4/LeftRightSplit.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | declare type Props = { 3 | initialLeftGridWidth?: string | number; 4 | minLeftPixels?: number; 5 | minRightPixels?: number; 6 | splitterWidth?: number; 7 | renderSplitter?: () => JSX.Element; 8 | }; 9 | export declare const LeftRightSplit: (props: React.PropsWithChildren) => JSX.Element; 10 | export {}; 11 | -------------------------------------------------------------------------------- /package/dist/1.0.4/TopBottomSplit.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | declare type Props = { 3 | initialTopGridHeight?: string | number; 4 | minTopPixels?: number; 5 | minBottomPixels?: number; 6 | splitterHeight?: number; 7 | renderSplitter?: () => JSX.Element; 8 | }; 9 | export declare const TopBottomSplit: (props: React.PropsWithChildren) => JSX.Element; 10 | export {}; 11 | -------------------------------------------------------------------------------- /demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { App } from './components/App'; 4 | import './stylesheet.css'; 5 | 6 | const appElement = document.getElementById('app'); 7 | 8 | // Creates an application 9 | const createApp = (AppComponent: typeof App) => { 10 | return ; 11 | }; 12 | 13 | // Initial rendering of the application 14 | ReactDOM.render(createApp(App), appElement); 15 | -------------------------------------------------------------------------------- /package/dist/2.1.2/DefaultSplitter.d.ts: -------------------------------------------------------------------------------- 1 | import { RenderSplitterProps } from './RenderSplitterProps'; 2 | import './defaultSplitter.css'; 3 | type Props = RenderSplitterProps & { 4 | color?: string; 5 | hoverColor?: string; 6 | dragColor?: string; 7 | }; 8 | /** 9 | * The default splitter which provides a thin line within a larger mouse hit area. 10 | */ 11 | export declare const DefaultSplitter: (props: Props) => JSX.Element; 12 | export {}; 13 | -------------------------------------------------------------------------------- /package/dist/2.0.0/DefaultSplitter.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | declare type Props = RenderSplitterProps & { 4 | color: string; 5 | hoverColor: string; 6 | }; 7 | /** 8 | * The default splitter which provides a thin line within a possibly larger mouse hit area. 9 | * @param props 10 | */ 11 | export declare const DefaultSplitter: (props: Props) => JSX.Element; 12 | export {}; 13 | -------------------------------------------------------------------------------- /package/dist/2.0.1/DefaultSplitter.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | declare type Props = RenderSplitterProps & { 4 | color: string; 5 | hoverColor: string; 6 | }; 7 | /** 8 | * The default splitter which provides a thin line within a possibly larger mouse hit area. 9 | * @param props 10 | */ 11 | export declare const DefaultSplitter: (props: Props) => JSX.Element; 12 | export {}; 13 | -------------------------------------------------------------------------------- /package/dist/2.0.2/DefaultSplitter.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | declare type Props = RenderSplitterProps & { 4 | color: string; 5 | hoverColor: string; 6 | }; 7 | /** 8 | * The default splitter which provides a thin line within a possibly larger mouse hit area. 9 | * @param props 10 | */ 11 | export declare const DefaultSplitter: (props: Props) => JSX.Element; 12 | export {}; 13 | -------------------------------------------------------------------------------- /package/dist/2.0.3/DefaultSplitter.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | declare type Props = RenderSplitterProps & { 4 | color: string; 5 | hoverColor: string; 6 | }; 7 | /** 8 | * The default splitter which provides a thin line within a possibly larger mouse hit area. 9 | * @param props 10 | */ 11 | export declare const DefaultSplitter: (props: Props) => JSX.Element; 12 | export {}; 13 | -------------------------------------------------------------------------------- /package/dist/2.1.0/DefaultSplitter.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | import './defaultSplitter.css'; 4 | declare type Props = RenderSplitterProps & { 5 | color?: string; 6 | hoverColor?: string; 7 | dragColor?: string; 8 | }; 9 | /** 10 | * The default splitter which provides a thin line within a larger mouse hit area. 11 | */ 12 | export declare const DefaultSplitter: (props: Props) => JSX.Element; 13 | export {}; 14 | -------------------------------------------------------------------------------- /package/dist/2.1.1/DefaultSplitter.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | import './defaultSplitter.css'; 4 | declare type Props = RenderSplitterProps & { 5 | color?: string; 6 | hoverColor?: string; 7 | dragColor?: string; 8 | }; 9 | /** 10 | * The default splitter which provides a thin line within a larger mouse hit area. 11 | */ 12 | export declare const DefaultSplitter: (props: Props) => JSX.Element; 13 | export {}; 14 | -------------------------------------------------------------------------------- /package/src/RenderSplitterProps.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Props for the renderSplitter callback in Split 3 | */ 4 | export type RenderSplitterProps = { 5 | /** 6 | * The measured size of the splitter in pixels. 7 | */ 8 | pixelSize: number; 9 | /** 10 | * True if the splitter is horizontal (i.e. top/bottom); false otherwise. 11 | */ 12 | horizontal: boolean; 13 | /** 14 | * True if the user is currently dragging the splitter; false otherwise. 15 | */ 16 | dragging: boolean; 17 | }; 18 | -------------------------------------------------------------------------------- /package/dist/2.1.2/RenderSplitterProps.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Props for the renderSplitter callback in Split 3 | */ 4 | export type RenderSplitterProps = { 5 | /** 6 | * The measured size of the splitter in pixels. 7 | */ 8 | pixelSize: number; 9 | /** 10 | * True if the splitter is horizontal (i.e. top/bottom); false otherwise. 11 | */ 12 | horizontal: boolean; 13 | /** 14 | * True if the user is currently dragging the splitter; false otherwise. 15 | */ 16 | dragging: boolean; 17 | }; 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "file": "${workspaceFolder}/demo/dist/index.html" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /package/dist/2.0.0/RenderSplitterProps.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Props for the renderSplitter callback in Split 3 | */ 4 | export declare type RenderSplitterProps = { 5 | /** 6 | * The measured size of the splitter in pixels. 7 | */ 8 | pixelSize: number; 9 | /** 10 | * True if the splitter is horizontal (i.e. top/bottom); false otherwise. 11 | */ 12 | horizontal: boolean; 13 | /** 14 | * True if the user is currently dragging the splitter; false otherwise. 15 | */ 16 | dragging: boolean; 17 | }; 18 | -------------------------------------------------------------------------------- /package/dist/2.0.1/RenderSplitterProps.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Props for the renderSplitter callback in Split 3 | */ 4 | export declare type RenderSplitterProps = { 5 | /** 6 | * The measured size of the splitter in pixels. 7 | */ 8 | pixelSize: number; 9 | /** 10 | * True if the splitter is horizontal (i.e. top/bottom); false otherwise. 11 | */ 12 | horizontal: boolean; 13 | /** 14 | * True if the user is currently dragging the splitter; false otherwise. 15 | */ 16 | dragging: boolean; 17 | }; 18 | -------------------------------------------------------------------------------- /package/dist/2.0.2/RenderSplitterProps.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Props for the renderSplitter callback in Split 3 | */ 4 | export declare type RenderSplitterProps = { 5 | /** 6 | * The measured size of the splitter in pixels. 7 | */ 8 | pixelSize: number; 9 | /** 10 | * True if the splitter is horizontal (i.e. top/bottom); false otherwise. 11 | */ 12 | horizontal: boolean; 13 | /** 14 | * True if the user is currently dragging the splitter; false otherwise. 15 | */ 16 | dragging: boolean; 17 | }; 18 | -------------------------------------------------------------------------------- /package/dist/2.0.3/RenderSplitterProps.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Props for the renderSplitter callback in Split 3 | */ 4 | export declare type RenderSplitterProps = { 5 | /** 6 | * The measured size of the splitter in pixels. 7 | */ 8 | pixelSize: number; 9 | /** 10 | * True if the splitter is horizontal (i.e. top/bottom); false otherwise. 11 | */ 12 | horizontal: boolean; 13 | /** 14 | * True if the user is currently dragging the splitter; false otherwise. 15 | */ 16 | dragging: boolean; 17 | }; 18 | -------------------------------------------------------------------------------- /package/dist/2.1.0/RenderSplitterProps.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Props for the renderSplitter callback in Split 3 | */ 4 | export declare type RenderSplitterProps = { 5 | /** 6 | * The measured size of the splitter in pixels. 7 | */ 8 | pixelSize: number; 9 | /** 10 | * True if the splitter is horizontal (i.e. top/bottom); false otherwise. 11 | */ 12 | horizontal: boolean; 13 | /** 14 | * True if the user is currently dragging the splitter; false otherwise. 15 | */ 16 | dragging: boolean; 17 | }; 18 | -------------------------------------------------------------------------------- /package/dist/2.1.1/RenderSplitterProps.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Props for the renderSplitter callback in Split 3 | */ 4 | export declare type RenderSplitterProps = { 5 | /** 6 | * The measured size of the splitter in pixels. 7 | */ 8 | pixelSize: number; 9 | /** 10 | * True if the splitter is horizontal (i.e. top/bottom); false otherwise. 11 | */ 12 | horizontal: boolean; 13 | /** 14 | * True if the user is currently dragging the splitter; false otherwise. 15 | */ 16 | dragging: boolean; 17 | }; 18 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "jsx": "react", 6 | "module": "es6", 7 | "moduleResolution": "node", 8 | "outDir": "./dist/", 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitReturns": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es6" 16 | }, 17 | "include": ["./src/**/*"], 18 | "exclude": ["node_modules", "dist"] 19 | } 20 | -------------------------------------------------------------------------------- /demo/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "chrome", 10 | "request": "launch", 11 | "name": "Launch Chrome against localhost", 12 | "url": "/dist/index.html", 13 | "webRoot": "${workspaceFolder}" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /demo/src/model/types.ts: -------------------------------------------------------------------------------- 1 | import { SplitProps } from '../../../package/src/Split'; 2 | 3 | /** 4 | * The type of splitter to allow for customization. 5 | */ 6 | export type SplitterType = 'default' | 'solid' | 'striped'; 7 | 8 | /** 9 | * The type options for each split. 10 | */ 11 | export type SplitOptions = SplitProps & { 12 | splitterType: SplitterType; 13 | }; 14 | 15 | /** 16 | * The state for tracking a split. 17 | */ 18 | export type SplitNode = { 19 | id: string; 20 | options?: SplitOptions; 21 | primaryId?: string; 22 | secondaryId?: string; 23 | }; 24 | -------------------------------------------------------------------------------- /demo/src/stylesheet.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"); 2 | 3 | body { 4 | height: 100vh; 5 | position: absolute; 6 | top: 0; 7 | right: 0; 8 | bottom: 0; 9 | left: 0; 10 | overflow: hidden; 11 | padding: 0; 12 | margin: 0; 13 | } 14 | 15 | body, button { 16 | font-family: 'Roboto', 'Segoe UI', Tahoma, 'Geneva', Verdana, sans-serif; 17 | font-size: 10pt; 18 | } 19 | 20 | body, 21 | div { 22 | box-sizing: border-box; 23 | } 24 | 25 | #app { 26 | position: relative; 27 | height: 100%; 28 | box-sizing: border-box; 29 | } 30 | -------------------------------------------------------------------------------- /demo/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RecoilRoot } from 'recoil'; 3 | import styled from 'styled-components'; 4 | import { Main } from './Main'; 5 | 6 | const Root = styled.div` 7 | width: 100%; 8 | height: 100%; 9 | outline: none; 10 | overflow: hidden; 11 | `; 12 | 13 | /** 14 | * The root user interface of the application. 15 | */ 16 | export const App = () => { 17 | // This demo leverages recoil for application state management. 18 | // Recoil is NOT a requirement to use the react-splitter. 19 | return ( 20 | 21 | 22 |
23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /demo/src/model/appModel.ts: -------------------------------------------------------------------------------- 1 | import { atom, atomFamily } from 'recoil'; 2 | import { SplitNode, SplitOptions } from './types'; 3 | 4 | /** 5 | * The state of each split by pane ID. 6 | */ 7 | export const splitStateFamily = atomFamily({ 8 | key: 'splitStateFamily', 9 | default: (id) => { 10 | return { id }; 11 | }, 12 | }); 13 | 14 | /** 15 | * The current split options for new splits. 16 | */ 17 | export const createSplitOptions = atom({ 18 | key: 'createSplitOptions', 19 | default: { 20 | horizontal: false, 21 | initialPrimarySize: '50%', 22 | minPrimarySize: '0px', 23 | minSecondarySize: '0px', 24 | splitterSize: '7px', 25 | splitterType: 'default', 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /package/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "declaration": true, 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "jsx": "react", 9 | "module": "es6", 10 | "moduleResolution": "node", 11 | "outDir": "build", 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitReturns": true, 14 | "noUnusedLocals": true, 15 | "noImplicitAny": true, 16 | "noImplicitThis": true, 17 | "noUnusedParameters": true, 18 | "strictNullChecks": true, 19 | "sourceMap": true, 20 | "strict": true, 21 | "target": "es6" 22 | }, 23 | "include": ["./src/**/*"], 24 | "exclude": ["node_modules", "build", "dist"] 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Geoff Cox 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 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build:dev": "webpack --config webpack.config.js --mode development", 8 | "build:prod": "webpack --config webpack.config.js --mode production" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@types/react": ">=16.8.0 <19.0.0", 14 | "@types/react-dom": ">=16.8.0 <19.0.0", 15 | "@types/react-measure": "^2.0.8", 16 | "@types/styled-components": "^5.1.26", 17 | "@types/uuid": "^9.0.0", 18 | "ts-loader": "^9.4.2", 19 | "css-loader": "^6.7.3", 20 | "html-webpack-plugin": "^5.5.0", 21 | "html-webpack-template": "^6.2.0", 22 | "source-map-loader": "^4.0.1", 23 | "style-loader": "^3.3.1", 24 | "typescript": "^4.9.4", 25 | "webpack": "^5.75.0 ", 26 | "webpack-cli": "^5.0.1" 27 | }, 28 | "dependencies": { 29 | "@geoffcox/react-splitter": "^2.1.2", 30 | "react": ">=16.8.0 <19.0.0", 31 | "react-dom": ">=16.8.0 <19.0.0", 32 | "react-measure": "^2.5.2", 33 | "recoil": "^0.7.6", 34 | "styled-components": "^5.3.5", 35 | "uuid": "^9.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package/src/DefaultSplitter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | import './defaultSplitter.css'; 4 | 5 | const getThinLineSize = (size: number) => `${size % 2 === 0 ? 2 : 3}px`; 6 | const getCenteredMargin = (size: number) => `${Math.max(0, Math.floor(size / 2) - 1)}px`; 7 | 8 | type Props = RenderSplitterProps & { 9 | color?: string; 10 | hoverColor?: string; 11 | dragColor?: string; 12 | }; 13 | 14 | /** 15 | * The default splitter which provides a thin line within a larger mouse hit area. 16 | */ 17 | export const DefaultSplitter = (props: Props) => { 18 | const { dragging, pixelSize, color = 'silver', hoverColor = 'gray', dragColor = 'black' } = props; 19 | 20 | const cssProperties = { 21 | '--default-splitter-line-margin': getCenteredMargin(pixelSize), 22 | '--default-splitter-line-size': getThinLineSize(pixelSize), 23 | '--default-splitter-line-color': dragging ? dragColor : color, 24 | '--default-splitter-line-hover-color': dragging ? dragColor : hoverColor, 25 | } as React.CSSProperties; 26 | 27 | return ( 28 |
29 |
30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /demo/src/components/Main.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | import { DynamicPane } from './DynamicPane'; 4 | import { SplitOptionsEditor } from './SplitOptionsEditor'; 5 | 6 | const fullDivCss = css` 7 | width: 100%; 8 | height: 100%; 9 | outline: none; 10 | overflow: hidden; 11 | `; 12 | 13 | const Root = styled.div` 14 | ${fullDivCss} 15 | display: grid; 16 | grid-template-rows: auto 1fr auto; 17 | grid-template-columns: 1fr; 18 | grid-template-areas: 'header' 'content' 'footer'; 19 | `; 20 | 21 | const Header = styled.div` 22 | font-size: 14pt; 23 | width: 100%; 24 | outline: none; 25 | overflow: hidden; 26 | grid-area: header; 27 | padding: 10px; 28 | background: #222; 29 | color: #ddd; 30 | `; 31 | 32 | const Content = styled.div` 33 | width: 100%; 34 | outline: none; 35 | overflow: hidden; 36 | grid-area: content; 37 | `; 38 | 39 | const Footer = styled.div` 40 | width: 100%; 41 | outline: none; 42 | overflow: hidden; 43 | grid-area: footer; 44 | background: #222; 45 | color: #ddd; 46 | `; 47 | 48 | export const Main = () => { 49 | return ( 50 | 51 |
@geoffcox/react-splitter demo
52 | 53 | 54 | 55 |
56 | 57 |
58 |
59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /package/src/defaultSplitter.css: -------------------------------------------------------------------------------- 1 | 2 | /* The default splitter within a react-split */ 3 | .react-split > .split-container > .splitter .default-splitter { 4 | box-sizing: border-box; 5 | height: 100%; 6 | outline: none; 7 | overflow: hidden; 8 | user-select: none; 9 | width: 100%; 10 | --default-splitter-line-color: silver; 11 | --default-splitter-line-hover-color: black; 12 | --default-splitter-line-margin: 2px; 13 | --default-splitter-line-size: 3px; 14 | } 15 | 16 | .react-split > .split-container.horizontal > .splitter .default-splitter { 17 | cursor: row-resize; 18 | } 19 | 20 | .react-split > .split-container.vertical > .splitter .default-splitter { 21 | cursor: col-resize; 22 | } 23 | 24 | /* The thin line within a default splitter hit area */ 25 | .react-split > .split-container > .splitter .default-splitter > .line { 26 | background: var(--default-splitter-line-color); 27 | } 28 | 29 | .react-split > .split-container > .splitter .default-splitter:hover > .line { 30 | background: var(--default-splitter-line-hover-color); 31 | } 32 | 33 | .react-split > .split-container.horizontal > .splitter .default-splitter > .line { 34 | height: var(--default-splitter-line-size); 35 | width: 100%; 36 | margin-top: var(--default-splitter-line-margin); 37 | margin-left: 0; 38 | } 39 | 40 | .react-split > .split-container.vertical > .splitter .default-splitter > .line { 41 | height: 100%; 42 | width: var(--default-splitter-line-size); 43 | margin-top: 0; 44 | margin-left: var(--default-splitter-line-margin); 45 | } -------------------------------------------------------------------------------- /package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@geoffcox/react-splitter", 3 | "version": "2.1.2", 4 | "description": "A splitter for React components that leverages CSS grid templates for simple and consistent resizing.", 5 | "main": "build/index.js", 6 | "files": [ 7 | "build/" 8 | ], 9 | "scripts": { 10 | "build:dev": "webpack --config webpack.config.js --mode development", 11 | "build:prod": "webpack --config webpack.config.js --mode production" 12 | }, 13 | "author": "Geoff Cox", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/GeoffCox/react-splitter/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/GeoffCox/react-splitter.git", 21 | "directory":"package" 22 | }, 23 | "homepage": "https://github.com/GeoffCox/react-splitter/", 24 | "keywords": [ 25 | "splitter", 26 | "resize", 27 | "resizer", 28 | "pane", 29 | "grid template", 30 | "react", 31 | "typescript" 32 | ], 33 | "devDependencies": { 34 | "@types/react": ">=16.8.0 <19.0.0", 35 | "@types/react-dom": ">=16.8.0 <19.0.0", 36 | "@types/react-measure": "^2.0.8", 37 | "ts-loader": "^9.4.2", 38 | "css-loader": "^6.7.3", 39 | "source-map-loader": "^4.0.1", 40 | "style-loader": "^3.3.1", 41 | "typescript": "^4.9.4", 42 | "webpack": "^5.75.0 ", 43 | "webpack-cli": "^5.0.1" 44 | }, 45 | "dependencies": { 46 | "react-measure": "^2.5.2" 47 | }, 48 | "peerDependencies": { 49 | "react": ">=16.8.0 <19.0.0", 50 | "react-dom": ">=16.8.0 <19.0.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /package/dist/2.0.0/Split.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | export declare type SplitProps = { 4 | /** 5 | * Add this attribute or set to true to create a top/bottom split. 6 | * Set to false to create a left|right split. 7 | */ 8 | horizontal?: boolean; 9 | /** 10 | * The initial width/height of the left/top pane. 11 | * Width is specified as a CSS unit (e.g. %, fr, px) 12 | */ 13 | initialPrimarySize?: string; 14 | /** 15 | * The preferred minimum width/height of the left/top pane. 16 | * Specified as a CSS unit (e.g. %, fr, px) 17 | */ 18 | minPrimarySize?: string; 19 | /** 20 | * The preferred minimum width/height of the right/bottom pane. 21 | * Specified as a CSS unit (e.g. %, fr, px) 22 | */ 23 | minSecondarySize?: string; 24 | /** 25 | * The width of the splitter between the left and right panes. 26 | * Specified as a CSS unit (e.g. %, fr, px) 27 | */ 28 | splitterSize?: string; 29 | /** 30 | * Render props for the splitter. 31 | * @param pixelSize The measured size of the splitter in pixels. 32 | * @param horizontal True if the splitter is horizontal (i.e. top/bottom); false otherwise. 33 | */ 34 | renderSplitter?: (props: RenderSplitterProps) => React.ReactNode | undefined; 35 | /** 36 | * When true, if the user double clicks the splitter it will reset to its initial position. 37 | * The default is false. 38 | */ 39 | resetOnDoubleClick?: boolean; 40 | /** 41 | * The colors to use for the default splitter. 42 | * Only used when renderSplitter is undefined; 43 | * The defaults are silver, gray, and black 44 | */ 45 | defaultSplitterColors?: { 46 | color: string; 47 | hover: string; 48 | drag: string; 49 | }; 50 | }; 51 | export declare const Split: (props: React.PropsWithChildren) => JSX.Element; 52 | -------------------------------------------------------------------------------- /package/dist/2.0.1/Split.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | export declare type SplitProps = { 4 | /** 5 | * Add this attribute or set to true to create a top/bottom split. 6 | * Set to false to create a left|right split. 7 | */ 8 | horizontal?: boolean; 9 | /** 10 | * The initial width/height of the left/top pane. 11 | * Width is specified as a CSS unit (e.g. %, fr, px) 12 | */ 13 | initialPrimarySize?: string; 14 | /** 15 | * The preferred minimum width/height of the left/top pane. 16 | * Specified as a CSS unit (e.g. %, fr, px) 17 | */ 18 | minPrimarySize?: string; 19 | /** 20 | * The preferred minimum width/height of the right/bottom pane. 21 | * Specified as a CSS unit (e.g. %, fr, px) 22 | */ 23 | minSecondarySize?: string; 24 | /** 25 | * The width of the splitter between the left and right panes. 26 | * Specified as a CSS unit (e.g. %, fr, px) 27 | */ 28 | splitterSize?: string; 29 | /** 30 | * Render props for the splitter. 31 | * @param pixelSize The measured size of the splitter in pixels. 32 | * @param horizontal True if the splitter is horizontal (i.e. top/bottom); false otherwise. 33 | */ 34 | renderSplitter?: (props: RenderSplitterProps) => React.ReactNode | undefined; 35 | /** 36 | * When true, if the user double clicks the splitter it will reset to its initial position. 37 | * The default is false. 38 | */ 39 | resetOnDoubleClick?: boolean; 40 | /** 41 | * The colors to use for the default splitter. 42 | * Only used when renderSplitter is undefined; 43 | * The defaults are silver, gray, and black 44 | */ 45 | defaultSplitterColors?: { 46 | color: string; 47 | hover: string; 48 | drag: string; 49 | }; 50 | }; 51 | export declare const Split: (props: React.PropsWithChildren) => JSX.Element; 52 | -------------------------------------------------------------------------------- /demo/src/components/LayoutIcons.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | 4 | const fullDivCss = css` 5 | width: 100%; 6 | height: 100%; 7 | outline: none; 8 | overflow: hidden; 9 | `; 10 | 11 | const Root = styled.div` 12 | ${fullDivCss} 13 | border: 2px solid darkgray; 14 | margin: 5px; 15 | user-select: none; 16 | `; 17 | 18 | const Pane = styled.div` 19 | ${fullDivCss} 20 | background: lightgray; 21 | `; 22 | 23 | const VSplit = styled.div` 24 | ${fullDivCss} 25 | background: darkgray; 26 | width: 2px; 27 | `; 28 | 29 | const HSplit = styled.div` 30 | ${fullDivCss} 31 | background: darkgray; 32 | height: 2px; 33 | `; 34 | 35 | const LeftRightLayout = styled.div.attrs( 36 | ({ leftWidth, rightWidth }: { leftWidth: string; rightWidth: string }): any => ({ 37 | style: { 38 | gridTemplateColumns: `${leftWidth} auto ${rightWidth}`, 39 | }, 40 | }) 41 | )` 42 | ${fullDivCss} 43 | display: grid; 44 | grid-template-rows: 1fr; 45 | `; 46 | 47 | const TopBottomLayout = styled.div.attrs( 48 | ({ topHeight, bottomHeight }: { topHeight: string; bottomHeight: string }): any => ({ 49 | style: { 50 | gridTemplateRows: `${topHeight} auto ${bottomHeight}`, 51 | }, 52 | }) 53 | )` 54 | ${fullDivCss} 55 | display: grid; 56 | grid-template-columns: 1fr; 57 | `; 58 | 59 | /** 60 | * The icon representing a left|right split. 61 | */ 62 | export const LeftRightLayoutIcon = () => { 63 | return ( 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | ); 72 | }; 73 | 74 | /** 75 | * The icon representing a top/bottom split. 76 | */ 77 | export const TopBottomLayoutIcon = () => { 78 | return ( 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ); 87 | }; 88 | -------------------------------------------------------------------------------- /demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const HtmlWebpackTemplate = require("html-webpack-template"); 4 | var webpack = require('webpack'); 5 | 6 | // package.json contains the version number of the dependencies 7 | // that we want to make external. Parsing the package.json 8 | // makes it automatic to keep the package version in sync with 9 | // the CDN URL used in the HtmlWebpackPlugin 10 | const packageJson = require(join(__dirname, 'package.json')); 11 | 12 | // This is the object webpack looks at for configuration. 13 | // Webpack doesn't care about any other javascript in the file. 14 | // Because this is javascript, you can write functions to help build up the configuration. 15 | module.exports = { 16 | 17 | // Tells webpack what kind of source maps to produce. 18 | // There are a lot of options, but I chose the standalone file option. 19 | devtool: "source-map", 20 | 21 | // Tells webpack where start walking the dependencies to build a bundle. 22 | entry: { 23 | app: [ 24 | join(__dirname, "src/index.tsx") 25 | ] 26 | }, 27 | 28 | // When the env is "development", this tells webpack to provide debuggable information in the source maps and turns off some optimizations. 29 | mode: process.env.NODE_ENV, 30 | 31 | // Tells webpack how to run file transformation pipeline of webpack. 32 | // Awesome-typescript-loader will run on all typescript files. 33 | // Source-map-loader will run on the JS files. 34 | module: { 35 | rules: [ 36 | // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. 37 | { test: /\.tsx?$/, loader: "ts-loader" }, 38 | 39 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 40 | { enforce: "pre", test: /\.js?$/, loader: "source-map-loader" }, 41 | 42 | { test: /\.css$/, use: ['style-loader', 'css-loader'] }, 43 | ] 44 | }, 45 | 46 | // Tells webpack not to touch __dirname and __filename. 47 | // If you run the bundle in node.js it falls back to these values of node.js. 48 | // https://github.com/webpack/webpack/issues/2010 49 | node: { 50 | __dirname: false, 51 | __filename: false 52 | }, 53 | 54 | // Tells webpack where to output the bundled javascript 55 | output: { 56 | filename: "index.js", 57 | path: join(__dirname, "dist") 58 | }, 59 | 60 | // Tells the HTML webpack plug-in to use a template and emit dist/index.html 61 | plugins: [ 62 | new HtmlWebpackPlugin({ 63 | title: "react-splitter demo", 64 | inject: false, 65 | template: HtmlWebpackTemplate, 66 | appMountId: "app", 67 | scripts: [ 68 | 'index.js' 69 | ] 70 | }) 71 | ], 72 | 73 | // Tells webpack what file extesions it should look at. 74 | resolve: { 75 | extensions: [".ts", ".tsx", ".js", ".json"] 76 | } 77 | }; -------------------------------------------------------------------------------- /package/dist/2.0.2/Split.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | export declare type SplitMeasuredSizes = { 4 | /** 5 | * The measured size of the primary pane in pixels. 6 | */ 7 | primary: number; 8 | /** 9 | * The measured size of the splitter in pixels. 10 | */ 11 | splitter: number; 12 | /** 13 | * The measured size of the secondary pane in pixels. 14 | */ 15 | secondary: number; 16 | }; 17 | export declare type SplitProps = { 18 | /** 19 | * Add this attribute or set to true to create a top/bottom split. 20 | * Set to false to create a left|right split. 21 | */ 22 | horizontal?: boolean; 23 | /** 24 | * The initial width/height of the left/top pane. 25 | * Width is specified as a CSS unit (e.g. %, fr, px). 26 | * The default is 50%. 27 | */ 28 | initialPrimarySize?: string; 29 | /** 30 | * The preferred minimum width/height of the left/top pane. 31 | * Specified as a CSS unit (e.g. %, fr, px). 32 | * The default is 0. 33 | */ 34 | minPrimarySize?: string; 35 | /** 36 | * The preferred minimum width/height of the right/bottom pane. 37 | * Specified as a CSS unit (e.g. %, fr, px). 38 | * The default is 0. 39 | */ 40 | minSecondarySize?: string; 41 | /** 42 | * The width of the splitter between the panes. 43 | * Specified as a CSS unit (e.g. %, fr, px). 44 | * The default is 7px. 45 | */ 46 | splitterSize?: string; 47 | /** 48 | * Render props for the splitter. 49 | * @param pixelSize The measured size of the splitter in pixels. 50 | * @param horizontal True if the splitter is horizontal (i.e. top/bottom); false otherwise. 51 | */ 52 | renderSplitter?: (props: RenderSplitterProps) => React.ReactNode | undefined; 53 | /** 54 | * When true, if the user double clicks the splitter it will reset to its initial position. 55 | * The default is false. 56 | */ 57 | resetOnDoubleClick?: boolean; 58 | /** 59 | * The colors to use for the default splitter. 60 | * Only used when renderSplitter is undefined; 61 | * The defaults are silver, gray, and black 62 | */ 63 | defaultSplitterColors?: { 64 | color: string; 65 | hover: string; 66 | drag: string; 67 | }; 68 | /** 69 | * Raised when the splitter moves to provide the primary size. 70 | * When the user has adjusted the splitter, this will be a percentage. 71 | * Otherwise, this will be the initialPrimarySize. 72 | */ 73 | onSplitChanged?: (primarySize: string) => void; 74 | /** 75 | * Raised whenever the primary, splitter, or secondary measured sizes change. 76 | * These values are debounced at 500ms to prevent spamming this event. 77 | */ 78 | onMeasuredSizesChanged?: (sizes: SplitMeasuredSizes) => void; 79 | }; 80 | export declare const Split: (props: React.PropsWithChildren) => JSX.Element; 81 | -------------------------------------------------------------------------------- /package/dist/2.0.3/Split.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | export declare type SplitMeasuredSizes = { 4 | /** 5 | * The measured size of the primary pane in pixels. 6 | */ 7 | primary: number; 8 | /** 9 | * The measured size of the splitter in pixels. 10 | */ 11 | splitter: number; 12 | /** 13 | * The measured size of the secondary pane in pixels. 14 | */ 15 | secondary: number; 16 | }; 17 | export declare type SplitProps = { 18 | /** 19 | * Add this attribute or set to true to create a top/bottom split. 20 | * Set to false to create a left|right split. 21 | */ 22 | horizontal?: boolean; 23 | /** 24 | * The initial width/height of the left/top pane. 25 | * Width is specified as a CSS unit (e.g. %, fr, px). 26 | * The default is 50%. 27 | */ 28 | initialPrimarySize?: string; 29 | /** 30 | * The preferred minimum width/height of the left/top pane. 31 | * Specified as a CSS unit (e.g. %, fr, px). 32 | * The default is 0. 33 | */ 34 | minPrimarySize?: string; 35 | /** 36 | * The preferred minimum width/height of the right/bottom pane. 37 | * Specified as a CSS unit (e.g. %, fr, px). 38 | * The default is 0. 39 | */ 40 | minSecondarySize?: string; 41 | /** 42 | * The width of the splitter between the panes. 43 | * Specified as a CSS unit (e.g. %, fr, px). 44 | * The default is 7px. 45 | */ 46 | splitterSize?: string; 47 | /** 48 | * Render props for the splitter. 49 | * @param pixelSize The measured size of the splitter in pixels. 50 | * @param horizontal True if the splitter is horizontal (i.e. top/bottom); false otherwise. 51 | */ 52 | renderSplitter?: (props: RenderSplitterProps) => React.ReactNode | undefined; 53 | /** 54 | * When true, if the user double clicks the splitter it will reset to its initial position. 55 | * The default is false. 56 | */ 57 | resetOnDoubleClick?: boolean; 58 | /** 59 | * The colors to use for the default splitter. 60 | * Only used when renderSplitter is undefined; 61 | * The defaults are silver, gray, and black 62 | */ 63 | defaultSplitterColors?: { 64 | color: string; 65 | hover: string; 66 | drag: string; 67 | }; 68 | /** 69 | * Raised when the splitter moves to provide the primary size. 70 | * When the user has adjusted the splitter, this will be a percentage. 71 | * Otherwise, this will be the initialPrimarySize. 72 | */ 73 | onSplitChanged?: (primarySize: string) => void; 74 | /** 75 | * Raised whenever the primary, splitter, or secondary measured sizes change. 76 | * These values are debounced at 500ms to prevent spamming this event. 77 | */ 78 | onMeasuredSizesChanged?: (sizes: SplitMeasuredSizes) => void; 79 | }; 80 | export declare const Split: (props: React.PropsWithChildren) => JSX.Element; 81 | -------------------------------------------------------------------------------- /package/dist/2.1.2/Split.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | import './split.css'; 4 | export type SplitMeasuredSizes = { 5 | /** 6 | * The measured size of the primary pane in pixels. 7 | */ 8 | primary: number; 9 | /** 10 | * The measured size of the splitter in pixels. 11 | */ 12 | splitter: number; 13 | /** 14 | * The measured size of the secondary pane in pixels. 15 | */ 16 | secondary: number; 17 | }; 18 | export type SplitProps = { 19 | /** 20 | * Add this attribute or set to true to create a top/bottom split. 21 | * Set to false to create a left|right split. 22 | */ 23 | horizontal?: boolean; 24 | /** 25 | * The initial width/height of the left/top pane. 26 | * Width is specified as a CSS unit (e.g. %, fr, px). 27 | * The default is 50%. 28 | */ 29 | initialPrimarySize?: string; 30 | /** 31 | * The preferred minimum width/height of the left/top pane. 32 | * Specified as a CSS unit (e.g. %, fr, px). 33 | * The default is 0. 34 | */ 35 | minPrimarySize?: string; 36 | /** 37 | * The preferred minimum width/height of the right/bottom pane. 38 | * Specified as a CSS unit (e.g. %, fr, px). 39 | * The default is 0. 40 | */ 41 | minSecondarySize?: string; 42 | /** 43 | * The width of the splitter between the panes. 44 | * Specified as a CSS unit (e.g. %, fr, px). 45 | * The default is 7px. 46 | */ 47 | splitterSize?: string; 48 | /** 49 | * Render props for the splitter. 50 | * @param pixelSize The measured size of the splitter in pixels. 51 | * @param horizontal True if the splitter is horizontal (i.e. top/bottom); false otherwise. 52 | */ 53 | renderSplitter?: (props: RenderSplitterProps) => React.ReactNode | undefined; 54 | /** 55 | * When true, if the user double clicks the splitter it will reset to its initial position. 56 | * The default is false. 57 | */ 58 | resetOnDoubleClick?: boolean; 59 | /** 60 | * The colors to use for the default splitter. 61 | * Only used when renderSplitter is undefined; 62 | * The defaults are silver, gray, and black 63 | */ 64 | defaultSplitterColors?: { 65 | color: string; 66 | hover: string; 67 | drag: string; 68 | }; 69 | /** 70 | * Raised when the splitter moves to provide the primary size. 71 | * When the user has adjusted the splitter, this will be a percentage. 72 | * Otherwise, this will be the initialPrimarySize. 73 | */ 74 | onSplitChanged?: (primarySize: string) => void; 75 | /** 76 | * Raised whenever the primary, splitter, or secondary measured sizes change. 77 | * These values are debounced at 500ms to prevent spamming this event. 78 | */ 79 | onMeasuredSizesChanged?: (sizes: SplitMeasuredSizes) => void; 80 | }; 81 | export declare const Split: (props: React.PropsWithChildren) => JSX.Element; 82 | -------------------------------------------------------------------------------- /package/dist/2.1.0/Split.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RenderSplitterProps } from './RenderSplitterProps'; 3 | import './split.css'; 4 | export declare type SplitMeasuredSizes = { 5 | /** 6 | * The measured size of the primary pane in pixels. 7 | */ 8 | primary: number; 9 | /** 10 | * The measured size of the splitter in pixels. 11 | */ 12 | splitter: number; 13 | /** 14 | * The measured size of the secondary pane in pixels. 15 | */ 16 | secondary: number; 17 | }; 18 | export declare type SplitProps = { 19 | /** 20 | * Add this attribute or set to true to create a top/bottom split. 21 | * Set to false to create a left|right split. 22 | */ 23 | horizontal?: boolean; 24 | /** 25 | * The initial width/height of the left/top pane. 26 | * Width is specified as a CSS unit (e.g. %, fr, px). 27 | * The default is 50%. 28 | */ 29 | initialPrimarySize?: string; 30 | /** 31 | * The preferred minimum width/height of the left/top pane. 32 | * Specified as a CSS unit (e.g. %, fr, px). 33 | * The default is 0. 34 | */ 35 | minPrimarySize?: string; 36 | /** 37 | * The preferred minimum width/height of the right/bottom pane. 38 | * Specified as a CSS unit (e.g. %, fr, px). 39 | * The default is 0. 40 | */ 41 | minSecondarySize?: string; 42 | /** 43 | * The width of the splitter between the panes. 44 | * Specified as a CSS unit (e.g. %, fr, px). 45 | * The default is 7px. 46 | */ 47 | splitterSize?: string; 48 | /** 49 | * Render props for the splitter. 50 | * @param pixelSize The measured size of the splitter in pixels. 51 | * @param horizontal True if the splitter is horizontal (i.e. top/bottom); false otherwise. 52 | */ 53 | renderSplitter?: (props: RenderSplitterProps) => React.ReactNode | undefined; 54 | /** 55 | * When true, if the user double clicks the splitter it will reset to its initial position. 56 | * The default is false. 57 | */ 58 | resetOnDoubleClick?: boolean; 59 | /** 60 | * The colors to use for the default splitter. 61 | * Only used when renderSplitter is undefined; 62 | * The defaults are silver, gray, and black 63 | */ 64 | defaultSplitterColors?: { 65 | color: string; 66 | hover: string; 67 | drag: string; 68 | }; 69 | /** 70 | * Raised when the splitter moves to provide the primary size. 71 | * When the user has adjusted the splitter, this will be a percentage. 72 | * Otherwise, this will be the initialPrimarySize. 73 | */ 74 | onSplitChanged?: (primarySize: string) => void; 75 | /** 76 | * Raised whenever the primary, splitter, or secondary measured sizes change. 77 | * These values are debounced at 500ms to prevent spamming this event. 78 | */ 79 | onMeasuredSizesChanged?: (sizes: SplitMeasuredSizes) => void; 80 | }; 81 | export declare const Split: (props: React.PropsWithChildren) => JSX.Element; 82 | -------------------------------------------------------------------------------- /package/src/split.css: -------------------------------------------------------------------------------- 1 | /* The top-level element of the splitter*/ 2 | .react-split { 3 | width: 100%; 4 | height: 100%; 5 | box-sizing: border-box; 6 | outline: none; 7 | overflow: hidden; 8 | --react-split-min-primary: 0; 9 | --react-split-min-secondary: 0; 10 | --react-split-primary: 50%; 11 | --react-split-splitter: 7px; 12 | } 13 | 14 | /* The container for the primary pane, splitter, and secondary pane.*/ 15 | .react-split > .split-container { 16 | width: 100%; 17 | height: 100%; 18 | box-sizing: border-box; 19 | outline: none; 20 | overflow: hidden; 21 | display: grid; 22 | } 23 | 24 | /* When the container is splitting horizontally */ 25 | .react-split > .split-container.horizontal { 26 | grid-template-columns: 1fr; 27 | grid-template-rows: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr); 28 | grid-template-areas: "primary" "split" "secondary"; 29 | } 30 | 31 | /* When the container is splitting vertical */ 32 | .react-split > .split-container.vertical { 33 | grid-template-columns: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr); 34 | grid-template-rows: 1fr; 35 | grid-template-areas: "primary split secondary"; 36 | } 37 | 38 | /* The primary pane. This is either the left or top depending on the split type */ 39 | .react-split > .split-container > .primary { 40 | grid-area: primary; 41 | box-sizing: border-box; 42 | outline: none; 43 | overflow: hidden; 44 | } 45 | 46 | .react-split > .split-container.horizontal > .primary { 47 | height: auto; 48 | width: 100%; 49 | } 50 | 51 | .react-split > .split-container.vertical > .primary { 52 | height: 100%; 53 | width: auto; 54 | } 55 | 56 | /* The splitter between panes. */ 57 | .react-split > .split-container > .splitter { 58 | grid-area: split; 59 | background: transparent; 60 | user-select: none; 61 | box-sizing: border-box; 62 | outline: none; 63 | overflow: hidden; 64 | } 65 | 66 | .react-split > .split-container.horizontal > .splitter { 67 | height: auto; 68 | width: 100%; 69 | cursor: row-resize; 70 | } 71 | 72 | .react-split > .split-container.vertical > .splitter { 73 | height: 100%; 74 | width: auto; 75 | cursor: col-resize; 76 | } 77 | 78 | /* The secondary pane. This is either the right or bottom depending on the split type */ 79 | .react-split > .split-container >.secondary { 80 | grid-area: secondary; 81 | box-sizing: border-box; 82 | outline: none; 83 | overflow: hidden; 84 | } 85 | 86 | .react-split > .split-container.horizontal > .secondary { 87 | height: auto; 88 | width: 100%; 89 | } 90 | 91 | .react-split > .split-container.vertical > .secondary { 92 | height: 100%; 93 | width: auto; 94 | } 95 | 96 | /* The content within the primary pane, splitter, or secondary pane.*/ 97 | .react-split .full-content { 98 | width: 100%; 99 | height: 100%; 100 | box-sizing: border-box; 101 | outline: none; 102 | overflow: hidden; 103 | } 104 | -------------------------------------------------------------------------------- /demo/src/components/CustomSplitters.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css, keyframes } from 'styled-components'; 2 | 3 | const fullDivCss = css` 4 | width: 100%; 5 | height: 100%; 6 | outline: none; 7 | overflow: hidden; 8 | `; 9 | 10 | /** 11 | * A splitter with a solid background taking up the full hit area. 12 | */ 13 | export const SolidSplitter = styled.div` 14 | ${fullDivCss} 15 | background: silver; 16 | `; 17 | 18 | const stripeVars = css` 19 | --stripe-size: 50px; 20 | --color1: silver; 21 | --color2: gray; 22 | --duration: 2s; 23 | `; 24 | 25 | const verticalStripeAnimation = keyframes` 26 | ${stripeVars} 27 | 0% { 28 | transform: translateX(0); 29 | } 30 | 100% { 31 | transform: translateX(calc(var(--stripe-size) * -1)); 32 | } 33 | `; 34 | 35 | const horizontalStripeAnimation = keyframes` 36 | ${stripeVars} 37 | 0% { 38 | transform: translateX(calc(var(--stripe-size) * -1)); 39 | } 40 | 100% { 41 | transform: translateX(0); 42 | } 43 | `; 44 | 45 | /** 46 | * An animated, striped splitter for left|right splits. 47 | */ 48 | export const VerticalStripedSplitter = styled.div` 49 | ${stripeVars} 50 | 51 | position: relative; 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | width: 100%; 56 | height: 100%; 57 | 58 | &::before { 59 | content: ''; 60 | position: absolute; 61 | top: 0; 62 | left: 0; 63 | width: calc(100% + var(--stripe-size)); 64 | height: 100%; 65 | background: repeating-linear-gradient( 66 | 45deg, 67 | var(--color2) 25%, 68 | var(--color2) 50%, 69 | var(--color1) 50%, 70 | var(--color1) 75% 71 | ); 72 | background-size: var(--stripe-size) var(--stripe-size); 73 | animation: ${verticalStripeAnimation} var(--duration) linear infinite; 74 | } 75 | 76 | &::after { 77 | content: ''; 78 | position: absolute; 79 | width: 100%; 80 | height: 100%; 81 | background: radial-gradient(ellipse at center, rgba(#1b2735, 0) 0%, #090a0f 100%); 82 | } 83 | `; 84 | 85 | /** 86 | * An animated, striped splitter for top/bottom splits. 87 | */ 88 | export const HorizontalStripedSplitter = styled.div` 89 | ${stripeVars} 90 | 91 | position: relative; 92 | display: flex; 93 | justify-content: center; 94 | align-items: center; 95 | width: 100%; 96 | height: 100%; 97 | 98 | &::before { 99 | content: ''; 100 | position: absolute; 101 | top: 0; 102 | left: 0; 103 | width: calc(100% + var(--stripe-size)); 104 | height: 100%; 105 | background: repeating-linear-gradient( 106 | 45deg, 107 | var(--color2) 25%, 108 | var(--color2) 50%, 109 | var(--color1) 50%, 110 | var(--color1) 75% 111 | ); 112 | background-size: var(--stripe-size) var(--stripe-size); 113 | animation: ${horizontalStripeAnimation} var(--duration) linear infinite; 114 | } 115 | 116 | &::after { 117 | content: ''; 118 | position: absolute; 119 | width: 100%; 120 | height: 100%; 121 | background: radial-gradient(ellipse at center, rgba(#1b2735, 0) 0%, #090a0f 100%); 122 | } 123 | `; 124 | -------------------------------------------------------------------------------- /package/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { join, resolve } = require("path"); 2 | var webpack = require('webpack'); 3 | 4 | // package.json contains the version number of the dependencies 5 | // that we want to make external. Parsing the package.json 6 | // makes it automatic to keep the package version in sync with 7 | // the CDN URL used in the HtmlWebpackPlugin 8 | const packageJson = require(join(__dirname, 'package.json')); 9 | 10 | // This is the object webpack looks at for configuration. 11 | // Webpack doesn't care about any other javascript in the file. 12 | // Because this is javascript, you can write functions to help build up the configuration. 13 | module.exports = { 14 | 15 | // Tells webpack what kind of source maps to produce. 16 | // There are a lot of options, but I chose the standalone file option. 17 | devtool: "source-map", 18 | 19 | // Tells webpack where start walking the dependencies to build a bundle. 20 | entry: { 21 | app: [ 22 | join(__dirname, "src/index.tsx") 23 | ] 24 | }, 25 | 26 | // When importing a module whose path matches one of the following, just 27 | // assume a corresponding global variable exists and use that instead. 28 | // This is important because it allows us to avoid bundling all of our 29 | // dependencies, which allows browsers to cache standard libraries like React once. 30 | externals: { 31 | 'react': { 32 | 'commonjs': 'react', 33 | 'commonjs2': 'react', 34 | 'amd': 'react', 35 | // React dep should be available as window.React, not window.react 36 | 'root': 'React' 37 | }, 38 | 'react-dom': { 39 | 'commonjs': 'react-dom', 40 | 'commonjs2': 'react-dom', 41 | 'amd': 'react-dom', 42 | 'root': 'ReactDOM' 43 | } 44 | }, 45 | 46 | // When the env is "development", this tells webpack to provide debuggable information in the source maps and turns off some optimizations. 47 | mode: process.env.NODE_ENV, 48 | 49 | // Tells webpack how to run file transformation pipeline of webpack. 50 | // Awesome-typescript-loader will run on all typescript files. 51 | // Source-map-loader will run on the JS files. 52 | module: { 53 | rules: [ 54 | // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. 55 | { test: /\.tsx?$/, loader: "ts-loader" }, 56 | 57 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 58 | { enforce: "pre", test: /\.js?$/, loader: "source-map-loader" }, 59 | 60 | { test: /\.css$/, use: ['style-loader', 'css-loader'] }, 61 | ] 62 | }, 63 | 64 | // Tells webpack not to touch __dirname and __filename. 65 | // If you run the bundle in node.js it falls back to these values of node.js. 66 | // https://github.com/webpack/webpack/issues/2010 67 | node: { 68 | __dirname: false, 69 | __filename: false 70 | }, 71 | 72 | // Tells webpack where to output the bundled javascript 73 | output: { 74 | filename: "index.js", 75 | library: 'ReactSplitter', 76 | libraryTarget: 'umd', 77 | umdNamedDefine: true, 78 | path: join(__dirname, "build") 79 | }, 80 | 81 | // Tells the HTML webpack plug-in to use a template and emit dist/index.html 82 | plugins: [ 83 | ], 84 | 85 | // Tells webpack what file extesions it should look at. 86 | resolve: { 87 | alias: { 88 | 'react': resolve(__dirname, './node_modules/react') , 89 | 'react-dom': resolve(__dirname, './node_modules/react-dom'), 90 | }, 91 | extensions: [".ts", ".tsx", ".js", ".json"] 92 | } 93 | }; -------------------------------------------------------------------------------- /UsingEmotionStyles.md: -------------------------------------------------------------------------------- 1 | # This is a placeholder for using emotion rather than styled-components 2 | 3 | ## Split Styles 4 | 5 | ```tsx 6 | const FullDiv = styled.div` 7 | box-sizing: border-box; 8 | outline: none; 9 | overflow: hidden; 10 | width: 100%; 11 | height: 100%; 12 | `; 13 | 14 | // To improve performance, the dynamic style feature of styled components puts the template row &columns into the style property. 15 | const Root = styled.div( 16 | ({ 17 | horizontal, 18 | primarySize, 19 | splitterSize, 20 | minPrimarySize, 21 | minSecondarySize, 22 | }: { 23 | horizontal: boolean; 24 | primarySize: string; 25 | splitterSize: string; 26 | minPrimarySize: string; 27 | minSecondarySize: string; 28 | }) => ({ 29 | boxSizing: 'border-box', 30 | outline: 'none', 31 | overflow: 'hidden', 32 | width: '100%', 33 | height: '100%', 34 | display: 'grid', 35 | label: 'Split', 36 | gridTemplateAreas: horizontal ? '"primary" "split" "secondary"' : '"primary split secondary"', 37 | gridTemplateColumns: horizontal 38 | ? '1fr' 39 | : `minmax(${minPrimarySize},${primarySize}) ${splitterSize} minmax(${minSecondarySize}, 1fr)`, 40 | gridTemplateRows: horizontal 41 | ? `minmax(${minPrimarySize},${primarySize}) ${splitterSize} minmax(${minSecondarySize}, 1fr)` 42 | : '1fr', 43 | }) 44 | ); 45 | 46 | const Primary = styled.div(({ horizontal }: { horizontal: boolean }) => ({ 47 | boxSizing: 'border-box', 48 | outline: 'none', 49 | overflow: 'hidden', 50 | height: horizontal ? 'auto' : '100%', 51 | width: horizontal ? '100%' : 'auto', 52 | gridArea: 'primary', 53 | label: 'SplitPrimary', 54 | })); 55 | 56 | const Splitter = styled.div(({ horizontal }: { horizontal: boolean }) => ({ 57 | boxSizing: 'border-box', 58 | outline: 'none', 59 | overflow: 'hidden', 60 | height: horizontal ? 'auto' : '100%', 61 | width: horizontal ? '100%' : 'auto', 62 | cursor: horizontal ? 'row-resize' : 'col-resize', 63 | gridArea: 'split', 64 | background: 'transparent', 65 | userSelect: 'none', 66 | label: 'SplitSplitter', 67 | })); 68 | 69 | const Secondary = styled.div(({ horizontal }: { horizontal: boolean }) => ({ 70 | boxSizing: 'border-box', 71 | outline: 'none', 72 | overflow: 'hidden', 73 | height: horizontal ? 'auto' : '100%', 74 | width: horizontal ? '100%' : 'auto', 75 | gridArea: 'secondary', 76 | label: 'SplitSecondary', 77 | })); 78 | ``` 79 | 80 | ## DefaultSplitter styles 81 | ```tsx 82 | const HitArea = styled.div(({ horizontal, hoverColor }: { horizontal: boolean; hoverColor: string }) => ({ 83 | boxSizing: 'border-box', 84 | outline: 'none', 85 | overflow: 'hidden', 86 | height: '100%', 87 | width: '100%', 88 | cursor: horizontal ? 'row-resize' : 'col-resize', 89 | background: 'transparent', 90 | '%:hover .default-split-visual': { 91 | background: `${hoverColor}`, 92 | }, 93 | userSelect: 'none', 94 | })); 95 | 96 | const getThinLineSize = (size: number) => `${size % 2 === 0 ? 2 : 3}px`; 97 | const getCenteredMargin = (size: number) => `${Math.max(0, Math.floor(size / 2) - 1)}px`; 98 | 99 | const Splitter = styled.div( 100 | ({ horizontal, splitterSize, color }: { horizontal: boolean; splitterSize: number; color: string }) => ({ 101 | boxSizing: 'border-box', 102 | outline: 'none', 103 | overflow: 'hidden', 104 | height: horizontal ? getThinLineSize(splitterSize) : '100%', 105 | width: horizontal ? '100%' : getThinLineSize(splitterSize), 106 | marginLeft: horizontal ? '0' : getCenteredMargin(splitterSize), 107 | marginTop: horizontal ? getCenteredMargin(splitterSize) : '0', 108 | background: `${color}`, 109 | }) 110 | ); 111 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @geoffcox/react-splitter 2 | 3 | A resizable splitter for React that leverages CSS display:grid 4 | 5 | [Live Demo](https://geoffcox.github.io/react-splitter-demo/index.html) 6 | 7 | ## Features 8 | - Vertical (left|right) and Horizontal (top/bottom) splitting 9 | - Set initial split size 10 | - Reset to initial split with double-click 11 | - Minimum pane sizes 12 | - Sizes can be any CSS unit (e.g. px, %, fr, em, pt, cm, mm) 13 | - Nested splits with correct resizing 14 | - Customize the splitter size, colors, or render it yourself. 15 | 16 | ## Technology 17 | - This splitter is built using React and Typescript. 18 | - CSS grid combined with a simple percentage split system provides accurate and responsive resizing. 19 | - Styled components provide high performance using dynamic styles. 20 | 21 | # Installation and Usage 22 | See [package/Readme.md](package/README.md) 23 | 24 | # Known limitations 25 | 26 | These are several limitations of the current release. 27 | 28 | - No keyboard control of splitter. The splitter can only be controlled with the mouse. 29 | - Resizing is immediate as the user drags the splitter. The resize may need to be debounced to correct visual update performance problems. 30 | - No callbacks for monitoring the split or pane resizing. For now, a workaround is to use something like react-measure to wrap children and observe sizing changes. 31 | 32 | If you overcome these limitations in your own code, pull requests are appreciated. :-) 33 | 34 | # Change History 35 | 36 | ## 1.0.0 - First project publication 37 | 38 | ## 1.0.1 - Bug fixes 39 | - CSS was incorrect on the splitter preventing hover state 40 | - Peer dependencies should not have included styled-components nor react-measure as npm no longer auto-installs peer dependencies. 41 | 42 | ## 1.0.2 - Bug fixes 43 | - Rollup config was incorrect causing 1.0.1 to not be published correctly. 44 | 45 | ## 1.0.3 - Measurement reaction bug fix 46 | - Resize of split was not reacting to overall area decreasing where the splitter gets hidden 47 | 48 | ## 1.0.4 - Custom splitter rendering 49 | - Added optional splitterWidth/splitterHeight to props to allow caller to control splitter size 50 | - Added option renderSplitter function to allow caller to render a custom splitter 51 | - Updated default splitter to be thin line with same 5px hit area 52 | - Updated demo to optionally show custom rendered splitter 53 | - Fixed bug with cursor on top/bottom splitter 54 | 55 | ## 2.0.0 - Overhauled 56 | - Collapsed LeftRightSplit and TopBottomSplit into a single Split component 57 | - Leveraged minmax to remove the need for any complex math 58 | - Changed mouse events to pointer events for improved responsiveness 59 | - Fixed issues with dragging when browser is zoomed in 60 | - Added support for rest on double-click 61 | - Added support for default splitter colors 62 | - Passed key properties to splitter render props 63 | - Improved default splitter layout 64 | - Added customization control to the demo 65 | - Moved default splitter to separate module 66 | 67 | ## 2.0.1 - Reduced size 68 | - Removed map files from distribution 69 | 70 | ## 2.0.2 - Provided events 71 | - Added onSplitChanged to provide the primarySize as the splitter changes. 72 | - Added onMeasuredSizesChanged to provide content, left pane, splitter, and right pane pixels sizes as the splitter changes. 73 | 74 | ## 2.0.3 - Bug fixes 75 | - Export RenderSplitterProps from package 76 | - Export DefaultSplitter from package 77 | 78 | ## 2.1.0 - Remove styled-components dependency 79 | - Updated the Split and DefaultSplitter React components to use CSS variables rather than take a dependency on styled-components 80 | - Switched from rollup to webpack to support CSS-in-JS without requiring styled-components 81 | - Updated all HTML elements in Split with semantic class names. 82 | 83 | ## 2.1.1 - Fix issue with horizontal prop changes 84 | - Thanks to Quang Ngo for finding this issue and following up with me to get it fixed! 85 | - Updated Split to track dimensions and properly update if the horizontal prop changes. 86 | - Removed debounce. Since moving to CSS and CSS vars, it is no longer neccessary. 87 | -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | # @geoffcox/react-splitter 2 | 3 | A resizable splitter for React that leverages CSS display:grid 4 | 5 | [Live Demo](https://geoffcox.github.io/react-splitter-demo/index.html) 6 | 7 | ## Overview 8 | See the [../Readme.md](../README.md) for features, version history, etc. 9 | 10 | # Installation 11 | 12 | ``` 13 | npm install --save @geoffcox/react-splitter 14 | ``` 15 | # Usage 16 | 17 | To create vertical or horizontal splits you only need the `Split` component. 18 | 19 | ## Vertical Split 20 | The default creates a left|right split down the middle, no minimum pane sizes, and renders the default splitter. 21 | 22 | ```tsx 23 | 24 |
This is the left pane.
25 |
This is the right pane.
26 | 27 | ``` 28 | 29 | ## Horizontal Split 30 | Add the `horizontal` attribute to split top/bottom. 31 | 32 | ```tsx 33 | 34 |
This is the top pane.
35 |
This is the bottom pane.
36 | 37 | ``` 38 | 39 | ## Set the initial split size 40 | Add the `initialPrimarySize` property to control where the initial split occurs. 41 | 42 | ```tsx 43 | 44 |
Primary pane
45 |
Secondary pane
46 | 47 | ``` 48 | 49 | To support double-clicking to reset back to the initial size, add the `resetOnDoubleClick` property. 50 | 51 | ```tsx 52 | 53 |
Primary pane
54 |
Secondary pane
55 | 56 | ``` 57 | 58 | ## Nest Splits 59 | Just nest Split componets to create whatever layout you like. 60 | Here is an example of a common layout for a main, detail, and output view. 61 | 62 | ```tsx 63 | 64 |
This is the left pane.
65 | 66 |
This is the right-top pane.
67 |
This is the right-bottom pane.
68 |
69 |
70 | ``` 71 | ## Constrain minimum pane sizes 72 | You can prevent either pane from becoming too small using the `minPrimarySize` and `minSecondarySize` properties. 73 | For vertical splits, primary is the left pane and secondary is the right pane. 74 | For horizontal splits, primary is the top pane and secondary is the bottom pane. 75 | 76 | ```tsx 77 | 78 |
This pane won't get smaller than 250 pixels.
79 |
This pane won't get any smaller than 15% of the overall size of the split control./
80 | 81 | ``` 82 | 83 | ## Customize the splitter size 84 | You can set the size of the hit area (where the user can click to start draggin the splitter) with the `splitterSize` property. 85 | 86 | ```tsx 87 | 88 |
Primary pane
89 |
Secondary pane
90 | 91 | ``` 92 | ## Customize the splitter colors 93 | You can change the colors of the default splitter with the `defaultSplitterColors` property. 94 | 95 | ```tsx 96 | const colors = { 97 | color: 'red', 98 | hover: '#00FF00', 99 | drag: 'blue' 100 | }; 101 | 102 | 103 |
Primary pane
104 |
Secondary pane
105 | 106 | ``` 107 | ## Custom render the splitter 108 | You can render your own splitter by passing a callback to the `renderSplitter` property. 109 | 110 | ```tsx 111 | const renderSplitter = (props: RenderSplitterProps) => { 112 | return
Your splitter code goes here.
113 | }; 114 | 115 | 116 |
Primary pane
117 |
Secondary pane
118 | 119 | ``` 120 | 121 | The callback receives the `RenderSplitterProps` to let you know the current size of the splitter, if the split is horizontal, and if the splitter is currently being dragged. 122 | 123 | ```ts 124 | export type RenderSplitterProps = { 125 | pixelSize: number; 126 | horizontal: boolean; 127 | dragging: boolean; 128 | }; 129 | ``` 130 | 131 | ## Monitor changes 132 | You can use event callbacks to monitor changes to the primary size and the measured sizes. The primary size is a CSS unit string (percentage or initial size). The measured sizes are pixels sizes. 133 | 134 | ```tsx 135 | const onSplitChanged = (primarySize: string) => { 136 | console.log(`The split is: ${primarySize}`); 137 | }; 138 | 139 | const onMeasuredSizesChanged = (sizes: SplitMeasuredPixelSizes) => { 140 | console.log(`The primary pane is: ${sizes.primary}px`); 141 | console.log(`The splitter is: ${sizes.splitter}px`); 142 | console.log(`The secondary pane is: ${sizes.secondary}px`); 143 | }; 144 | 145 | 146 |
Primary pane
147 |
Secondary pane
148 | 149 | ``` 150 | # Integrating into a web application 151 | 152 | If you are using a style framework like Fluent, Material UI, or Bootstrap then your root div will likely have CSS styles applied that help this splitter work correctly. 153 | If you have no root stylesheet, you might have problems with vertical scrolling. 154 | 155 | Here are some recommended CSS properties for your top-level divs if you are building a single-page application. In this case #app is the mounting point for React.Render. You can see this approach used in the demo application. 156 | 157 | ```css 158 | body { 159 | height: 100vh; 160 | position: absolute; 161 | top: 0; 162 | right: 0; 163 | bottom: 0; 164 | left: 0; 165 | overflow: hidden; 166 | padding: 0; 167 | margin: 0; 168 | } 169 | 170 | body, 171 | div { 172 | box-sizing: border-box; 173 | } 174 | 175 | #app { 176 | position: relative; 177 | box-sizing: border-box; 178 | width: 100%; 179 | height: 100%; 180 | outline: none; 181 | overflow: hidden; 182 | } 183 | ``` -------------------------------------------------------------------------------- /demo/src/components/SplitOptionsEditor.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useRecoilState } from 'recoil'; 3 | import styled, { css } from 'styled-components'; 4 | import { createSplitOptions } from '../model/appModel'; 5 | import { SplitterType } from '../model/types'; 6 | import { LeftRightLayoutIcon, TopBottomLayoutIcon } from './LayoutIcons'; 7 | 8 | const fullDivCss = css` 9 | width: 100%; 10 | height: 100%; 11 | outline: none; 12 | overflow: hidden; 13 | `; 14 | 15 | const Root = styled.div` 16 | ${fullDivCss} 17 | display: grid; 18 | grid-template-rows: auto auto 1fr; 19 | grid-template-columns: 1fr; 20 | grid-template-areas: 'header' 'options' 'content'; 21 | `; 22 | 23 | const PropertyGrid = styled.div` 24 | display: grid; 25 | grid-template-columns: minmax(175px, auto) auto; 26 | grid-auto-flow: row; 27 | align-content: start; 28 | justify-content: start; 29 | margin: 20px; 30 | gap: 15px 10px; 31 | `; 32 | 33 | const PropertyLabel = styled.label` 34 | margin: 3px 0 0 0; 35 | `; 36 | 37 | const RadioOptions = styled.div` 38 | display: flex; 39 | flex-direction: row; 40 | align-items: center; 41 | `; 42 | 43 | const RadioOption = styled.div` 44 | display: flex; 45 | flex-direction: row; 46 | align-items: center; 47 | margin: 0 15px 0 0; 48 | `; 49 | 50 | const PropertyInput = styled.input` 51 | width: 150px; 52 | `; 53 | 54 | const SplitDirectionRadio = styled.input` 55 | margin: 0 5px 0 0; 56 | `; 57 | 58 | const SplitDirectionIcon = styled.div` 59 | width: 85px; 60 | height: 50px; 61 | `; 62 | 63 | /** 64 | * A form to allows the user to set the different options for new splits. 65 | */ 66 | export const SplitOptionsEditor = () => { 67 | const [options, setOptions] = useRecoilState(createSplitOptions); 68 | 69 | const onHorizontalChanged = (horizontal: boolean) => { 70 | setOptions({ 71 | ...options, 72 | horizontal, 73 | }); 74 | }; 75 | 76 | const onInitialPrimarySizeChanged = (initialPrimarySize: string) => { 77 | setOptions({ 78 | ...options, 79 | initialPrimarySize, 80 | }); 81 | }; 82 | 83 | const onMinPrimarySizeChanged = (minPrimarySize: string) => { 84 | setOptions({ 85 | ...options, 86 | minPrimarySize, 87 | }); 88 | }; 89 | 90 | const onMinSecondarySizeChanged = (minSecondarySize: string) => { 91 | setOptions({ 92 | ...options, 93 | minSecondarySize, 94 | }); 95 | }; 96 | 97 | const onSplitterSizeChanged = (splitterSize: string) => { 98 | setOptions({ 99 | ...options, 100 | splitterSize, 101 | }); 102 | }; 103 | 104 | const onSplitterTypeChanged = (splitterType: string) => { 105 | setOptions({ 106 | ...options, 107 | splitterType: splitterType as SplitterType, 108 | }); 109 | }; 110 | 111 | const { horizontal, initialPrimarySize, minPrimarySize, minSecondarySize, splitterSize, splitterType } = options; 112 | 113 | return ( 114 | 115 | 116 | Split Direction 117 | 118 | 119 | onHorizontalChanged(!e.target.checked)} 126 | /> 127 | 132 | 133 | 134 | onHorizontalChanged(e.target.checked)} 141 | /> 142 | 147 | 148 | 149 | Initial Primary Size 150 | onInitialPrimarySizeChanged(e.target.value)} 155 | /> 156 | Min Primary Size 157 | onMinPrimarySizeChanged(e.target.value)} 162 | /> 163 | Min Secondary Size 164 | onMinSecondarySizeChanged(e.target.value)} 169 | /> 170 | Splitter Size 171 | onSplitterSizeChanged(e.target.value)} 176 | /> 177 | Splitter Type 178 | 179 | 180 | onSplitterTypeChanged(e.target.value)} 186 | /> 187 | 188 | 189 | 190 | onSplitterTypeChanged(e.target.value)} 196 | /> 197 | 198 | 199 | 200 | onSplitterTypeChanged(e.target.value)} 206 | /> 207 | 208 | 209 | 210 | 211 | 212 | ); 213 | }; 214 | -------------------------------------------------------------------------------- /demo/src/components/DynamicPane.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DefaultSplitter, Split, RenderSplitterProps } from '@geoffcox/react-splitter'; 3 | import styled, { css } from 'styled-components'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; 6 | import { createSplitOptions, splitStateFamily } from '../model/appModel'; 7 | import { SplitNode } from '../model/types'; 8 | import { VerticalStripedSplitter, HorizontalStripedSplitter, SolidSplitter } from './CustomSplitters'; 9 | 10 | const fullDivCss = css` 11 | width: 100%; 12 | height: 100%; 13 | outline: none; 14 | overflow: hidden; 15 | `; 16 | 17 | const Root = styled.div` 18 | ${fullDivCss} 19 | display: grid; 20 | grid-template-rows: 1fr; 21 | grid-template-columns: 1fr; 22 | grid-template-areas: 'content'; 23 | user-select: none; 24 | `; 25 | 26 | const ActionsArea = styled.div` 27 | display: flex; 28 | flex-direction: column; 29 | justify-content: stretch; 30 | align-content: stretch; 31 | align-items: stretch; 32 | outline: none; 33 | overflow: hidden; 34 | `; 35 | 36 | const ActionButtons = styled.div` 37 | display: flex; 38 | flex-direction: row; 39 | flex-wrap: wrap; 40 | justify-content: center; 41 | align-content: center; 42 | align-items: center; 43 | outline: none; 44 | overflow: hidden; 45 | margin: auto auto; 46 | `; 47 | 48 | const ActionButton = styled.button` 49 | padding: 5px; 50 | margin: 0 0 5px 5px; 51 | user-select: none; 52 | `; 53 | 54 | type Props = { 55 | id: string; 56 | onRemove?: (childId: string) => void; 57 | onToggleParentSplit?: () => void; 58 | }; 59 | 60 | /** 61 | * A pane that can be split recursively. 62 | * @param props 63 | */ 64 | export const DynamicPane = (props: Props) => { 65 | const { id, onRemove, onToggleParentSplit } = props; 66 | 67 | const [splitNode, setSplitNode] = useRecoilState(splitStateFamily(id)); 68 | const { options, primaryId, secondaryId } = splitNode; 69 | const createOptions = useRecoilValue(createSplitOptions); 70 | 71 | // When a split occurs, a unique ID is assigned to each pane for tracking later. 72 | // This is for demo purposes and not necessary to use the react-splitter. 73 | const onSplit = () => { 74 | const primaryId = uuidv4(); 75 | const secondaryId = uuidv4(); 76 | const newNode: SplitNode = { 77 | ...splitNode, 78 | options: createOptions, 79 | primaryId, 80 | secondaryId, 81 | }; 82 | 83 | setSplitNode(newNode); 84 | }; 85 | 86 | // When the child pane notifies it wants to be removed, the remaining pane should 'replace' this pane. 87 | // We do this by saving the remaining pane's split options as this pane's split options. 88 | // Finally, we clear up the child and remaining state. 89 | const onRemoveChildPane = useRecoilCallback( 90 | ({ snapshot, set, reset }) => 91 | async (childId: string) => { 92 | const node = await snapshot.getPromise(splitStateFamily(id)); 93 | 94 | const remainingId = 95 | node?.primaryId === childId ? node.secondaryId : node?.secondaryId === childId ? node.primaryId : undefined; 96 | 97 | if (remainingId === undefined) { 98 | return; 99 | } 100 | 101 | const remainingNode = await snapshot.getPromise(splitStateFamily(remainingId)); 102 | 103 | set(splitStateFamily(id), { 104 | ...remainingNode, 105 | id: node.id, 106 | }); 107 | 108 | reset(splitStateFamily(childId)); 109 | reset(splitStateFamily(remainingId)); 110 | }, 111 | [id] 112 | ); 113 | 114 | const onToggleSplit = () => { 115 | const newNode: SplitNode = { 116 | ...splitNode, 117 | options: { 118 | ...createOptions, 119 | horizontal: !splitNode.options?.horizontal, 120 | }, 121 | }; 122 | 123 | setSplitNode(newNode); 124 | }; 125 | 126 | const renderActions = () => { 127 | return ( 128 | 129 | 130 | Split 131 | {onToggleParentSplit && ( 132 | onToggleParentSplit?.()} title="Toggle Parent Split"> 133 | / 134 | 135 | )} 136 | {onRemove && ( 137 | onRemove(id)} title="Remove Split"> 138 | X 139 | 140 | )} 141 | 142 | 143 | ); 144 | }; 145 | 146 | const renderSplitter = (renderSplitterProps: RenderSplitterProps) => { 147 | const { horizontal } = renderSplitterProps; 148 | switch (options?.splitterType) { 149 | case 'solid': 150 | return ; 151 | case 'striped': 152 | return horizontal ? : ; 153 | default: 154 | return ; 155 | } 156 | }; 157 | 158 | const renderLeftRightSplit = () => { 159 | return ( 160 | options && ( 161 | 169 | {primaryId ? ( 170 | 171 | ) : ( 172 |
ERROR
173 | )} 174 | {secondaryId ? ( 175 | 176 | ) : ( 177 |
ERROR
178 | )} 179 |
180 | ) 181 | ); 182 | }; 183 | 184 | const renderTopBottomSplit = () => { 185 | return ( 186 | 195 | {primaryId ? ( 196 | 197 | ) : ( 198 |
ERROR
199 | )} 200 | {secondaryId ? ( 201 | 202 | ) : ( 203 |
ERROR
204 | )} 205 |
206 | ); 207 | }; 208 | 209 | const renderLayout = () => { 210 | return options ? (options.horizontal ? renderTopBottomSplit() : renderLeftRightSplit()) : renderActions(); 211 | }; 212 | 213 | return {renderLayout()}; 214 | }; 215 | -------------------------------------------------------------------------------- /package/src/Split.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { default as Measure, ContentRect } from 'react-measure'; 3 | import { DefaultSplitter } from './DefaultSplitter'; 4 | import { RenderSplitterProps } from './RenderSplitterProps'; 5 | import './split.css'; 6 | 7 | type MeasuredDimensions = { 8 | height: number; 9 | width: number; 10 | }; 11 | 12 | export type SplitMeasuredSizes = { 13 | /** 14 | * The measured size of the primary pane in pixels. 15 | */ 16 | primary: number; 17 | /** 18 | * The measured size of the splitter in pixels. 19 | */ 20 | splitter: number; 21 | /** 22 | * The measured size of the secondary pane in pixels. 23 | */ 24 | secondary: number; 25 | }; 26 | 27 | export type SplitProps = { 28 | /** 29 | * Add this attribute or set to true to create a top/bottom split. 30 | * Set to false to create a left|right split. 31 | */ 32 | horizontal?: boolean; 33 | /** 34 | * The initial width/height of the left/top pane. 35 | * Width is specified as a CSS unit (e.g. %, fr, px). 36 | * The default is 50%. 37 | */ 38 | initialPrimarySize?: string; 39 | /** 40 | * The preferred minimum width/height of the left/top pane. 41 | * Specified as a CSS unit (e.g. %, fr, px). 42 | * The default is 0. 43 | */ 44 | minPrimarySize?: string; 45 | /** 46 | * The preferred minimum width/height of the right/bottom pane. 47 | * Specified as a CSS unit (e.g. %, fr, px). 48 | * The default is 0. 49 | */ 50 | minSecondarySize?: string; 51 | /** 52 | * The width of the splitter between the panes. 53 | * Specified as a CSS unit (e.g. %, fr, px). 54 | * The default is 7px. 55 | */ 56 | splitterSize?: string; 57 | /** 58 | * Render props for the splitter. 59 | * @param pixelSize The measured size of the splitter in pixels. 60 | * @param horizontal True if the splitter is horizontal (i.e. top/bottom); false otherwise. 61 | */ 62 | renderSplitter?: (props: RenderSplitterProps) => React.ReactNode | undefined; 63 | /** 64 | * When true, if the user double clicks the splitter it will reset to its initial position. 65 | * The default is false. 66 | */ 67 | resetOnDoubleClick?: boolean; 68 | /** 69 | * The colors to use for the default splitter. 70 | * Only used when renderSplitter is undefined; 71 | * The defaults are silver, gray, and black 72 | */ 73 | defaultSplitterColors?: { 74 | color: string; 75 | hover: string; 76 | drag: string; 77 | }; 78 | 79 | /** 80 | * Raised when the splitter moves to provide the primary size. 81 | * When the user has adjusted the splitter, this will be a percentage. 82 | * Otherwise, this will be the initialPrimarySize. 83 | */ 84 | onSplitChanged?: (primarySize: string) => void; 85 | 86 | /** 87 | * Raised whenever the primary, splitter, or secondary measured sizes change. 88 | * These values are debounced at 500ms to prevent spamming this event. 89 | */ 90 | onMeasuredSizesChanged?: (sizes: SplitMeasuredSizes) => void; 91 | }; 92 | 93 | export const Split = (props: React.PropsWithChildren): JSX.Element => { 94 | const { 95 | horizontal = false, 96 | initialPrimarySize = '50%', 97 | minPrimarySize = '0px', 98 | minSecondarySize = '0px', 99 | splitterSize = '7px', 100 | renderSplitter, 101 | resetOnDoubleClick = false, 102 | defaultSplitterColors = { 103 | color: 'silver', 104 | hover: 'gray', 105 | drag: 'black', 106 | }, 107 | onSplitChanged, 108 | onMeasuredSizesChanged, 109 | } = props; 110 | 111 | const [contentMeasuredDimensions, setContentMeasuredDimensions] = React.useState({ 112 | height: 0, 113 | width: 0, 114 | }); 115 | const [primaryMeasuredDimensions, setPrimaryMeasuredDimensions] = React.useState({ 116 | height: 0, 117 | width: 0, 118 | }); 119 | const [splitterMeasuredDimensions, setSplitterMeasuredDimensions] = React.useState({ 120 | height: 0, 121 | width: 0, 122 | }); 123 | 124 | const currentContentSize = React.useMemo( 125 | () => (horizontal ? contentMeasuredDimensions.height : contentMeasuredDimensions.width), 126 | [horizontal, contentMeasuredDimensions] 127 | ); 128 | const currentPrimarySize = React.useMemo( 129 | () => (horizontal ? primaryMeasuredDimensions.height : primaryMeasuredDimensions.width), 130 | [horizontal, primaryMeasuredDimensions] 131 | ); 132 | const currentSplitterSize = React.useMemo( 133 | () => (horizontal ? splitterMeasuredDimensions.height : splitterMeasuredDimensions.width), 134 | [horizontal, splitterMeasuredDimensions] 135 | ); 136 | 137 | const [percent, setPercent] = React.useState(undefined); 138 | 139 | const [clientStart, setClientStart] = React.useState(0); 140 | const [primaryStart, setPrimaryStart] = React.useState(0); 141 | const [dragging, setDragging] = React.useState(false); 142 | 143 | React.useEffect(() => { 144 | if (onSplitChanged) { 145 | onSplitChanged(percent !== undefined ? `${percent}%` : initialPrimarySize); 146 | } 147 | }, [percent, initialPrimarySize]); 148 | 149 | React.useEffect(() => { 150 | if (onMeasuredSizesChanged) { 151 | onMeasuredSizesChanged({ 152 | primary: currentPrimarySize, 153 | splitter: currentSplitterSize, 154 | secondary: currentContentSize - (currentPrimarySize + currentSplitterSize), 155 | }); 156 | } 157 | }, [horizontal, currentContentSize, currentPrimarySize, currentSplitterSize]); 158 | 159 | const onMeasureContent = (contentRect: ContentRect) => { 160 | contentRect.bounds && 161 | setContentMeasuredDimensions({ height: contentRect.bounds.height, width: contentRect.bounds.width }); 162 | }; 163 | 164 | const onMeasurePrimary = (contentRect: ContentRect) => { 165 | contentRect.bounds && 166 | setPrimaryMeasuredDimensions({ height: contentRect.bounds.height, width: contentRect.bounds.width }); 167 | }; 168 | 169 | const onMeasureSplitter = (contentRect: ContentRect) => { 170 | contentRect.bounds && 171 | setSplitterMeasuredDimensions({ height: contentRect.bounds.height, width: contentRect.bounds.width }); 172 | }; 173 | 174 | const onSplitPointerDown = (event: React.PointerEvent) => { 175 | event.currentTarget.setPointerCapture(event.pointerId); 176 | setClientStart(horizontal ? event.clientY : event.clientX); 177 | setPrimaryStart(currentPrimarySize); 178 | setDragging(true); 179 | }; 180 | 181 | const onSplitPointerMove = (event: React.PointerEvent) => { 182 | if (event.currentTarget.hasPointerCapture(event.pointerId)) { 183 | const position = horizontal ? event.clientY : event.clientX; 184 | const primarySize = primaryStart + (position - clientStart); 185 | const newPrimary = Math.max(0, Math.min(primarySize, currentContentSize)); 186 | const newPercent = (newPrimary / currentContentSize) * 100; 187 | setPercent(newPercent); 188 | } 189 | }; 190 | 191 | const onSplitPointerUp = (event: React.PointerEvent) => { 192 | event.currentTarget.releasePointerCapture(event.pointerId); 193 | setDragging(false); 194 | }; 195 | 196 | const onSplitDoubleClick = () => { 197 | resetOnDoubleClick && setPercent(undefined); 198 | }; 199 | 200 | const children = React.Children.toArray(props.children); 201 | const primaryChild = children.length > 0 ? children[0] :
; 202 | const secondaryChild = children.length > 1 ? children[1] :
; 203 | 204 | const renderSizes = { 205 | primary: percent !== undefined ? `${percent}%` : initialPrimarySize, 206 | minPrimary: minPrimarySize ?? '0px', 207 | minSecondary: minSecondarySize ?? '0px', 208 | }; 209 | 210 | const renderSplitterProps = { 211 | pixelSize: currentSplitterSize, 212 | horizontal, 213 | dragging: dragging, 214 | }; 215 | 216 | const renderSplitVisual = 217 | renderSplitter ?? 218 | (() => { 219 | return ( 220 | 225 | ); 226 | }); 227 | 228 | const rootClassName = horizontal ? 'split-container horizontal' : 'split-container vertical'; 229 | 230 | const rootStyle = { 231 | '--react-split-min-primary': renderSizes.minPrimary, 232 | '--react-split-min-secondary': renderSizes.minSecondary, 233 | '--react-split-primary': renderSizes.primary, 234 | '--react-split-splitter': splitterSize, 235 | } as React.CSSProperties; 236 | 237 | return ( 238 | 239 | {({ measureRef: contentRef }) => ( 240 |
241 |
242 |
243 | 244 | {({ measureRef: primaryRef }) => ( 245 |
246 | {primaryChild} 247 |
248 | )} 249 |
250 |
251 |
259 | 260 | {({ measureRef: splitterRef }) => ( 261 |
262 | {renderSplitVisual(renderSplitterProps)} 263 |
264 | )} 265 |
266 |
267 |
268 |
{secondaryChild}
269 |
270 |
271 |
272 | )} 273 |
274 | ); 275 | }; 276 | -------------------------------------------------------------------------------- /package/dist/2.1.0/index.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define("ReactSplitter",["react"],t):"object"==typeof exports?exports.ReactSplitter=t(require("react")):e.ReactSplitter=t(e.React)}(window,(function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=9)}([function(t,n){t.exports=e},function(e,t,n){e.exports=n(13)()},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=n(0);n(11);const i=e=>(e%2==0?2:3)+"px",o=e=>{const{dragging:t,pixelSize:n,color:o="silver",hoverColor:s="gray",dragColor:a="black"}=e,c={"--default-splitter-line-margin":(l=n,Math.max(0,Math.floor(l/2)-1)+"px"),"--default-splitter-line-size":i(n),"--default-splitter-line-color":t?a:o,"--default-splitter-line-hover-color":t?a:s};var l;return r.createElement("div",{className:"default-splitter",style:c},r.createElement("div",{className:"line"}))}},function(e,t,n){"use strict";var r,i=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},o=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),s=[];function a(e){for(var t=-1,n=0;n-1&&(n.client={top:e.clientTop,left:e.clientLeft,width:e.clientWidth,height:e.clientHeight}),t.indexOf("offset")>-1&&(n.offset={top:e.offsetTop,left:e.offsetLeft,width:e.offsetWidth,height:e.offsetHeight}),t.indexOf("scroll")>-1&&(n.scroll={top:e.scrollTop,left:e.scrollLeft,width:e.scrollWidth,height:e.scrollHeight}),t.indexOf("bounds")>-1){var r=e.getBoundingClientRect();n.bounds={top:r.top,right:r.right,bottom:r.bottom,left:r.left,width:r.width,height:r.height}}if(t.indexOf("margin")>-1){var i=getComputedStyle(e);n.margin={top:i?parseInt(i.marginTop):0,right:i?parseInt(i.marginRight):0,bottom:i?parseInt(i.marginBottom):0,left:i?parseInt(i.marginLeft):0}}return n}function f(e){return e&&e.ownerDocument&&e.ownerDocument.defaultView||window}var d,h,v,m,b=(d=function(e){var t=e.measure,n=e.measureRef,r=e.contentRect;return(0,e.children)({measure:t,measureRef:n,contentRect:r})},v=h=function(e){var t,n;function s(){for(var t,n=arguments.length,r=new Array(n),i=0;i=0||(i[n]=e[n]);return i}(e,["innerRef","onResize"]));return Object(r.createElement)(d,i({},t,{measureRef:this._handleRef,measure:this.measure,contentRect:this.state.contentRect}))},s}(r.Component),h.propTypes={client:a.a.bool,offset:a.a.bool,scroll:a.a.bool,bounds:a.a.bool,margin:a.a.bool,innerRef:a.a.oneOfType([a.a.object,a.a.func]),onResize:a.a.func},v);b.displayName="Measure",b.propTypes.children=a.a.func;var y=b,g=n(2);const _=(e,t)=>{const[n,i]=r.useState(e);return r.useEffect(()=>{const n=setTimeout(()=>{i(e)},t);return()=>{clearTimeout(n)}},[e,t]),n};n(16);const w=e=>{const{horizontal:t=!1,initialPrimarySize:n="50%",minPrimarySize:i="0px",minSecondarySize:o="0px",splitterSize:s="7px",renderSplitter:a,resetOnDoubleClick:c=!1,defaultSplitterColors:l={color:"silver",hover:"gray",drag:"black"},onSplitChanged:u,onMeasuredSizesChanged:p}=e,[f,d]=r.useState(0),[h,v]=r.useState(0),[m,b]=r.useState(0),[w,O]=r.useState(void 0),[x,E]=r.useState(0),[z,R]=r.useState(0),[T,S]=r.useState(!1),M=_(f,500),j=_(h,500),C=_(m,500);r.useEffect(()=>{u&&u(void 0!==w?w+"%":n)},[w,n]),r.useEffect(()=>{p&&p({primary:j,splitter:C,secondary:M-(j+C)})},[M,j,C]);const A=e=>{e.bounds&&v(t?e.bounds.height:e.bounds.width)},P=e=>{e.bounds&&b(t?e.bounds.height:e.bounds.width)},k=e=>{e.currentTarget.setPointerCapture(e.pointerId),E(t?e.clientY:e.clientX),R(h),S(!0)},D=e=>{if(e.currentTarget.hasPointerCapture(e.pointerId)){const n=t?e.clientY:e.clientX,r=z+(n-x),i=Math.max(0,Math.min(r,f));O(i/f*100)}},I=e=>{e.currentTarget.releasePointerCapture(e.pointerId),S(!1)},N=()=>{c&&O(void 0)},L=r.Children.toArray(e.children),W=L.length>0?L[0]:r.createElement("div",null),F=L.length>1?L[1]:r.createElement("div",null),q={primary:void 0!==w?w+"%":n,minPrimary:null!=i?i:"0px",minSecondary:null!=o?o:"0px"},B={pixelSize:m,horizontal:t,dragging:T},H=null!=a?a:()=>r.createElement(g.a,Object.assign({},B,{color:T?l.drag:l.color,hoverColor:T?l.drag:l.hover})),U=t?"split-container horizontal":"split-container vertical",V={"--react-split-min-primary":q.minPrimary,"--react-split-min-secondary":q.minSecondary,"--react-split-primary":q.primary,"--react-split-splitter":s};return r.createElement(y,{bounds:!0,onResize:e=>{e.bounds&&d(t?e.bounds.height:e.bounds.width)}},({measureRef:e})=>r.createElement("div",{className:"react-split",ref:e},r.createElement("div",{className:U,style:V},r.createElement("div",{className:"primary"},r.createElement(y,{bounds:!0,onResize:A},({measureRef:e})=>r.createElement("div",{className:"full-content",ref:e},W))),r.createElement("div",{className:"splitter",tabIndex:-1,onPointerDown:k,onPointerUp:I,onPointerMove:D,onDoubleClick:N},r.createElement(y,{bounds:!0,onResize:P},({measureRef:e})=>r.createElement("div",{className:"full-content",ref:e},H(B)))),r.createElement("div",{className:"secondary"},r.createElement("div",{className:"full-content"},F)))))}},function(e,t){function n(t,r){return e.exports=n=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},e.exports.default=e.exports,e.exports.__esModule=!0,n(t,r)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){"use strict";(function(e){var n=function(){if("undefined"!=typeof Map)return Map;function e(e,t){var n=-1;return e.some((function(e,r){return e[0]===t&&(n=r,!0)})),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(t){var n=e(this.__entries__,t),r=this.__entries__[n];return r&&r[1]},t.prototype.set=function(t,n){var r=e(this.__entries__,t);~r?this.__entries__[r][1]=n:this.__entries__.push([t,n])},t.prototype.delete=function(t){var n=this.__entries__,r=e(n,t);~r&&n.splice(r,1)},t.prototype.has=function(t){return!!~e(this.__entries__,t)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(e,t){void 0===t&&(t=null);for(var n=0,r=this.__entries__;n0},e.prototype.connect_=function(){r&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),a?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){r&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;s.some((function(e){return!!~n.indexOf(e)}))&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),l=function(e,t){for(var n=0,r=Object.keys(t);n0},e}(),w="undefined"!=typeof WeakMap?new WeakMap:new n,O=function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=c.getInstance(),r=new _(t,n,this);w.set(this,r)};["observe","unobserve","disconnect"].forEach((function(e){O.prototype[e]=function(){var t;return(t=w.get(this))[e].apply(t,arguments)}}));var x=void 0!==i.ResizeObserver?i.ResizeObserver:O;t.a=x}).call(this,n(15))},function(e,t,n){e.exports=n(10)},function(e,t,n){"use strict";n.r(t);var r=n(2);n.d(t,"DefaultSplitter",(function(){return r.a}));var i=n(5);for(var o in i)["default","DefaultSplitter"].indexOf(o)<0&&function(e){n.d(t,e,(function(){return i[e]}))}(o);var s=n(6);n.d(t,"Split",(function(){return s.a}))},function(e,t,n){var r=n(3),i=n(12);"string"==typeof(i=i.__esModule?i.default:i)&&(i=[[e.i,i,""]]);var o={insert:"head",singleton:!1};r(i,o);e.exports=i.locals||{}},function(e,t,n){(t=n(4)(!1)).push([e.i,"\r\n/* The default splitter within a react-split */\r\n.react-split > .split-container > .splitter .default-splitter {\r\n box-sizing: border-box;\r\n height: 100%;\r\n outline: none;\r\n overflow: hidden;\r\n user-select: none;\r\n width: 100%;\r\n --default-splitter-line-color: silver;\r\n --default-splitter-line-hover-color: black;\r\n --default-splitter-line-margin: 2px;\r\n --default-splitter-line-size: 3px;\r\n}\r\n\r\n.react-split > .split-container.horizontal > .splitter .default-splitter {\r\n cursor: row-resize;\r\n}\r\n\r\n.react-split > .split-container.vertical > .splitter .default-splitter {\r\n cursor: col-resize;\r\n}\r\n\r\n/* The thin line within a default splitter hit area */\r\n.react-split > .split-container > .splitter .default-splitter > .line {\r\n background: var(--default-splitter-line-color);\r\n}\r\n\r\n.react-split > .split-container > .splitter .default-splitter:hover > .line {\r\n background: var(--default-splitter-line-hover-color);\r\n}\r\n\r\n.react-split > .split-container.horizontal > .splitter .default-splitter > .line {\r\n height: var(--default-splitter-line-size);\r\n width: 100%;\r\n margin-top: var(--default-splitter-line-margin);\r\n margin-left: 0;\r\n}\r\n\r\n.react-split > .split-container.vertical > .splitter .default-splitter > .line {\r\n height: 100%;\r\n width: var(--default-splitter-line-size);\r\n margin-top: 0;\r\n margin-left: var(--default-splitter-line-margin);\r\n}",""]),e.exports=t},function(e,t,n){"use strict";var r=n(14);function i(){}function o(){}o.resetWarningCache=i,e.exports=function(){function e(e,t,n,i,o,s){if(s!==r){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:i};return n.PropTypes=n,n}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){var r=n(3),i=n(17);"string"==typeof(i=i.__esModule?i.default:i)&&(i=[[e.i,i,""]]);var o={insert:"head",singleton:!1};r(i,o);e.exports=i.locals||{}},function(e,t,n){(t=n(4)(!1)).push([e.i,'/* The top-level element of the splitter*/\r\n.react-split {\r\n width: 100%;\r\n height: 100%;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n --react-split-min-primary: 0;\r\n --react-split-min-secondary: 0;\r\n --react-split-primary: 50%;\r\n --react-split-splitter: 7px;\r\n}\r\n\r\n/* The container for the primary pane, splitter, and secondary pane.*/\r\n.react-split > .split-container {\r\n width: 100%;\r\n height: 100%;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n display: grid;\r\n}\r\n\r\n/* When the container is splitting horizontally */\r\n.react-split > .split-container.horizontal {\r\n grid-template-columns: 1fr;\r\n grid-template-rows: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr);\r\n grid-template-areas: "primary" "split" "secondary";\r\n}\r\n\r\n/* When the container is splitting vertical */\r\n.react-split > .split-container.vertical {\r\n grid-template-columns: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr);\r\n grid-template-rows: 1fr;\r\n grid-template-areas: "primary split secondary";\r\n}\r\n\r\n/* The primary pane. This is either the left or top depending on the split type */\r\n.react-split > .split-container > .primary {\r\n grid-area: primary;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n}\r\n\r\n.react-split > .split-container.horizontal > .primary {\r\n height: auto;\r\n width: 100%;\r\n}\r\n\r\n.react-split > .split-container.vertical > .primary {\r\n height: 100%;\r\n width: auto;\r\n}\r\n\r\n/* The splitter between panes. */\r\n.react-split > .split-container > .splitter {\r\n grid-area: split;\r\n background: transparent;\r\n user-select: none;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n}\r\n\r\n.react-split > .split-container.horizontal > .splitter {\r\n height: auto;\r\n width: 100%;\r\n cursor: row-resize;\r\n}\r\n\r\n.react-split > .split-container.vertical > .splitter {\r\n height: 100%;\r\n width: auto;\r\n cursor: col-resize;\r\n}\r\n\r\n/* The secondary pane. This is either the right or bottom depending on the split type */\r\n.react-split > .split-container >.secondary {\r\n grid-area: secondary;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n}\r\n\r\n.react-split > .split-container.horizontal > .secondary {\r\n height: auto;\r\n width: 100%;\r\n}\r\n\r\n.react-split > .split-container.vertical > .secondary {\r\n height: 100%;\r\n width: auto;\r\n}\r\n\r\n/* The content within the primary pane, splitter, or secondary pane.*/\r\n.react-split .full-content {\r\n width: 100%;\r\n height: 100%;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n}\r\n',""]),e.exports=t}])})); 2 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /package/dist/2.1.1/index.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define("ReactSplitter",["react"],t):"object"==typeof exports?exports.ReactSplitter=t(require("react")):e.ReactSplitter=t(e.React)}(window,(function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=9)}([function(t,n){t.exports=e},function(e,t,n){e.exports=n(13)()},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=n(0);n(11);const i=e=>(e%2==0?2:3)+"px",o=e=>{const{dragging:t,pixelSize:n,color:o="silver",hoverColor:s="gray",dragColor:a="black"}=e,c={"--default-splitter-line-margin":(l=n,Math.max(0,Math.floor(l/2)-1)+"px"),"--default-splitter-line-size":i(n),"--default-splitter-line-color":t?a:o,"--default-splitter-line-hover-color":t?a:s};var l;return r.createElement("div",{className:"default-splitter",style:c},r.createElement("div",{className:"line"}))}},function(e,t,n){"use strict";var r,i=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},o=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),s=[];function a(e){for(var t=-1,n=0;n-1&&(n.client={top:e.clientTop,left:e.clientLeft,width:e.clientWidth,height:e.clientHeight}),t.indexOf("offset")>-1&&(n.offset={top:e.offsetTop,left:e.offsetLeft,width:e.offsetWidth,height:e.offsetHeight}),t.indexOf("scroll")>-1&&(n.scroll={top:e.scrollTop,left:e.scrollLeft,width:e.scrollWidth,height:e.scrollHeight}),t.indexOf("bounds")>-1){var r=e.getBoundingClientRect();n.bounds={top:r.top,right:r.right,bottom:r.bottom,left:r.left,width:r.width,height:r.height}}if(t.indexOf("margin")>-1){var i=getComputedStyle(e);n.margin={top:i?parseInt(i.marginTop):0,right:i?parseInt(i.marginRight):0,bottom:i?parseInt(i.marginBottom):0,left:i?parseInt(i.marginLeft):0}}return n}function d(e){return e&&e.ownerDocument&&e.ownerDocument.defaultView||window}var f,h,v,m,b=(f=function(e){var t=e.measure,n=e.measureRef,r=e.contentRect;return(0,e.children)({measure:t,measureRef:n,contentRect:r})},v=h=function(e){var t,n;function s(){for(var t,n=arguments.length,r=new Array(n),i=0;i=0||(i[n]=e[n]);return i}(e,["innerRef","onResize"]));return Object(r.createElement)(f,i({},t,{measureRef:this._handleRef,measure:this.measure,contentRect:this.state.contentRect}))},s}(r.Component),h.propTypes={client:a.a.bool,offset:a.a.bool,scroll:a.a.bool,bounds:a.a.bool,margin:a.a.bool,innerRef:a.a.oneOfType([a.a.object,a.a.func]),onResize:a.a.func},v);b.displayName="Measure",b.propTypes.children=a.a.func;var g=b,y=n(2);n(16);const _=e=>{const{horizontal:t=!1,initialPrimarySize:n="50%",minPrimarySize:i="0px",minSecondarySize:o="0px",splitterSize:s="7px",renderSplitter:a,resetOnDoubleClick:c=!1,defaultSplitterColors:l={color:"silver",hover:"gray",drag:"black"},onSplitChanged:u,onMeasuredSizesChanged:p}=e,[d,f]=r.useState({height:0,width:0}),[h,v]=r.useState({height:0,width:0}),[m,b]=r.useState({height:0,width:0}),_=r.useMemo(()=>t?d.height:d.width,[t,d]),w=r.useMemo(()=>t?h.height:h.width,[t,h]),O=r.useMemo(()=>t?m.height:m.width,[t,m]),[x,E]=r.useState(void 0),[z,R]=r.useState(0),[T,S]=r.useState(0),[M,j]=r.useState(!1);r.useEffect(()=>{u&&u(void 0!==x?x+"%":n)},[x,n]),r.useEffect(()=>{p&&p({primary:w,splitter:O,secondary:_-(w+O)})},[t,_,w,O]);const C=e=>{e.bounds&&v({height:e.bounds.height,width:e.bounds.width})},A=e=>{e.bounds&&b({height:e.bounds.height,width:e.bounds.width})},P=e=>{e.currentTarget.setPointerCapture(e.pointerId),R(t?e.clientY:e.clientX),S(w),j(!0)},k=e=>{if(e.currentTarget.hasPointerCapture(e.pointerId)){const n=t?e.clientY:e.clientX,r=T+(n-z),i=Math.max(0,Math.min(r,_));E(i/_*100)}},D=e=>{e.currentTarget.releasePointerCapture(e.pointerId),j(!1)},I=()=>{c&&E(void 0)},N=r.Children.toArray(e.children),L=N.length>0?N[0]:r.createElement("div",null),W=N.length>1?N[1]:r.createElement("div",null),F={primary:void 0!==x?x+"%":n,minPrimary:null!=i?i:"0px",minSecondary:null!=o?o:"0px"},q={pixelSize:O,horizontal:t,dragging:M},B=null!=a?a:()=>r.createElement(y.a,Object.assign({},q,{color:M?l.drag:l.color,hoverColor:M?l.drag:l.hover})),H=t?"split-container horizontal":"split-container vertical",U={"--react-split-min-primary":F.minPrimary,"--react-split-min-secondary":F.minSecondary,"--react-split-primary":F.primary,"--react-split-splitter":s};return r.createElement(g,{bounds:!0,onResize:e=>{e.bounds&&f({height:e.bounds.height,width:e.bounds.width})}},({measureRef:e})=>r.createElement("div",{className:"react-split",ref:e},r.createElement("div",{className:H,style:U},r.createElement("div",{className:"primary"},r.createElement(g,{bounds:!0,onResize:C},({measureRef:e})=>r.createElement("div",{className:"full-content",ref:e},L))),r.createElement("div",{className:"splitter",tabIndex:-1,onPointerDown:P,onPointerUp:D,onPointerMove:k,onDoubleClick:I},r.createElement(g,{bounds:!0,onResize:A},({measureRef:e})=>r.createElement("div",{className:"full-content",ref:e},B(q)))),r.createElement("div",{className:"secondary"},r.createElement("div",{className:"full-content"},W)))))}},function(e,t){function n(t,r){return e.exports=n=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},e.exports.default=e.exports,e.exports.__esModule=!0,n(t,r)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){"use strict";(function(e){var n=function(){if("undefined"!=typeof Map)return Map;function e(e,t){var n=-1;return e.some((function(e,r){return e[0]===t&&(n=r,!0)})),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(t){var n=e(this.__entries__,t),r=this.__entries__[n];return r&&r[1]},t.prototype.set=function(t,n){var r=e(this.__entries__,t);~r?this.__entries__[r][1]=n:this.__entries__.push([t,n])},t.prototype.delete=function(t){var n=this.__entries__,r=e(n,t);~r&&n.splice(r,1)},t.prototype.has=function(t){return!!~e(this.__entries__,t)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(e,t){void 0===t&&(t=null);for(var n=0,r=this.__entries__;n0},e.prototype.connect_=function(){r&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),a?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){r&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;s.some((function(e){return!!~n.indexOf(e)}))&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),l=function(e,t){for(var n=0,r=Object.keys(t);n0},e}(),w="undefined"!=typeof WeakMap?new WeakMap:new n,O=function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=c.getInstance(),r=new _(t,n,this);w.set(this,r)};["observe","unobserve","disconnect"].forEach((function(e){O.prototype[e]=function(){var t;return(t=w.get(this))[e].apply(t,arguments)}}));var x=void 0!==i.ResizeObserver?i.ResizeObserver:O;t.a=x}).call(this,n(15))},function(e,t,n){e.exports=n(10)},function(e,t,n){"use strict";n.r(t);var r=n(2);n.d(t,"DefaultSplitter",(function(){return r.a}));var i=n(5);for(var o in i)["default","DefaultSplitter"].indexOf(o)<0&&function(e){n.d(t,e,(function(){return i[e]}))}(o);var s=n(6);n.d(t,"Split",(function(){return s.a}))},function(e,t,n){var r=n(3),i=n(12);"string"==typeof(i=i.__esModule?i.default:i)&&(i=[[e.i,i,""]]);var o={insert:"head",singleton:!1};r(i,o);e.exports=i.locals||{}},function(e,t,n){(t=n(4)(!1)).push([e.i,"\r\n/* The default splitter within a react-split */\r\n.react-split > .split-container > .splitter .default-splitter {\r\n box-sizing: border-box;\r\n height: 100%;\r\n outline: none;\r\n overflow: hidden;\r\n user-select: none;\r\n width: 100%;\r\n --default-splitter-line-color: silver;\r\n --default-splitter-line-hover-color: black;\r\n --default-splitter-line-margin: 2px;\r\n --default-splitter-line-size: 3px;\r\n}\r\n\r\n.react-split > .split-container.horizontal > .splitter .default-splitter {\r\n cursor: row-resize;\r\n}\r\n\r\n.react-split > .split-container.vertical > .splitter .default-splitter {\r\n cursor: col-resize;\r\n}\r\n\r\n/* The thin line within a default splitter hit area */\r\n.react-split > .split-container > .splitter .default-splitter > .line {\r\n background: var(--default-splitter-line-color);\r\n}\r\n\r\n.react-split > .split-container > .splitter .default-splitter:hover > .line {\r\n background: var(--default-splitter-line-hover-color);\r\n}\r\n\r\n.react-split > .split-container.horizontal > .splitter .default-splitter > .line {\r\n height: var(--default-splitter-line-size);\r\n width: 100%;\r\n margin-top: var(--default-splitter-line-margin);\r\n margin-left: 0;\r\n}\r\n\r\n.react-split > .split-container.vertical > .splitter .default-splitter > .line {\r\n height: 100%;\r\n width: var(--default-splitter-line-size);\r\n margin-top: 0;\r\n margin-left: var(--default-splitter-line-margin);\r\n}",""]),e.exports=t},function(e,t,n){"use strict";var r=n(14);function i(){}function o(){}o.resetWarningCache=i,e.exports=function(){function e(e,t,n,i,o,s){if(s!==r){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:i};return n.PropTypes=n,n}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){var r=n(3),i=n(17);"string"==typeof(i=i.__esModule?i.default:i)&&(i=[[e.i,i,""]]);var o={insert:"head",singleton:!1};r(i,o);e.exports=i.locals||{}},function(e,t,n){(t=n(4)(!1)).push([e.i,'/* The top-level element of the splitter*/\r\n.react-split {\r\n width: 100%;\r\n height: 100%;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n --react-split-min-primary: 0;\r\n --react-split-min-secondary: 0;\r\n --react-split-primary: 50%;\r\n --react-split-splitter: 7px;\r\n}\r\n\r\n/* The container for the primary pane, splitter, and secondary pane.*/\r\n.react-split > .split-container {\r\n width: 100%;\r\n height: 100%;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n display: grid;\r\n}\r\n\r\n/* When the container is splitting horizontally */\r\n.react-split > .split-container.horizontal {\r\n grid-template-columns: 1fr;\r\n grid-template-rows: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr);\r\n grid-template-areas: "primary" "split" "secondary";\r\n}\r\n\r\n/* When the container is splitting vertical */\r\n.react-split > .split-container.vertical {\r\n grid-template-columns: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr);\r\n grid-template-rows: 1fr;\r\n grid-template-areas: "primary split secondary";\r\n}\r\n\r\n/* The primary pane. This is either the left or top depending on the split type */\r\n.react-split > .split-container > .primary {\r\n grid-area: primary;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n}\r\n\r\n.react-split > .split-container.horizontal > .primary {\r\n height: auto;\r\n width: 100%;\r\n}\r\n\r\n.react-split > .split-container.vertical > .primary {\r\n height: 100%;\r\n width: auto;\r\n}\r\n\r\n/* The splitter between panes. */\r\n.react-split > .split-container > .splitter {\r\n grid-area: split;\r\n background: transparent;\r\n user-select: none;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n}\r\n\r\n.react-split > .split-container.horizontal > .splitter {\r\n height: auto;\r\n width: 100%;\r\n cursor: row-resize;\r\n}\r\n\r\n.react-split > .split-container.vertical > .splitter {\r\n height: 100%;\r\n width: auto;\r\n cursor: col-resize;\r\n}\r\n\r\n/* The secondary pane. This is either the right or bottom depending on the split type */\r\n.react-split > .split-container >.secondary {\r\n grid-area: secondary;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n}\r\n\r\n.react-split > .split-container.horizontal > .secondary {\r\n height: auto;\r\n width: 100%;\r\n}\r\n\r\n.react-split > .split-container.vertical > .secondary {\r\n height: 100%;\r\n width: auto;\r\n}\r\n\r\n/* The content within the primary pane, splitter, or secondary pane.*/\r\n.react-split .full-content {\r\n width: 100%;\r\n height: 100%;\r\n box-sizing: border-box;\r\n outline: none;\r\n overflow: hidden;\r\n}\r\n',""]),e.exports=t}])})); 2 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /package/dist/2.1.2/index.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define("ReactSplitter",["react"],t):"object"==typeof exports?exports.ReactSplitter=t(require("react")):e.ReactSplitter=t(e.React)}(self,(e=>(()=>{var t={58:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(864),i=n.n(r),o=n(352),s=n.n(o)()(i());s.push([e.id,"\n/* The default splitter within a react-split */\n.react-split > .split-container > .splitter .default-splitter {\n box-sizing: border-box;\n height: 100%;\n outline: none;\n overflow: hidden;\n user-select: none;\n width: 100%;\n --default-splitter-line-color: silver;\n --default-splitter-line-hover-color: black;\n --default-splitter-line-margin: 2px;\n --default-splitter-line-size: 3px;\n}\n\n.react-split > .split-container.horizontal > .splitter .default-splitter {\n cursor: row-resize;\n}\n\n.react-split > .split-container.vertical > .splitter .default-splitter {\n cursor: col-resize;\n}\n\n/* The thin line within a default splitter hit area */\n.react-split > .split-container > .splitter .default-splitter > .line {\n background: var(--default-splitter-line-color);\n}\n\n.react-split > .split-container > .splitter .default-splitter:hover > .line {\n background: var(--default-splitter-line-hover-color);\n}\n\n.react-split > .split-container.horizontal > .splitter .default-splitter > .line {\n height: var(--default-splitter-line-size);\n width: 100%;\n margin-top: var(--default-splitter-line-margin);\n margin-left: 0;\n}\n\n.react-split > .split-container.vertical > .splitter .default-splitter > .line {\n height: 100%;\n width: var(--default-splitter-line-size);\n margin-top: 0;\n margin-left: var(--default-splitter-line-margin);\n}","",{version:3,sources:["webpack://./src/defaultSplitter.css"],names:[],mappings:";AACA,8CAA8C;AAC9C;EACE,sBAAsB;EACtB,YAAY;EACZ,aAAa;EACb,gBAAgB;EAChB,iBAAiB;EACjB,WAAW;EACX,qCAAqC;EACrC,0CAA0C;EAC1C,mCAAmC;EACnC,iCAAiC;AACnC;;AAEA;EACE,kBAAkB;AACpB;;AAEA;EACE,kBAAkB;AACpB;;AAEA,qDAAqD;AACrD;EACE,8CAA8C;AAChD;;AAEA;EACE,oDAAoD;AACtD;;AAEA;EACE,yCAAyC;EACzC,WAAW;EACX,+CAA+C;EAC/C,cAAc;AAChB;;AAEA;EACE,YAAY;EACZ,wCAAwC;EACxC,aAAa;EACb,gDAAgD;AAClD",sourcesContent:["\n/* The default splitter within a react-split */\n.react-split > .split-container > .splitter .default-splitter {\n box-sizing: border-box;\n height: 100%;\n outline: none;\n overflow: hidden;\n user-select: none;\n width: 100%;\n --default-splitter-line-color: silver;\n --default-splitter-line-hover-color: black;\n --default-splitter-line-margin: 2px;\n --default-splitter-line-size: 3px;\n}\n\n.react-split > .split-container.horizontal > .splitter .default-splitter {\n cursor: row-resize;\n}\n\n.react-split > .split-container.vertical > .splitter .default-splitter {\n cursor: col-resize;\n}\n\n/* The thin line within a default splitter hit area */\n.react-split > .split-container > .splitter .default-splitter > .line {\n background: var(--default-splitter-line-color);\n}\n\n.react-split > .split-container > .splitter .default-splitter:hover > .line {\n background: var(--default-splitter-line-hover-color);\n}\n\n.react-split > .split-container.horizontal > .splitter .default-splitter > .line {\n height: var(--default-splitter-line-size);\n width: 100%;\n margin-top: var(--default-splitter-line-margin);\n margin-left: 0;\n}\n\n.react-split > .split-container.vertical > .splitter .default-splitter > .line {\n height: 100%;\n width: var(--default-splitter-line-size);\n margin-top: 0;\n margin-left: var(--default-splitter-line-margin);\n}"],sourceRoot:""}]);const a=s},672:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(864),i=n.n(r),o=n(352),s=n.n(o)()(i());s.push([e.id,'/* The top-level element of the splitter*/\n.react-split {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n --react-split-min-primary: 0;\n --react-split-min-secondary: 0;\n --react-split-primary: 50%;\n --react-split-splitter: 7px;\n}\n\n/* The container for the primary pane, splitter, and secondary pane.*/\n.react-split > .split-container {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n display: grid;\n}\n\n/* When the container is splitting horizontally */\n.react-split > .split-container.horizontal {\n grid-template-columns: 1fr;\n grid-template-rows: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr);\n grid-template-areas: "primary" "split" "secondary";\n}\n\n/* When the container is splitting vertical */\n.react-split > .split-container.vertical {\n grid-template-columns: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr);\n grid-template-rows: 1fr;\n grid-template-areas: "primary split secondary";\n}\n\n/* The primary pane. This is either the left or top depending on the split type */\n.react-split > .split-container > .primary {\n grid-area: primary;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n}\n\n.react-split > .split-container.horizontal > .primary {\n height: auto;\n width: 100%;\n}\n\n.react-split > .split-container.vertical > .primary {\n height: 100%;\n width: auto;\n}\n\n/* The splitter between panes. */\n.react-split > .split-container > .splitter {\n grid-area: split;\n background: transparent;\n user-select: none;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n}\n\n.react-split > .split-container.horizontal > .splitter {\n height: auto;\n width: 100%;\n cursor: row-resize;\n}\n\n.react-split > .split-container.vertical > .splitter {\n height: 100%;\n width: auto;\n cursor: col-resize;\n}\n\n/* The secondary pane. This is either the right or bottom depending on the split type */\n.react-split > .split-container >.secondary {\n grid-area: secondary;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n}\n\n.react-split > .split-container.horizontal > .secondary {\n height: auto;\n width: 100%;\n}\n\n.react-split > .split-container.vertical > .secondary {\n height: 100%;\n width: auto;\n}\n\n/* The content within the primary pane, splitter, or secondary pane.*/\n.react-split .full-content {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n}\n',"",{version:3,sources:["webpack://./src/split.css"],names:[],mappings:"AAAA,yCAAyC;AACzC;EACE,WAAW;EACX,YAAY;EACZ,sBAAsB;EACtB,aAAa;EACb,gBAAgB;EAChB,4BAA4B;EAC5B,8BAA8B;EAC9B,0BAA0B;EAC1B,2BAA2B;AAC7B;;AAEA,qEAAqE;AACrE;EACE,WAAW;EACX,YAAY;EACZ,sBAAsB;EACtB,aAAa;EACb,gBAAgB;EAChB,aAAa;AACf;;AAEA,iDAAiD;AACjD;EACE,0BAA0B;EAC1B,+JAA+J;EAC/J,kDAAkD;AACpD;;AAEA,6CAA6C;AAC7C;EACE,kKAAkK;EAClK,uBAAuB;EACvB,8CAA8C;AAChD;;AAEA,iFAAiF;AACjF;EACE,kBAAkB;EAClB,sBAAsB;EACtB,aAAa;EACb,gBAAgB;AAClB;;AAEA;EACE,YAAY;EACZ,WAAW;AACb;;AAEA;EACE,YAAY;EACZ,WAAW;AACb;;AAEA,gCAAgC;AAChC;EACE,gBAAgB;EAChB,uBAAuB;EACvB,iBAAiB;EACjB,sBAAsB;EACtB,aAAa;EACb,gBAAgB;AAClB;;AAEA;EACE,YAAY;EACZ,WAAW;EACX,kBAAkB;AACpB;;AAEA;EACE,YAAY;EACZ,WAAW;EACX,kBAAkB;AACpB;;AAEA,uFAAuF;AACvF;EACE,oBAAoB;EACpB,sBAAsB;EACtB,aAAa;EACb,gBAAgB;AAClB;;AAEA;EACE,YAAY;EACZ,WAAW;AACb;;AAEA;EACE,YAAY;EACZ,WAAW;AACb;;AAEA,qEAAqE;AACrE;EACE,WAAW;EACX,YAAY;EACZ,sBAAsB;EACtB,aAAa;EACb,gBAAgB;AAClB",sourcesContent:['/* The top-level element of the splitter*/\n.react-split {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n --react-split-min-primary: 0;\n --react-split-min-secondary: 0;\n --react-split-primary: 50%;\n --react-split-splitter: 7px;\n}\n\n/* The container for the primary pane, splitter, and secondary pane.*/\n.react-split > .split-container {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n display: grid;\n}\n\n/* When the container is splitting horizontally */\n.react-split > .split-container.horizontal {\n grid-template-columns: 1fr;\n grid-template-rows: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr);\n grid-template-areas: "primary" "split" "secondary";\n}\n\n/* When the container is splitting vertical */\n.react-split > .split-container.vertical {\n grid-template-columns: minmax(var(--react-split-min-primary),var(--react-split-primary)) var(--react-split-splitter) minmax(var(--react-split-min-secondary), 1fr);\n grid-template-rows: 1fr;\n grid-template-areas: "primary split secondary";\n}\n\n/* The primary pane. This is either the left or top depending on the split type */\n.react-split > .split-container > .primary {\n grid-area: primary;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n}\n\n.react-split > .split-container.horizontal > .primary {\n height: auto;\n width: 100%;\n}\n\n.react-split > .split-container.vertical > .primary {\n height: 100%;\n width: auto;\n}\n\n/* The splitter between panes. */\n.react-split > .split-container > .splitter {\n grid-area: split;\n background: transparent;\n user-select: none;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n}\n\n.react-split > .split-container.horizontal > .splitter {\n height: auto;\n width: 100%;\n cursor: row-resize;\n}\n\n.react-split > .split-container.vertical > .splitter {\n height: 100%;\n width: auto;\n cursor: col-resize;\n}\n\n/* The secondary pane. This is either the right or bottom depending on the split type */\n.react-split > .split-container >.secondary {\n grid-area: secondary;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n}\n\n.react-split > .split-container.horizontal > .secondary {\n height: auto;\n width: 100%;\n}\n\n.react-split > .split-container.vertical > .secondary {\n height: 100%;\n width: auto;\n}\n\n/* The content within the primary pane, splitter, or secondary pane.*/\n.react-split .full-content {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n outline: none;\n overflow: hidden;\n}\n'],sourceRoot:""}]);const a=s},352:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",r=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),r&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),r&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,r,i,o){"string"==typeof e&&(e=[[null,e,void 0]]);var s={};if(r)for(var a=0;a0?" ".concat(p[5]):""," {").concat(p[1],"}")),p[5]=o),n&&(p[2]?(p[1]="@media ".concat(p[2]," {").concat(p[1],"}"),p[2]=n):p[2]=n),i&&(p[4]?(p[1]="@supports (".concat(p[4],") {").concat(p[1],"}"),p[4]=i):p[4]="".concat(i)),t.push(p))}},t}},864:e=>{"use strict";e.exports=function(e){var t=e[1],n=e[3];if(!n)return t;if("function"==typeof btoa){var r=btoa(unescape(encodeURIComponent(JSON.stringify(n)))),i="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(r),o="/*# ".concat(i," */");return[t].concat([o]).join("\n")}return[t].join("\n")}},372:(e,t,n)=>{"use strict";var r=n(567);function i(){}function o(){}o.resetWarningCache=i,e.exports=function(){function e(e,t,n,i,o,s){if(s!==r){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:i};return n.PropTypes=n,n}},652:(e,t,n)=>{e.exports=n(372)()},567:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},701:e=>{"use strict";var t=[];function n(e){for(var n=-1,r=0;r{"use strict";var t={};e.exports=function(e,n){var r=function(e){if(void 0===t[e]){var n=document.querySelector(e);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}t[e]=n}return t[e]}(e);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(n)}},182:e=>{"use strict";e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},850:(e,t,n)=>{"use strict";e.exports=function(e){var t=n.nc;t&&e.setAttribute("nonce",t)}},236:e=>{"use strict";e.exports=function(e){var t=e.insertStyleElement(e);return{update:function(n){!function(e,t,n){var r="";n.supports&&(r+="@supports (".concat(n.supports,") {")),n.media&&(r+="@media ".concat(n.media," {"));var i=void 0!==n.layer;i&&(r+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),r+=n.css,i&&(r+="}"),n.media&&(r+="}"),n.supports&&(r+="}");var o=n.sourceMap;o&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(o))))," */")),t.styleTagTransform(r,e,t.options)}(t,e,n)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}},213:e=>{"use strict";e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}},359:t=>{"use strict";t.exports=e}},n={};function r(e){var i=n[e];if(void 0!==i)return i.exports;var o=n[e]={id:e,exports:{}};return t[e](o,o.exports,r),o.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nc=void 0;var i={};return(()=>{"use strict";r.r(i),r.d(i,{DefaultSplitter:()=>g,Split:()=>$});var e=r(359),t=r(701),n=r.n(t),o=r(236),s=r.n(o),a=r(80),l=r.n(a),c=r(850),p=r.n(c),u=r(182),h=r.n(u),d=r(213),f=r.n(d),A=r(58),m={};m.styleTagTransform=f(),m.setAttributes=p(),m.insert=l().bind(null,"head"),m.domAPI=s(),m.insertStyleElement=h(),n()(A.Z,m),A.Z&&A.Z.locals&&A.Z.locals;const v=e=>(e%2==0?2:3)+"px",g=t=>{const{dragging:n,pixelSize:r,color:i="silver",hoverColor:o="gray",dragColor:s="black"}=t,a={"--default-splitter-line-margin":(l=r,`${Math.max(0,Math.floor(l/2)-1)}px`),"--default-splitter-line-size":v(r),"--default-splitter-line-color":n?s:i,"--default-splitter-line-hover-color":n?s:o};var l;return e.createElement("div",{className:"default-splitter",style:a},e.createElement("div",{className:"line"}))};function b(){return b=Object.assign?Object.assign.bind():function(e){for(var t=1;t0},e.prototype.connect_=function(){_&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),O?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){_&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;z.some((function(e){return!!~n.indexOf(e)}))&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),R=function(e,t){for(var n=0,r=Object.keys(t);n0},e}(),F="undefined"!=typeof WeakMap?new WeakMap:new w,q=function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=T.getInstance(),r=new Z(t,n,this);F.set(this,r)};["observe","unobserve","disconnect"].forEach((function(e){q.prototype[e]=function(){var t;return(t=F.get(this))[e].apply(t,arguments)}}));const N=void 0!==B.ResizeObserver?B.ResizeObserver:q;var L=["client","offset","scroll","bounds","margin"];function H(e){var t=[];return L.forEach((function(n){e[n]&&t.push(n)})),t}function X(e,t){var n={};if(t.indexOf("client")>-1&&(n.client={top:e.clientTop,left:e.clientLeft,width:e.clientWidth,height:e.clientHeight}),t.indexOf("offset")>-1&&(n.offset={top:e.offsetTop,left:e.offsetLeft,width:e.offsetWidth,height:e.offsetHeight}),t.indexOf("scroll")>-1&&(n.scroll={top:e.scrollTop,left:e.scrollLeft,width:e.scrollWidth,height:e.scrollHeight}),t.indexOf("bounds")>-1){var r=e.getBoundingClientRect();n.bounds={top:r.top,right:r.right,bottom:r.bottom,left:r.left,width:r.width,height:r.height}}if(t.indexOf("margin")>-1){var i=getComputedStyle(e);n.margin={top:i?parseInt(i.marginTop):0,right:i?parseInt(i.marginRight):0,bottom:i?parseInt(i.marginBottom):0,left:i?parseInt(i.marginLeft):0}}return n}function U(e){return e&&e.ownerDocument&&e.ownerDocument.defaultView||window}var V=function(t){var n,r;return r=n=function(n){var r,i;function o(){for(var e,t=arguments.length,r=new Array(t),i=0;i=0||(i[n]=e[n]);return i}(n,["innerRef","onResize"]));return(0,e.createElement)(t,b({},r,{measureRef:this._handleRef,measure:this.measure,contentRect:this.state.contentRect}))},o}(e.Component),n.propTypes={client:C().bool,offset:C().bool,scroll:C().bool,bounds:C().bool,margin:C().bool,innerRef:C().oneOfType([C().object,C().func]),onResize:C().func},r}((function(e){var t=e.measure,n=e.measureRef,r=e.contentRect;return(0,e.children)({measure:t,measureRef:n,contentRect:r})}));V.displayName="Measure",V.propTypes.children=C().func;const G=V;var J=r(672),K={};K.styleTagTransform=f(),K.setAttributes=p(),K.insert=l().bind(null,"head"),K.domAPI=s(),K.insertStyleElement=h(),n()(J.Z,K),J.Z&&J.Z.locals&&J.Z.locals;const $=t=>{const{horizontal:n=!1,initialPrimarySize:r="50%",minPrimarySize:i="0px",minSecondarySize:o="0px",splitterSize:s="7px",renderSplitter:a,resetOnDoubleClick:l=!1,defaultSplitterColors:c={color:"silver",hover:"gray",drag:"black"},onSplitChanged:p,onMeasuredSizesChanged:u}=t,[h,d]=e.useState({height:0,width:0}),[f,A]=e.useState({height:0,width:0}),[m,v]=e.useState({height:0,width:0}),b=e.useMemo((()=>n?h.height:h.width),[n,h]),y=e.useMemo((()=>n?f.height:f.width),[n,f]),E=e.useMemo((()=>n?m.height:m.width),[n,m]),[C,w]=e.useState(void 0),[_,B]=e.useState(0),[x,z]=e.useState(0),[O,T]=e.useState(!1);e.useEffect((()=>{p&&p(void 0!==C?`${C}%`:r)}),[C,r]),e.useEffect((()=>{u&&u({primary:y,splitter:E,secondary:b-(y+E)})}),[n,b,y,E]);const R=e=>{e.bounds&&A({height:e.bounds.height,width:e.bounds.width})},S=e=>{e.bounds&&v({height:e.bounds.height,width:e.bounds.width})},M=e=>{e.currentTarget.setPointerCapture(e.pointerId),B(n?e.clientY:e.clientX),z(y),T(!0)},k=e=>{if(e.currentTarget.hasPointerCapture(e.pointerId)){const t=n?e.clientY:e.clientX,r=x+(t-_),i=Math.max(0,Math.min(r,b));w(i/b*100)}},D=e=>{e.currentTarget.releasePointerCapture(e.pointerId),T(!1)},W=()=>{l&&w(void 0)},j=e.Children.toArray(t.children),P=j.length>0?j[0]:e.createElement("div",null),I=j.length>1?j[1]:e.createElement("div",null),Y={primary:void 0!==C?`${C}%`:r,minPrimary:null!=i?i:"0px",minSecondary:null!=o?o:"0px"},Z={pixelSize:E,horizontal:n,dragging:O},F=null!=a?a:()=>e.createElement(g,Object.assign({},Z,{color:O?c.drag:c.color,hoverColor:O?c.drag:c.hover})),q=n?"split-container horizontal":"split-container vertical",N={"--react-split-min-primary":Y.minPrimary,"--react-split-min-secondary":Y.minSecondary,"--react-split-primary":Y.primary,"--react-split-splitter":s};return e.createElement(G,{bounds:!0,onResize:e=>{e.bounds&&d({height:e.bounds.height,width:e.bounds.width})}},(({measureRef:t})=>e.createElement("div",{className:"react-split",ref:t},e.createElement("div",{className:q,style:N},e.createElement("div",{className:"primary"},e.createElement(G,{bounds:!0,onResize:R},(({measureRef:t})=>e.createElement("div",{className:"full-content",ref:t},P)))),e.createElement("div",{className:"splitter",tabIndex:-1,onPointerDown:M,onPointerUp:D,onPointerMove:k,onDoubleClick:W},e.createElement(G,{bounds:!0,onResize:S},(({measureRef:t})=>e.createElement("div",{className:"full-content",ref:t},F(Z))))),e.createElement("div",{className:"secondary"},e.createElement("div",{className:"full-content"},I))))))}})(),i})())); 2 | //# sourceMappingURL=index.js.map --------------------------------------------------------------------------------