├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bundler ├── builder.json ├── node_modules │ └── cbuild │ │ └── process-dev.js └── package.json ├── config.js ├── index.html ├── package.json ├── src ├── Dialog.ts ├── DialogLayout.ts ├── DialogMessage.ts ├── FloatArea.ts ├── FloatLayout.ts ├── SimpleLayout.ts ├── index.ts └── tsconfig.json ├── style └── index.css ├── test ├── .gitignore ├── App.ts └── tsconfig.json └── www ├── config-base.js ├── config-npm.js └── css ├── content.css ├── index.css └── phosphor ├── commandpalette.css ├── content.css ├── dockpanel.css ├── index.css ├── menu.css ├── menubar.css └── tabbar.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | package-lock.json 4 | *.log.* 5 | *.log 6 | *.tgz 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bundler/ 3 | test/ 4 | www/ 5 | src/ 6 | package-lock.json 7 | config.js 8 | index.html 9 | .travis.yml 10 | *.log.* 11 | *.log 12 | *.tgz 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 BusFaster Ltd 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | phosphor-float-area 2 | =================== 3 | 4 | Draggable, dockable, resizable, floating, tabbed `Dialog` and `FloatArea` widgets: 5 | 6 | ![Screen recording](https://raw.githubusercontent.com/charto/phosphor-float-area/gh-pages/demo.gif) 7 | 8 | 100% Virtual DOM, TypeScript, [PhosphorJS](https://github.com/phosphorjs/phosphor) 9 | based modern JavaScript goodness :cake: 10 | 11 | [![dependency status](https://david-dm.org/charto/phosphor-float-area.svg)](https://david-dm.org/charto/phosphor-float-area) 12 | [![npm version](https://img.shields.io/npm/v/phosphor-float-area.svg)](https://www.npmjs.com/package/phosphor-float-area) 13 | 14 | Live demo 15 | --------- 16 | 17 | [**Try it now!**](https://charto.github.io/phosphor-float-area/) 18 | 19 | Alternatively, run the following commands and then open [localhost:8080](http://localhost:8080/) to see it in action: 20 | 21 | ``` 22 | git clone https://github.com/charto/phosphor-float-area.git 23 | cd phosphor-float-area 24 | npm install 25 | npm run prepublish 26 | npm start 27 | ``` 28 | 29 | The demo uses [SystemJS](https://github.com/systemjs/systemjs). 30 | Works directly from the public directory of any HTTP server. 31 | With `compileOnSave` (eg. [`atom-typescript`](https://atom.io/packages/atom-typescript) or 32 | [TypeScript for VS Code](https://github.com/mrcrowl/vscode/releases/tag/13.10.8)) 33 | the demo page always stays up to date while editing TypeScript source code. 34 | 35 | Usage 36 | ----- 37 | 38 | 1. Install: 39 | 40 | ```bash 41 | npm install --save phosphor-float-area 42 | ``` 43 | 44 | 2. Use: 45 | 46 | ```TypeScript 47 | import '@phosphor/dragdrop/style/index.css!'; 48 | import '@phosphor/widgets/style/index.css!'; 49 | import 'phosphor-float-area/style/index.css!'; 50 | 51 | import { Widget, DockPanel } from '@phosphor/widgets'; 52 | import { FloatArea } from 'phosphor-float-area'; 53 | 54 | const area = new FloatArea(); 55 | const dock = new DockPanel(); 56 | 57 | dock.addWidget(area); 58 | dock.addWidget(new Widget(), { mode: 'split-left', ref: area }); 59 | dock.addWidget(new Widget(), { mode: 'split-right', ref: area }); 60 | 61 | area.addWidget(new Widget(), { placement: 'backdrop' }); 62 | 63 | Widget.attach(dock, document.body); 64 | ``` 65 | 66 | The `addWidget` method of `FloatArea` accepts an options object as the second argument, 67 | with the following optional members: 68 | 69 | - `placement`: string, `float` by default. Passing `backdrop` adds the widget as a non-floating backdrop, 70 | behind any floating dialogs. 71 | - `left`, `top`, `width`, `height`: number. Initial pixel size and placement of the floating dialog to add. 72 | 73 | Project structure 74 | ----------------- 75 | 76 | ### `src` 77 | 78 | TypeScript source code (git only). 79 | 80 | ### `style` 81 | 82 | CSS rules needed by the widgets. 83 | 84 | ### `dist` 85 | 86 | Compiled JavaScript (ES5) code (npm only). 87 | 88 | ### `test` 89 | 90 | TypeScript source code of demo application (git only). 91 | 92 | ### `www` 93 | 94 | Support files for demo application (git only). 95 | 96 | ### `bundler` 97 | 98 | Bundler and configuration autogenerator for demo application (git only). 99 | 100 | Required for demo application to work after referencing new NPM packages in the code. 101 | Updates [`www/config-npm.js`](https://github.com/charto/phosphor-float-area/blob/master/www/config-npm.js). 102 | 103 | Usage: 104 | 105 | ```bash 106 | cd bundler 107 | npm install 108 | npm run bundle 109 | ``` 110 | 111 | Afterwards, the demo will use the static bundle for faster loading. 112 | Remove `dist/bundle.js` to always load the latest code when developing. 113 | 114 | License 115 | ======= 116 | 117 | [The MIT License](https://raw.githubusercontent.com/charto/phosphor-float-area/master/LICENSE) 118 | 119 | Copyright (c) 2017 BusFaster Ltd 120 | -------------------------------------------------------------------------------- /bundler/builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "minify": true, 3 | "config": { 4 | "buildCSS": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /bundler/node_modules/cbuild/process-dev.js: -------------------------------------------------------------------------------- 1 | exports = { env: { 'NODE_ENV': 'development' } }; 2 | -------------------------------------------------------------------------------- /bundler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bundler", 3 | "scripts": { 4 | "cbuild": "cbuild", 5 | "bundle": "cbuild -d -v -p .. -b builder.json -s ../test/App.js -o ../dist/bundle.js -C ../www/config-npm.js" 6 | }, 7 | "devDependencies": { 8 | "cbuild": "~0.1.5" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | transpiler: false, 3 | 4 | map: { 5 | crypto: '@empty', 6 | css: 'node_modules/systemjs-plugin-css/css.js' 7 | }, 8 | 9 | meta: { 10 | 'dist/bundle.js': { 11 | format: 'system' 12 | } 13 | }, 14 | 15 | packages: { 16 | 'dist/': { 17 | defaultExtension: 'js', 18 | meta: { 19 | '*.css': { loader: 'css' } 20 | } 21 | }, 22 | 'test/': { 23 | defaultExtension: 'js', 24 | meta: { 25 | '*.css': { loader: 'css' } 26 | } 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | phosphor-float-area 5 | 6 | 7 | 8 | 9 | 10 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phosphor-float-area", 3 | "version": "0.1.2", 4 | "description": "Floating dialog container with DockPanel drag support for PhosphorJS", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "tsc": "tsc", 9 | "start": "csrv .", 10 | "prepublish": "tsc -p src && tsc -p test" 11 | }, 12 | "author": "Juha Järvi", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/charto/phosphor-float-area.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/charto/phosphor-float-area/issues" 20 | }, 21 | "homepage": "https://github.com/charto/phosphor-float-area#readme", 22 | "keywords": [ 23 | "phosphorjs", 24 | "widget", 25 | "mdi", 26 | "window", 27 | "dialog" 28 | ], 29 | "devDependencies": { 30 | "csrv": "^0.1.0", 31 | "font-awesome": "^4.7.0", 32 | "systemjs": "^0.20.19", 33 | "systemjs-plugin-css": "^0.1.36", 34 | "typescript": "^2.6.1" 35 | }, 36 | "dependencies": { 37 | "@phosphor/messaging": "^1.2.2", 38 | "@phosphor/widgets": "^1.5.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Dialog.ts: -------------------------------------------------------------------------------- 1 | // This file is part of phosphor-float-area, copyright (C) 2017 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { Message, MessageLoop, ConflatableMessage } from '@phosphor/messaging'; 5 | import { Widget, LayoutItem } from '@phosphor/widgets'; 6 | 7 | import { DialogUpdateMessage, DialogRaiseMessage } from './DialogMessage'; 8 | import { DialogLayout } from './DialogLayout'; 9 | 10 | /** Size and position information for drag events to handle move and resize. */ 11 | 12 | interface DragData { 13 | /** Flag whether drag causes horizontal movement. */ 14 | moveX: 0 | 1; 15 | /** Flag whether drag causes vertical movement. */ 16 | moveY: 0 | 1; 17 | /** Correlation between horizontal change in mouse position and dialog size. */ 18 | resizeX: 0 | 1 | -1; 19 | /** Correlation between vertical change in mouse position and dialog size. */ 20 | resizeY: 0 | 1 | -1; 21 | 22 | /** Horizontal offset from mouse to dialog position. */ 23 | offsetX: number; 24 | /** Vertical offset from mouse to dialog position. */ 25 | offsetY: number; 26 | startWidth: number; 27 | startHeight: number; 28 | } 29 | 30 | export class Dialog extends Widget { 31 | 32 | constructor(options: Dialog.Options = {}) { 33 | super({ node: Dialog.createNode() }); 34 | 35 | this.addClass('charto-Dialog'); 36 | this.addClass('charto-Dialog-mod-dimmable'); 37 | 38 | for(let classList of 'ns n,ew e,ns s,ew w,nwse nw,nesw ne,nesw sw,nwse se'.split(',')) { 39 | const content = document.createElement('div'); 40 | content.className = ( 41 | 'charto-Dialog-resize ' + 42 | classList.split(' ').map( 43 | (resizer: string) => 'charto-Dialog-resize-' + resizer 44 | ).join(' ') 45 | ); 46 | this.node.appendChild(content); 47 | } 48 | 49 | this.layout = new DialogLayout(); 50 | } 51 | 52 | static createNode(): HTMLElement { 53 | const node = document.createElement('div'); 54 | return(node); 55 | } 56 | 57 | protected onBeforeAttach(msg: Message) { 58 | this.node.addEventListener('click', this); 59 | this.node.addEventListener('mousedown', this); 60 | } 61 | 62 | protected onAfterDetach(msg: Message) { 63 | this.node.removeEventListener('click', this); 64 | this.node.removeEventListener('mousedown', this); 65 | } 66 | 67 | handleEvent(event: Event) { 68 | const mouseEvent = event as MouseEvent; 69 | switch(event.type) { 70 | case 'click': 71 | if(this.handleClick(mouseEvent)) break; 72 | return; 73 | case 'mousedown': 74 | if(this.handleMouseDown(mouseEvent)) break; 75 | return; 76 | case 'mousemove': 77 | this.handleMouseMove(mouseEvent); 78 | break; 79 | case 'mouseup': 80 | if(this.handleMouseUp(mouseEvent)) break; 81 | return; 82 | } 83 | 84 | event.preventDefault(); 85 | event.stopPropagation(); 86 | } 87 | 88 | handleClick(event: MouseEvent) { 89 | if(event.button != 0) return(false); 90 | 91 | MessageLoop.postMessage(this.parent!, new DialogRaiseMessage(this, event)); 92 | 93 | return(false); 94 | } 95 | 96 | handleMouseDown(event: MouseEvent) { 97 | if(event.button != 0) return(false); 98 | 99 | let moveX: 0 | 1 = 0; 100 | let moveY: 0 | 1 = 0; 101 | let resizeX: 0 | 1 | -1 = 0; 102 | let resizeY: 0 | 1 | -1 = 0; 103 | const target = event.target as Element; 104 | 105 | if(target.parentNode == this.node) { 106 | const match = target.className.match(/charto-Dialog-resize-([ns]?)([ew]?)( |$)/); 107 | if(!match) return(false); 108 | 109 | // Vertical resize. 110 | if(match[1]) { 111 | if(match[1] == 'n') { 112 | moveY = 1; 113 | resizeY = -1; 114 | } else resizeY = 1; 115 | } 116 | 117 | // Horizontal resize. 118 | if(match[2]) { 119 | if(match[2] == 'w') { 120 | moveX = 1; 121 | resizeX = -1; 122 | } else resizeX = 1; 123 | } 124 | } else if( 125 | target.className == 'p-TabBar-content' && 126 | target.parentNode!.parentNode!.parentNode == this.node 127 | ) { 128 | // Move the dialog. 129 | moveX = 1; 130 | moveY = 1; 131 | } else return(false); 132 | 133 | this.removeClass('charto-Dialog-mod-dimmable'); 134 | 135 | document.addEventListener('mousemove', this, true); 136 | document.addEventListener('mouseup', this, true); 137 | document.addEventListener('keydown', this, true); 138 | 139 | const node = this.node; 140 | 141 | this.drag = { 142 | moveX, moveY, resizeX, resizeY, 143 | offsetX: node.offsetLeft - event.clientX * moveX, 144 | offsetY: node.offsetTop - event.clientY * moveY, 145 | startWidth: node.offsetWidth - event.clientX * resizeX, 146 | startHeight: node.offsetHeight - event.clientY * resizeY 147 | }; 148 | 149 | return(true); 150 | } 151 | 152 | handleMouseMove(event: MouseEvent) { 153 | const drag = this.drag; 154 | if(!drag) return; 155 | 156 | MessageLoop.postMessage(this.parent!, new DialogUpdateMessage( 157 | this, 158 | drag.offsetX + event.clientX * drag.moveX, 159 | drag.offsetY + event.clientY * drag.moveY, 160 | drag.startWidth + event.clientX * drag.resizeX, 161 | drag.startHeight + event.clientY * drag.resizeY, 162 | event 163 | )); 164 | } 165 | 166 | handleMouseUp(event: MouseEvent) { 167 | if(event.button != 0) return(false); 168 | 169 | this.addClass('charto-Dialog-mod-dimmable'); 170 | 171 | document.removeEventListener('mousemove', this, true); 172 | document.removeEventListener('mouseup', this, true); 173 | document.removeEventListener('keydown', this, true); 174 | this.drag = null; 175 | 176 | return(true); 177 | } 178 | 179 | addWidget(widget: Widget, options: DialogLayout.AddOptions = {}): void { 180 | (this.layout as DialogLayout).addWidget(widget, options); 181 | } 182 | 183 | private drag: DragData | null; 184 | 185 | } 186 | 187 | export namespace Dialog { 188 | export interface Options { 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/DialogLayout.ts: -------------------------------------------------------------------------------- 1 | // This file is part of phosphor-float-area, copyright (C) 2017 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { Message } from '@phosphor/messaging'; 5 | import { Widget, LayoutItem } from '@phosphor/widgets'; 6 | 7 | import { Dialog } from './Dialog'; 8 | import { SimpleLayout } from './SimpleLayout'; 9 | 10 | export class DialogLayout extends SimpleLayout { 11 | constructor(options: DialogLayout.Options = {}) { 12 | super(); 13 | } 14 | 15 | addWidget(widget: Widget, options: DialogLayout.AddOptions = {}) { 16 | return(super.addWidget(widget)); 17 | } 18 | 19 | removeWidget(widget: Widget) { 20 | super.removeWidget(widget); 21 | } 22 | 23 | onUpdate() { 24 | const box = this.box; 25 | 26 | // Resize content to match the dialog. 27 | this.itemMap.forEach(item => this.updateItem(item, box.x, box.y, box.innerWidth, box.innerHeight)); 28 | } 29 | 30 | } 31 | 32 | export namespace DialogLayout { 33 | export interface Options { 34 | } 35 | 36 | export interface AddOptions { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/DialogMessage.ts: -------------------------------------------------------------------------------- 1 | // This file is part of phosphor-float-area, copyright (C) 2017 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { Message, ConflatableMessage } from '@phosphor/messaging'; 5 | 6 | import { Dialog } from './Dialog'; 7 | 8 | /** Message sent by a dialog, for processing in a parent layout. */ 9 | 10 | export interface DialogMessage extends Message { 11 | 12 | /** Dialog sending the message. */ 13 | widget: Dialog, 14 | /** Mouse event causing the message to be sent, if applicable. */ 15 | event?: MouseEvent 16 | 17 | } 18 | 19 | /** Message sent by a dialog requesting a parent layout to move or resize it. */ 20 | 21 | export class DialogUpdateMessage extends ConflatableMessage implements DialogMessage { 22 | 23 | constructor( 24 | /** Dialog to move or resize. */ 25 | public widget: Dialog, 26 | public x: number, 27 | public y: number, 28 | public width: number, 29 | public height: number, 30 | /** Mouse event causing the message to be sent, if applicable. */ 31 | public event?: MouseEvent 32 | ) { 33 | super('dialog-update'); 34 | } 35 | 36 | /** Conflate subsequent update messages to leave only the latest one. */ 37 | 38 | conflate(other: DialogUpdateMessage) { 39 | // Only conflate messages related to the same widget. 40 | if(this.widget != other.widget) return(false); 41 | 42 | // Copy information from the newer message (which will be discarded). 43 | this.x = other.x; 44 | this.y = other.y; 45 | this.width = other.width; 46 | this.height = other.height; 47 | 48 | // Accept the conflation. 49 | return(true); 50 | } 51 | 52 | } 53 | 54 | /** Message sent by a dialog requesting a parent layout to raise it 55 | * over other dialogs in the z-order. */ 56 | 57 | export class DialogRaiseMessage extends Message implements DialogMessage { 58 | 59 | constructor( 60 | /** Dialog to raise. */ 61 | public widget: Dialog, 62 | /** Mouse event causing the message to be sent, if applicable. */ 63 | public event?: MouseEvent 64 | ) { 65 | super('dialog-raise'); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/FloatArea.ts: -------------------------------------------------------------------------------- 1 | // This file is part of phosphor-float-area, copyright (C) 2017 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { Message } from '@phosphor/messaging'; 5 | import { ElementExt } from '@phosphor/domutils'; 6 | import { IDragEvent } from '@phosphor/dragdrop'; 7 | import { Widget, DockPanel } from '@phosphor/widgets'; 8 | 9 | import { DialogUpdateMessage, DialogRaiseMessage } from './DialogMessage'; 10 | import { Dialog } from './Dialog'; 11 | import { FloatLayout, sendLeaveEvent } from './FloatLayout'; 12 | 13 | const EDGE_SIZE = 40; 14 | 15 | interface DragData { 16 | rect: ClientRect; 17 | 18 | width: number; 19 | height: number; 20 | 21 | imageOffsetX: number; 22 | imageOffsetY: number; 23 | 24 | offsetLeft: number; 25 | offsetTop: number; 26 | offsetRight: number; 27 | offsetBottom: number; 28 | } 29 | 30 | export class FloatArea extends Widget { 31 | 32 | constructor(options: FloatArea.Options = {}) { 33 | super({ node: FloatArea.createNode() }); 34 | 35 | this.addClass('charto-FloatArea'); 36 | 37 | this.backdropNode = document.createElement('div'); 38 | this.backdropNode.className = 'charto-FloatArea-content'; 39 | 40 | this.node.appendChild(this.backdropNode); 41 | 42 | if(options.overlay) { 43 | // Re-use an existing transparent overlay. 44 | // Pass it to a parent DockPanel first. 45 | this.overlay = options.overlay; 46 | this.overlayParent = this.overlay.node.parentNode as HTMLElement; 47 | this.ownOverlay = false; 48 | } else { 49 | // Create a new transparent overlay inside this widget. 50 | this.overlay = new DockPanel.Overlay(); 51 | this.overlay.node.classList.add('charto-mod-noTransition'); 52 | this.node.appendChild(this.overlay.node); 53 | this.overlayParent = this.node; 54 | this.ownOverlay = true; 55 | } 56 | 57 | const parentBox = ElementExt.boxSizing(this.overlayParent); 58 | this.edgeWidth = parentBox.borderLeft + parentBox.borderRight; 59 | this.edgeHeight = parentBox.borderTop + parentBox.borderBottom; 60 | 61 | this.layout = new FloatLayout(); 62 | } 63 | 64 | static createNode(): HTMLElement { 65 | const node = document.createElement('div'); 66 | return(node); 67 | } 68 | 69 | protected onBeforeAttach(msg: Message) { 70 | this.node.addEventListener('p-dragenter', this); 71 | this.node.addEventListener('p-dragleave', this); 72 | this.node.addEventListener('p-dragover', this); 73 | this.node.addEventListener('p-drop', this); 74 | } 75 | 76 | protected onAfterDetach(msg: Message) { 77 | this.node.removeEventListener('p-dragenter', this); 78 | this.node.removeEventListener('p-dragleave', this); 79 | this.node.removeEventListener('p-dragover', this); 80 | this.node.removeEventListener('p-drop', this); 81 | } 82 | 83 | processMessage(msg: Message): void { 84 | switch(msg.type) { 85 | case 'dialog-update': 86 | const move = msg as DialogUpdateMessage; 87 | 88 | (this.layout as FloatLayout).updateWidget(move.widget, move.x, move.y, move.width, move.height); 89 | break; 90 | 91 | case 'dialog-raise': 92 | const raise = msg as DialogRaiseMessage; 93 | 94 | (this.layout as FloatLayout).raiseWidget(raise.widget, raise.event); 95 | break; 96 | 97 | default: 98 | super.processMessage(msg); 99 | } 100 | } 101 | 102 | handleEvent(event: Event) { 103 | switch(event.type) { 104 | case 'p-dragenter': 105 | if(this.handleDragEnter(event as IDragEvent)) break; 106 | return; 107 | case 'p-dragleave': 108 | this.handleDragLeave(event as IDragEvent); 109 | 110 | // Allow dragleave events to bubble up so overlay's parent 111 | // can see if it's time to hide it. 112 | return; 113 | case 'p-dragover': 114 | if(this.handleDragOver(event as IDragEvent)) break; 115 | return; 116 | case 'p-drop': 117 | if(this.handleDrop(event as IDragEvent)) break; 118 | return; 119 | } 120 | 121 | // Note: p-dragenter must be eaten to receive other drag events. 122 | event.preventDefault(); 123 | event.stopPropagation(); 124 | } 125 | 126 | protected handleDragEnter(event: IDragEvent) { 127 | const widget = this.getDragged(event); 128 | if(!widget) return(false); 129 | 130 | let imageOffsetX = 0; 131 | let imageOffsetY = 0; 132 | let imageHeight = 0; 133 | 134 | // Equivalent to (dockPanel as any)._drag.dragImage if we had access. 135 | const dragImage = document.body.querySelector('.p-mod-drag-image') as HTMLElement; 136 | 137 | if(dragImage) { 138 | const imageRect = dragImage.getBoundingClientRect(); 139 | imageOffsetX = dragImage.offsetLeft - imageRect.left; 140 | imageOffsetY = dragImage.offsetTop - imageRect.top; 141 | imageHeight = dragImage.offsetHeight; 142 | } 143 | 144 | const rect = this.node.getBoundingClientRect(); 145 | const parentRect = this.overlayParent.getBoundingClientRect(); 146 | let width = widget.node.offsetWidth; 147 | let height = widget.node.offsetHeight; 148 | 149 | const goldenRatio = 0.618; 150 | let inDialog = false; 151 | 152 | for(let parent = widget.parent; parent; parent = parent.parent) { 153 | if(parent instanceof Dialog) { 154 | inDialog = true; 155 | break; 156 | } 157 | } 158 | 159 | if(!inDialog) { 160 | // Widget is not inside a dialog, so it's probably docked. 161 | // Likely only one dimension was set by the user, 162 | // so make the proportions match the golden ratio. 163 | if(width > height / goldenRatio) width = height / goldenRatio; 164 | else if(height > width * goldenRatio) height = width * goldenRatio; 165 | } 166 | 167 | // Restrict initial floating panel size so its longer dimension 168 | // is half that of the area it's floating over. 169 | if(width > rect.width / 2) { 170 | width = rect.width / 2; 171 | height = width * goldenRatio; 172 | } 173 | if(height > rect.height / 2) { 174 | height = rect.height / 2; 175 | width = height / goldenRatio; 176 | } 177 | 178 | // Round size to integer. 179 | width = ~~(width + 0.5); 180 | height = ~~(height + 0.5); 181 | 182 | this.drag = { 183 | rect, 184 | 185 | width, 186 | height, 187 | 188 | imageOffsetX, 189 | imageOffsetY, 190 | 191 | offsetLeft: parentRect.left + imageOffsetX, 192 | offsetTop: parentRect.top + imageOffsetY - imageHeight, 193 | offsetRight: parentRect.width - width - this.edgeWidth, 194 | offsetBottom: parentRect.height - height - this.edgeHeight 195 | }; 196 | 197 | this.overlayVisible = false; 198 | this.handleDragOver(event); 199 | 200 | return(true); 201 | } 202 | 203 | protected handleDragLeave(event: IDragEvent) { 204 | const related = event.relatedTarget as HTMLElement; 205 | 206 | if(!related || !this.node.contains(related)) { 207 | // Mouse left the bounds of this widget. 208 | this.hideOverlay(event); 209 | this.drag = null; 210 | } 211 | } 212 | 213 | protected handleDragOver(event: IDragEvent) { 214 | const drag = this.drag; 215 | if(!drag) return(false); 216 | 217 | if(this.onEdge(event)) { 218 | this.hideOverlay(event); 219 | return(false); 220 | } else { 221 | this.showOverlay(event); 222 | } 223 | 224 | const left = event.clientX - drag.offsetLeft; 225 | const top = event.clientY - drag.offsetTop; 226 | 227 | this.overlay.show({ 228 | left, top, 229 | right: drag.offsetRight - left, 230 | bottom: drag.offsetBottom - top 231 | }); 232 | 233 | // Tentatively accept the drag. 234 | event.dropAction = event.proposedAction; 235 | 236 | return(true); 237 | } 238 | 239 | protected handleDrop(event: IDragEvent) { 240 | this.overlay.hide(0); 241 | 242 | if(!this.ownOverlay) { 243 | // Enable animated transitions in overlay movement. 244 | this.overlay.node.classList.remove('charto-mod-noTransition'); 245 | } 246 | 247 | const drag = this.drag; 248 | if(!drag) return(false); 249 | 250 | // Let a parent dock panel handle drops near area edges. 251 | if(this.onEdge(event)) return(false); 252 | 253 | const widget = this.getDragged(event); 254 | 255 | if(!widget) { 256 | event.dropAction = 'none'; 257 | return(false); 258 | } 259 | 260 | // Deparent the widget and wait for layout changes to settle. 261 | widget.parent = null; 262 | 263 | (this.layout as FloatLayout).afterUpdate(() => { 264 | // Get updated float area bounds. 265 | const rect = this.node.getBoundingClientRect(); 266 | 267 | // Take ownership of the dragged widget. 268 | this.addWidget(widget, { 269 | left: event.clientX - rect.left - drag.imageOffsetX, 270 | top: event.clientY - rect.top - drag.imageOffsetY, 271 | width: drag.width, 272 | height: drag.height 273 | }); 274 | }); 275 | 276 | this.update(); 277 | 278 | // Accept the drag. 279 | event.dropAction = event.proposedAction; 280 | return(true); 281 | } 282 | 283 | getDragged(event: IDragEvent) { 284 | // Only handle drag events containing widgets. 285 | if(!(event as IDragEvent).mimeData.hasData('application/vnd.phosphor.widget-factory')) return(null); 286 | 287 | const factory = event.mimeData.getData('application/vnd.phosphor.widget-factory'); 288 | const widget = (typeof(factory) == 'function' && factory()); 289 | 290 | // Ensure the dragged widget is known and is not a parent of this widget. 291 | if(!(widget instanceof Widget) || widget.contains(this)) return(null); 292 | 293 | return(widget); 294 | } 295 | 296 | onEdge(event: IDragEvent) { 297 | const rect = this.drag!.rect; 298 | 299 | return( 300 | event.clientX - rect.left < EDGE_SIZE || 301 | event.clientY - rect.top < EDGE_SIZE || 302 | rect.right - event.clientX < EDGE_SIZE || 303 | rect.bottom - event.clientY < EDGE_SIZE 304 | ); 305 | } 306 | 307 | showOverlay(event: IDragEvent) { 308 | if(this.overlayVisible) return; 309 | this.overlayVisible = true; 310 | 311 | if(this.ownOverlay) { 312 | if(this.node.parentNode) { 313 | // In case a parent DockPanel is also showing an overlay, 314 | // send a p-dragleave event to trigger hiding it. 315 | sendLeaveEvent(event, this.node.parentNode as HTMLElement); 316 | } 317 | } else { 318 | // Probably re-using a DockPanel's overlay, 319 | // so disable animated transitions in its movement. 320 | this.overlay.node.classList.add('charto-mod-noTransition'); 321 | } 322 | } 323 | 324 | hideOverlay(event: IDragEvent) { 325 | if(!this.overlayVisible) return; 326 | this.overlayVisible = false; 327 | 328 | if(this.ownOverlay) { 329 | this.overlay.hide(0); 330 | } else { 331 | // Enable animated transitions in overlay movement. 332 | this.overlay.node.classList.remove('charto-mod-noTransition'); 333 | } 334 | } 335 | 336 | addWidget(widget: Widget, options: FloatLayout.AddOptions = {}): void { 337 | let targetNode: HTMLElement | undefined; 338 | 339 | if(options.placement == 'backdrop') targetNode = this.backdropNode; 340 | 341 | (this.layout as FloatLayout).addWidget(widget, options, targetNode); 342 | } 343 | 344 | backdropNode: HTMLElement; 345 | 346 | /** Transparent overlay indicating position of dragged widget if dropped. */ 347 | overlay: DockPanel.IOverlay; 348 | /** Parent DOM node of the overlay. */ 349 | overlayParent: HTMLElement; 350 | overlayVisible: boolean; 351 | /** Flag whether the overlay was created by this widget. */ 352 | ownOverlay: boolean; 353 | /** Horizontal padding of overlayParent in pixels. */ 354 | edgeWidth: number; 355 | /** Vertical padding of overlayParent in pixels. */ 356 | edgeHeight: number; 357 | 358 | private drag: DragData | null; 359 | } 360 | 361 | export namespace FloatArea { 362 | export interface Options { 363 | overlay?: DockPanel.IOverlay; 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/FloatLayout.ts: -------------------------------------------------------------------------------- 1 | // This file is part of phosphor-float-area, copyright (C) 2017 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { Message, MessageLoop, IMessageHandler } from '@phosphor/messaging'; 5 | import { IDragEvent } from '@phosphor/dragdrop'; 6 | import { ElementExt } from '@phosphor/domutils'; 7 | import { Widget, LayoutItem, DockPanel, TabBar } from '@phosphor/widgets'; 8 | 9 | import { Dialog } from './Dialog'; 10 | import { SimpleLayout, SimpleItem, SimpleBox } from './SimpleLayout'; 11 | 12 | export class FloatLayoutItem extends LayoutItem { 13 | 14 | updateUser(x: number, y: number, width: number, height: number) { 15 | this.userX = x; 16 | this.userY = y; 17 | this.userWidth = width; 18 | this.userHeight = height; 19 | } 20 | 21 | updateClip(box: SimpleBox) { 22 | let x = this.userX; 23 | let y = this.userY; 24 | let width = this.userWidth; 25 | let height = this.userHeight; 26 | 27 | if(x < box.x) x = box.x; 28 | if(y < box.y) y = box.y; 29 | 30 | width = Math.max(Math.min(width, this.maxWidth || Infinity), this.minWidth); 31 | height = Math.max(Math.min(height, this.maxHeight || Infinity), this.minHeight); 32 | 33 | if(width > box.innerWidth) width = box.innerWidth; 34 | if(height > box.innerHeight) height = box.innerHeight; 35 | 36 | if(x - box.x + width > box.innerWidth) x = box.x + box.innerWidth - width; 37 | if(y - box.y + height > box.innerHeight) y = box.y + box.innerHeight - height; 38 | 39 | this.update(x, y, width, height); 40 | } 41 | 42 | userY: number; 43 | userX: number; 44 | userWidth: number; 45 | userHeight: number; 46 | 47 | } 48 | 49 | export class FloatLayout extends SimpleLayout { 50 | 51 | constructor(options: FloatLayout.Options = {}) { 52 | super(FloatLayoutItem); 53 | } 54 | 55 | addWidget(widget: Widget, options: FloatLayout.AddOptions = {}, targetNode?: HTMLElement) { 56 | if(targetNode) { 57 | return(super.addItem(new SimpleItem(widget, targetNode))); 58 | } 59 | 60 | const dialog = new Dialog(); 61 | const dockPanel = new DockPanel(); 62 | 63 | dockPanel.addWidget(widget); 64 | dialog.addWidget(dockPanel); 65 | 66 | const handleEvent = dockPanel.handleEvent; 67 | let leaveEventSent = false; 68 | 69 | dockPanel.handleEvent = function(this: DockPanel, event: Event) { 70 | switch(event.type) { 71 | case 'p-dragover': 72 | if(!leaveEventSent && this.node.parentNode) { 73 | // In case a parent DockPanel is also showing an overlay, 74 | // send a p-dragleave event to trigger hiding it. 75 | sendLeaveEvent(event as IDragEvent, this.node.parentNode as HTMLElement); 76 | leaveEventSent = true; 77 | } 78 | break; 79 | 80 | case 'p-dragleave': 81 | leaveEventSent = false; 82 | break; 83 | } 84 | 85 | if(handleEvent) handleEvent.apply(this, arguments); 86 | } 87 | 88 | dockPanel.parent = dialog; 89 | dialog.parent = this.parent; 90 | 91 | MessageLoop.installMessageHook(dockPanel, (handler: IMessageHandler, msg: Message) => { 92 | if(msg.type == 'child-removed' && (msg as Widget.ChildMessage).child instanceof TabBar) { 93 | // Allow the panel to process the message first. 94 | setTimeout(() => { 95 | if(dockPanel.isEmpty) dialog.close(); 96 | // TODO: dispose? 97 | }, 1); 98 | } 99 | // Let the message through. 100 | return(true); 101 | }); 102 | 103 | const layoutItem = super.addWidget(dialog); 104 | 105 | dialog.node.style.zIndex = '' + this.zTop; 106 | this.activeWidget = dialog; 107 | 108 | const box = ElementExt.boxSizing(dialog.node); 109 | const tabBar = (dockPanel.node.querySelector('.p-TabBar') || {}) as HTMLElement; 110 | 111 | if(layoutItem instanceof FloatLayoutItem) { 112 | layoutItem.updateUser( 113 | (options.left || 0) - box.paddingLeft - box.borderLeft, 114 | (options.top || 0) - box.paddingTop - box.borderTop, 115 | (options.width || 320) + box.horizontalSum, 116 | (options.height || 240) + box.verticalSum + (tabBar.offsetHeight || 0) 117 | ); 118 | 119 | layoutItem.updateClip(this.box); 120 | } 121 | 122 | return(layoutItem); 123 | } 124 | 125 | removeWidget(widget: Widget) { 126 | super.removeWidget(widget); 127 | } 128 | 129 | onUpdate() { 130 | const box = this.box; 131 | 132 | // Resize content to match the dialog. 133 | this.itemMap.forEach(item => 134 | item instanceof FloatLayoutItem ? 135 | item.updateClip(box) : 136 | item.update(box.x, box.y, box.innerWidth, box.innerHeight) 137 | ); 138 | } 139 | 140 | updateWidget(widget: Widget, x: number, y: number, width: number, height: number) { 141 | const item = this.itemMap.get(widget); 142 | 143 | if(item instanceof FloatLayoutItem) { 144 | item.updateUser(x, y, width, height); 145 | item.updateClip(this.box); 146 | } 147 | } 148 | 149 | raiseWidget(widget: Widget, event?: MouseEvent) { 150 | if(widget != this.activeWidget) { 151 | widget.node.style.zIndex = '' + (++this.zTop); 152 | this.activeWidget = widget; 153 | } 154 | } 155 | 156 | protected onFitRequest(msg: Message): void { 157 | // TODO: Calculate required size to fit children. 158 | // See DockLayout._fit 159 | 160 | super.onFitRequest(msg); 161 | } 162 | 163 | activeWidget: Widget; 164 | zTop = 0; 165 | 166 | } 167 | 168 | /** Dispatch a new p-dragleave event outside any widgets. */ 169 | export function sendLeaveEvent(event: IDragEvent, node: HTMLElement) { 170 | const leaveEvent = document.createEvent('MouseEvent'); 171 | const oob = -1000; 172 | 173 | // Claim that the mouse entered the document body at faraway coordinates, 174 | // so any event receivers will consider it outside their bounds. 175 | 176 | leaveEvent.initMouseEvent( 177 | 'p-dragleave', true, true, window, 0, 178 | oob, oob, 179 | oob, oob, 180 | event.ctrlKey, event.altKey, 181 | event.shiftKey, event.metaKey, 182 | event.button, document.body 183 | ); 184 | 185 | node.dispatchEvent(leaveEvent); 186 | } 187 | 188 | export namespace FloatLayout { 189 | export interface Options { 190 | } 191 | 192 | export type Placement = 'backdrop' | 'float'; 193 | 194 | export interface AddOptions { 195 | placement?: Placement; 196 | left?: number; 197 | top?: number; 198 | width?: number; 199 | height?: number; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/SimpleLayout.ts: -------------------------------------------------------------------------------- 1 | // This file is part of phosphor-float-area, copyright (C) 2017 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { Message, MessageLoop } from '@phosphor/messaging'; 5 | import { IIterator, iter } from '@phosphor/algorithm'; 6 | import { ElementExt } from '@phosphor/domutils'; 7 | import { Widget, Layout, LayoutItem } from '@phosphor/widgets'; 8 | 9 | export interface GenericItem { 10 | dispose(): void; 11 | fit(): void; 12 | update(x?: number, y?: number, width?: number, height?: number): void; 13 | 14 | widget: Widget; 15 | parentNode?: HTMLElement; 16 | 17 | minWidth: number; 18 | maxWidth: number; 19 | minHeight: number; 20 | maxHeight: number; 21 | } 22 | 23 | export interface SimpleBox { 24 | x: number; 25 | y: number; 26 | innerWidth: number; 27 | innerHeight: number; 28 | outerWidth: number; 29 | outerHeight: number; 30 | } 31 | 32 | export class SimpleItem implements GenericItem { 33 | 34 | constructor(public widget: Widget, public parentNode?: HTMLElement) {} 35 | 36 | dispose() {} 37 | 38 | fit() { 39 | const limits = ElementExt.sizeLimits(this.widget.node); 40 | 41 | this.minWidth = limits.minWidth; 42 | this.maxWidth = limits.maxWidth; 43 | this.minHeight = limits.minHeight; 44 | this.maxHeight = limits.maxHeight; 45 | } 46 | 47 | update(x?: number, y?: number, width?: number, height?: number) { 48 | if(width && height && (width != this.width || height != this.height)) { 49 | this.width = width; 50 | this.height = height; 51 | 52 | MessageLoop.sendMessage(this.widget, new Widget.ResizeMessage(width, height)); 53 | } 54 | } 55 | 56 | width: number; 57 | height: number; 58 | minWidth: number; 59 | maxWidth: number; 60 | minHeight: number; 61 | maxHeight: number; 62 | 63 | } 64 | 65 | export class SimpleLayout extends Layout { 66 | 67 | constructor( 68 | protected Item = ( 69 | LayoutItem as { new(widget: Widget): GenericItem } 70 | ) as { new(widget: Widget): Item } 71 | ) { 72 | super(); 73 | } 74 | 75 | iter(): IIterator { 76 | return iter(this.widgetList); 77 | } 78 | 79 | init() { 80 | super.init(); 81 | 82 | if(this.parent) { 83 | for(let widget of this.widgetList) this.attachWidget(widget); 84 | 85 | this.parent.fit(); 86 | } 87 | } 88 | 89 | dispose() { 90 | this.itemMap.forEach(item => item.dispose()); 91 | this.itemMap.clear(); 92 | 93 | for(let widget of this.widgetList) widget.dispose(); 94 | 95 | super.dispose(); 96 | } 97 | 98 | afterUpdate(handler: () => void) { 99 | this.afterUpdateList.push(handler); 100 | } 101 | 102 | addItem(item: Item) { 103 | this.itemMap.set(item.widget, item); 104 | 105 | return(SimpleLayout.prototype.addWidget.call(this, item.widget)); 106 | } 107 | 108 | addWidget(widget: Widget) { 109 | let item: Item | undefined; 110 | 111 | this.widgetList.push(widget); 112 | 113 | if(this.parent) { 114 | item = this.attachWidget(widget); 115 | this.parent.fit(); 116 | } 117 | 118 | return(item); 119 | } 120 | 121 | removeWidget(widget: Widget) { 122 | this.widgetList.splice(this.widgetList.indexOf(widget), 1); 123 | 124 | if(this.parent) { 125 | this.detachWidget(widget); 126 | this.parent.fit(); 127 | } 128 | } 129 | 130 | protected attachWidget(widget: Widget) { 131 | let item = this.itemMap.get(widget); 132 | const parentNode = (item && item.parentNode) || this.parent!.node; 133 | 134 | if(widget.node.parentNode == parentNode) return(item); 135 | 136 | const parentAttached = this.parent!.isAttached; 137 | 138 | if(parentAttached) MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); 139 | 140 | parentNode.appendChild(widget.node); 141 | 142 | if(parentAttached) MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); 143 | 144 | if(!item) { 145 | item = new this.Item(widget); 146 | this.itemMap.set(widget, item); 147 | } 148 | 149 | return(item); 150 | } 151 | 152 | protected detachWidget(widget: Widget) { 153 | const item = this.itemMap.get(widget); 154 | const parentNode = (item && item.parentNode) || this.parent!.node; 155 | 156 | if(widget.node.parentNode != parentNode) return; 157 | 158 | const parentAttached = this.parent!.isAttached; 159 | 160 | if(parentAttached) MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); 161 | 162 | parentNode.removeChild(widget.node); 163 | 164 | if(parentAttached) MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); 165 | 166 | if(item) { 167 | this.itemMap.delete(widget); 168 | item.dispose(); 169 | } 170 | } 171 | 172 | protected onBeforeAttach(msg: Message) { 173 | super.onBeforeAttach(msg); 174 | this.parent!.fit(); 175 | } 176 | 177 | protected onFitRequest(msg: Message) { 178 | if(!this.parent!.isAttached) return; 179 | 180 | let minWidth = 0; 181 | let minHeight = 0; 182 | let maxWidth = Infinity; 183 | let maxHeight = Infinity; 184 | 185 | this.itemMap.forEach((item: Item) => { 186 | item.fit(); 187 | minWidth = Math.max(minWidth, item.minWidth); 188 | minHeight = Math.max(minHeight, item.minHeight); 189 | maxWidth = Math.min(maxWidth, item.maxWidth); 190 | maxHeight = Math.min(maxHeight, item.maxHeight); 191 | }); 192 | 193 | this.updateBox(); 194 | const extraWidth = this.box.outerWidth - this.box.innerWidth; 195 | const extraHeight = this.box.outerHeight - this.box.innerHeight; 196 | const style = this.parent!.node.style; 197 | 198 | minWidth += extraWidth; 199 | minHeight += extraHeight; 200 | maxWidth += extraWidth; 201 | maxHeight += extraHeight; 202 | 203 | style.minWidth = minWidth + 'px'; 204 | style.minHeight = minHeight + 'px'; 205 | style.maxWidth = maxWidth + 'px'; 206 | style.maxHeight = maxHeight + 'px'; 207 | 208 | this.isUpdated = false; 209 | 210 | if(this.parent!.parent) { 211 | MessageLoop.sendMessage(this.parent!.parent!, Widget.Msg.FitRequest); 212 | } 213 | 214 | if(!this.isUpdated) { 215 | MessageLoop.sendMessage(this.parent!, Widget.Msg.UpdateRequest); 216 | } 217 | } 218 | 219 | protected onUpdateRequest(msg: Message): void { 220 | if(this.parent!.isVisible) this.update(); 221 | } 222 | 223 | protected onResize(msg: Widget.ResizeMessage): void { 224 | if(this.parent!.isVisible) this.update(msg.width, msg.height); 225 | } 226 | 227 | updateBox(width = this.parent!.node.offsetWidth, height = this.parent!.node.offsetHeight) { 228 | const box = this.box; 229 | const sizing = ElementExt.boxSizing(this.parent!.node); 230 | 231 | box.x = sizing.paddingLeft, 232 | box.y = sizing.paddingTop, 233 | box.innerWidth = width - sizing.horizontalSum; 234 | box.innerHeight = height - sizing.verticalSum; 235 | box.outerWidth = width; 236 | box.outerHeight = height; 237 | } 238 | 239 | update(width?: number, height?: number) { 240 | this.isUpdated = true; 241 | 242 | this.updateBox(width, height); 243 | 244 | this.onUpdate(); 245 | 246 | if(this.afterUpdateList.length) { 247 | for(let handler of this.afterUpdateList) handler(); 248 | 249 | this.afterUpdateList = []; 250 | } 251 | } 252 | 253 | onUpdate() {} 254 | 255 | updateItem(item: Item, x: number, y: number, width: number, height: number) { 256 | item.update(x, y, width, height); 257 | } 258 | 259 | protected box: SimpleBox = { x: 0, y: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 }; 260 | 261 | protected widgetList: Widget[] = []; 262 | protected itemMap = new Map(); 263 | 264 | private afterUpdateList: (() => void)[] = []; 265 | 266 | protected isUpdated = true; 267 | 268 | } 269 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // This file is part of phosphor-float-area, copyright (C) 2017 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | export * from './FloatArea'; 5 | export * from './FloatLayout'; 6 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "experimentalDecorators": true, 7 | "lib": [ "dom", "es5", "es2015.promise", "es2015.collection" ], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitThis": true, 12 | "outDir": "../dist", 13 | "removeComments": false, 14 | "sourceMap": true, 15 | "strictNullChecks": true, 16 | "target": "es5" 17 | }, 18 | "files": [ 19 | "index.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | .charto-mod-noTransition { 2 | transition: none !important; 3 | } 4 | 5 | .charto-Dialog > .p-DockPanel > .p-DockPanel-tabBar > .p-TabBar-content { 6 | /* Change mouse pointer over drag handle. */ 7 | cursor: move; 8 | } 9 | 10 | .charto-Dialog > .p-DockPanel > .p-DockPanel-tabBar > .p-TabBar-content > .p-TabBar-tab { 11 | /* Reset mouse pointer over tabs. */ 12 | cursor: default; 13 | } 14 | 15 | .charto-Dialog > .charto-Dialog-resize { 16 | position: absolute; 17 | } 18 | 19 | .charto-Dialog > .charto-Dialog-resize-ns { 20 | left: 16px; 21 | right: 16px; 22 | height: 8px; 23 | cursor: ns-resize; 24 | } 25 | 26 | .charto-Dialog > .charto-Dialog-resize-n { top: 0px; } 27 | .charto-Dialog > .charto-Dialog-resize-s { bottom: 0px; } 28 | 29 | .charto-Dialog > .charto-Dialog-resize-ew { 30 | top: 16px; 31 | bottom: 16px; 32 | width: 8px; 33 | cursor: ew-resize; 34 | } 35 | 36 | .charto-Dialog > .charto-Dialog-resize-w { left: 0px; } 37 | .charto-Dialog > .charto-Dialog-resize-e { right: 0px; } 38 | 39 | .charto-Dialog > .charto-Dialog-resize-nesw { 40 | width: 16px; 41 | height: 16px; 42 | cursor: nesw-resize; 43 | } 44 | 45 | .charto-Dialog > .charto-Dialog-resize-ne { right: 0px; top: 0px; } 46 | .charto-Dialog > .charto-Dialog-resize-sw { left: 0px; bottom: 0px; } 47 | 48 | .charto-Dialog > .charto-Dialog-resize-nwse { 49 | width: 16px; 50 | height: 16px; 51 | cursor: nwse-resize; 52 | } 53 | 54 | .charto-Dialog > .charto-Dialog-resize-nw { left: 0px; top: 0px; } 55 | .charto-Dialog > .charto-Dialog-resize-se { right: 0px; bottom: 0px; } 56 | 57 | /* Draw a drag handle on the right side of the tab bar. */ 58 | 59 | .charto-Dialog > .p-DockPanel > .p-DockPanel-tabBar > .p-TabBar-content:after { 60 | content: ''; 61 | flex: 1 1 auto; 62 | border-top: 1px solid #c0c0c0; 63 | margin: 0 0 16px 8px; 64 | box-shadow: 65 | 0 4px 0 0 #c0c0c0, 66 | 0 8px 0 0 #c0c0c0; 67 | } 68 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.js.map 3 | *.d.ts 4 | -------------------------------------------------------------------------------- /test/App.ts: -------------------------------------------------------------------------------- 1 | import '@phosphor/dragdrop/style/index.css!'; 2 | import '@phosphor/widgets/style/index.css!'; 3 | import 'font-awesome/css/font-awesome.min.css!'; 4 | import '../style/index.css!'; 5 | import '../www/css/content.css!'; 6 | import '../www/css/phosphor/index.css!'; 7 | 8 | import { Widget, DockPanel } from '@phosphor/widgets'; 9 | import { FloatArea } from '../dist/index'; 10 | 11 | class AreaWidget extends FloatArea { 12 | 13 | constructor() { 14 | super(); 15 | 16 | this.addClass('charto-content'); 17 | 18 | this.content = document.createElement('div'); 19 | this.content.className = 'charto-content-inner'; 20 | 21 | this.node.appendChild(this.content); 22 | 23 | this.title.label = 'Main'; 24 | this.title.closable = false; 25 | this.title.caption = 'Main working area'; 26 | } 27 | 28 | content: HTMLDivElement; 29 | 30 | } 31 | 32 | class ContentWidget extends Widget { 33 | 34 | static createNode(): HTMLElement { 35 | const node = document.createElement('div'); 36 | const content = document.createElement('div'); 37 | const input = document.createElement('input'); 38 | 39 | content.classList.add('charto-content-inner'); 40 | 41 | input.placeholder = 'Placeholder...'; 42 | input.className = 'placeholder'; 43 | content.appendChild(input); 44 | node.appendChild(content); 45 | 46 | return(node); 47 | } 48 | 49 | constructor(name: string) { 50 | super({ node: ContentWidget.createNode() }); 51 | 52 | this.addClass('charto-content'); 53 | this.addClass('demo-content'); 54 | this.addClass(name.toLowerCase()); 55 | 56 | this.title.label = name; 57 | this.title.closable = true; 58 | this.title.caption = 'Description of ' + name; 59 | } 60 | 61 | content: HTMLDivElement; 62 | 63 | } 64 | 65 | const overlay = new DockPanel.Overlay(); 66 | const dockPanel = new DockPanel({ overlay }); 67 | dockPanel.id = 'main'; 68 | 69 | const area = new AreaWidget(); 70 | const red = new ContentWidget('Red'); 71 | const yellow = new ContentWidget('Yellow'); 72 | const blue = new ContentWidget('Blue'); 73 | dockPanel.addWidget(area); 74 | dockPanel.addWidget(red, { mode: 'split-left', ref: area }); 75 | dockPanel.addWidget(yellow, { mode: 'split-bottom', ref: red }); 76 | dockPanel.addWidget(blue, { mode: 'split-right', ref: area }); 77 | 78 | Widget.attach(dockPanel, document.body); 79 | 80 | window.onresize = () => { dockPanel.update(); }; 81 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "experimentalDecorators": true, 7 | "lib": [ "dom", "es5", "es2015.promise", "es2015.collection" ], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitThis": true, 12 | "removeComments": false, 13 | "sourceMap": true, 14 | "strictNullChecks": true, 15 | "target": "es5" 16 | }, 17 | "files": [ 18 | "App.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /www/config-base.js: -------------------------------------------------------------------------------- 1 | System.config({ baseURL: window.location.pathname }); 2 | 3 | -------------------------------------------------------------------------------- /www/config-npm.js: -------------------------------------------------------------------------------- 1 | // Autogenerated using cbuild 2 | System.config({ 3 | map: { 4 | "./array": "node_modules/@phosphor/algorithm/lib/array.js", 5 | "./boxengine": "node_modules/@phosphor/widgets/lib/boxengine.js", 6 | "./boxlayout": "node_modules/@phosphor/widgets/lib/boxlayout.js", 7 | "./boxpanel": "node_modules/@phosphor/widgets/lib/boxpanel.js", 8 | "./chain": "node_modules/@phosphor/algorithm/lib/chain.js", 9 | "./commandpalette": "node_modules/@phosphor/widgets/lib/commandpalette.js", 10 | "./contextmenu": "node_modules/@phosphor/widgets/lib/contextmenu.js", 11 | "./docklayout": "node_modules/@phosphor/widgets/lib/docklayout.js", 12 | "./dockpanel": "node_modules/@phosphor/widgets/lib/dockpanel.js", 13 | "./element": "node_modules/@phosphor/domutils/lib/element.js", 14 | "./empty": "node_modules/@phosphor/algorithm/lib/empty.js", 15 | "./filter": "node_modules/@phosphor/algorithm/lib/filter.js", 16 | "./find": "node_modules/@phosphor/algorithm/lib/find.js", 17 | "./focustracker": "node_modules/@phosphor/widgets/lib/focustracker.js", 18 | "./gridlayout": "node_modules/@phosphor/widgets/lib/gridlayout.js", 19 | "./iter": "node_modules/@phosphor/algorithm/lib/iter.js", 20 | "./json": "node_modules/@phosphor/coreutils/lib/json.js", 21 | "./layout": "node_modules/@phosphor/widgets/lib/layout.js", 22 | "./linkedlist": "node_modules/@phosphor/collections/lib/linkedlist.js", 23 | "./map": "node_modules/@phosphor/algorithm/lib/map.js", 24 | "./menu": "node_modules/@phosphor/widgets/lib/menu.js", 25 | "./menubar": "node_modules/@phosphor/widgets/lib/menubar.js", 26 | "./mime": "node_modules/@phosphor/coreutils/lib/mime.js", 27 | "./panel": "node_modules/@phosphor/widgets/lib/panel.js", 28 | "./panellayout": "node_modules/@phosphor/widgets/lib/panellayout.js", 29 | "./platform": "node_modules/@phosphor/domutils/lib/platform.js", 30 | "./promise": "node_modules/@phosphor/coreutils/lib/promise.js", 31 | "./random": "node_modules/@phosphor/coreutils/lib/random.js", 32 | "./range": "node_modules/@phosphor/algorithm/lib/range.js", 33 | "./reduce": "node_modules/@phosphor/algorithm/lib/reduce.js", 34 | "./repeat": "node_modules/@phosphor/algorithm/lib/repeat.js", 35 | "./retro": "node_modules/@phosphor/algorithm/lib/retro.js", 36 | "./scrollbar": "node_modules/@phosphor/widgets/lib/scrollbar.js", 37 | "./selector": "node_modules/@phosphor/domutils/lib/selector.js", 38 | "./sort": "node_modules/@phosphor/algorithm/lib/sort.js", 39 | "./splitlayout": "node_modules/@phosphor/widgets/lib/splitlayout.js", 40 | "./splitpanel": "node_modules/@phosphor/widgets/lib/splitpanel.js", 41 | "./stackedlayout": "node_modules/@phosphor/widgets/lib/stackedlayout.js", 42 | "./stackedpanel": "node_modules/@phosphor/widgets/lib/stackedpanel.js", 43 | "./stride": "node_modules/@phosphor/algorithm/lib/stride.js", 44 | "./string": "node_modules/@phosphor/algorithm/lib/string.js", 45 | "./tabbar": "node_modules/@phosphor/widgets/lib/tabbar.js", 46 | "./tabpanel": "node_modules/@phosphor/widgets/lib/tabpanel.js", 47 | "./take": "node_modules/@phosphor/algorithm/lib/take.js", 48 | "./title": "node_modules/@phosphor/widgets/lib/title.js", 49 | "./token": "node_modules/@phosphor/coreutils/lib/token.js", 50 | "./uuid": "node_modules/@phosphor/coreutils/lib/uuid.js", 51 | "./widget": "node_modules/@phosphor/widgets/lib/widget.js", 52 | "./zip": "node_modules/@phosphor/algorithm/lib/zip.js", 53 | "@phosphor/algorithm": "node_modules/@phosphor/algorithm", 54 | "@phosphor/collections": "node_modules/@phosphor/collections", 55 | "@phosphor/commands": "node_modules/@phosphor/commands", 56 | "@phosphor/coreutils": "node_modules/@phosphor/coreutils", 57 | "@phosphor/disposable": "node_modules/@phosphor/disposable", 58 | "@phosphor/domutils": "node_modules/@phosphor/domutils", 59 | "@phosphor/dragdrop": "node_modules/@phosphor/dragdrop", 60 | "@phosphor/dragdrop/style/index.css": "node_modules/@phosphor/dragdrop/style/index.css", 61 | "@phosphor/keyboard": "node_modules/@phosphor/keyboard", 62 | "@phosphor/messaging": "node_modules/@phosphor/messaging", 63 | "@phosphor/properties": "node_modules/@phosphor/properties", 64 | "@phosphor/signaling": "node_modules/@phosphor/signaling", 65 | "@phosphor/virtualdom": "node_modules/@phosphor/virtualdom", 66 | "@phosphor/widgets": "node_modules/@phosphor/widgets", 67 | "@phosphor/widgets/style/index.css": "node_modules/@phosphor/widgets/style/index.css", 68 | "font-awesome/css/font-awesome.min.css": "node_modules/font-awesome/css/font-awesome.min.css" 69 | }, 70 | meta: { 71 | "node_modules/*": { globals: { process: "bundler/node_modules/cbuild/process-dev.js" } } 72 | }, 73 | packages: { 74 | "@phosphor/algorithm": { 75 | main: "lib/index.js" 76 | }, 77 | "@phosphor/collections": { 78 | main: "lib/index.js" 79 | }, 80 | "@phosphor/commands": { 81 | main: "lib/index.js" 82 | }, 83 | "@phosphor/coreutils": { 84 | main: "lib/index.js" 85 | }, 86 | "@phosphor/disposable": { 87 | main: "lib/index.js" 88 | }, 89 | "@phosphor/domutils": { 90 | main: "lib/index.js" 91 | }, 92 | "@phosphor/dragdrop": { 93 | main: "lib/index.js" 94 | }, 95 | "@phosphor/keyboard": { 96 | main: "lib/index.js" 97 | }, 98 | "@phosphor/messaging": { 99 | main: "lib/index.js" 100 | }, 101 | "@phosphor/properties": { 102 | main: "lib/index.js" 103 | }, 104 | "@phosphor/signaling": { 105 | main: "lib/index.js" 106 | }, 107 | "@phosphor/virtualdom": { 108 | main: "lib/index.js" 109 | }, 110 | "@phosphor/widgets": { 111 | main: "lib/index.js" 112 | } 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /www/css/content.css: -------------------------------------------------------------------------------- 1 | .p-DockPanel > .charto-content { 2 | padding: 8px; 3 | border: 1px solid #c0c0c0; 4 | border-top: none; 5 | background: #ffffff; 6 | box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); 7 | } 8 | 9 | .charto-content-inner { 10 | z-index: 0; 11 | width: 100%; 12 | height: 100%; 13 | border: 1px solid #808080; 14 | } 15 | 16 | .charto-Dialog { 17 | min-width: 64px; 18 | min-height: 64px; 19 | padding: 8px; 20 | border: 1px solid #808080; 21 | box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); 22 | } 23 | 24 | .red > div { 25 | background: #e74c3c; 26 | } 27 | 28 | 29 | .yellow > div { 30 | background: #f1C40f; 31 | } 32 | 33 | 34 | .green > div { 35 | background: #27ae60; 36 | } 37 | 38 | 39 | .blue > div { 40 | background: #3498db; 41 | } 42 | -------------------------------------------------------------------------------- /www/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fcf8f4; 3 | } 4 | 5 | .Red {} 6 | 7 | /* Make dialogs semi-transparent when the mouse is not over them. */ 8 | 9 | .charto-Dialog, 10 | .charto-content, 11 | .p-TabBar-tab { 12 | transition: background-color 0.125s; 13 | transition: opacity 0.125s; 14 | } 15 | 16 | .charto-Dialog { 17 | background: #fcf8f4; 18 | } 19 | 20 | .charto-Dialog-mod-dimmable:not(:hover) { 21 | background-color: rgba(255, 255, 255, 0.5); 22 | opacity: 0.875; 23 | } 24 | 25 | .placeholder { 26 | width: 96px; 27 | } 28 | 29 | .demo-content { 30 | min-width: 118px; 31 | min-height: 38px; 32 | } 33 | -------------------------------------------------------------------------------- /www/css/phosphor/commandpalette.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2014-2017, PhosphorJS Contributors 3 | | 4 | | Distributed under the terms of the BSD 3-Clause License. 5 | | 6 | | The full license is in the file LICENSE, distributed with this software. 7 | |----------------------------------------------------------------------------*/ 8 | 9 | 10 | .p-CommandPalette { 11 | font-family: sans-serif; 12 | background: #F5F5F5; 13 | } 14 | 15 | 16 | .p-CommandPalette-search { 17 | padding: 8px; 18 | } 19 | 20 | 21 | .p-CommandPalette-wrapper { 22 | padding: 4px 6px; 23 | background: white; 24 | border: 1px solid #E0E0E0; 25 | } 26 | 27 | 28 | .p-CommandPalette-input { 29 | width: 100%; 30 | border: none; 31 | outline: none; 32 | font-size: 16px; 33 | } 34 | 35 | 36 | .p-CommandPalette-header { 37 | padding: 4px; 38 | color: #757575; 39 | font-size: 12px; 40 | font-weight: 600; 41 | background: #E1E1E1; 42 | cursor: pointer; 43 | } 44 | 45 | 46 | .p-CommandPalette-header:hover::before { 47 | content: '\2026'; /* ellipsis */ 48 | float: right; 49 | margin-right: 4px; 50 | } 51 | 52 | 53 | .p-CommandPalette-header > mark { 54 | background-color: transparent; 55 | font-weight: bold; 56 | } 57 | 58 | 59 | .p-CommandPalette-item { 60 | padding: 4px 8px; 61 | color: #757575; 62 | font-size: 13px; 63 | font-weight: 500; 64 | } 65 | 66 | 67 | .p-CommandPalette-emptyMessage { 68 | padding: 4px; 69 | color: #757575; 70 | font-size: 12px; 71 | font-weight: 600; 72 | text-align: center; 73 | } 74 | 75 | 76 | .p-CommandPalette-item.p-mod-disabled { 77 | color: rgba(0, 0, 0, 0.25); 78 | } 79 | 80 | 81 | .p-CommandPalette-item.p-mod-active { 82 | background: #7FDBFF; 83 | } 84 | 85 | 86 | .p-CommandPalette-item:hover:not(.p-mod-active):not(.p-mod-disabled) { 87 | background: #E5E5E5; 88 | } 89 | 90 | 91 | .p-CommandPalette-itemLabel > mark { 92 | background-color: transparent; 93 | font-weight: bold; 94 | } 95 | 96 | 97 | .p-CommandPalette-item.p-mod-disabled mark { 98 | color: rgba(0, 0, 0, 0.4); 99 | } 100 | 101 | 102 | .p-CommandPalette-itemCaption { 103 | color: #9E9E9E; 104 | font-size: 11px; 105 | font-weight: 400; 106 | } 107 | -------------------------------------------------------------------------------- /www/css/phosphor/content.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2014-2017, PhosphorJS Contributors 3 | | 4 | | Distributed under the terms of the BSD 3-Clause License. 5 | | 6 | | The full license is in the file LICENSE, distributed with this software. 7 | |----------------------------------------------------------------------------*/ 8 | 9 | 10 | .content { 11 | min-width: 50px; 12 | min-height: 50px; 13 | display: flex; 14 | flex-direction: column; 15 | padding: 8px; 16 | border: 1px solid #C0C0C0; 17 | border-top: none; 18 | background: white; 19 | box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); 20 | } 21 | 22 | 23 | .content > div { 24 | flex: 1 1 auto; 25 | border: 1px solid #505050; 26 | overflow: auto; 27 | } 28 | 29 | 30 | .content input { 31 | margin: 8px; 32 | } 33 | 34 | 35 | .red > div { 36 | background: #E74C3C; 37 | } 38 | 39 | 40 | .yellow > div { 41 | background: #F1C40F; 42 | } 43 | 44 | 45 | .green > div { 46 | background: #27AE60; 47 | } 48 | 49 | 50 | .blue > div { 51 | background: #3498DB; 52 | } 53 | -------------------------------------------------------------------------------- /www/css/phosphor/dockpanel.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2014-2017, PhosphorJS Contributors 3 | | 4 | | Distributed under the terms of the BSD 3-Clause License. 5 | | 6 | | The full license is in the file LICENSE, distributed with this software. 7 | |----------------------------------------------------------------------------*/ 8 | 9 | 10 | .p-DockPanel-overlay { 11 | background: rgba(255, 255, 255, 0.6); 12 | border: 1px dashed black; 13 | transition-property: top, left, right, bottom; 14 | transition-duration: 150ms; 15 | transition-timing-function: ease; 16 | } 17 | 18 | .p-mod-overlay-jump { 19 | transition: none; 20 | } 21 | -------------------------------------------------------------------------------- /www/css/phosphor/index.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2014-2017, PhosphorJS Contributors 3 | | 4 | | Distributed under the terms of the BSD 3-Clause License. 5 | | 6 | | The full license is in the file LICENSE, distributed with this software. 7 | |----------------------------------------------------------------------------*/ 8 | @import './commandpalette.css'; 9 | @import './dockpanel.css'; 10 | @import './menu.css'; 11 | @import './menubar.css'; 12 | @import './tabbar.css'; 13 | 14 | 15 | body { 16 | display: flex; 17 | flex-direction: column; 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | right: 0; 22 | bottom: 0; 23 | margin: 0; 24 | padding: 0; 25 | overflow: hidden; 26 | } 27 | 28 | 29 | #menuBar { 30 | flex: 0 0 auto; 31 | } 32 | 33 | #main { 34 | height: 100%; 35 | padding: 4px; 36 | } 37 | -------------------------------------------------------------------------------- /www/css/phosphor/menu.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2014-2017, PhosphorJS Contributors 3 | | 4 | | Distributed under the terms of the BSD 3-Clause License. 5 | | 6 | | The full license is in the file LICENSE, distributed with this software. 7 | |----------------------------------------------------------------------------*/ 8 | 9 | 10 | .p-Menu { 11 | padding: 3px 0px; 12 | background: white; 13 | color: rgba(0, 0, 0, 0.87); 14 | border: 1px solid #C0C0C0; 15 | font: 12px Helvetica, Arial, sans-serif; 16 | box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.2); 17 | } 18 | 19 | 20 | .p-Menu-item.p-mod-active { 21 | background: #E5E5E5; 22 | } 23 | 24 | 25 | .p-Menu-item.p-mod-disabled { 26 | color: rgba(0, 0, 0, 0.25); 27 | } 28 | 29 | 30 | .p-Menu-itemIcon { 31 | width: 21px; 32 | padding: 4px 2px; 33 | } 34 | 35 | 36 | .p-Menu-itemLabel { 37 | padding: 4px 35px 4px 2px; 38 | } 39 | 40 | 41 | .p-Menu-itemMnemonic { 42 | text-decoration: underline; 43 | } 44 | 45 | 46 | .p-Menu-itemShortcut { 47 | padding: 4px 0px; 48 | } 49 | 50 | 51 | .p-Menu-itemSubmenuIcon { 52 | width: 16px; 53 | padding: 4px 0px; 54 | } 55 | 56 | 57 | .p-Menu-item[data-type='separator'] > div { 58 | padding: 0; 59 | height: 9px; 60 | } 61 | 62 | 63 | .p-Menu-item[data-type='separator'] > div::after { 64 | content: ''; 65 | display: block; 66 | position: relative; 67 | top: 4px; 68 | border-top: 1px solid #DDDDDD; 69 | } 70 | 71 | 72 | .p-Menu-itemIcon::before, 73 | .p-Menu-itemSubmenuIcon::before { 74 | font-family: FontAwesome; 75 | } 76 | 77 | 78 | .p-Menu-item.p-mod-toggled > .p-Menu-itemIcon::before { 79 | content: '\f00c'; 80 | } 81 | 82 | 83 | .p-Menu-item[data-type='submenu'] > .p-Menu-itemSubmenuIcon::before { 84 | content: '\f0da'; 85 | } 86 | -------------------------------------------------------------------------------- /www/css/phosphor/menubar.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2014-2017, PhosphorJS Contributors 3 | | 4 | | Distributed under the terms of the BSD 3-Clause License. 5 | | 6 | | The full license is in the file LICENSE, distributed with this software. 7 | |----------------------------------------------------------------------------*/ 8 | 9 | 10 | .p-MenuBar { 11 | padding-left: 5px; 12 | background: #FAFAFA; 13 | color: rgba(0, 0, 0, 0.87); 14 | border-bottom: 1px solid #DDDDDD; 15 | font: 13px Helvetica, Arial, sans-serif; 16 | } 17 | 18 | 19 | .p-MenuBar-menu { 20 | transform: translateY(-1px); 21 | } 22 | 23 | 24 | .p-MenuBar-item { 25 | padding: 4px 8px; 26 | border-left: 1px solid transparent; 27 | border-right: 1px solid transparent; 28 | } 29 | 30 | 31 | .p-MenuBar-item.p-mod-active { 32 | background: #E5E5E5; 33 | } 34 | 35 | 36 | .p-MenuBar.p-mod-active .p-MenuBar-item.p-mod-active { 37 | z-index: 10001; 38 | background: white; 39 | border-left: 1px solid #C0C0C0; 40 | border-right: 1px solid #C0C0C0; 41 | box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.2); 42 | } 43 | -------------------------------------------------------------------------------- /www/css/phosphor/tabbar.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2014-2017, PhosphorJS Contributors 3 | | 4 | | Distributed under the terms of the BSD 3-Clause License. 5 | | 6 | | The full license is in the file LICENSE, distributed with this software. 7 | |----------------------------------------------------------------------------*/ 8 | 9 | 10 | .p-TabBar { 11 | min-height: 24px; 12 | max-height: 24px; 13 | } 14 | 15 | 16 | .p-TabBar-content { 17 | min-width: 0; 18 | min-height: 0; 19 | align-items: flex-end; 20 | border-bottom: 1px solid #C0C0C0; 21 | } 22 | 23 | 24 | .p-TabBar-tab { 25 | padding: 0px 10px; 26 | background: #E5E5E5; 27 | border: 1px solid #C0C0C0; 28 | border-bottom: none; 29 | font: 12px Helvetica, Arial, sans-serif; 30 | flex: 0 1 125px; 31 | min-height: 20px; 32 | max-height: 20px; 33 | min-width: 35px; 34 | margin-left: -1px; 35 | line-height: 20px; 36 | } 37 | 38 | 39 | .p-TabBar-tab.p-mod-current { 40 | background: white; 41 | } 42 | 43 | 44 | .p-TabBar-tab:hover:not(.p-mod-current) { 45 | background: #F0F0F0; 46 | } 47 | 48 | 49 | .p-TabBar-tab:first-child { 50 | margin-left: 0; 51 | } 52 | 53 | 54 | .p-TabBar-tab.p-mod-current { 55 | min-height: 23px; 56 | max-height: 23px; 57 | transform: translateY(1px); 58 | } 59 | 60 | 61 | .p-TabBar-tabIcon, 62 | .p-TabBar-tabLabel, 63 | .p-TabBar-tabCloseIcon { 64 | display: inline-block; 65 | } 66 | 67 | 68 | .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon { 69 | margin-left: 4px; 70 | } 71 | 72 | 73 | .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon:before { 74 | content: '\f00d'; 75 | font-family: FontAwesome; 76 | } 77 | 78 | 79 | .p-TabBar-tab.p-mod-drag-image { 80 | min-height: 23px; 81 | max-height: 23px; 82 | min-width: 125px; 83 | border: none; 84 | box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); 85 | transform: translateX(-40%) translateY(-58%); 86 | } 87 | --------------------------------------------------------------------------------