├── .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 | 
7 |
8 | 100% Virtual DOM, TypeScript, [PhosphorJS](https://github.com/phosphorjs/phosphor)
9 | based modern JavaScript goodness :cake:
10 |
11 | [](https://david-dm.org/charto/phosphor-float-area)
12 | [](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 |
--------------------------------------------------------------------------------