| <!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>✕</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. |
| * |
| */ |
| tv.exportTo('tv.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 = tv.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', |
| tv.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(tv.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; |
| |
| tv.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(); |
| tv.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 = tv.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> |