blob: 350c91bd54d6a84c405df0f3c19a8d4ec7495132 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/base/utils.html">
<link rel="import" href="/base/properties.html">
<link rel="import" href="/base/events.html">
<link rel="import" href="/base/ui.html">
<template id="overlay-template">
<style>
overlay-mask {
left: 0;
padding: 8px;
position: absolute;
top: 0;
z-index: 1000;
font-family: sans-serif;
-webkit-justify-content: center;
background: rgba(0, 0, 0, 0.8);
display: -webkit-flex;
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
}
overlay-mask:focus {
outline: none;
}
overlay-vertical-centering-container {
-webkit-justify-content: center;
-webkit-flex-direction: column;
display: -webkit-flex;
}
overlay-frame {
z-index: 1100;
background: rgb(255, 255, 255);
border: 1px solid #ccc;
margin: 75px;
display: -webkit-flex;
-webkit-flex-direction: column;
min-height: 0;
}
title-bar {
-webkit-align-items: center;
-webkit-flex-direction: row;
border-bottom: 1px solid #ccc;
background-color: #ddd;
display: -webkit-flex;
padding: 5px;
-webkit-flex: 0 0 auto;
}
title {
display: inline;
font-weight: bold;
-webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
}
close-button {
-webkit-align-self: flex-end;
border: 1px solid #eee;
background-color: #999;
font-size: 10pt;
font-weight: bold;
padding: 2px;
text-align: center;
width: 16px;
}
close-button:hover {
background-color: #ddd;
border-color: black;
cursor: pointer;
}
overlay-content {
display: -webkit-flex;
-webkit-flex: 1 1 auto;
-webkit-flex-direction: column;
overflow-y: auto;
padding: 10px;
min-width: 300px;
min-height: 0;
}
button-bar {
-webkit-align-items: baseline;
border-top: 1px solid #ccc;
display: -webkit-flex;
-webkit-flex: 0 0 auto;
-webkit-flex-direction: row-reverse;
padding: 4px;
}
</style>
<overlay-mask>
<overlay-vertical-centering-container>
<overlay-frame>
<title-bar>
<title></title>
<close-button>&#x2715</close-button>
</title-bar>
<overlay-content>
<content></content>
</overlay-content>
<button-bar></button-bar>
</overlay-frame>
</overlay-vertical-centering-container>
</overlay-mask>
</template>
<script>
'use strict';
/**
* @fileoverview Implements an element that is hidden by default, but
* when shown, dims and (attempts to) disable the main document.
*
* You can turn any div into an overlay. Note that while an
* overlay element is shown, its parent is changed. Hiding the overlay
* restores its original parentage.
*
*/
tr.exportTo('tr.b.ui', function() {
var THIS_DOC = document.currentScript.ownerDocument;
/**
* Creates a new overlay element. It will not be visible until shown.
* @constructor
* @extends {HTMLDivElement}
*/
var Overlay = tr.b.ui.define('overlay');
Overlay.prototype = {
__proto__: HTMLDivElement.prototype,
/**
* Initializes the overlay element.
*/
decorate: function() {
this.classList.add('overlay');
this.parentEl_ = this.ownerDocument.body;
this.visible_ = false;
this.userCanClose_ = true;
this.onKeyDown_ = this.onKeyDown_.bind(this);
this.onClick_ = this.onClick_.bind(this);
this.onFocusIn_ = this.onFocusIn_.bind(this);
this.onDocumentClick_ = this.onDocumentClick_.bind(this);
this.onClose_ = this.onClose_.bind(this);
this.addEventListener('visibleChange',
tr.b.ui.Overlay.prototype.onVisibleChange_.bind(this), true);
// Setup the shadow root
var createShadowRoot = this.createShadowRoot ||
this.webkitCreateShadowRoot;
this.shadow_ = createShadowRoot.call(this);
this.shadow_.appendChild(tr.b.instantiateTemplate('#overlay-template',
THIS_DOC));
this.closeBtn_ = this.shadow_.querySelector('close-button');
this.closeBtn_.addEventListener('click', this.onClose_);
this.shadow_
.querySelector('overlay-frame')
.addEventListener('click', this.onClick_);
this.observer_ = new WebKitMutationObserver(
this.didButtonBarMutate_.bind(this));
this.observer_.observe(this.shadow_.querySelector('button-bar'),
{ childList: true });
// title is a variable on regular HTMLElements. However, we want to
// use it for something more useful.
Object.defineProperty(
this, 'title', {
get: function() {
return this.shadow_.querySelector('title').textContent;
},
set: function(title) {
this.shadow_.querySelector('title').textContent = title;
}
});
},
set userCanClose(userCanClose) {
this.userCanClose_ = userCanClose;
this.closeBtn_.style.display =
userCanClose ? 'block' : 'none';
},
get buttons() {
return this.shadow_.querySelector('button-bar');
},
get visible() {
return this.visible_;
},
set visible(newValue) {
if (this.visible_ === newValue)
return;
tr.b.setPropertyAndDispatchChange(this, 'visible', newValue);
},
onVisibleChange_: function() {
this.visible_ ? this.show_() : this.hide_();
},
show_: function() {
this.parentEl_.appendChild(this);
if (this.userCanClose_) {
this.addEventListener('keydown', this.onKeyDown_.bind(this));
this.addEventListener('click', this.onDocumentClick_.bind(this));
}
this.parentEl_.addEventListener('focusin', this.onFocusIn_);
this.tabIndex = 0;
// Focus the first thing we find that makes sense. (Skip the close button
// as it doesn't make sense as the first thing to focus.)
var focusEl = undefined;
var elList = this.querySelectorAll('button, input, list, select, a');
if (elList.length > 0) {
if (elList[0] === this.closeBtn_) {
if (elList.length > 1)
focusEl = elList[1];
} else {
focusEl = elList[0];
}
}
if (focusEl === undefined)
focusEl = this;
focusEl.focus();
},
hide_: function() {
this.parentEl_.removeChild(this);
this.parentEl_.removeEventListener('focusin', this.onFocusIn_);
if (this.closeBtn_)
this.closeBtn_.removeEventListener(this.onClose_);
document.removeEventListener('keydown', this.onKeyDown_);
document.removeEventListener('click', this.onDocumentClick_);
},
onClose_: function(e) {
this.visible = false;
if ((e.type != 'keydown') ||
(e.type === 'keydown' && e.keyCode === 27))
e.stopPropagation();
e.preventDefault();
tr.b.dispatchSimpleEvent(this, 'closeclick');
},
onFocusIn_: function(e) {
if (e.target === this)
return;
window.setTimeout(function() { this.focus(); }, 0);
e.preventDefault();
e.stopPropagation();
},
didButtonBarMutate_: function(e) {
var hasButtons = this.buttons.children.length > 0;
if (hasButtons)
this.shadow_.querySelector('button-bar').style.display = undefined;
else
this.shadow_.querySelector('button-bar').style.display = 'none';
},
onKeyDown_: function(e) {
// Disallow shift-tab back to another element.
if (e.keyCode === 9 && // tab
e.shiftKey &&
e.target === this) {
e.preventDefault();
return;
}
if (e.keyCode !== 27) // escape
return;
this.onClose_(e);
},
onClick_: function(e) {
e.stopPropagation();
},
onDocumentClick_: function(e) {
if (!this.userCanClose_)
return;
this.onClose_(e);
}
};
Overlay.showError = function(msg, opt_err) {
var o = new Overlay();
o.title = 'Error';
o.textContent = msg;
if (opt_err) {
var e = tr.b.normalizeException(opt_err);
var stackDiv = document.createElement('pre');
stackDiv.textContent = e.stack;
stackDiv.style.paddingLeft = '8px';
stackDiv.style.margin = 0;
o.appendChild(stackDiv);
}
var b = document.createElement('button');
b.textContent = 'OK';
b.addEventListener('click', function() {
o.visible = false;
});
o.buttons.appendChild(b);
o.visible = true;
return o;
}
return {
Overlay: Overlay
};
});
</script>