3 Copyright (c) 2014 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
8 <link rel="import" href="/base/utils.html">
9 <link rel="import" href="/base/properties.html">
10 <link rel="import" href="/base/events.html">
11 <link rel="import" href="/base/ui.html">
13 <template id="overlay-template">
21 font-family: sans-serif;
22 -webkit-justify-content: center;
23 background: rgba(0, 0, 0, 0.8);
24 display: -webkit-flex;
34 overlay-vertical-centering-container {
35 -webkit-justify-content: center;
36 -webkit-flex-direction: column;
37 display: -webkit-flex;
41 background: rgb(255, 255, 255);
42 border: 1px solid #ccc;
44 display: -webkit-flex;
45 -webkit-flex-direction: column;
48 -webkit-align-items: center;
49 -webkit-flex-direction: row;
50 border-bottom: 1px solid #ccc;
51 background-color: #ddd;
52 display: -webkit-flex;
54 -webkit-flex: 0 0 auto;
60 -webkit-flex: 1 1 auto;
63 -webkit-align-self: flex-end;
64 border: 1px solid #eee;
65 background-color: #999;
73 background-color: #ddd;
78 display: -webkit-flex;
79 -webkit-flex: 1 1 auto;
80 -webkit-flex-direction: column;
86 -webkit-align-items: baseline;
87 border-top: 1px solid #ccc;
88 display: -webkit-flex;
89 -webkit-flex: 0 0 auto;
90 -webkit-flex-direction: row-reverse;
96 <overlay-vertical-centering-container>
100 <close-button>✕</close-button>
105 <button-bar></button-bar>
107 </overlay-vertical-centering-container>
115 * @fileoverview Implements an element that is hidden by default, but
116 * when shown, dims and (attempts to) disable the main document.
118 * You can turn any div into an overlay. Note that while an
119 * overlay element is shown, its parent is changed. Hiding the overlay
120 * restores its original parentage.
123 tv.exportTo('tv.ui', function() {
124 var THIS_DOC = document.currentScript.ownerDocument;
127 * Creates a new overlay element. It will not be visible until shown.
129 * @extends {HTMLDivElement}
131 var Overlay = tv.ui.define('overlay');
133 Overlay.prototype = {
134 __proto__: HTMLDivElement.prototype,
137 * Initializes the overlay element.
139 decorate: function() {
140 this.classList.add('overlay');
142 this.parentEl_ = this.ownerDocument.body;
144 this.visible_ = false;
145 this.userCanClose_ = true;
147 this.onKeyDown_ = this.onKeyDown_.bind(this);
148 this.onClick_ = this.onClick_.bind(this);
149 this.onFocusIn_ = this.onFocusIn_.bind(this);
150 this.onDocumentClick_ = this.onDocumentClick_.bind(this);
151 this.onClose_ = this.onClose_.bind(this);
153 this.addEventListener('visibleChange',
154 tv.ui.Overlay.prototype.onVisibleChange_.bind(this), true);
156 // Setup the shadow root
157 var createShadowRoot = this.createShadowRoot ||
158 this.webkitCreateShadowRoot;
159 this.shadow_ = createShadowRoot.call(this);
160 this.shadow_.appendChild(tv.instantiateTemplate('#overlay-template',
163 this.closeBtn_ = this.shadow_.querySelector('close-button');
164 this.closeBtn_.addEventListener('click', this.onClose_);
167 .querySelector('overlay-frame')
168 .addEventListener('click', this.onClick_);
170 this.observer_ = new WebKitMutationObserver(
171 this.didButtonBarMutate_.bind(this));
172 this.observer_.observe(this.shadow_.querySelector('button-bar'),
173 { childList: true });
175 // title is a variable on regular HTMLElements. However, we want to
176 // use it for something more useful.
177 Object.defineProperty(
180 return this.shadow_.querySelector('title').textContent;
182 set: function(title) {
183 this.shadow_.querySelector('title').textContent = title;
188 set userCanClose(userCanClose) {
189 this.userCanClose_ = userCanClose;
190 this.closeBtn_.style.display =
191 userCanClose ? 'block' : 'none';
195 return this.shadow_.querySelector('button-bar');
199 return this.visible_;
202 set visible(newValue) {
203 if (this.visible_ === newValue)
206 tv.setPropertyAndDispatchChange(this, 'visible', newValue);
209 onVisibleChange_: function() {
210 this.visible_ ? this.show_() : this.hide_();
214 this.parentEl_.appendChild(this);
216 if (this.userCanClose_) {
217 document.addEventListener('keydown', this.onKeyDown_);
218 document.addEventListener('click', this.onDocumentClick_);
221 this.parentEl_.addEventListener('focusin', this.onFocusIn_);
224 // Focus the first thing we find that makes sense. (Skip the close button
225 // as it doesn't make sense as the first thing to focus.)
226 var focusEl = undefined;
227 var elList = this.querySelectorAll('button, input, list, select, a');
228 if (elList.length > 0) {
229 if (elList[0] === this.closeBtn_) {
230 if (elList.length > 1)
236 if (focusEl === undefined)
242 this.parentEl_.removeChild(this);
244 this.parentEl_.removeEventListener('focusin', this.onFocusIn_);
247 this.closeBtn_.removeEventListener(this.onClose_);
249 document.removeEventListener('keydown', this.onKeyDown_);
250 document.removeEventListener('click', this.onDocumentClick_);
253 onClose_: function(e) {
254 this.visible = false;
255 if (e.type != 'keydown')
258 tv.dispatchSimpleEvent(this, 'closeclick');
261 onFocusIn_: function(e) {
262 if (e.target === this)
265 window.setTimeout(function() { this.focus(); }, 0);
270 didButtonBarMutate_: function(e) {
271 var hasButtons = this.buttons.children.length > 0;
273 this.shadow_.querySelector('button-bar').style.display = undefined;
275 this.shadow_.querySelector('button-bar').style.display = 'none';
278 onKeyDown_: function(e) {
279 // Disallow shift-tab back to another element.
280 if (e.keyCode === 9 && // tab
287 if (e.keyCode !== 27) // escape
293 onClick_: function(e) {
297 onDocumentClick_: function(e) {
298 if (!this.userCanClose_)
305 Overlay.showError = function(msg, opt_err) {
306 var o = new Overlay();
310 var e = tv.normalizeException(opt_err);
312 var stackDiv = document.createElement('pre');
313 stackDiv.textContent = e.stack;
314 stackDiv.style.paddingLeft = '8px';
315 stackDiv.style.margin = 0;
316 o.appendChild(stackDiv);
318 var b = document.createElement('button');
319 b.textContent = 'OK';
320 b.addEventListener('click', function() {
323 o.buttons.appendChild(b);