Browse Source

wip update main area widget

wip main area widget updates

Update help extension

Use main area widget where applicable

clean up main area widget handling

finish main area widgets

move activation to docregistry options

Update main area widget tests

update tests and integrity

Update tests

integrity update

address review

fix conflicts

Remove coverage files

Remove coverage files
Steven Silvester 7 years ago
parent
commit
8782ee7877

+ 5 - 1
packages/application/src/shell.ts

@@ -252,7 +252,7 @@ class ApplicationShell extends Widget {
     // layout has been restored.
     widgets.forEach(widget => {
       if (!widget.parent) {
-        this.addToMainArea(widget);
+        this.addToMainArea(widget, { activate: false });
       }
     });
 
@@ -403,6 +403,10 @@ class ApplicationShell extends Widget {
     let mode = options.mode || 'tab-after';
 
     dock.addWidget(widget, { mode, ref });
+
+    if (options.activate !== false) {
+      dock.activateWidget(widget);
+    }
   }
 
   /**

+ 139 - 23
packages/apputils/src/mainareawidget.ts

@@ -1,37 +1,124 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import {
+  uuid
+} from '@jupyterlab/coreutils';
+
 import {
   Message
 } from '@phosphor/messaging';
 
 import {
-  Layout, PanelLayout, Widget
+  BoxLayout, Widget
 } from '@phosphor/widgets';
 
+import {
+  Toolbar
+} from './toolbar';
+
 
 /**
  * A widget which handles tab events according to JupyterLab convention.
+ *
+ * #### Notes
+ * Mirrors all of the `title` attributes of the child.
+ * This widget is `closable` by default.
+ * This widget is automatically disposed when closed.
+ * This widget ensures its own focus when activated.
  */
 export
-class MainAreaWidget extends Widget {
+class MainAreaWidget<T extends Widget = Widget> extends Widget {
   /**
    * Construct a new main area widget.
    *
    * @param options - The options for initializing the widget.
    */
-  constructor(options: MainAreaWidget.IOptions = {}) {
+  constructor(options: MainAreaWidget.IOptions<T>) {
     super(options);
-    this.node.tabIndex = -1;
     this.addClass('jp-MainAreaWidget');
-    this.layout = Private.createLayout(options);
+    this.id = uuid();
+    let content = this.content = options.content;
+    if (!content.id) {
+      content.id = uuid();
+    }
+    content.node.tabIndex = -1;
+    let layout = this.layout = new BoxLayout();
+    layout.direction = 'top-to-bottom';
+    let toolbar = this.toolbar = options.toolbar || new Toolbar();
+
+    layout.addWidget(toolbar);
+    layout.addWidget(content);
+    BoxLayout.setStretch(toolbar, 0);
+    BoxLayout.setStretch(content, 1);
+
+    this._updateTitle();
+    content.title.changed.connect(this._updateTitle, this);
+    this.title.closable = true;
+    this.title.changed.connect(this._updateContentTitle, this);
+    content.disposed.connect(() => this.dispose());
+  }
+
+  /**
+   * The content hosted by the widget.
+   */
+  readonly content: T;
+
+  /**
+   * The toolbar hosted by the widget.
+   */
+  readonly toolbar: Toolbar;
+
+  /**
+   * Handle the DOM events for the widget.
+   *
+   * @param event - The DOM event sent to the widget.
+   *
+   * #### Notes
+   * This method implements the DOM `EventListener` interface and is
+   * called in response to events on the dock panel's node. It should
+   * not be called directly by user code.
+   */
+  handleEvent(event: Event): void {
+    switch (event.type) {
+    case 'mouseup':
+    case 'mouseout':
+      let target = event.target as HTMLElement;
+      if (this.toolbar.node.contains(document.activeElement) &&
+          target.tagName !== 'SELECT') {
+        this.content.node.focus();
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+  /**
+   * Handle `after-attach` messages for the widget.
+   */
+  protected onAfterAttach(msg: Message): void {
+    this.toolbar.node.addEventListener('mouseup', this);
+    this.toolbar.node.addEventListener('mouseout', this);
+  }
+
+  /**
+   * Handle `before-detach` messages for the widget.
+   */
+  protected onBeforeDetach(msg: Message): void {
+    this.toolbar.node.removeEventListener('mouseup', this);
+    this.toolbar.node.removeEventListener('mouseout', this);
   }
 
   /**
    * Handle `'activate-request'` messages.
    */
   protected onActivateRequest(msg: Message): void {
-    this.node.focus();
+    if (!this.node.contains(document.activeElement)) {
+      this.content.node.focus();
+    }
+    // Give the content a chance to activate.
+    this.content.activate();
   }
 
   /**
@@ -40,8 +127,47 @@ class MainAreaWidget extends Widget {
   protected onCloseRequest(msg: Message): void {
     this.dispose();
   }
+
+  /**
+   * Update the title based on the attributes of the child widget.
+   */
+  private _updateTitle(): void {
+    if (this._changeGuard) {
+      return;
+    }
+    this._changeGuard = true;
+    this.title.label = this.content.title.label;
+    this.title.mnemonic = this.content.title.mnemonic;
+    this.title.iconClass = this.content.title.iconClass;
+    this.title.iconLabel = this.content.title.iconLabel;
+    this.title.caption = this.content.title.caption;
+    this.title.className = this.content.title.className;
+    this.title.dataset = this.content.title.dataset;
+    this._changeGuard = false;
+  }
+
+  /**
+   * Update the content title based on attributes of the main widget.
+   */
+  private _updateContentTitle(): void {
+    if (this._changeGuard) {
+      return;
+    }
+    this._changeGuard = true;
+    this.content.title.label = this.title.label;
+    this.content.title.mnemonic = this.title.mnemonic;
+    this.content.title.iconClass = this.title.iconClass;
+    this.content.title.iconLabel = this.title.iconLabel;
+    this.content.title.caption = this.title.caption;
+    this.content.title.className = this.title.className;
+    this.content.title.dataset = this.title.dataset;
+    this._changeGuard = false;
+  }
+
+  private _changeGuard = false;
 }
 
+
 /**
  * The namespace for the `MainAreaWidget` class statics.
  */
@@ -51,25 +177,15 @@ namespace MainAreaWidget {
    * An options object for creating a main area widget.
    */
   export
-  interface IOptions extends Widget.IOptions {
+  interface IOptions<T extends Widget = Widget> extends Widget.IOptions {
     /**
-     * The layout to use for the main area widget.
-     *
-     * The default is a new `PanelLayout`.
+     * The child widget to wrap.
      */
-    layout?: Layout;
-  }
-}
+    content: T;
 
-/**
- * The namespace for the module implementation details.
- */
-namespace Private {
-  /**
-   * Create a layout for the given options.
-   */
-  export
-  function createLayout(options: MainAreaWidget.IOptions): Layout {
-    return options.layout || new PanelLayout();
+    /**
+     * The toolbar to use for the widget.  Defaults to an empty toolbar.
+     */
+    toolbar?: Toolbar;
   }
 }

+ 1 - 1
packages/apputils/src/toolbar.ts

@@ -89,7 +89,7 @@ const TOOLBAR_IDLE_CLASS = 'jp-CircleIcon';
  * A class which provides a toolbar widget.
  */
 export
-class Toolbar<T extends Widget> extends Widget {
+class Toolbar<T extends Widget = Widget> extends Widget {
   /**
    * Construct a new toolbar widget.
    */

+ 1 - 1
packages/apputils/style/mainareawidget.css

@@ -4,6 +4,6 @@
 | Distributed under the terms of the Modified BSD License.
 |----------------------------------------------------------------------------*/
 
-.jp-MainAreaWidget:focus {
+.jp-MainAreaWidget > :focus {
   outline: none;
 }

+ 1 - 1
packages/apputils/style/toolbar.css

@@ -15,7 +15,7 @@
   display: flex;
   flex-direction: row;
   border-bottom: var(--jp-border-width) solid var(--jp-toolbar-border-color);
-  height: var(--jp-private-toolbar-height);
+  min-height: var(--jp-toolbar-micro-height);
 }
 
 

+ 5 - 0
packages/docregistry/src/registry.ts

@@ -870,6 +870,11 @@ namespace DocumentRegistry {
      * to the main area relative to a reference widget.
      */
     mode?: DockLayout.InsertMode;
+
+    /**
+     * Whether to activate the widget.  Defaults to `true`.
+     */
+    activate?: boolean;
   }
 
   /**

+ 1 - 3
packages/faq-extension/package.json

@@ -34,9 +34,7 @@
     "@jupyterlab/application": "^0.15.4",
     "@jupyterlab/apputils": "^0.15.5",
     "@jupyterlab/rendermime": "^0.15.4",
-    "@phosphor/coreutils": "^1.3.0",
-    "@phosphor/messaging": "^1.2.2",
-    "@phosphor/widgets": "^1.5.0"
+    "@phosphor/coreutils": "^1.3.0"
   },
   "devDependencies": {
     "@types/node": "~8.0.47",

+ 8 - 52
packages/faq-extension/src/index.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/application';
 
 import {
-  ICommandPalette, InstanceTracker
+  ICommandPalette, InstanceTracker, MainAreaWidget
 } from '@jupyterlab/apputils';
 
 import {
@@ -17,14 +17,6 @@ import {
   JSONExt
 } from '@phosphor/coreutils';
 
-import {
-  Message
-} from '@phosphor/messaging';
-
-import {
-  PanelLayout, Widget
-} from '@phosphor/widgets';
-
 import '../style/index.css';
 
 
@@ -62,45 +54,6 @@ const SOURCE = require('../faq.md');
 /* tslint:enable */
 
 
-/**
- * A widget which is an faq viewer.
- */
-class FAQWidget extends Widget {
-  /**
-   * Construct a new `AppWidget`.
-   */
-  constructor(content: Widget) {
-    super();
-    this.addClass('jp-FAQ');
-    this.title.closable = true;
-    this.node.tabIndex = -1;
-    this.id = 'faq';
-    this.title.label = 'FAQ';
-
-    let toolbar = new Widget();
-    toolbar.addClass('jp-FAQ-toolbar');
-
-    let layout = this.layout = new PanelLayout();
-    layout.addWidget(toolbar);
-    layout.addWidget(content);
-  }
-
-  /**
-   * Handle `close-request` events for the widget.
-   */
-  onCloseRequest(message: Message): void {
-    this.dispose();
-  }
-
-  /**
-   * Handle `activate-request` events for the widget.
-   */
-  onActivateRequest(message: Message): void {
-    this.node.focus();
-  }
-}
-
-
 /**
  * Activate the FAQ plugin.
  */
@@ -108,7 +61,7 @@ function activate(app: JupyterLab, palette: ICommandPalette, restorer: ILayoutRe
   const category = 'Help';
   const command = CommandIDs.open;
   const { commands, shell } = app;
-  const tracker = new InstanceTracker<Widget>({ namespace: 'faq' });
+  const tracker = new InstanceTracker<MainAreaWidget>({ namespace: 'faq' });
 
   // Handle state restoration.
   restorer.restore(tracker, {
@@ -124,10 +77,13 @@ function activate(app: JupyterLab, palette: ICommandPalette, restorer: ILayoutRe
     });
     content.renderModel(model);
     content.addClass('jp-FAQ-content');
-    return new FAQWidget(content);
+    let widget = new MainAreaWidget({ content });
+    widget.addClass('jp-FAQ');
+    widget.title.label = 'FAQ';
+    return widget;
   };
 
-  let widget: FAQWidget;
+  let widget: MainAreaWidget;
 
   commands.addCommand(command, {
     label: 'Open FAQ',
@@ -137,7 +93,7 @@ function activate(app: JupyterLab, palette: ICommandPalette, restorer: ILayoutRe
       }
       if (!tracker.has(widget)) {
         tracker.add(widget);
-        shell.addToMainArea(widget);
+        shell.addToMainArea(widget, { activate: false });
       }
       shell.activateById(widget.id);
     }

+ 0 - 8
packages/faq-extension/style/index.css

@@ -17,14 +17,6 @@
 }
 
 
-.jp-FAQ-toolbar {
-  min-height: var(--jp-toolbar-micro-height);
-  border-bottom: var(--jp-border-width) solid var(--jp-toolbar-border-color);
-  box-shadow: var(--jp-toolbar-box-shadow);
-  background-color: var(--jp-layout-color1);
-}
-
-
 .jp-FAQ-content {
   overflow-y: auto;
   padding: 32px;

+ 0 - 1
packages/help-extension/package.json

@@ -35,7 +35,6 @@
     "@jupyterlab/coreutils": "^1.0.9",
     "@jupyterlab/mainmenu": "^0.4.4",
     "@jupyterlab/services": "^1.1.4",
-    "@phosphor/messaging": "^1.2.2",
     "@phosphor/virtualdom": "^1.1.2",
     "@phosphor/widgets": "^1.5.0"
   },

+ 17 - 58
packages/help-extension/src/index.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/application';
 
 import {
-  Dialog, ICommandPalette, IFrame, InstanceTracker, showDialog
+  Dialog, ICommandPalette, IFrame, InstanceTracker, MainAreaWidget, showDialog
 } from '@jupyterlab/apputils';
 
 import {
@@ -21,16 +21,12 @@ import {
   KernelMessage
 } from '@jupyterlab/services';
 
-import {
-  Message
-} from '@phosphor/messaging';
-
 import {
   h
 } from '@phosphor/virtualdom';
 
 import {
-  Menu, PanelLayout, Widget
+  Menu
 } from '@phosphor/widgets';
 
 import '../style/index.css';
@@ -116,44 +112,6 @@ const plugin: JupyterLabPlugin<void> = {
  */
 export default plugin;
 
-/*
-  * An IFrame the disposes itself when closed.
-  *
-  * This is needed to clear the state restoration db when IFrames are closed.
- */
-class HelpWidget extends Widget {
-  /**
-   * Construct a new help widget.
-   */
-  constructor(url: string) {
-    super();
-    let layout = this.layout = new PanelLayout();
-    let iframe = new IFrame();
-    this.url = iframe.url = url;
-    layout.addWidget(iframe);
-  }
-
-  /**
-   * The url of the widget.
-   */
-  readonly url: string;
-
-  /**
-   * Handle activate requests for the widget.
-   */
-  protected onActivateRequest(msg: Message): void {
-    this.node.tabIndex = -1;
-    this.node.focus();
-  }
-
-  /**
-   * Dispose of the IFrame when closing.
-   */
-  protected onCloseRequest(msg: Message): void {
-    this.dispose();
-  }
-}
-
 
 /**
  * Activate the help handler extension.
@@ -168,26 +126,27 @@ function activate(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette
   const namespace = 'help-doc';
   const baseUrl = PageConfig.getBaseUrl();
   const { commands, shell, info, serviceManager } = app;
-  const tracker = new InstanceTracker<HelpWidget>({ namespace });
+  const tracker = new InstanceTracker<MainAreaWidget>({ namespace });
 
   // Handle state restoration.
   restorer.restore(tracker, {
     command: CommandIDs.open,
-    args: widget => ({ url: widget.url, text: widget.title.label }),
-    name: widget => widget.url
+    args: widget => ({ url: widget.content.url, text: widget.content.title.label }),
+    name: widget => widget.content.url
   });
 
   /**
    * Create a new HelpWidget widget.
    */
-  function newClosableIFrame(url: string, text: string): HelpWidget {
-    let iframe = new HelpWidget(url);
-    iframe.addClass(HELP_CLASS);
-    iframe.title.label = text;
-    iframe.title.closable = true;
-    iframe.id = `${namespace}-${++counter}`;
-    tracker.add(iframe);
-    return iframe;
+  function newHelpWidget(url: string, text: string): MainAreaWidget {
+    let content = new IFrame();
+    content.url = url;
+    content.addClass(HELP_CLASS);
+    content.title.label = text;
+    content.id = `${namespace}-${++counter}`;
+    let widget = new MainAreaWidget({ content });
+    widget.addClass('jp-Help');
+    return widget;
   }
 
   // Populate the Help menu.
@@ -373,9 +332,9 @@ function activate(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette
         return;
       }
 
-      let iframe = newClosableIFrame(url, text);
-      shell.addToMainArea(iframe);
-      shell.activateById(iframe.id);
+      let widget = newHelpWidget(url, text);
+      tracker.add(widget);
+      shell.addToMainArea(widget);
     }
   });
 

+ 0 - 11
packages/help-extension/style/index.css

@@ -12,17 +12,6 @@
 }
 
 
-.jp-Help::before {
-    content: '';
-    display: block;
-    height: var(--jp-toolbar-micro-height);
-    background: var(--jp-toolbar-background);
-    border-bottom: 1px solid var(--jp-toolbar-border-color);
-    box-shadow: var(--jp-toolbar-box-shadow);
-    z-index: 1;
-}
-
-
 .jp-Help > iframe {
   border: none;
 }

+ 7 - 7
packages/inspector-extension/src/index.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/application';
 
 import {
-  ICommandPalette, InstanceTracker
+  ICommandPalette, InstanceTracker, MainAreaWidget
 } from '@jupyterlab/apputils';
 
 import {
@@ -50,7 +50,7 @@ const inspector: JupyterLabPlugin<IInspector> = {
     const command = CommandIDs.open;
     const label = 'Open Inspector';
     const namespace = 'inspector';
-    const tracker = new InstanceTracker<InspectorPanel>({ namespace });
+    const tracker = new InstanceTracker<MainAreaWidget<InspectorPanel>>({ namespace });
 
     /**
      * Create and track a new inspector.
@@ -60,7 +60,6 @@ const inspector: JupyterLabPlugin<IInspector> = {
 
       inspector.id = 'jp-inspector';
       inspector.title.label = 'Inspector';
-      inspector.title.closable = true;
       inspector.disposed.connect(() => {
         if (manager.inspector === inspector) {
           manager.inspector = null;
@@ -68,7 +67,8 @@ const inspector: JupyterLabPlugin<IInspector> = {
       });
 
       // Track the inspector.
-      tracker.add(inspector);
+      let widget = new MainAreaWidget({ content: inspector });
+      tracker.add(widget);
 
       // Add the default inspector child items.
       Private.defaultInspectorItems.forEach(item => { inspector.add(item); });
@@ -89,11 +89,11 @@ const inspector: JupyterLabPlugin<IInspector> = {
       execute: () => {
         if (!manager.inspector || manager.inspector.isDisposed) {
           manager.inspector = newInspectorPanel();
-          shell.addToMainArea(manager.inspector);
         }
-        if (manager.inspector.isAttached) {
-          shell.activateById(manager.inspector.id);
+        if (!manager.inspector.isAttached) {
+          shell.addToMainArea(manager.inspector.parent, { activate: false });
         }
+        shell.activateById(manager.inspector.parent.id);
       }
     });
     palette.addItem({ command, category });

+ 0 - 1
packages/inspector/package.json

@@ -37,7 +37,6 @@
     "@jupyterlab/services": "^1.1.4",
     "@phosphor/coreutils": "^1.3.0",
     "@phosphor/disposable": "^1.1.2",
-    "@phosphor/messaging": "^1.2.2",
     "@phosphor/signaling": "^1.2.2",
     "@phosphor/widgets": "^1.5.0"
   },

+ 0 - 20
packages/inspector/src/inspector.ts

@@ -13,10 +13,6 @@ import {
   DisposableDelegate, IDisposable
 } from '@phosphor/disposable';
 
-import {
-  Message
-} from '@phosphor/messaging';
-
 import {
   ISignal
 } from '@phosphor/signaling';
@@ -275,22 +271,6 @@ class InspectorPanel extends TabPanel implements IInspector {
     super.dispose();
   }
 
-  /**
-   * Handle `'activate-request'` messages.
-   */
-  protected onActivateRequest(msg: Message): void {
-    this.node.tabIndex = -1;
-    this.node.focus();
-  }
-
-  /**
-   * Handle `'close-request'` messages.
-   */
-  protected onCloseRequest(msg: Message): void {
-    super.onCloseRequest(msg);
-    this.dispose();
-  }
-
   /**
    * Handle inspector update signals.
    */

+ 8 - 10
packages/launcher-extension/src/index.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/application';
 
 import {
-  ICommandPalette
+  ICommandPalette, MainAreaWidget
 } from '@jupyterlab/apputils';
 
 import {
@@ -69,7 +69,6 @@ function activate(app: JupyterLab, palette: ICommandPalette): ILauncher {
       const id = `launcher-${Private.id++}`;
       const callback = (item: Widget) => {
         shell.addToMainArea(item, { ref: id });
-        shell.activateById(item.id);
       };
       const launcher = new Launcher({ cwd, callback });
 
@@ -78,20 +77,19 @@ function activate(app: JupyterLab, palette: ICommandPalette): ILauncher {
       launcher.title.label = 'Launcher';
       launcher.title.iconClass = 'jp-LauncherIcon';
 
+      let main = new MainAreaWidget({ content: launcher });
+
       // If there are any other widgets open, remove the launcher close icon.
-      launcher.title.closable = !!toArray(shell.widgets('main')).length;
+      main.title.closable = !!toArray(shell.widgets('main')).length;
 
-      shell.addToMainArea(launcher);
-      if (args['activate'] !== false) {
-        shell.activateById(launcher.id);
-      }
+      shell.addToMainArea(main, { activate: args['activate'] as boolean });
 
       shell.layoutModified.connect(() => {
         // If there is only a launcher open, remove the close icon.
-        launcher.title.closable = toArray(shell.widgets('main')).length > 1;
-      }, launcher);
+        main.title.closable = toArray(shell.widgets('main')).length > 1;
+      }, main);
 
-      return launcher;
+      return main;
     }
   });
 

+ 0 - 1
packages/launcher/package.json

@@ -34,7 +34,6 @@
     "@phosphor/algorithm": "^1.1.2",
     "@phosphor/coreutils": "^1.3.0",
     "@phosphor/disposable": "^1.1.2",
-    "@phosphor/messaging": "^1.2.2",
     "@phosphor/properties": "^1.1.2",
     "@phosphor/widgets": "^1.5.0",
     "react": "~16.0.0"

+ 0 - 12
packages/launcher/src/index.tsx

@@ -17,10 +17,6 @@ import {
   DisposableDelegate, IDisposable
 } from '@phosphor/disposable';
 
-import {
-  Message
-} from '@phosphor/messaging';
-
 import {
   AttachedProperty
 } from '@phosphor/properties';
@@ -257,14 +253,6 @@ class Launcher extends VDomRenderer<LauncherModel> {
     this._pending = value;
   }
 
-  /**
-   * Handle `'activate-request'` messages.
-   */
-  protected onActivateRequest(msg: Message): void {
-    this.node.tabIndex = -1;
-    this.node.focus();
-  }
-
   /**
    * Render the launcher to virtual DOM nodes.
    */

+ 0 - 12
packages/launcher/style/index.css

@@ -34,18 +34,6 @@
 }
 
 
-.jp-Launcher::before {
-  content: '';
-  display: block;
-  height: var(--jp-toolbar-micro-height);
-  width: 100%;
-  background: var(--jp-toolbar-background);
-  border-bottom: 1px solid var(--jp-toolbar-border-color);
-  box-shadow: var(--jp-toolbar-box-shadow);
-  z-index: 1;
-}
-
-
 .jp-Launcher-body {
   width: 100%;
   height: 100%;

+ 3 - 10
packages/notebook-extension/src/index.ts

@@ -56,7 +56,7 @@ import {
 } from '@phosphor/messaging';
 
 import {
-  Menu, PanelLayout, Widget
+  Menu, Widget
 } from '@phosphor/widgets';
 
 
@@ -1276,21 +1276,14 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Noteboo
       // Clone the OutputArea
       const current = getCurrent({ ...args, activate: false });
       const nb = current.notebook;
-      const outputAreaView = (nb.activeCell as CodeCell).cloneOutputArea();
-      // Create an empty toolbar
-      const toolbar = new Widget();
-      toolbar.addClass('jp-Toolbar');
-      toolbar.addClass('jp-LinkedOutputView-toolbar');
+      const content = (nb.activeCell as CodeCell).cloneOutputArea();
       // Create a MainAreaWidget
-      const layout = new PanelLayout();
-      const widget = new MainAreaWidget({ layout });
+      const widget = new MainAreaWidget({ content });
       widget.id = `LinkedOutputView-${uuid()}`;
       widget.title.label = 'Output View';
       widget.title.icon = NOTEBOOK_ICON_CLASS;
       widget.title.caption = current.title.label ? `For Notebook: ${current.title.label}` : 'For Notebook:';
       widget.addClass('jp-LinkedOutputView');
-      layout.addWidget(toolbar);
-      layout.addWidget(outputAreaView);
       current.context.addSibling(
         widget, { ref: current.id, mode: 'split-bottom' }
       );

+ 12 - 12
packages/settingeditor-extension/src/index.ts

@@ -8,7 +8,7 @@ import {
 } from '@jupyterlab/application';
 
 import {
-  ICommandPalette, InstanceTracker
+  ICommandPalette, InstanceTracker, MainAreaWidget
 } from '@jupyterlab/apputils';
 
 import {
@@ -73,7 +73,7 @@ function activate(app: JupyterLab, restorer: ILayoutRestorer, registry: ISetting
   const namespace = 'setting-editor';
   const factoryService = editorServices.factoryService;
   const editorFactory = factoryService.newInlineEditor;
-  const tracker = new InstanceTracker<SettingEditor>({ namespace });
+  const tracker = new InstanceTracker<MainAreaWidget<SettingEditor>>({ namespace });
   let editor: SettingEditor;
 
   // Handle state restoration.
@@ -84,10 +84,10 @@ function activate(app: JupyterLab, restorer: ILayoutRestorer, registry: ISetting
   });
 
   commands.addCommand(CommandIDs.debug, {
-    execute: () => { tracker.currentWidget.toggleDebug(); },
+    execute: () => { tracker.currentWidget.content.toggleDebug(); },
     iconClass: 'jp-MaterialIcon jp-BugIcon',
     label: 'Debug User Settings In Inspector',
-    isToggled: () => tracker.currentWidget.isDebugVisible
+    isToggled: () => tracker.currentWidget.content.isDebugVisible
   });
 
   commands.addCommand(CommandIDs.open, {
@@ -117,30 +117,30 @@ function activate(app: JupyterLab, restorer: ILayoutRestorer, registry: ISetting
         args.forEach(id => { commands.notifyCommandChanged(id); });
       });
 
-      tracker.add(editor);
       editor.id = namespace;
       editor.title.label = 'Settings';
       editor.title.iconClass = 'jp-SettingsIcon';
-      editor.title.closable = true;
-      shell.addToMainArea(editor);
-      shell.activateById(editor.id);
+
+      let main = new MainAreaWidget({ content: editor });
+      tracker.add(main);
+      shell.addToMainArea(main);
     },
     label: 'Advanced Settings Editor'
   });
   palette.addItem({ category: 'Settings', command: CommandIDs.open });
 
   commands.addCommand(CommandIDs.revert, {
-    execute: () => { tracker.currentWidget.revert(); },
+    execute: () => { tracker.currentWidget.content.revert(); },
     iconClass: 'jp-MaterialIcon jp-RefreshIcon',
     label: 'Revert User Settings',
-    isEnabled: () => tracker.currentWidget.canRevertRaw
+    isEnabled: () => tracker.currentWidget.content.canRevertRaw
   });
 
   commands.addCommand(CommandIDs.save, {
-    execute: () => tracker.currentWidget.save(),
+    execute: () => tracker.currentWidget.content.save(),
     iconClass: 'jp-MaterialIcon jp-SaveIcon',
     label: 'Save User Settings',
-    isEnabled: () => tracker.currentWidget.canSaveRaw
+    isEnabled: () => tracker.currentWidget.content.canSaveRaw
   });
 
   return tracker;

+ 3 - 3
packages/settingeditor/src/index.ts

@@ -2,7 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  IInstanceTracker
+  IInstanceTracker, MainAreaWidget
 } from '@jupyterlab/apputils';
 
 import {
@@ -23,7 +23,7 @@ export * from './settingeditor';
  * The setting editor tracker token.
  */
 export
-const ISettingEditorTracker = new Token<SettingEditor>('@jupyterlab/settingeditor:ISettingEditorTracker');
+const ISettingEditorTracker = new Token<ISettingEditorTracker>('@jupyterlab/settingeditor:ISettingEditorTracker');
 /* tslint:enable */
 
 
@@ -31,4 +31,4 @@ const ISettingEditorTracker = new Token<SettingEditor>('@jupyterlab/settingedito
  * A class that tracks the setting editor.
  */
 export
-interface ISettingEditorTracker extends IInstanceTracker<SettingEditor> {}
+interface ISettingEditorTracker extends IInstanceTracker<MainAreaWidget<SettingEditor>> {}

+ 0 - 8
packages/settingeditor/src/settingeditor.ts

@@ -246,14 +246,6 @@ class SettingEditor extends Widget {
     this._editor.raw.toggleDebug();
   }
 
-  /**
-   * Handle `'activate-request'` messages.
-   */
-  protected onActivateRequest(msg: Message): void {
-    this.node.tabIndex = -1;
-    this.node.focus();
-  }
-
   /**
    * Handle `'after-attach'` messages.
    */

+ 1 - 13
packages/settingeditor/style/settingeditor.css

@@ -19,7 +19,6 @@
   min-width: 360px;
   min-height: 240px;
   background-color: var(--jp-layout-color0);
-  border-top: var(--jp-border-width) solid var(--jp-border-color1);
   margin-top: -1px;
   outline: none;
   /* This is needed so that all font sizing of children done in ems is
@@ -28,20 +27,9 @@
 }
 
 
-#setting-editor::before {
-  content: '';
-  display: block;
-  height: var(--jp-toolbar-micro-height);
-  background: var(--jp-toolbar-background);
-  border-bottom: 1px solid var(--jp-toolbar-border-color);
-  box-shadow: var(--jp-toolbar-box-shadow);
-  z-index: 10;
-}
-
-
 #setting-editor > .p-Widget {
   position: absolute;
-  top: var(--jp-toolbar-micro-height);
+  top: 0;
   bottom: 0;
   left: 0;
   right: 0;

+ 22 - 17
packages/terminal-extension/src/index.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/application';
 
 import {
-  ICommandPalette, InstanceTracker
+  ICommandPalette, InstanceTracker, MainAreaWidget
 } from '@jupyterlab/apputils';
 
 import {
@@ -82,7 +82,7 @@ function activate(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette
   const { commands, serviceManager } = app;
   const category = 'Terminal';
   const namespace = 'terminal';
-  const tracker = new InstanceTracker<Terminal>({ namespace });
+  const tracker = new InstanceTracker<MainAreaWidget<Terminal>>({ namespace });
 
   // Bail if there are no terminals available.
   if (!serviceManager.terminals.isAvailable()) {
@@ -93,8 +93,8 @@ function activate(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette
   // Handle state restoration.
   restorer.restore(tracker, {
     command: CommandIDs.createNew,
-    args: widget => ({ name: widget.session.name }),
-    name: widget => widget.session && widget.session.name
+    args: widget => ({ name: widget.content.session.name }),
+    name: widget => widget.content.session && widget.content.session.name
   });
 
   addCommands(app, serviceManager, tracker);
@@ -142,7 +142,7 @@ function activate(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette
  * Add the commands for the terminal.
  */
 export
-function addCommands(app: JupyterLab, services: ServiceManager, tracker: InstanceTracker<Terminal>) {
+function addCommands(app: JupyterLab, services: ServiceManager, tracker: InstanceTracker<MainAreaWidget<Terminal>>) {
   let { commands, shell } = app;
 
   /**
@@ -164,17 +164,17 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Instanc
       const promise = name ? services.terminals.connectTo(name)
         : services.terminals.startNew();
 
-      term.title.closable = true;
       term.title.icon = TERMINAL_ICON_CLASS;
       term.title.label = '...';
-      shell.addToMainArea(term);
+      let main = new MainAreaWidget({ content: term });
+      shell.addToMainArea(main);
 
       return promise.then(session => {
         term.session = session;
-        tracker.add(term);
-        shell.activateById(term.id);
+        tracker.add(main);
+        shell.activateById(main.id);
 
-        return term;
+        return main;
       }).catch(() => { term.dispose(); });
     }
   });
@@ -184,7 +184,8 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Instanc
       const name = args['name'] as string;
       // Check for a running terminal with the given name.
       const widget = tracker.find(value => {
-        return value.session && value.session.name === name || false;
+        let content = value.content;
+        return content.session && content.session.name === name || false;
       });
       if (widget) {
         shell.activateById(widget.id);
@@ -205,9 +206,9 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Instanc
       }
       shell.activateById(current.id);
 
-      return current.refresh().then(() => {
+      return current.content.refresh().then(() => {
         if (current) {
-          current.activate();
+          current.content.activate();
         }
       });
     },
@@ -220,7 +221,9 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Instanc
       let options = Terminal.defaultOptions;
       if (options.fontSize < 72) {
         options.fontSize++;
-        tracker.forEach(widget => { widget.fontSize = options.fontSize; });
+        tracker.forEach(widget => {
+          widget.content.fontSize = options.fontSize;
+        });
       }
     },
     isEnabled
@@ -232,7 +235,9 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Instanc
       let options = Terminal.defaultOptions;
       if (options.fontSize > 9) {
         options.fontSize--;
-        tracker.forEach(widget => { widget.fontSize = options.fontSize; });
+        tracker.forEach(widget => {
+          widget.content.fontSize = options.fontSize;
+        });
       }
     },
     isEnabled
@@ -246,8 +251,8 @@ function addCommands(app: JupyterLab, services: ServiceManager, tracker: Instanc
     execute: () => {
       terminalTheme = terminalTheme === 'dark' ? 'light' : 'dark';
       tracker.forEach(widget => {
-        if (widget.theme !== terminalTheme) {
-          widget.theme = terminalTheme;
+        if (widget.content.theme !== terminalTheme) {
+          widget.content.theme = terminalTheme;
         }
       });
     },

+ 2 - 2
packages/terminal/src/index.ts

@@ -2,7 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  IInstanceTracker
+  IInstanceTracker, MainAreaWidget
 } from '@jupyterlab/apputils';
 
 import {
@@ -21,7 +21,7 @@ export * from './widget';
  * A class that tracks editor widgets.
  */
 export
-interface ITerminalTracker extends IInstanceTracker<Terminal> {}
+interface ITerminalTracker extends IInstanceTracker<MainAreaWidget<Terminal>> {}
 
 
 /* tslint:disable */

+ 0 - 8
packages/terminal/src/widget.ts

@@ -199,14 +199,6 @@ class Terminal extends Widget {
     this.update();
   }
 
-  /**
-   * Dispose of the terminal when closing.
-   */
-  protected onCloseRequest(msg: Message): void {
-    super.onCloseRequest(msg);
-    this.dispose();
-  }
-
   /**
    * On resize, use the computed row and column sizes to resize the terminal.
    */

+ 44 - 15
tests/test-apputils/src/mainareawidget.spec.ts

@@ -6,7 +6,7 @@ import {
 } from 'chai';
 
 import {
-  MainAreaWidget
+  MainAreaWidget, Toolbar
 } from '@jupyterlab/apputils';
 
 import {
@@ -14,7 +14,7 @@ import {
 } from '@phosphor/messaging';
 
 import {
-  PanelLayout, Widget
+  Widget
 } from '@phosphor/widgets';
 
 
@@ -25,21 +25,19 @@ describe('@jupyterlab/apputils', () => {
     describe('#constructor()', () => {
 
       it('should create a new main area widget', () => {
-        const widget = new MainAreaWidget();
+        const content = new Widget();
+        const widget = new MainAreaWidget({ content });
         expect(widget).to.be.an.instanceof(MainAreaWidget);
         expect(widget.hasClass('jp-MainAreaWidget')).to.equal(true);
-        expect(widget.node.tabIndex).to.equal(-1);
-        expect(widget.layout).to.be.an.instanceof(PanelLayout);
+        expect(widget.content.node.tabIndex).to.equal(-1);
+        expect(widget.title.closable).to.equal(true);
       });
 
-      it('should allow node and layout options', () => {
-        const layout = new PanelLayout();
-        const node = document.createElement('div');
-        const widget = new MainAreaWidget({node, layout});
+      it('should allow toolbar options', () => {
+        const content = new Widget();
+        const toolbar = new Toolbar();
+        const widget = new MainAreaWidget({ content, toolbar });
         expect(widget.hasClass('jp-MainAreaWidget')).to.equal(true);
-        expect(widget.node).to.equal(node);
-        expect(widget.node.tabIndex).to.equal(-1);
-        expect(widget.layout).to.equal(layout);
       });
 
     });
@@ -47,10 +45,11 @@ describe('@jupyterlab/apputils', () => {
     describe('#onActivateRequest()', () => {
 
       it('should focus on activation', () => {
-        const widget = new MainAreaWidget();
+        const content = new Widget();
+        const widget = new MainAreaWidget({ content });
         Widget.attach(widget, document.body);
         MessageLoop.sendMessage(widget, Widget.Msg.ActivateRequest);
-        expect(document.activeElement).to.equal(widget.node);
+        expect(document.activeElement).to.equal(widget.content.node);
       });
 
     });
@@ -58,7 +57,8 @@ describe('@jupyterlab/apputils', () => {
     describe('#onCloseRequest()', () => {
 
       it('should dispose on close', () => {
-        const widget = new MainAreaWidget();
+        const content = new Widget();
+        const widget = new MainAreaWidget({ content });
         Widget.attach(widget, document.body);
         MessageLoop.sendMessage(widget, Widget.Msg.CloseRequest);
         expect(widget.isDisposed).to.equal(true);
@@ -66,6 +66,35 @@ describe('@jupyterlab/apputils', () => {
 
     });
 
+    context('title', () => {
+
+      it('should proxy from content to main', () => {
+        const content = new Widget();
+        const widget = new MainAreaWidget({ content });
+        content.title.label = 'foo';
+        expect(widget.title.label).to.equal('foo');
+      });
+
+      it('should proxy from main to content', () => {
+        const content = new Widget();
+        const widget = new MainAreaWidget({ content });
+        widget.title.label = 'foo';
+        expect(content.title.label).to.equal('foo');
+      });
+
+    });
+
+    context('dispose', () => {
+
+      it('should dispose of main', () => {
+        const content = new Widget();
+        const widget = new MainAreaWidget({ content });
+        content.dispose();
+        expect(widget.isDisposed).to.equal(true);
+      });
+
+    });
+
   });
 
 });

+ 0 - 15
tests/test-terminal/src/terminal.spec.ts

@@ -38,11 +38,6 @@ class LogTerminal extends Terminal {
     this.methods.push('onAfterShow');
   }
 
-  protected onCloseRequest(msg: Message): void {
-    super.onCloseRequest(msg);
-    this.methods.push('onCloseRequest');
-  }
-
   protected onResize(msg: Widget.ResizeMessage): void {
     super.onResize(msg);
     this.methods.push('onResize');
@@ -210,16 +205,6 @@ describe('terminal/index', () => {
 
     });
 
-    describe('#onCloseRequest', () => {
-
-      it('should dispose of the terminal after closing', () => {
-        widget.close();
-        expect(widget.methods).to.contain('onCloseRequest');
-        expect(widget.isDisposed).to.be(true);
-      });
-
-    });
-
     describe('#onResize()', () => {
 
       it('should trigger an update request', (done) => {