├── .babelrc ├── .github ├── FUNDING.yml └── workflows │ └── build-www.yml ├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── HISTORY.md ├── LICENSE ├── README.md ├── dist ├── rc-dock-dark.css └── rc-dock.css ├── es ├── Algorithm.d.ts ├── Algorithm.js ├── Divider.d.ts ├── Divider.js ├── DividerBox.d.ts ├── DividerBox.js ├── DockBox.d.ts ├── DockBox.js ├── DockData.d.ts ├── DockData.js ├── DockDropEdge.d.ts ├── DockDropEdge.js ├── DockDropLayer.d.ts ├── DockDropLayer.js ├── DockLayout.d.ts ├── DockLayout.js ├── DockPanel.d.ts ├── DockPanel.js ├── DockTabBar.d.ts ├── DockTabBar.js ├── DockTabPane.d.ts ├── DockTabPane.js ├── DockTabs.d.ts ├── DockTabs.js ├── FloatBox.d.ts ├── FloatBox.js ├── MaxBox.d.ts ├── MaxBox.js ├── Serializer.d.ts ├── Serializer.js ├── Utils.d.ts ├── Utils.js ├── WindowBox.d.ts ├── WindowBox.js ├── WindowPanel.d.ts ├── WindowPanel.js ├── dragdrop │ ├── DragDropDiv.d.ts │ ├── DragDropDiv.js │ ├── DragManager.d.ts │ ├── DragManager.js │ ├── GestureManager.d.ts │ └── GestureManager.js ├── index.d.ts ├── index.js └── util │ ├── Compare.d.ts │ └── Compare.js ├── example ├── adv-save-layout.html ├── adv-save-layout.jsx ├── adv-tab-update.html ├── adv-tab-update.jsx ├── basic.html ├── basic.jsx ├── controlled-layout.html ├── controlled-layout.jsx ├── dark-theme.html ├── dark-theme.jsx ├── disable-dock.html ├── disable-dock.jsx ├── divider-box.html ├── divider-box.jsx ├── drag-new-tab.html ├── drag-new-tab.jsx ├── drop-mode.html ├── drop-mode.jsx ├── gesture.html ├── gesture.jsx ├── index.html ├── index.jsx ├── new-window.html ├── new-window.jsx ├── panel-extra.html ├── panel-extra.jsx ├── panel-style.html ├── panel-style.jsx ├── prism-coy.css ├── prism-tabs.tsx ├── save-layout.html ├── save-layout.jsx ├── shared-import.js ├── standalone-divider.html ├── standalone-divider.jsx ├── style-dark.less ├── style.less ├── tab-cache.html ├── tab-cache.jsx ├── tab-min-size.html ├── tab-min-size.jsx ├── tab-update.html └── tab-update.jsx ├── lib ├── Algorithm.d.ts ├── Algorithm.js ├── Divider.d.ts ├── Divider.js ├── DividerBox.d.ts ├── DividerBox.js ├── DockBox.d.ts ├── DockBox.js ├── DockData.d.ts ├── DockData.js ├── DockDropEdge.d.ts ├── DockDropEdge.js ├── DockDropLayer.d.ts ├── DockDropLayer.js ├── DockLayout.d.ts ├── DockLayout.js ├── DockPanel.d.ts ├── DockPanel.js ├── DockTabBar.d.ts ├── DockTabBar.js ├── DockTabPane.d.ts ├── DockTabPane.js ├── DockTabs.d.ts ├── DockTabs.js ├── FloatBox.d.ts ├── FloatBox.js ├── MaxBox.d.ts ├── MaxBox.js ├── NewWindow.d.ts ├── NewWindow.js ├── Serializer.d.ts ├── Serializer.js ├── Utils.d.ts ├── Utils.js ├── WindowBox.d.ts ├── WindowBox.js ├── WindowPanel.d.ts ├── WindowPanel.js ├── dragdrop │ ├── DragDropDiv.d.ts │ ├── DragDropDiv.js │ ├── DragManager.d.ts │ ├── DragManager.js │ ├── GestureManager.d.ts │ └── GestureManager.js ├── index.d.ts ├── index.js └── util │ ├── Compare.d.ts │ └── Compare.js ├── package.json ├── src ├── Algorithm.ts ├── Divider.tsx ├── DividerBox.tsx ├── DockBox.tsx ├── DockData.ts ├── DockDropEdge.tsx ├── DockDropLayer.tsx ├── DockLayout.tsx ├── DockPanel.tsx ├── DockTabBar.tsx ├── DockTabPane.tsx ├── DockTabs.tsx ├── FloatBox.tsx ├── MaxBox.tsx ├── Serializer.ts ├── Utils.ts ├── WindowBox.tsx ├── WindowPanel.tsx ├── dragdrop │ ├── DragDropDiv.tsx │ ├── DragManager.ts │ └── GestureManager.ts └── index.ts ├── style ├── dragging.less ├── index-dark.less ├── index-light.less ├── index.less ├── panel.less ├── predefined-panels.less └── tabs.less ├── tool └── build-www.ts ├── tsconfig.json ├── tslint.json ├── typedoc.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-export-default-from" 4 | ], 5 | "presets": [ 6 | [ 7 | "@babel/env", 8 | { 9 | "targets": { 10 | "browsers": [ 11 | "last 1 Chrome version", 12 | "last 1 edge version", 13 | "last 1 firefox version" 14 | ] 15 | } 16 | } 17 | ] 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: [rinick] 4 | patreon: ticlo 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /.github/workflows/build-www.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build github pages 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [16.x] 15 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - uses: actions/checkout@v2 # checkout gh-pages into a different folder 24 | with: 25 | ref: gh-pages 26 | path: www 27 | fetch-depth: 1 28 | - run: yarn install 29 | - run: yarn build-www 30 | - run: | 31 | git config --global user.name "github-actions[bot]" 32 | git config --global user.email "noreply@ticlo.com" 33 | git add -A 34 | git commit -m "Deploy Github Action" 35 | working-directory: ./www 36 | - run: git push 37 | working-directory: ./www 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | 4 | /.nyc_output 5 | /coverage 6 | /playground 7 | /.idea 8 | /.cache 9 | build 10 | /src/**/*.js 11 | /test/**/*.js 12 | /lib/example 13 | /lib/src 14 | *.map 15 | /dist/*.*.* 16 | /tool/**/*.js 17 | /temp* 18 | /www 19 | /yarn-error.log 20 | /.parcel-cache 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | 4 | /.nyc_output 5 | /coverage 6 | /playground 7 | /.idea 8 | /.cache 9 | /.babelrc 10 | build 11 | /src/**/*.js 12 | /test/**/*.js 13 | /lib/example 14 | /lib/src 15 | *.map 16 | /dist/*.*.* 17 | /tool/**/*.js 18 | /temp* 19 | /www 20 | 21 | /tool 22 | /example 23 | /.github 24 | /typedocconfig.js 25 | /.travis.yml 26 | /.vscode 27 | /yarn.lock 28 | /yarn-error.log 29 | /.parcel-cache 30 | /typedoc.json 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/*.js.map": true, 5 | "**/*.js": { 6 | "when": "$(basename).ts" 7 | }, 8 | "**/*?.js": { 9 | "when": "$(basename).tsx" 10 | } 11 | }, 12 | "files.eol": "\n", 13 | "editor.tabSize": 2, 14 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false 15 | } 16 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | ---- 3 | 4 | ## 3.2.18 / 2023-06-08 5 | - fix the issue that sometimes dock icons are shown and no UI response after switching tabs quickly 6 | 7 | ## 3.2.14 / 2022-11-25 8 | - make rc-dock compatible with preact 9 | 10 | ## 3.2.11 / 2022-08-24 11 | - add new parameter to dockmove() for default float panel position 12 | 13 | ## 3.2.9 / 2022-01-17 14 | - new TabGroup.floatable option: "singleTab" 15 | 16 | ## 3.2.8 / 2022-01-10 17 | - add support to resize float panel by dragging panel edges 18 | 19 | ## 3.2.1 / 2021-10-09 20 | - TabGroup and panelLock now have 2 new properties: widthFlex and heightFlex, this allows panel to stretch at different ratio during window resize 21 | 22 | ## 3.2.0 / 2021-10-03 23 | - in a float panel with single tab, dragging the tab should work the same way as dragging the panel header 24 | 25 | ## 3.1.0 / 2021-09-06 26 | - add dark theme 27 | 28 | ## 3.0.14 / 2021-05-15 29 | - panel navigation with arrow keys 30 | 31 | ## 3.0.10 / 2021-04-11 32 | - make it possible for Docklayout.updateTab to update tab without changing activeId 33 | 34 | ## 3.0.8 / 2021-03-10 35 | - when controlled layout is used and onLayoutChange callback doesn't set new layout prop, there should be a forceUpdate() so the original layout prop is re-rendered 36 | 37 | ## 3.0.7 / 2021-03-09 38 | - add direction parameter to onLayoutChange callback 39 | - onLayoutChange callback now gives the tabId when tab is removed, this is changed from previous version where tabId=null when it's removed 40 | 41 | ## 3.0.5 / 2021-03-08 42 | - allow mouse event handler on tab title 43 | - fix the issue that calling updateTab() doesn't trigger onLayoutChange 44 | 45 | ## 3.0.2 / 2021-02-06 46 | - fix serialization of popup window 47 | 48 | ## 3.0.0 / 2020-12-05 49 | - switch to rc-tabs 11.x, internal component structure changed 50 | - tabs overflow is now shown as dropdown menu instead of left/right scroll 51 | - support popup window with rc-new-window 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dock Layout for React Component 2 | 3 | ![](https://ticlo.github.io/rc-dock/images/demo.gif) 4 | 5 | #### Popup panel as new browser window 6 | ![](https://ticlo.github.io/rc-dock/images/new-window.gif) 7 | 8 | #### Dark Theme 9 | ![](https://ticlo.github.io/rc-dock/images/dark-theme.png) 10 | 11 | - **Examples:** https://ticlo.github.io/rc-dock/examples 12 | - **Docs:** https://ticlo.github.io/rc-dock 13 | - **Discord:** [![Discord](https://img.shields.io/discord/434106806503997445.svg?color=7289DA&logo=discord&logoColor=white 14 | )](https://discord.gg/G7pw9DR) 15 | 16 | ## Usage 17 | 18 | [![rc-tabs](https://nodei.co/npm/rc-dock.png)](https://npmjs.org/package/rc-dock) 19 | 20 | ```jsx 21 | import DockLayout from 'rc-dock' 22 | import "rc-dock/dist/rc-dock.css"; 23 | 24 | ... 25 | 26 | defaultLayout = { 27 | dockbox: { 28 | mode: 'horizontal', 29 | children: [ 30 | { 31 | tabs: [ 32 | {id: 'tab1', title: 'tab1', content:
Hello World
} 33 | ] 34 | } 35 | ] 36 | } 37 | }; 38 | 39 | render() { 40 | return ( 41 | 51 | ) 52 | } 53 | 54 | ``` 55 | - use as **uncontrolled layout** 56 | - set layout object in **[DockLayout.defaultLayout](https://ticlo.github.io/rc-dock/interfaces/docklayout.layoutprops.html#defaultlayout)** 57 | - use as **controlled layout** 58 | - set layout object in **[DockLayout.layout](https://ticlo.github.io/rc-dock/interfaces/docklayout.layoutprops.html#layout)** 59 | 60 | 61 | ## types 62 | 63 | 64 | ### LayoutData [🗎](https://ticlo.github.io/rc-dock/interfaces/dockdata.layoutdata.html) 65 | | Property | Type | Comments | Default | 66 | | :---: | :---: | :---: | :---: | 67 | | dockbox | BoxData | main dock box | empty BoxData | 68 | | floatbox | BoxData | main float box, children can only be PanelData | empty BoxData | 69 | 70 | ### BoxData [🗎](https://ticlo.github.io/rc-dock/interfaces/dockdata.boxdata.html) 71 | a box is the layout element that contains other boxes or panels 72 | 73 | | Property | Type | Comments | Default | 74 | | :---: | :---: | :---: | :---: | 75 | | mode | 'horizontal' | 'vertical' | 'float' | layout mode of the box | | 76 | | children | (BoxData | PanelData)[] | children boxes or panels | **required** | 77 | 78 | ### PanelData [🗎](https://ticlo.github.io/rc-dock/interfaces/dockdata.paneldata.html) 79 | a panel is a visiaul container with tabs button in the title bar 80 | 81 | | Property | Type | Comments | Default | 82 | | :---: | :---: | :---: | :---: | 83 | | tabs | TabData[] | children tabs | **required** | 84 | | panelLock | PanelLock | addition information of a panel, this prevents the panel from being removed when there is no tab inside, a locked panel can not be moved to float layer either | | 85 | 86 | 87 | ### TabData [🗎](https://ticlo.github.io/rc-dock/interfaces/dockdata.tabdata.html) 88 | | Property | Type | Comments | Default | 89 | | :---: | :---: | :---: | :---: | 90 | | id | string | unique id | **required** | 91 | | title | string | ReactElement | tab title | **required** | 92 | | content | ReactElement | (tab: TabData) => ReactElement | tab content | **required** | 93 | | closable | bool | whether tab can be closed | false | 94 | | group | string | tabs with different tab group can not be put in same panel, more options for the group can be defined as TabGroup in DefaultLayout.groups | | 95 | 96 | ## DockLayout API 97 | 98 | get the `ref` of the DockLayout component to use the following API 99 | 100 | ### saveLayout [🗎](https://ticlo.github.io/rc-dock/interfaces/docklayout.layoutprops.html) 101 | save layout 102 | 103 | ```typescript 104 | saveLayout(): SavedLayout 105 | ``` 106 | 107 | ### loadLayout [🗎](https://ticlo.github.io/rc-dock/interfaces/docklayout.layoutprops.html) 108 | load layout 109 | 110 | ```typescript 111 | loadLayout(savedLayout: SavedLayout): void 112 | ``` 113 | 114 | ### dockMove [🗎](https://ticlo.github.io/rc-dock/classes/docklayout.docklayout-1.html#dockmove) 115 | move a tab or a panel, if source is already in the layout, you can use the find method to get it with id first 116 | 117 | ```typescript 118 | dockMove(source: TabData | PanelData, target: string | TabData | PanelData | BoxData, direction: DropDirection): void; 119 | ``` 120 | 121 | ### find [🗎](https://ticlo.github.io/rc-dock/classes/docklayout.docklayout-1.html#find) 122 | find PanelData or TabData by id 123 | 124 | ```typescript 125 | find(id: string | ((item: PanelData | TabData | BoxData) => boolean), filter?: Filter): PanelData | TabData | BoxData | undefined; 126 | ``` 127 | 128 | ### updateTab [🗎](https://ticlo.github.io/rc-dock/classes/docklayout.docklayout-1.html#updatetab) 129 | update a tab with new TabData 130 | 131 | returns false if the tab is not found 132 | 133 | ```typescript 134 | updateTab(id: string, newTab: TabData): boolean; 135 | ``` 136 | -------------------------------------------------------------------------------- /es/Algorithm.d.ts: -------------------------------------------------------------------------------- 1 | import { BoxData, DropDirection, LayoutData, PanelData, TabBase, TabData, TabGroup } from "./DockData"; 2 | export declare function getUpdatedObject(obj: any): any; 3 | export declare function nextId(): string; 4 | export declare function nextZIndex(current?: number): number; 5 | export declare enum Filter { 6 | Tab = 1, 7 | Panel = 2, 8 | Box = 4, 9 | Docked = 8, 10 | Floated = 16, 11 | Windowed = 32, 12 | Max = 64, 13 | EveryWhere = 120, 14 | AnyTab = 121, 15 | AnyPanel = 122, 16 | AnyTabPanel = 123, 17 | All = 127 18 | } 19 | export declare function find(layout: LayoutData, id: string | ((item: PanelData | TabData | BoxData) => boolean), filter?: Filter): PanelData | TabData | BoxData | undefined; 20 | export declare function addNextToTab(layout: LayoutData, source: TabData | PanelData, target: TabData, direction: DropDirection): LayoutData; 21 | export declare function addTabToPanel(layout: LayoutData, source: TabData | PanelData, panel: PanelData, idx?: number): LayoutData; 22 | export declare function converToPanel(source: TabData | PanelData): PanelData; 23 | export declare function dockPanelToPanel(layout: LayoutData, newPanel: PanelData, panel: PanelData, direction: DropDirection): LayoutData; 24 | export declare function dockPanelToBox(layout: LayoutData, newPanel: PanelData, box: BoxData, direction: DropDirection): LayoutData; 25 | export declare function floatPanel(layout: LayoutData, newPanel: PanelData, rect?: { 26 | left: number; 27 | top: number; 28 | width: number; 29 | height: number; 30 | }): LayoutData; 31 | export declare function panelToWindow(layout: LayoutData, newPanel: PanelData): LayoutData; 32 | export declare function removeFromLayout(layout: LayoutData, source: TabData | PanelData): LayoutData; 33 | export declare function moveToFront(layout: LayoutData, source: TabData | PanelData): LayoutData; 34 | export declare function maximize(layout: LayoutData, source: TabData | PanelData): LayoutData; 35 | export declare function fixFloatPanelPos(layout: LayoutData, layoutWidth?: number, layoutHeight?: number): LayoutData; 36 | export declare function fixLayoutData(layout: LayoutData, groups?: { 37 | [key: string]: TabGroup; 38 | }, loadTab?: (tab: TabBase) => TabData): LayoutData; 39 | export declare function replacePanel(layout: LayoutData, panel: PanelData, newPanel: PanelData): LayoutData; 40 | export declare function getFloatPanelSize(panel: HTMLElement, tabGroup: TabGroup): number[]; 41 | export declare function findNearestPanel(rectFrom: DOMRect, rectTo: DOMRect, direction: string): number; 42 | -------------------------------------------------------------------------------- /es/Divider.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DragState } from "./dragdrop/DragManager"; 3 | export interface DividerChild { 4 | size: number; 5 | minSize?: number; 6 | } 7 | export interface DividerData { 8 | element: HTMLElement; 9 | beforeDivider: DividerChild[]; 10 | afterDivider: DividerChild[]; 11 | } 12 | interface DividerProps { 13 | idx: number; 14 | className?: string; 15 | isVertical?: boolean; 16 | getDividerData(idx: number): DividerData; 17 | changeSizes(sizes: number[]): void; 18 | onDragEnd?(): void; 19 | } 20 | declare class BoxDataCache implements DividerData { 21 | element: HTMLElement; 22 | beforeDivider: DividerChild[]; 23 | afterDivider: DividerChild[]; 24 | beforeSize: number; 25 | beforeMinSize: number; 26 | afterSize: number; 27 | afterMinSize: number; 28 | constructor(data: DividerData); 29 | } 30 | export declare class Divider extends React.PureComponent { 31 | boxData: BoxDataCache; 32 | startDrag: (e: DragState) => void; 33 | dragMove: (e: DragState) => void; 34 | dragMove2(dx: number, dy: number): void; 35 | dragMoveAll(dx: number, dy: number): void; 36 | dragEnd: (e: DragState) => void; 37 | render(): React.ReactNode; 38 | } 39 | export {}; 40 | -------------------------------------------------------------------------------- /es/Divider.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DragDropDiv } from "./dragdrop/DragDropDiv"; 3 | class BoxDataCache { 4 | constructor(data) { 5 | this.beforeSize = 0; 6 | this.beforeMinSize = 0; 7 | this.afterSize = 0; 8 | this.afterMinSize = 0; 9 | this.element = data.element; 10 | this.beforeDivider = data.beforeDivider; 11 | this.afterDivider = data.afterDivider; 12 | for (let child of this.beforeDivider) { 13 | this.beforeSize += child.size; 14 | if (child.minSize > 0) { 15 | this.beforeMinSize += child.minSize; 16 | } 17 | } 18 | for (let child of this.afterDivider) { 19 | this.afterSize += child.size; 20 | if (child.minSize > 0) { 21 | this.afterMinSize += child.minSize; 22 | } 23 | } 24 | } 25 | } 26 | // split size among children 27 | function spiltSize(newSize, oldSize, children) { 28 | let reservedSize = -1; 29 | let sizes = []; 30 | let requiredMinSize = 0; 31 | while (requiredMinSize !== reservedSize) { 32 | reservedSize = requiredMinSize; 33 | requiredMinSize = 0; 34 | let ratio = (newSize - reservedSize) / (oldSize - reservedSize); 35 | if (!(ratio >= 0)) { 36 | // invalid input 37 | break; 38 | } 39 | for (let i = 0; i < children.length; ++i) { 40 | let size = children[i].size * ratio; 41 | if (size < children[i].minSize) { 42 | size = children[i].minSize; 43 | requiredMinSize += size; 44 | } 45 | sizes[i] = size; 46 | } 47 | } 48 | return sizes; 49 | } 50 | export class Divider extends React.PureComponent { 51 | constructor() { 52 | super(...arguments); 53 | this.startDrag = (e) => { 54 | this.boxData = new BoxDataCache(this.props.getDividerData(this.props.idx)); 55 | e.startDrag(this.boxData.element, null); 56 | }; 57 | this.dragMove = (e) => { 58 | if (e.event.shiftKey || e.event.ctrlKey || e.event.altKey) { 59 | this.dragMoveAll(e.dx, e.dy); 60 | } 61 | else { 62 | this.dragMove2(e.dx, e.dy); 63 | } 64 | }; 65 | this.dragEnd = (e) => { 66 | let { onDragEnd } = this.props; 67 | this.boxData = null; 68 | if (onDragEnd) { 69 | onDragEnd(); 70 | } 71 | }; 72 | } 73 | dragMove2(dx, dy) { 74 | let { isVertical, changeSizes } = this.props; 75 | let { beforeDivider, afterDivider } = this.boxData; 76 | if (!(beforeDivider.length && afterDivider.length)) { 77 | // invalid input 78 | return; 79 | } 80 | let d = isVertical ? dy : dx; 81 | let leftChild = beforeDivider.at(-1); 82 | let rightChild = afterDivider[0]; 83 | let leftSize = leftChild.size + d; 84 | let rightSize = rightChild.size - d; 85 | // check min size 86 | if (d > 0) { 87 | if (rightSize < rightChild.minSize) { 88 | rightSize = rightChild.minSize; 89 | leftSize = leftChild.size + rightChild.size - rightSize; 90 | } 91 | } 92 | else if (leftSize < leftChild.minSize) { 93 | leftSize = leftChild.minSize; 94 | rightSize = leftChild.size + rightChild.size - leftSize; 95 | } 96 | let sizes = beforeDivider.concat(afterDivider).map((child) => child.size); 97 | sizes[beforeDivider.length - 1] = leftSize; 98 | sizes[beforeDivider.length] = rightSize; 99 | changeSizes(sizes); 100 | } 101 | dragMoveAll(dx, dy) { 102 | let { isVertical, changeSizes } = this.props; 103 | let { beforeSize, beforeMinSize, afterSize, afterMinSize, beforeDivider, afterDivider } = this.boxData; 104 | let d = isVertical ? dy : dx; 105 | let newBeforeSize = beforeSize + d; 106 | let newAfterSize = afterSize - d; 107 | // check total min size 108 | if (d > 0) { 109 | if (newAfterSize < afterMinSize) { 110 | newAfterSize = afterMinSize; 111 | newBeforeSize = beforeSize + afterSize - afterMinSize; 112 | } 113 | } 114 | else if (newBeforeSize < beforeMinSize) { 115 | newBeforeSize = beforeMinSize; 116 | newAfterSize = beforeSize + afterSize - beforeMinSize; 117 | } 118 | changeSizes(spiltSize(newBeforeSize, beforeSize, beforeDivider).concat(spiltSize(newAfterSize, afterSize, afterDivider))); 119 | } 120 | render() { 121 | let { className } = this.props; 122 | if (!className) { 123 | className = 'dock-divider'; 124 | } 125 | return (React.createElement(DragDropDiv, { className: className, onDragStartT: this.startDrag, onDragMoveT: this.dragMove, onDragEndT: this.dragEnd })); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /es/DividerBox.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockContext } from "./DockData"; 3 | import { DividerChild } from "./Divider"; 4 | interface Props extends React.HTMLAttributes { 5 | mode?: 'horizontal' | 'vertical'; 6 | } 7 | export declare class DividerBox extends React.PureComponent { 8 | static contextType: React.Context; 9 | context: DockContext; 10 | _ref: HTMLDivElement; 11 | getRef: (r: HTMLDivElement) => void; 12 | getDividerData: (idx: number) => { 13 | element: HTMLDivElement; 14 | beforeDivider: DividerChild[]; 15 | afterDivider: DividerChild[]; 16 | }; 17 | changeSizes: (sizes: number[]) => void; 18 | render(): React.ReactNode; 19 | } 20 | export {}; 21 | -------------------------------------------------------------------------------- /es/DividerBox.js: -------------------------------------------------------------------------------- 1 | var __rest = (this && this.__rest) || function (s, e) { 2 | var t = {}; 3 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 4 | t[p] = s[p]; 5 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 6 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 7 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 8 | t[p[i]] = s[p[i]]; 9 | } 10 | return t; 11 | }; 12 | import * as React from "react"; 13 | import { DockContextType } from "./DockData"; 14 | import { Divider } from "./Divider"; 15 | export class DividerBox extends React.PureComponent { 16 | constructor() { 17 | super(...arguments); 18 | this.getRef = (r) => { 19 | this._ref = r; 20 | }; 21 | this.getDividerData = (idx) => { 22 | if (!this._ref) { 23 | return null; 24 | } 25 | let { children, mode } = this.props; 26 | let nodes = this._ref.childNodes; 27 | let length = 1; 28 | if (Array.isArray(children)) { 29 | length = children.length; 30 | } 31 | if (nodes.length !== length * 2 - 1) { 32 | return; 33 | } 34 | let dividerChildren = []; 35 | for (let i = 0; i < length; ++i) { 36 | if (mode === 'vertical') { 37 | dividerChildren.push({ size: nodes[i * 2].offsetHeight }); 38 | } 39 | else { 40 | dividerChildren.push({ size: nodes[i * 2].offsetWidth }); 41 | } 42 | } 43 | return { 44 | element: this._ref, 45 | beforeDivider: dividerChildren.slice(0, idx), 46 | afterDivider: dividerChildren.slice(idx) 47 | }; 48 | }; 49 | this.changeSizes = (sizes) => { 50 | let { mode } = this.props; 51 | let nodes = this._ref.childNodes; 52 | if (nodes.length === sizes.length * 2 - 1) { 53 | for (let i = 0; i < sizes.length; ++i) { 54 | if (mode === 'vertical') { 55 | nodes[i * 2].style.height = `${sizes[i]}px`; 56 | } 57 | else { 58 | nodes[i * 2].style.width = `${sizes[i]}px`; 59 | } 60 | } 61 | this.forceUpdate(); 62 | } 63 | }; 64 | } 65 | render() { 66 | let _a = this.props, { children, mode, className } = _a, others = __rest(_a, ["children", "mode", "className"]); 67 | let isVertical = mode === 'vertical'; 68 | let childrenRender = []; 69 | if (Array.isArray((children))) { 70 | for (let i = 0; i < children.length; ++i) { 71 | if (i > 0) { 72 | childrenRender.push(React.createElement(Divider, { idx: i, key: i, isVertical: isVertical, getDividerData: this.getDividerData, changeSizes: this.changeSizes })); 73 | } 74 | childrenRender.push(children[i]); 75 | } 76 | } 77 | else { 78 | childrenRender = children; 79 | } 80 | let cls; 81 | if (mode === 'vertical') { 82 | cls = 'divider-box dock-vbox'; 83 | } 84 | else { 85 | cls = 'divider-box dock-hbox'; 86 | } 87 | if (className) { 88 | cls = `${cls} ${className}`; 89 | } 90 | return (React.createElement("div", Object.assign({ ref: this.getRef, className: cls }, others), childrenRender)); 91 | } 92 | } 93 | DividerBox.contextType = DockContextType; 94 | -------------------------------------------------------------------------------- /es/DockBox.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { BoxData, DockContext } from "./DockData"; 3 | import { DividerChild } from "./Divider"; 4 | interface Props { 5 | size: number; 6 | boxData: BoxData; 7 | } 8 | export declare class DockBox extends React.PureComponent { 9 | static contextType: React.Context; 10 | context: DockContext; 11 | _ref: HTMLDivElement; 12 | getRef: (r: HTMLDivElement) => void; 13 | getDividerData: (idx: number) => { 14 | element: HTMLDivElement; 15 | beforeDivider: DividerChild[]; 16 | afterDivider: DividerChild[]; 17 | }; 18 | changeSizes: (sizes: number[]) => void; 19 | onDragEnd: () => void; 20 | render(): React.ReactNode; 21 | } 22 | export {}; 23 | -------------------------------------------------------------------------------- /es/DockBox.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockContextType } from "./DockData"; 3 | import { Divider } from "./Divider"; 4 | import { DockPanel } from "./DockPanel"; 5 | export class DockBox extends React.PureComponent { 6 | constructor() { 7 | super(...arguments); 8 | this.getRef = (r) => { 9 | this._ref = r; 10 | }; 11 | this.getDividerData = (idx) => { 12 | if (!this._ref) { 13 | return null; 14 | } 15 | let { children, mode } = this.props.boxData; 16 | let nodes = this._ref.childNodes; 17 | if (nodes.length !== children.length * 2 - 1) { 18 | return; 19 | } 20 | let dividerChildren = []; 21 | for (let i = 0; i < children.length; ++i) { 22 | if (mode === 'vertical') { 23 | dividerChildren.push({ size: nodes[i * 2].offsetHeight, minSize: children[i].minHeight }); 24 | } 25 | else { 26 | dividerChildren.push({ size: nodes[i * 2].offsetWidth, minSize: children[i].minWidth }); 27 | } 28 | } 29 | return { 30 | element: this._ref, 31 | beforeDivider: dividerChildren.slice(0, idx), 32 | afterDivider: dividerChildren.slice(idx) 33 | }; 34 | }; 35 | this.changeSizes = (sizes) => { 36 | let { children } = this.props.boxData; 37 | if (children.length !== sizes.length) { 38 | return; 39 | } 40 | for (let i = 0; i < children.length; ++i) { 41 | children[i].size = sizes[i]; 42 | } 43 | this.forceUpdate(); 44 | }; 45 | this.onDragEnd = () => { 46 | this.context.onSilentChange(null, 'move'); 47 | }; 48 | } 49 | render() { 50 | let { boxData } = this.props; 51 | let { minWidth, minHeight, size, children, mode, id, widthFlex, heightFlex } = boxData; 52 | let isVertical = mode === 'vertical'; 53 | let childrenRender = []; 54 | for (let i = 0; i < children.length; ++i) { 55 | if (i > 0) { 56 | childrenRender.push(React.createElement(Divider, { idx: i, key: i, isVertical: isVertical, onDragEnd: this.onDragEnd, getDividerData: this.getDividerData, changeSizes: this.changeSizes })); 57 | } 58 | let child = children[i]; 59 | if ('tabs' in child) { 60 | childrenRender.push(React.createElement(DockPanel, { size: child.size, panelData: child, key: child.id })); 61 | // render DockPanel 62 | } 63 | else if ('children' in child) { 64 | childrenRender.push(React.createElement(DockBox, { size: child.size, boxData: child, key: child.id })); 65 | } 66 | } 67 | let cls; 68 | let flex = 1; 69 | if (mode === 'vertical') { 70 | cls = 'dock-box dock-vbox'; 71 | if (widthFlex != null) { 72 | flex = widthFlex; 73 | } 74 | } 75 | else { 76 | // since special boxes dont reuse this render function, this can only be horizontal box 77 | cls = 'dock-box dock-hbox'; 78 | if (heightFlex != null) { 79 | flex = heightFlex; 80 | } 81 | } 82 | let flexGrow = flex * size; 83 | let flexShrink = flex * 1000000; 84 | if (flexShrink < 1) { 85 | flexShrink = 1; 86 | } 87 | return (React.createElement("div", { ref: this.getRef, className: cls, "data-dockid": id, style: { minWidth, minHeight, flex: `${flexGrow} ${flexShrink} ${size}px` } }, childrenRender)); 88 | } 89 | } 90 | DockBox.contextType = DockContextType; 91 | -------------------------------------------------------------------------------- /es/DockData.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | /** @ignore */ 3 | export const defaultGroup = { 4 | floatable: true, 5 | maximizable: true, 6 | }; 7 | /** @ignore */ 8 | export const placeHolderStyle = 'place-holder'; 9 | /** @ignore */ 10 | export const maximePlaceHolderId = '-maximized-placeholder-'; 11 | /** @ignore */ 12 | export const placeHolderGroup = { 13 | floatable: false, 14 | }; 15 | /** @ignore */ 16 | export const DockContextType = React.createContext(null); 17 | /** @ignore */ 18 | export const DockContextProvider = DockContextType.Provider; 19 | /** @ignore */ 20 | export const DockContextConsumer = DockContextType.Consumer; 21 | -------------------------------------------------------------------------------- /es/DockDropEdge.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockContext, DockMode, DropDirection, PanelData, TabGroup } from "./DockData"; 3 | import { DragState } from "./dragdrop/DragManager"; 4 | interface DockDropEdgeProps { 5 | panelData: PanelData; 6 | panelElement: HTMLElement; 7 | dropFromPanel: PanelData; 8 | } 9 | export declare class DockDropEdge extends React.PureComponent { 10 | static contextType: React.Context; 11 | context: DockContext; 12 | _ref: HTMLDivElement; 13 | getRef: (r: HTMLDivElement) => void; 14 | getDirection(e: DragState, group: TabGroup, samePanel: boolean, tabLength: number): { 15 | direction: DropDirection; 16 | mode?: DockMode; 17 | depth: number; 18 | }; 19 | getActualDepth(depth: number, mode: DockMode, direction: DropDirection): number; 20 | onDragOver: (e: DragState) => void; 21 | onDragLeave: (e: DragState) => void; 22 | onDrop: (e: DragState) => void; 23 | render(): React.ReactNode; 24 | componentWillUnmount(): void; 25 | } 26 | export {}; 27 | -------------------------------------------------------------------------------- /es/DockDropLayer.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockContext, DockMode, DropDirection, PanelData } from "./DockData"; 3 | import { DragState } from "./dragdrop/DragManager"; 4 | interface DockDropSquareProps { 5 | direction: DropDirection; 6 | depth?: number; 7 | panelData: PanelData; 8 | panelElement: HTMLElement; 9 | } 10 | interface DockDropSquareState { 11 | dropping: boolean; 12 | } 13 | export declare class DockDropSquare extends React.PureComponent { 14 | static contextType: React.Context; 15 | context: DockContext; 16 | state: { 17 | dropping: boolean; 18 | }; 19 | onDragOver: (e: DragState) => void; 20 | onDragLeave: (e: DragState) => void; 21 | onDrop: (e: DragState) => void; 22 | render(): React.ReactNode; 23 | componentWillUnmount(): void; 24 | } 25 | interface DockDropLayerProps { 26 | panelData: PanelData; 27 | panelElement: HTMLElement; 28 | dropFromPanel: PanelData; 29 | } 30 | export declare class DockDropLayer extends React.PureComponent { 31 | static contextType: React.Context; 32 | context: DockContext; 33 | static addDepthSquare(children: React.ReactNode[], mode: DockMode, panelData: PanelData, panelElement: HTMLElement, depth?: number): void; 34 | render(): React.ReactNode; 35 | } 36 | export {}; 37 | -------------------------------------------------------------------------------- /es/DockPanel.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockContext, PanelData } from "./DockData"; 3 | import { DragState } from "./dragdrop/DragManager"; 4 | interface Props { 5 | panelData: PanelData; 6 | size: number; 7 | } 8 | interface State { 9 | dropFromPanel: PanelData; 10 | draggingHeader: boolean; 11 | } 12 | export declare class DockPanel extends React.PureComponent { 13 | static contextType: React.Context; 14 | context: DockContext; 15 | _ref: HTMLDivElement; 16 | getRef: (r: HTMLDivElement) => void; 17 | static _droppingPanel: DockPanel; 18 | static set droppingPanel(panel: DockPanel); 19 | state: State; 20 | onDragOver: (e: DragState) => void; 21 | onDragOverOtherPanel(): void; 22 | _movingX: number; 23 | _movingY: number; 24 | onPanelHeaderDragStart: (event: DragState) => void; 25 | onPanelHeaderDragMove: (e: DragState) => void; 26 | onPanelHeaderDragEnd: (e: DragState) => void; 27 | _movingW: number; 28 | _movingH: number; 29 | _movingCorner: string; 30 | onPanelCornerDragT: (e: DragState) => void; 31 | onPanelCornerDragB: (e: DragState) => void; 32 | onPanelCornerDragL: (e: DragState) => void; 33 | onPanelCornerDragR: (e: DragState) => void; 34 | onPanelCornerDragTL: (e: DragState) => void; 35 | onPanelCornerDragTR: (e: DragState) => void; 36 | onPanelCornerDragBL: (e: DragState) => void; 37 | onPanelCornerDragBR: (e: DragState) => void; 38 | onPanelCornerDrag(e: DragState, corner: string): void; 39 | onPanelCornerDragMove: (e: DragState) => void; 40 | onPanelCornerDragEnd: (e: DragState) => void; 41 | onFloatPointerDown: () => void; 42 | onPanelClicked: (e: React.MouseEvent) => void; 43 | render(): React.ReactNode; 44 | _unmounted: boolean; 45 | componentWillUnmount(): void; 46 | } 47 | export {}; 48 | -------------------------------------------------------------------------------- /es/DockTabBar.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as DragManager from "./dragdrop/DragManager"; 3 | import type { TabNavListProps } from "rc-tabs/lib/TabNavList"; 4 | interface DockTabBarProps extends TabNavListProps { 5 | isMaximized: boolean; 6 | onDragStart?: DragManager.DragHandler; 7 | onDragMove?: DragManager.DragHandler; 8 | onDragEnd?: DragManager.DragHandler; 9 | TabNavList: React.ComponentType; 10 | } 11 | export declare function DockTabBar(props: DockTabBarProps): React.JSX.Element; 12 | export {}; 13 | -------------------------------------------------------------------------------- /es/DockTabBar.js: -------------------------------------------------------------------------------- 1 | var __rest = (this && this.__rest) || function (s, e) { 2 | var t = {}; 3 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 4 | t[p] = s[p]; 5 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 6 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 7 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 8 | t[p[i]] = s[p[i]]; 9 | } 10 | return t; 11 | }; 12 | import * as React from "react"; 13 | import { DragDropDiv } from "./dragdrop/DragDropDiv"; 14 | import { DockContextType } from "./DockData"; 15 | /** 16 | * @return returns true if navigation is handled in local tab move, otherwise returns false 17 | */ 18 | function checkLocalTabMove(key, tabbar) { 19 | if (key === 'ArrowLeft' || key === 'ArrowRight') { 20 | let tabs = Array.from(tabbar.querySelectorAll('.dock-tab-btn')); 21 | let activeTab = tabbar.querySelector('.dock-tab-active>.dock-tab-btn'); 22 | let i = tabs.indexOf(activeTab); 23 | if (i >= 0) { 24 | if (key === 'ArrowLeft') { 25 | if (i > 0) { 26 | tabs[i - 1].click(); 27 | tabs[i - 1].focus(); 28 | return true; 29 | } 30 | } 31 | else { 32 | if (i < tabs.length - 1) { 33 | tabs[i + 1].click(); 34 | tabs[i + 1].focus(); 35 | return true; 36 | } 37 | } 38 | } 39 | } 40 | return false; 41 | } 42 | export function DockTabBar(props) { 43 | const { onDragStart, onDragMove, onDragEnd, TabNavList, isMaximized } = props, restProps = __rest(props, ["onDragStart", "onDragMove", "onDragEnd", "TabNavList", "isMaximized"]); 44 | const layout = React.useContext(DockContextType); 45 | const ref = React.useRef(); 46 | const getRef = (div) => { 47 | ref.current = div; 48 | }; 49 | const onKeyDown = (e) => { 50 | if (e.key.startsWith('Arrow')) { 51 | if (!checkLocalTabMove(e.key, ref.current) && !isMaximized) { 52 | layout.navigateToPanel(ref.current, e.key); 53 | } 54 | e.stopPropagation(); 55 | e.preventDefault(); 56 | } 57 | }; 58 | return (React.createElement(DragDropDiv, { onDragStartT: onDragStart, onDragMoveT: onDragMove, onDragEndT: onDragEnd, role: "tablist", className: "dock-bar", onKeyDown: onKeyDown, getRef: getRef, tabIndex: -1 }, 59 | React.createElement(TabNavList, Object.assign({}, restProps)))); 60 | } 61 | -------------------------------------------------------------------------------- /es/DockTabPane.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DockContext, TabPaneCache } from "./DockData"; 3 | import { TabPaneProps } from "rc-tabs"; 4 | interface DockTabPaneProps extends TabPaneProps { 5 | cacheId?: string; 6 | cached: boolean; 7 | } 8 | export default class DockTabPane extends React.PureComponent { 9 | static contextType: React.Context; 10 | context: DockContext; 11 | _ref: HTMLDivElement; 12 | getRef: (r: HTMLDivElement) => void; 13 | updateCache(): void; 14 | visited: boolean; 15 | _cache: TabPaneCache; 16 | render(): React.JSX.Element; 17 | componentDidMount(): void; 18 | componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void; 19 | componentWillUnmount(): void; 20 | } 21 | export {}; 22 | -------------------------------------------------------------------------------- /es/DockTabPane.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import classNames from 'classnames'; 3 | import { DockContextType } from "./DockData"; 4 | export default class DockTabPane extends React.PureComponent { 5 | constructor() { 6 | super(...arguments); 7 | this.getRef = (r) => { 8 | this._ref = r; 9 | }; 10 | } 11 | updateCache() { 12 | const { cached, children, cacheId } = this.props; 13 | if (this._cache) { 14 | if (!cached || cacheId !== this._cache.id) { 15 | this.context.removeTabCache(this._cache.id, this); 16 | this._cache = null; 17 | } 18 | } 19 | if (cached && this._ref) { 20 | this._cache = this.context.getTabCache(cacheId, this); 21 | if (!this._ref.contains(this._cache.div)) { 22 | this._ref.appendChild(this._cache.div); 23 | } 24 | this.context.updateTabCache(this._cache.id, children); 25 | } 26 | } 27 | render() { 28 | const { cacheId, cached, prefixCls, forceRender, className, style, id, active, animated, destroyInactiveTabPane, tabKey, children, } = this.props; 29 | if (active) { 30 | this.visited = true; 31 | } 32 | else if (destroyInactiveTabPane) { 33 | this.visited = false; 34 | } 35 | const mergedStyle = {}; 36 | if (!active) { 37 | if (animated) { 38 | mergedStyle.visibility = 'hidden'; 39 | mergedStyle.height = 0; 40 | mergedStyle.overflowY = 'hidden'; 41 | } 42 | else { 43 | mergedStyle.display = 'none'; 44 | } 45 | } 46 | // when cached == undefined, it will still cache the children inside tabs component, but not across whole dock layout 47 | // when cached == false, children are destroyed when not active 48 | const isRender = cached === false ? active : this.visited; 49 | let renderChildren = null; 50 | if (cached) { 51 | renderChildren = null; 52 | } 53 | else if (isRender || forceRender) { 54 | renderChildren = children; 55 | } 56 | let getRef = cached ? this.getRef : null; 57 | return (React.createElement("div", { ref: getRef, id: cacheId, role: "tabpanel", "aria-labelledby": id && `${id}-tab-${tabKey}`, "aria-hidden": !active, style: Object.assign(Object.assign({}, mergedStyle), style), className: classNames(`${prefixCls}-tabpane`, active && `${prefixCls}-tabpane-active`, className) }, (active || this.visited || forceRender) && renderChildren)); 58 | } 59 | componentDidMount() { 60 | this.updateCache(); 61 | } 62 | componentDidUpdate(prevProps, prevState, snapshot) { 63 | this.updateCache(); 64 | } 65 | componentWillUnmount() { 66 | if (this._cache) { 67 | this.context.removeTabCache(this._cache.id, this); 68 | } 69 | } 70 | } 71 | DockTabPane.contextType = DockContextType; 72 | -------------------------------------------------------------------------------- /es/DockTabs.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockContext, DropDirection, PanelData, TabData } from "./DockData"; 3 | import * as DragManager from "./dragdrop/DragManager"; 4 | export declare class TabCache { 5 | _ref: HTMLDivElement; 6 | getRef: (r: HTMLDivElement) => void; 7 | _hitAreaRef: HTMLDivElement; 8 | getHitAreaRef: (r: HTMLDivElement) => void; 9 | data: TabData; 10 | context: DockContext; 11 | content: React.ReactElement; 12 | constructor(context: DockContext); 13 | setData(data: TabData): boolean; 14 | onCloseClick: (e: React.MouseEvent) => void; 15 | onDragStart: (e: DragManager.DragState) => void; 16 | onDragOver: (e: DragManager.DragState) => void; 17 | onDragLeave: (e: DragManager.DragState) => void; 18 | onDrop: (e: DragManager.DragState) => void; 19 | getDropDirection(e: DragManager.DragState): DropDirection; 20 | render(): React.ReactElement; 21 | destroy(): void; 22 | } 23 | interface Props { 24 | panelData: PanelData; 25 | onPanelDragStart: DragManager.DragHandler; 26 | onPanelDragMove: DragManager.DragHandler; 27 | onPanelDragEnd: DragManager.DragHandler; 28 | } 29 | export declare class DockTabs extends React.PureComponent { 30 | static contextType: React.Context; 31 | static readonly propKeys: string[]; 32 | context: DockContext; 33 | _cache: Map; 34 | cachedTabs: TabData[]; 35 | updateTabs(tabs: TabData[]): void; 36 | onMaximizeClick: (e: React.MouseEvent) => void; 37 | onNewWindowClick: () => void; 38 | addNewWindowMenu(element: React.ReactElement, showWithLeftClick: boolean): React.JSX.Element; 39 | onCloseAll: () => void; 40 | renderTabBar: (props: any, TabNavList: React.ComponentType) => React.JSX.Element; 41 | onTabChange: (activeId: string) => void; 42 | render(): React.ReactNode; 43 | } 44 | export {}; 45 | -------------------------------------------------------------------------------- /es/FloatBox.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { BoxData } from "./DockData"; 3 | interface Props { 4 | boxData: BoxData; 5 | } 6 | export declare class FloatBox extends React.PureComponent { 7 | render(): React.ReactNode; 8 | } 9 | export {}; 10 | -------------------------------------------------------------------------------- /es/FloatBox.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockPanel } from "./DockPanel"; 3 | export class FloatBox extends React.PureComponent { 4 | render() { 5 | let { children } = this.props.boxData; 6 | let childrenRender = []; 7 | for (let child of children) { 8 | if ('tabs' in child) { 9 | childrenRender.push(React.createElement(DockPanel, { size: child.size, panelData: child, key: child.id })); 10 | } 11 | } 12 | return (React.createElement("div", { className: 'dock-box dock-fbox' }, childrenRender)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /es/MaxBox.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { BoxData, PanelData } from "./DockData"; 3 | interface Props { 4 | boxData: BoxData; 5 | } 6 | export declare class MaxBox extends React.PureComponent { 7 | hidePanelData: PanelData; 8 | render(): React.ReactNode; 9 | } 10 | export {}; 11 | -------------------------------------------------------------------------------- /es/MaxBox.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockPanel } from "./DockPanel"; 3 | export class MaxBox extends React.PureComponent { 4 | render() { 5 | let panelData = this.props.boxData.children[0]; 6 | if (panelData) { 7 | this.hidePanelData = Object.assign(Object.assign({}, panelData), { id: '', tabs: [] }); 8 | return (React.createElement("div", { className: "dock-box dock-mbox dock-mbox-show" }, 9 | React.createElement(DockPanel, { size: 100, panelData: panelData }))); 10 | } 11 | else if (this.hidePanelData) { 12 | // use the hiden data only once, dont keep it for too long 13 | let hidePanelData = this.hidePanelData; 14 | this.hidePanelData = null; 15 | return (React.createElement("div", { className: "dock-box dock-mbox dock-mbox-hide" }, 16 | React.createElement(DockPanel, { size: 100, panelData: hidePanelData }))); 17 | } 18 | else { 19 | return (React.createElement("div", { className: "dock-box dock-mbox dock-mbox-hide" })); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /es/Serializer.d.ts: -------------------------------------------------------------------------------- 1 | import { BoxData, LayoutData, PanelData, LayoutBase, PanelBase, TabBase, TabData } from "./DockData"; 2 | interface DefaultLayoutCache { 3 | panels: Map; 4 | tabs: Map; 5 | } 6 | export declare function createLayoutCache(defaultLayout: LayoutData | BoxData): DefaultLayoutCache; 7 | export declare function saveLayoutData(layout: LayoutData, saveTab?: (tab: TabData) => TabBase, afterPanelSaved?: (savedPanel: PanelBase, panel: PanelData) => void): LayoutBase; 8 | export declare function loadLayoutData(savedLayout: LayoutBase, defaultLayout: LayoutData, loadTab?: (savedTab: TabBase) => TabData, afterPanelLoaded?: (savedPanel: PanelBase, panel: PanelData) => void): LayoutData; 9 | export {}; 10 | -------------------------------------------------------------------------------- /es/Utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare const groupClassNames: (groupNames?: string) => string[]; 2 | -------------------------------------------------------------------------------- /es/Utils.js: -------------------------------------------------------------------------------- 1 | export const groupClassNames = (groupNames = '') => groupNames 2 | .split(' ') 3 | .filter((value) => value !== '') 4 | .map((name) => `dock-style-${name}`); 5 | -------------------------------------------------------------------------------- /es/WindowBox.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { BoxData } from "./DockData"; 3 | interface Props { 4 | boxData: BoxData; 5 | } 6 | export declare class WindowBox extends React.PureComponent { 7 | static enabled: boolean; 8 | render(): React.ReactNode; 9 | } 10 | export {}; 11 | -------------------------------------------------------------------------------- /es/WindowBox.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { WindowPanel } from "./WindowPanel"; 3 | export class WindowBox extends React.PureComponent { 4 | render() { 5 | let { children } = this.props.boxData; 6 | let childrenRender = []; 7 | for (let child of children) { 8 | if ('tabs' in child) { 9 | childrenRender.push(React.createElement(WindowPanel, { key: child.id, panelData: child })); 10 | } 11 | } 12 | return (React.createElement(React.Fragment, null, childrenRender)); 13 | } 14 | } 15 | WindowBox.enabled = typeof window === 'object' && ((window === null || window === void 0 ? void 0 : window.navigator.platform) === 'Win32' || (window === null || window === void 0 ? void 0 : window.navigator.platform) === 'MacIntel'); 16 | -------------------------------------------------------------------------------- /es/WindowPanel.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DockContext, PanelData } from "./DockData"; 3 | interface Props { 4 | panelData: PanelData; 5 | } 6 | export declare class WindowPanel extends React.PureComponent { 7 | static contextType: React.Context; 8 | context: DockContext; 9 | _window: Window; 10 | onOpen: (w: Window) => void; 11 | onUnload: () => void; 12 | initPopupInnerRect: () => any; 13 | render(): React.ReactNode; 14 | } 15 | export {}; 16 | -------------------------------------------------------------------------------- /es/WindowPanel.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import NewWindow from "rc-new-window"; 3 | import { DockContextType } from "./DockData"; 4 | import { DockPanel } from "./DockPanel"; 5 | import { mapElementToScreenRect, mapWindowToElement } from "rc-new-window/lib/ScreenPosition"; 6 | export class WindowPanel extends React.PureComponent { 7 | constructor() { 8 | super(...arguments); 9 | this.onOpen = (w) => { 10 | if (!this._window && w) { 11 | this._window = w; 12 | } 13 | }; 14 | this.onUnload = () => { 15 | let { panelData } = this.props; 16 | let layoutRoot = this.context.getRootElement(); 17 | const rect = mapWindowToElement(layoutRoot, this._window); 18 | if (rect.width > 0 && rect.height > 0) { 19 | panelData.x = rect.left; 20 | panelData.y = rect.top; 21 | panelData.w = rect.width; 22 | panelData.h = rect.height; 23 | } 24 | this.context.dockMove(panelData, null, 'float'); 25 | }; 26 | this.initPopupInnerRect = () => { 27 | let { panelData } = this.props; 28 | return mapElementToScreenRect(this.context.getRootElement(), { 29 | left: panelData.x, 30 | top: panelData.y, 31 | width: panelData.w, 32 | height: panelData.h 33 | }); 34 | }; 35 | } 36 | render() { 37 | let { panelData } = this.props; 38 | let { x, y, w, h } = panelData; 39 | return React.createElement(NewWindow, { copyStyles: true, onOpen: this.onOpen, onClose: this.onUnload, onBlock: this.onUnload, initPopupInnerRect: this.initPopupInnerRect, width: w, height: h }, 40 | React.createElement("div", { className: 'dock-wbox' }, 41 | React.createElement(DockPanel, { size: panelData.size, panelData: panelData, key: panelData.id }))); 42 | } 43 | } 44 | WindowPanel.contextType = DockContextType; 45 | -------------------------------------------------------------------------------- /es/dragdrop/DragDropDiv.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as DragManager from "./DragManager"; 3 | import { GestureState } from "./GestureManager"; 4 | export declare type AbstractPointerEvent = MouseEvent | TouchEvent; 5 | interface DragDropDivProps extends React.HTMLAttributes { 6 | getRef?: React.ForwardedRef; 7 | onDragStartT?: DragManager.DragHandler; 8 | onDragMoveT?: DragManager.DragHandler; 9 | onDragEndT?: DragManager.DragHandler; 10 | onDragOverT?: DragManager.DragHandler; 11 | onDragLeaveT?: DragManager.DragHandler; 12 | /** 13 | * Anything returned by onDropT will be stored in DragState. 14 | * return false to indicate the drop is canceled 15 | */ 16 | onDropT?: DragManager.DropHandler; 17 | /** 18 | * by default onDragStartT will be called on first drag move 19 | * but if directDragT is true, onDragStartT will be called as soon as mouse is down 20 | */ 21 | directDragT?: boolean; 22 | captureT?: boolean; 23 | useRightButtonDragT?: boolean; 24 | onGestureStartT?: (state: GestureState) => boolean; 25 | onGestureMoveT?: (state: GestureState) => void; 26 | onGestureEndT?: () => void; 27 | gestureSensitivity?: number; 28 | } 29 | export declare class DragDropDiv extends React.PureComponent { 30 | element: HTMLElement; 31 | ownerDocument: Document; 32 | _getRef: (r: HTMLDivElement) => void; 33 | getHandlers(): DragManager.DragHandlers; 34 | dragType: DragManager.DragType; 35 | baseX: number; 36 | baseY: number; 37 | scaleX: number; 38 | scaleY: number; 39 | waitingMove: boolean; 40 | listening: boolean; 41 | gesturing: boolean; 42 | baseX2: number; 43 | baseY2: number; 44 | baseDis: number; 45 | baseAng: number; 46 | onPointerDown: (e: React.MouseEvent | React.TouchEvent) => void; 47 | onDragStart(event: MouseEvent | TouchEvent): void; 48 | addDragListeners(event: MouseEvent | TouchEvent): void; 49 | checkFirstMove(e: AbstractPointerEvent): boolean; 50 | executeFirstMove(state: DragManager.DragState): boolean; 51 | onMouseMove: (e: MouseEvent) => void; 52 | onTouchMove: (e: TouchEvent) => void; 53 | onDragEnd: (e?: TouchEvent | MouseEvent) => void; 54 | addGestureListeners(event: TouchEvent): void; 55 | onGestureStart(event: TouchEvent): void; 56 | onGestureMove: (e: TouchEvent) => void; 57 | onGestureEnd: (e?: TouchEvent) => void; 58 | onKeyDown: (e: KeyboardEvent) => void; 59 | cancel(): void; 60 | removeListeners(): void; 61 | cleanupDrag(state: DragManager.DragState): void; 62 | render(): React.ReactNode; 63 | componentDidUpdate(prevProps: DragDropDivProps): void; 64 | componentWillUnmount(): void; 65 | } 66 | export {}; 67 | -------------------------------------------------------------------------------- /es/dragdrop/DragManager.d.ts: -------------------------------------------------------------------------------- 1 | export declare type DragType = 'left' | 'right' | 'touch'; 2 | interface DragDropComponent { 3 | element: HTMLElement; 4 | ownerDocument: Document; 5 | dragType: DragType; 6 | baseX: number; 7 | baseY: number; 8 | scaleX: number; 9 | scaleY: number; 10 | } 11 | export declare class DragState { 12 | _init: boolean; 13 | event: MouseEvent | TouchEvent; 14 | component: DragDropComponent; 15 | pageX: number; 16 | pageY: number; 17 | clientX: number; 18 | clientY: number; 19 | dx: number; 20 | dy: number; 21 | dropped: any; 22 | constructor(event: MouseEvent | TouchEvent, component: DragDropComponent, init?: boolean); 23 | moved(): boolean; 24 | /** 25 | * @param refElement, the element being moved 26 | * @param draggingHtml, the element show in the dragging layer 27 | */ 28 | startDrag(refElement?: HTMLElement, draggingHtml?: HTMLElement | string): void; 29 | setData(data?: { 30 | [key: string]: any; 31 | }, scope?: any): void; 32 | static getData(field: string, scope?: any): any; 33 | get dragType(): DragType; 34 | acceptMessage: string; 35 | rejected: boolean; 36 | accept(message?: string): void; 37 | reject(): void; 38 | _onMove(): void; 39 | _onDragEnd(canceled?: boolean): void; 40 | } 41 | export declare type DragHandler = (state: DragState) => void; 42 | export declare type DropHandler = (state: DragState) => any; 43 | export interface DragHandlers { 44 | onDragOverT?: DragHandler; 45 | onDragLeaveT?: DragHandler; 46 | onDropT?: DropHandler; 47 | } 48 | export interface HandlerHost { 49 | getHandlers(): DragHandlers; 50 | } 51 | export declare function isDragging(): boolean; 52 | export declare function addHandlers(element: HTMLElement, handler: HandlerHost): void; 53 | export declare function removeHandlers(element: HTMLElement): void; 54 | export declare function destroyDraggingElement(e: DragState): void; 55 | export declare function addDragStateListener(callback: (scope: any) => void): void; 56 | export declare function removeDragStateListener(callback: (scope: any) => void): void; 57 | export {}; 58 | -------------------------------------------------------------------------------- /es/dragdrop/GestureManager.d.ts: -------------------------------------------------------------------------------- 1 | import { DragType } from "./DragManager"; 2 | interface GestureComponent { 3 | element: HTMLElement; 4 | dragType: DragType; 5 | baseX: number; 6 | baseY: number; 7 | baseX2: number; 8 | baseY2: number; 9 | scaleX: number; 10 | scaleY: number; 11 | baseDis: number; 12 | baseAng: number; 13 | } 14 | export declare class GestureState { 15 | _init: boolean; 16 | event: TouchEvent; 17 | component: GestureComponent; 18 | dx1: number; 19 | dy1: number; 20 | dx2: number; 21 | dy2: number; 22 | scale: number; 23 | rotate: number; 24 | dx: number; 25 | dy: number; 26 | moved(): number; 27 | pageCenter(): [number, number]; 28 | clientCenter(): [number, number]; 29 | constructor(event: TouchEvent, component: GestureComponent, init?: boolean); 30 | } 31 | export {}; 32 | -------------------------------------------------------------------------------- /es/dragdrop/GestureManager.js: -------------------------------------------------------------------------------- 1 | export class GestureState { 2 | constructor(event, component, init = false) { 3 | this.dx1 = 0; 4 | this.dy1 = 0; 5 | this.dx2 = 0; 6 | this.dy2 = 0; 7 | this.scale = 1; 8 | this.rotate = 0; 9 | this.dx = 0; 10 | this.dy = 0; 11 | this.event = event; 12 | this.component = component; 13 | this._init = init; 14 | if (!event || event.touches.length !== 2) { 15 | return; 16 | } 17 | let touch1 = event.touches[0]; 18 | let touch2 = event.touches[1]; 19 | this.dx1 = (touch1.pageX - component.baseX) * component.scaleX; 20 | this.dy1 = (touch1.pageY - component.baseY) * component.scaleY; 21 | this.dx2 = (touch2.pageX - component.baseX2) * component.scaleX; 22 | this.dy2 = (touch2.pageY - component.baseY2) * component.scaleY; 23 | if (this.dx1 * this.dx2 >= 0) { 24 | this.dx = (this.dx1 + this.dx2) / 2; 25 | } 26 | if (this.dy1 * this.dy2 >= 0) { 27 | this.dy = (this.dy1 + this.dy2) / 2; 28 | } 29 | this.scale = Math.sqrt(Math.pow(touch2.pageX - touch1.pageX, 2) + Math.pow(touch2.pageY - touch1.pageY, 2)) / component.baseDis; 30 | this.rotate = Math.atan2(touch2.pageY - touch1.pageY, touch2.pageX - touch1.pageX) - component.baseAng; 31 | if (this.rotate > Math.PI) { 32 | this.rotate -= Math.PI * 2; 33 | } 34 | else if (this.rotate < -Math.PI) { 35 | this.rotate += Math.PI * 2; 36 | } 37 | } 38 | moved() { 39 | return Math.max(Math.abs(this.dx1), Math.abs(this.dx2), Math.abs(this.dy1), Math.abs(this.dy2)); 40 | } 41 | pageCenter() { 42 | let touch1 = this.event.touches[0]; 43 | let touch2 = this.event.touches[1]; 44 | return [(touch1.pageX + touch2.pageX) / 2, (touch1.pageY + touch2.pageY) / 2]; 45 | } 46 | clientCenter() { 47 | let touch1 = this.event.touches[0]; 48 | let touch2 = this.event.touches[1]; 49 | return [(touch1.clientX + touch2.clientX) / 2, (touch1.clientY + touch2.clientY) / 2]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /es/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./DockTabs"; 2 | export * from "./DockData"; 3 | export * from "./DockPanel"; 4 | export * from "./DockBox"; 5 | export * from "./DockLayout"; 6 | export * from "./dragdrop/DragManager"; 7 | export * from "./dragdrop/GestureManager"; 8 | export * from "./dragdrop/DragDropDiv"; 9 | export * from './Divider'; 10 | export * from './DividerBox'; 11 | import { DockLayout } from './DockLayout'; 12 | export default DockLayout; 13 | -------------------------------------------------------------------------------- /es/index.js: -------------------------------------------------------------------------------- 1 | export * from "./DockTabs"; 2 | export * from "./DockData"; 3 | export * from "./DockPanel"; 4 | export * from "./DockBox"; 5 | export * from "./DockLayout"; 6 | export * from "./dragdrop/DragManager"; 7 | export * from "./dragdrop/GestureManager"; 8 | export * from "./dragdrop/DragDropDiv"; 9 | export * from './Divider'; 10 | export * from './DividerBox'; 11 | import { DockLayout } from './DockLayout'; 12 | export default DockLayout; 13 | -------------------------------------------------------------------------------- /es/util/Compare.d.ts: -------------------------------------------------------------------------------- 1 | export declare function compareKeys(a: { 2 | [key: string]: any; 3 | }, b: { 4 | [key: string]: any; 5 | }, keys: string[]): boolean; 6 | export declare function compareArray(a: { 7 | [key: string]: any; 8 | }[], b: { 9 | [key: string]: any; 10 | }[]): boolean; 11 | -------------------------------------------------------------------------------- /es/util/Compare.js: -------------------------------------------------------------------------------- 1 | export function compareKeys(a, b, keys) { 2 | if (a === b) { 3 | return true; 4 | } 5 | if (a && b && typeof a === 'object' && typeof b === 'object') { 6 | for (let key of keys) { 7 | if (a[key] !== b[key]) { 8 | return false; 9 | } 10 | } 11 | return true; 12 | } 13 | return false; 14 | } 15 | const isArray = Array.isArray; 16 | export function compareArray(a, b) { 17 | if (a === b) { 18 | return true; 19 | } 20 | if (isArray(a) && isArray(b)) { 21 | let len = a.length; 22 | if (len !== b.length) { 23 | return false; 24 | } 25 | for (let i = 0; i < len; ++i) { 26 | if (a[i] !== b[i]) { 27 | return false; 28 | } 29 | } 30 | return true; 31 | } 32 | return false; 33 | } 34 | -------------------------------------------------------------------------------- /example/adv-save-layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/adv-save-layout.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {htmlTab, jsxTab} from "./prism-tabs"; 5 | import {DockLayout} from '../lib'; 6 | 7 | let groups = { 8 | allowWindow: { 9 | floatable: true, 10 | newWindow: true, 11 | maximizable: true, 12 | } 13 | }; 14 | 15 | class InputTab extends React.PureComponent { 16 | 17 | onChange = (e) => { 18 | this.props.tabData.inputValue = e.target.value; 19 | this.forceUpdate(); 20 | }; 21 | 22 | render() { 23 | return ( 24 |
25 |

input value will be saved

26 | 27 |
28 | ) 29 | } 30 | 31 | static create(tabData) { 32 | return ; 33 | } 34 | } 35 | 36 | let tab0 = { 37 | id: 'tab0', title: 'tab0', 38 | content:
This tab will be added back to main panel every time you load layout.
39 | }; 40 | 41 | class Demo extends React.Component { 42 | getRef = (r) => { 43 | this.dockLayout = r; 44 | }; 45 | 46 | state = {saved: null}; 47 | 48 | defaultLayout = { 49 | dockbox: { 50 | mode: 'horizontal', 51 | children: [ 52 | { 53 | size: 200, 54 | tabs: [{id: 'tab1'}, {id: 'tab2'}], 55 | }, 56 | { 57 | id: 'main-panel', 58 | size: 400, 59 | tabs: [{id: 'tab0'}, {id: 'jsxTab'}, {id: 'htmlTab'}], 60 | panelLock: { 61 | panelStyle: 'main' 62 | } 63 | }, 64 | { 65 | size: 200, 66 | tabs: [{id: 'tab5'}, {id: 'tab6'}], 67 | }, 68 | ] 69 | } 70 | }; 71 | 72 | saveTab = (tabData) => { 73 | let {id, inputValue} = tabData; 74 | // add inputValue from saved data; 75 | if (id === 'tab0') { 76 | return null; 77 | } 78 | return {id, inputValue}; 79 | }; 80 | loadTab = (savedTab) => { 81 | let id = savedTab.id; 82 | switch (id) { 83 | case 'tab0': 84 | return tab0; 85 | case 'jsxTab': 86 | return jsxTab; 87 | case 'htmlTab': 88 | return htmlTab; 89 | default: 90 | return {id, title: id, content: InputTab.create, inputValue: savedTab.inputValue, group: 'allowWindow'}; 91 | } 92 | }; 93 | 94 | // add tab0 to the main panel 95 | afterPanelLoaded = (savedPanel, panelData) => { 96 | let id = savedPanel.id; 97 | 98 | if (id === 'main-panel') { 99 | panelData.panelLock = { 100 | panelStyle: 'main' 101 | }; 102 | panelData.tabs.unshift({...tab0}); 103 | } 104 | }; 105 | 106 | render() { 107 | return ( 108 |
109 | 112 |
113 | 117 | 121 |
122 |
123 | ); 124 | } 125 | } 126 | 127 | createRoot(document.getElementById("app")).render(); 128 | -------------------------------------------------------------------------------- /example/adv-tab-update.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/adv-tab-update.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {htmlTab, jsxTab} from "./prism-tabs"; 5 | import {DockLayout} from '../lib'; 6 | 7 | let groups = { 8 | 'locked': { 9 | floatable: false, 10 | tabLocked: true 11 | } 12 | }; 13 | 14 | function getTab(id, value) { 15 | return { 16 | id, 17 | content: ( 18 |
19 |

It's easier to use React Context to update tab,
20 | but in some use cases you might need to directly update the tab.

21 | { 22 | id !== `tab${value}` ? 23 |

Only current active tab will be changed

24 | : null 25 | } 26 | value is {value} 27 |
28 | ), 29 | title: id, 30 | group: 'locked', 31 | } 32 | } 33 | 34 | class Demo extends React.Component { 35 | getRef = (r) => { 36 | this.dockLayout = r; 37 | }; 38 | 39 | count = 4; 40 | addValue = () => { 41 | let panelData = this.dockLayout.find('my_panel'); 42 | let tabId = panelData.activeId; 43 | // docklayout will find the same tab id and replace the previous tab 44 | this.dockLayout.updateTab(tabId, getTab(tabId, ++this.count)); 45 | }; 46 | addTab = () => { 47 | ++this.count; 48 | let newTab = getTab(`tab${this.count}`, this.count) 49 | this.dockLayout.dockMove(newTab, 'my_panel', 'middle'); 50 | }; 51 | defaultLayout = { 52 | dockbox: { 53 | mode: 'vertical', 54 | children: [ 55 | { 56 | tabs: [ 57 | { 58 | id: 'id2', title: 'change', content: ( 59 |
60 |

Click here to change the other panel.

61 | 62 | 63 |
64 | ) 65 | }, 66 | jsxTab, 67 | htmlTab 68 | ], 69 | }, 70 | { 71 | id: 'my_panel', 72 | tabs: [getTab('tab1', 1), getTab('tab2', 2), getTab('tab3', 3), getTab('tab4', 4)], 73 | }, 74 | ] 75 | } 76 | }; 77 | 78 | state = {saved: null}; 79 | 80 | render() { 81 | return ( 82 | 84 | ); 85 | } 86 | } 87 | 88 | createRoot(document.getElementById("app")).render(); 89 | -------------------------------------------------------------------------------- /example/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/basic.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {jsxTab, htmlTab} from './prism-tabs'; 5 | import {DockLayout, DockContextType} from '../lib'; 6 | 7 | let tab = { 8 | content:
Tab Content
, 9 | closable: true, 10 | }; 11 | 12 | let layout = { 13 | dockbox: { 14 | mode: 'horizontal', 15 | children: [ 16 | { 17 | mode: 'vertical', 18 | size: 200, 19 | children: [ 20 | { 21 | tabs: [{...tab, id: 't1', title: 'Tab 1'}, {...tab, id: 't2', title: 'Tab 2'}], 22 | }, 23 | { 24 | tabs: [{ 25 | ...tab, id: 't3', title: 'Min Size', content: ( 26 |
27 |

This tab has a minimal size

28 | 150 x 150 px 29 |
30 | ), minWidth: 150, minHeight: 150, 31 | }, {...tab, id: 't4', title: 'Tab 4'}], 32 | }, 33 | ] 34 | }, 35 | { 36 | size: 1000, 37 | tabs: [ 38 | { 39 | ...tab, id: 't5', title: 'basic demo', content: ( 40 |
41 | This panel won't be removed from layout even when last Tab is closed 42 |
43 | ), 44 | }, 45 | jsxTab, 46 | htmlTab, 47 | ], 48 | panelLock: {panelStyle: 'main'}, 49 | }, 50 | { 51 | size: 200, 52 | tabs: [{...tab, id: 't8', title: 'Tab 8'}], 53 | }, 54 | ] 55 | }, 56 | floatbox: { 57 | mode: 'float', 58 | children: [ 59 | { 60 | tabs: [ 61 | {...tab, id: 't9', title: 'Tab 9', content:
Float
}, 62 | {...tab, id: 't10', title: 'Tab 10'} 63 | ], 64 | x: 300, y: 150, w: 400, h: 300 65 | } 66 | ] 67 | } 68 | } 69 | ; 70 | if (window.innerWidth < 600) { 71 | // remove a column for mobile 72 | layout.dockbox.children.pop(); 73 | } 74 | 75 | let count = 0; 76 | 77 | class Demo extends React.Component { 78 | 79 | onDragNewTab = (e) => { 80 | let content = `New Tab ${count++}`; 81 | DragStore.dragStart(DockContextType, { 82 | tab: { 83 | id: content, 84 | content:
{content}
, 85 | title: content, 86 | closable: true, 87 | } 88 | }, e.nativeEvent); 89 | }; 90 | 91 | render() { 92 | return ( 93 | 94 | ); 95 | } 96 | } 97 | 98 | createRoot(document.getElementById("app")).render(); 99 | -------------------------------------------------------------------------------- /example/controlled-layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/controlled-layout.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {htmlTab, jsxTab} from "./prism-tabs"; 5 | import {DockLayout} from '../lib'; 6 | 7 | let tab0 = { 8 | title: 'Controlled Layout', 9 | content: ( 10 |
11 |

When you use layout instead of defaultLayout on <DockLayout>

12 |

DockLayout will work as a controlled component

13 |
14 | ) 15 | }; 16 | 17 | let box = { 18 | dockbox: { 19 | mode: 'horizontal', 20 | children: [ 21 | { 22 | mode: 'vertical', 23 | children: [ 24 | { 25 | tabs: [{id: 't0'}, htmlTab, jsxTab], 26 | }, 27 | { 28 | tabs: [{id: 'protect1'}, {id: 't4'}, {id: 't5'}, {id: 't6'}], 29 | } 30 | ] 31 | }, 32 | { 33 | tabs: [{id: 't7'}, {id: 't8'}, {id: 't9'}], 34 | }, 35 | ] 36 | } 37 | }; 38 | 39 | class Demo extends React.Component { 40 | state = {layout: box}; 41 | 42 | loadTab = (data) => { 43 | let {id} = data; 44 | switch (id) { 45 | case 't0': 46 | return {...tab0, id}; 47 | case 'protect1' : 48 | return { 49 | id, title: 'Protect', 50 | closable: true, 51 | content:
52 |

Removal of this tab will be rejected

53 | This is done in the onLayoutChange callback 54 |
55 | }; 56 | case jsxTab.id: 57 | return jsxTab; 58 | case htmlTab.id: 59 | return htmlTab; 60 | } 61 | 62 | return { 63 | id, title: id, 64 | content:
Tab Content
65 | }; 66 | }; 67 | 68 | onLayoutChange = (newLayout, currentTabId, direction) => { 69 | // control DockLayout from state 70 | console.log(currentTabId, newLayout, direction); 71 | if (currentTabId === 'protect1' && direction === 'remove') { 72 | alert('removal of this tab is rejected'); 73 | } else { 74 | this.setState({layout: newLayout}); 75 | } 76 | }; 77 | 78 | render() { 79 | return ( 80 | 82 | ); 83 | } 84 | } 85 | 86 | createRoot(document.getElementById("app")).render(); 87 | -------------------------------------------------------------------------------- /example/dark-theme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/dark-theme.jsx: -------------------------------------------------------------------------------- 1 | // The jsx is same as basic example. 2 | // Only difference is in the css, just use dist/rc-dock-dark.css for dark theme. 3 | -------------------------------------------------------------------------------- /example/disable-dock.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/disable-dock.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {jsxTab, htmlTab} from './prism-tabs'; 5 | import {DockLayout, DockContextType} from '../lib'; 6 | 7 | 8 | let groups = { 9 | floatOnly: { 10 | floatable: true, 11 | disableDock: true, 12 | } 13 | }; 14 | 15 | let layout = { 16 | dockbox: { 17 | mode: 'horizontal', 18 | children: [ 19 | { 20 | tabs: [{id: 't1', title: 'Dock', content:
Dock Content
}, jsxTab, htmlTab], 21 | } 22 | ] 23 | }, 24 | floatbox: { 25 | mode: 'float', 26 | children: [ 27 | { 28 | tabs: [ 29 | {id: 't2', title: 'Float', content:
Float Content
, group: 'floatOnly'}, 30 | {id: 't3', title: 'Float', content:
Float Content
, group: 'floatOnly'} 31 | ], 32 | x: 300, y: 150, w: 400, h: 300 33 | } 34 | ] 35 | } 36 | }; 37 | 38 | 39 | class Demo extends React.Component { 40 | 41 | render() { 42 | return ( 43 | 45 | ); 46 | } 47 | } 48 | 49 | createRoot(document.getElementById("app")).render(); 50 | -------------------------------------------------------------------------------- /example/divider-box.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/divider-box.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {jsxTab, htmlTab} from './prism-tabs'; 5 | import {DockLayout, DividerBox} from '../lib'; 6 | 7 | let layoutLeft = { 8 | dockbox: { 9 | mode: 'vertical', 10 | children: [ 11 | { 12 | tabs: [{id: 't1', title: 'Tool', content:
Left Side Dock Layout
}], 13 | }, 14 | { 15 | tabs: [{id: 't2', title: 'Tool', content:
Left Side Dock Layout
}], 16 | } 17 | ] 18 | }, 19 | }; 20 | 21 | 22 | let layoutRight = { 23 | dockbox: { 24 | mode: 'horizontal', 25 | children: [ 26 | { 27 | tabs: [{ 28 | id: 't3', 29 | title: 'Dock', 30 | content:
Right Side Dock Layout.
31 | }, jsxTab, htmlTab], 32 | } 33 | ] 34 | }, 35 | }; 36 | 37 | 38 | class Demo extends React.Component { 39 | render() { 40 | return ( 41 | 42 | 43 | 44 |
not DockLayout, only DividerBox
45 |
You can use DividerBox for fixed Tool Panels
46 |
47 | 48 |
49 | ); 50 | } 51 | } 52 | 53 | createRoot(document.getElementById("app")).render(); 54 | -------------------------------------------------------------------------------- /example/drag-new-tab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/drag-new-tab.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {htmlTab, jsxTab} from "./prism-tabs"; 5 | import {DockLayout, DragDropDiv} from '../lib'; 6 | 7 | let tab = { 8 | title: 'Tab', 9 | content:
Tab Content
10 | }; 11 | let box = { 12 | dockbox: { 13 | mode: 'horizontal', 14 | children: [ 15 | { 16 | mode: 'vertical', 17 | children: [ 18 | { 19 | tabs: [jsxTab, htmlTab], 20 | }, 21 | { 22 | tabs: [{...tab, id: 't4'}, {...tab, id: 't5'}, {...tab, id: 't6'}], 23 | } 24 | ] 25 | }, 26 | { 27 | tabs: [{...tab, id: 't7'}, {...tab, id: 't8'}, {...tab, id: 't9'}] 28 | }, 29 | ] 30 | } 31 | }; 32 | 33 | class Demo extends React.Component { 34 | newTabId = 0; 35 | getRef = (r) => { 36 | this.dockLayout = r; 37 | }; 38 | getButtonRef = (r) => { 39 | this.buttonRef = r; 40 | } 41 | 42 | onDragStart = (e) => { 43 | console.log(this.dockLayout, this.buttonRef) 44 | e.setData({ 45 | tab: {...tab, id: `newTab-${++this.newTabId}`}, 46 | panelSize: [400, 300] 47 | }, this.dockLayout.getDockId()); 48 | e.startDrag(this.buttonRef.element, this.buttonRef.element); 49 | }; 50 | 51 | render() { 52 | return ( 53 |
54 | 56 |
57 | 58 | 61 | 62 |
63 |
64 | ); 65 | } 66 | } 67 | 68 | createRoot(document.getElementById("app")).render(); 69 | -------------------------------------------------------------------------------- /example/drop-mode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/drop-mode.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {htmlTab, jsxTab} from "./prism-tabs"; 5 | import {DockLayout} from '../lib'; 6 | 7 | let dropModeTab = { 8 | title: 'Drop Mode', 9 | content: ( 10 |
11 |

When you set dropMode='edge' on <DockLayout>

12 |

The distance between mouse cursor and panel border is used to pick drop location.

13 |
14 | ) 15 | }; 16 | 17 | let tab = { 18 | title: 'Tab', 19 | content:
Tab Content
20 | }; 21 | let box = { 22 | dockbox: { 23 | mode: 'horizontal', 24 | children: [ 25 | { 26 | mode: 'vertical', 27 | children: [ 28 | { 29 | tabs: [{...dropModeTab, id: 't1'}, jsxTab, htmlTab], 30 | }, 31 | { 32 | tabs: [{...tab, id: 't4'}, {...tab, id: 't5'}, {...tab, id: 't6'}], 33 | } 34 | ] 35 | }, 36 | { 37 | tabs: [{...tab, id: 't7'}, {...tab, id: 't8'}, {...tab, id: 't9'}], 38 | panelLock: {panelStyle: 'main'}, 39 | }, 40 | ] 41 | } 42 | }; 43 | 44 | class Demo extends React.Component { 45 | render() { 46 | return ( 47 | 49 | ); 50 | } 51 | } 52 | 53 | createRoot(document.getElementById("app")).render(); 54 | -------------------------------------------------------------------------------- /example/gesture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/gesture.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {jsxTab} from "./prism-tabs"; 5 | import {DragDropDiv, GestureState} from '../lib'; 6 | 7 | class Demo extends React.PureComponent { 8 | state = {scale: 0, rotate: 0, dx: 0, dy: 0}; 9 | getRef = (r) => { 10 | this._ref = r; 11 | }; 12 | 13 | onGestureStart = (e) => { 14 | return true; 15 | }; 16 | onGestureMove = (e) => { 17 | let {scale, rotate, dx, dy} = e; 18 | this.setState({scale, rotate, dx, dy}); 19 | }; 20 | 21 | 22 | render() { 23 | let {scale, rotate, dx, dy} = this.state; 24 | return ( 25 |
26 | 27 |
28 | scale: {scale}
29 | rotate: {rotate}
30 | dx: {dx}
31 | dy: {dy}
32 |
33 | {jsxTab.content} 34 |
35 | ); 36 | } 37 | } 38 | 39 | createRoot(document.getElementById("app")).render(); 40 | console.log(Buffer.from("Hello World").toString('base64')); -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rc-dock 7 | 8 | 9 | 91 | 92 | 93 |
94 | 95 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /example/index.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { createRoot } from "react-dom/client"; 4 | import {Divider} from '../lib'; 5 | // keep the above unused import so tools script can understand this jsx 6 | 7 | let demos = ['basic', 'dark-theme', 'panel-style', 'drop-mode', 'tab-update', 'save-layout', 'panel-extra']; 8 | let advance = ['new-window', 'adv-tab-update', 'adv-save-layout', 'controlled-layout', 'tab-cache', 'divider-box', 'drag-new-tab']; 9 | 10 | let defaultPage = window.location.hash.substr(1); 11 | if (!(demos.includes(defaultPage) || advance.includes(defaultPage))) { 12 | defaultPage = 'basic'; 13 | } 14 | 15 | class App extends React.Component { 16 | state = {current: defaultPage}; 17 | 18 | render() { 19 | let {current} = this.state; 20 | let demoPages = []; 21 | for (let page of demos) { 22 | let cls = ''; 23 | if (page === current) { 24 | cls = 'current'; 25 | } 26 | demoPages.push( 27 | this.setState({current: page})}> 28 | {page} 29 | 30 | ) 31 | } 32 | let advancePages = []; 33 | for (let page of advance) { 34 | let cls = ''; 35 | if (page === current) { 36 | cls = 'current'; 37 | } 38 | advancePages.push( 39 | this.setState({current: page})}> 40 | {page} 41 | 42 | ) 43 | } 44 | return ( 45 |
46 | 63 |