Index: third_party/polymer/components-chromium/core-overlay/core-overlay-extracted.js |
diff --git a/third_party/polymer/components-chromium/core-overlay/core-overlay-extracted.js b/third_party/polymer/components-chromium/core-overlay/core-overlay-extracted.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..589952944df063799f6dfd2902cdd58730875ac4 |
--- /dev/null |
+++ b/third_party/polymer/components-chromium/core-overlay/core-overlay-extracted.js |
@@ -0,0 +1,588 @@ |
+ |
+(function() { |
+ |
+ Polymer('core-overlay', { |
+ |
+ publish: { |
+ /** |
+ * The target element that will be shown when the overlay is |
+ * opened. If unspecified, the core-overlay itself is the target. |
+ * |
+ * @attribute target |
+ * @type Object |
+ * @default the overlay element |
+ */ |
+ target: null, |
+ |
+ |
+ /** |
+ * A `core-overlay`'s size is guaranteed to be |
+ * constrained to the window size. To achieve this, the sizingElement |
+ * is sized with a max-height/width. By default this element is the |
+ * target element, but it can be specifically set to a specific element |
+ * inside the target if that is more appropriate. This is useful, for |
+ * example, when a region inside the overlay should scroll if needed. |
+ * |
+ * @attribute sizingTarget |
+ * @type Object |
+ * @default the target element |
+ */ |
+ sizingTarget: null, |
+ |
+ /** |
+ * Set opened to true to show an overlay and to false to hide it. |
+ * A `core-overlay` may be made initially opened by setting its |
+ * `opened` attribute. |
+ * @attribute opened |
+ * @type boolean |
+ * @default false |
+ */ |
+ opened: false, |
+ |
+ /** |
+ * If true, the overlay has a backdrop darkening the rest of the screen. |
+ * The backdrop element is attached to the document body and may be styled |
+ * with the class `core-overlay-backdrop`. When opened the `core-opened` |
+ * class is applied. |
+ * |
+ * @attribute backdrop |
+ * @type boolean |
+ * @default false |
+ */ |
+ backdrop: false, |
+ |
+ /** |
+ * If true, the overlay is guaranteed to display above page content. |
+ * |
+ * @attribute layered |
+ * @type boolean |
+ * @default false |
+ */ |
+ layered: false, |
+ |
+ /** |
+ * By default an overlay will close automatically if the user |
+ * taps outside it or presses the escape key. Disable this |
+ * behavior by setting the `autoCloseDisabled` property to true. |
+ * @attribute autoCloseDisabled |
+ * @type boolean |
+ * @default false |
+ */ |
+ autoCloseDisabled: false, |
+ |
+ /** |
+ * This property specifies an attribute on elements that should |
+ * close the overlay on tap. Should not set `closeSelector` if this |
+ * is set. |
+ * |
+ * @attribute closeAttribute |
+ * @type string |
+ * @default "core-overlay-toggle" |
+ */ |
+ closeAttribute: 'core-overlay-toggle', |
+ |
+ /** |
+ * This property specifies a selector matching elements that should |
+ * close the overlay on tap. Should not set `closeAttribute` if this |
+ * is set. |
+ * |
+ * @attribute closeSelector |
+ * @type string |
+ * @default "" |
+ */ |
+ closeSelector: '', |
+ |
+ /** |
+ * A `core-overlay` target's size is constrained to the window size. |
+ * The `margin` property specifies a pixel amount around the overlay |
+ * that will be reserved. It's useful for ensuring that, for example, |
+ * a shadow displayed outside the target will always be visible. |
+ * |
+ * @attribute margin |
+ * @type number |
+ * @default 0 |
+ */ |
+ margin: 0, |
+ |
+ /** |
+ * The transition property specifies a string which identifies a |
+ * <a href="../core-transition/">`core-transition`</a> element that |
+ * will be used to help the overlay open and close. The default |
+ * `core-transition-fade` will cause the overlay to fade in and out. |
+ * |
+ * @attribute transition |
+ * @type string |
+ * @default 'core-transition-fade' |
+ */ |
+ transition: 'core-transition-fade' |
+ |
+ }, |
+ |
+ captureEventName: 'tap', |
+ targetListeners: { |
+ 'tap': 'tapHandler', |
+ 'keydown': 'keydownHandler', |
+ 'core-transitionend': 'transitionend' |
+ }, |
+ |
+ registerCallback: function(element) { |
+ this.layer = document.createElement('core-overlay-layer'); |
+ this.keyHelper = document.createElement('core-key-helper'); |
+ this.meta = document.createElement('core-transition'); |
+ this.scrim = document.createElement('div'); |
+ this.scrim.className = 'core-overlay-backdrop'; |
+ }, |
+ |
+ ready: function() { |
+ this.target = this.target || this; |
+ // flush to ensure styles are installed before paint |
+ Platform.flush(); |
+ }, |
+ |
+ /** |
+ * Toggle the opened state of the overlay. |
+ * @method toggle |
+ */ |
+ toggle: function() { |
+ this.opened = !this.opened; |
+ }, |
+ |
+ /** |
+ * Open the overlay. This is equivalent to setting the `opened` |
+ * property to true. |
+ * @method open |
+ */ |
+ open: function() { |
+ this.opened = true; |
+ }, |
+ |
+ /** |
+ * Close the overlay. This is equivalent to setting the `opened` |
+ * property to false. |
+ * @method close |
+ */ |
+ close: function() { |
+ this.opened = false; |
+ }, |
+ |
+ domReady: function() { |
+ this.ensureTargetSetup(); |
+ }, |
+ |
+ targetChanged: function(old) { |
+ if (this.target) { |
+ // really make sure tabIndex is set |
+ if (this.target.tabIndex < 0) { |
+ this.target.tabIndex = -1; |
+ } |
+ this.addElementListenerList(this.target, this.targetListeners); |
+ this.target.style.display = 'none'; |
+ } |
+ if (old) { |
+ this.removeElementListenerList(old, this.targetListeners); |
+ var transition = this.getTransition(); |
+ if (transition) { |
+ transition.teardown(old); |
+ } else { |
+ old.style.position = ''; |
+ old.style.outline = ''; |
+ } |
+ old.style.display = ''; |
+ } |
+ }, |
+ |
+ // NOTE: wait to call this until we're as sure as possible that target |
+ // is styled. |
+ ensureTargetSetup: function() { |
+ if (!this.target || this.target.__overlaySetup) { |
+ return; |
+ } |
+ this.target.__overlaySetup = true; |
+ this.target.style.display = ''; |
+ var transition = this.getTransition(); |
+ if (transition) { |
+ transition.setup(this.target); |
+ } |
+ var computed = getComputedStyle(this.target); |
+ this.targetStyle = { |
+ position: computed.position === 'static' ? 'fixed' : |
+ computed.position |
+ } |
+ if (!transition) { |
+ this.target.style.position = this.targetStyle.position; |
+ this.target.style.outline = 'none'; |
+ } |
+ this.target.style.display = 'none'; |
+ }, |
+ |
+ openedChanged: function() { |
+ this.transitioning = true; |
+ this.ensureTargetSetup(); |
+ this.prepareRenderOpened(); |
+ // continue styling after delay so display state can change |
+ // without aborting transitions |
+ // note: we wait a full frame so that transition changes executed |
+ // during measuring do not cause transition |
+ this.async(function() { |
+ this.target.style.display = ''; |
+ this.async('renderOpened'); |
+ }); |
+ this.fire('core-overlay-open', this.opened); |
+ }, |
+ |
+ // tasks which must occur before opening; e.g. making the element visible |
+ prepareRenderOpened: function() { |
+ if (this.opened) { |
+ addOverlay(this); |
+ } |
+ this.prepareBackdrop(); |
+ // async so we don't auto-close immediately via a click. |
+ this.async(function() { |
+ if (!this.autoCloseDisabled) { |
+ this.enableElementListener(this.opened, document, |
+ this.captureEventName, 'captureHandler', true); |
+ } |
+ }); |
+ this.enableElementListener(this.opened, window, 'resize', |
+ 'resizeHandler'); |
+ |
+ if (this.opened) { |
+ // TODO(sorvell): force SD Polyfill to render |
+ forcePolyfillRender(this.target); |
+ if (!this._shouldPosition) { |
+ this.target.style.position = 'absolute'; |
+ var computed = getComputedStyle(this.target); |
+ var t = (computed.top === 'auto' && computed.bottom === 'auto'); |
+ var l = (computed.left === 'auto' && computed.right === 'auto'); |
+ this.target.style.position = this.targetStyle.position; |
+ this._shouldPosition = {top: t, left: l}; |
+ } |
+ // if we are showing, then take care when measuring |
+ this.prepareMeasure(this.target); |
+ this.updateTargetDimensions(); |
+ this.finishMeasure(this.target); |
+ if (this.layered) { |
+ this.layer.addElement(this.target); |
+ this.layer.opened = this.opened; |
+ } |
+ } |
+ }, |
+ |
+ // tasks which cause the overlay to actually open; typically play an |
+ // animation |
+ renderOpened: function() { |
+ var transition = this.getTransition(); |
+ if (transition) { |
+ transition.go(this.target, {opened: this.opened}); |
+ } else { |
+ this.transitionend(); |
+ } |
+ this.renderBackdropOpened(); |
+ }, |
+ |
+ // finishing tasks; typically called via a transition |
+ transitionend: function(e) { |
+ // make sure this is our transition event. |
+ if (e && e.target !== this.target) { |
+ return; |
+ } |
+ this.transitioning = false; |
+ if (!this.opened) { |
+ this.resetTargetDimensions(); |
+ this.target.style.display = 'none'; |
+ this.completeBackdrop(); |
+ removeOverlay(this); |
+ if (this.layered) { |
+ if (!currentOverlay()) { |
+ this.layer.opened = this.opened; |
+ } |
+ this.layer.removeElement(this.target); |
+ } |
+ } |
+ this.applyFocus(); |
+ }, |
+ |
+ prepareBackdrop: function() { |
+ if (this.backdrop && this.opened) { |
+ if (!this.scrim.parentNode) { |
+ document.body.appendChild(this.scrim); |
+ this.scrim.style.zIndex = currentOverlayZ() - 1; |
+ } |
+ trackBackdrop(this); |
+ } |
+ }, |
+ |
+ renderBackdropOpened: function() { |
+ if (this.backdrop && getBackdrops().length < 2) { |
+ this.scrim.classList.toggle('core-opened', this.opened); |
+ } |
+ }, |
+ |
+ completeBackdrop: function() { |
+ if (this.backdrop) { |
+ trackBackdrop(this); |
+ if (getBackdrops().length === 0) { |
+ this.scrim.parentNode.removeChild(this.scrim); |
+ } |
+ } |
+ }, |
+ |
+ prepareMeasure: function(target) { |
+ target.style.transition = target.style.webkitTransition = 'none'; |
+ target.style.transform = target.style.webkitTransform = 'none'; |
+ target.style.display = ''; |
+ }, |
+ |
+ finishMeasure: function(target) { |
+ target.style.display = 'none'; |
+ target.style.transform = target.style.webkitTransform = ''; |
+ target.style.transition = target.style.webkitTransition = ''; |
+ }, |
+ |
+ getTransition: function() { |
+ return this.meta.byId(this.transition); |
+ }, |
+ |
+ getFocusNode: function() { |
+ return this.target.querySelector('[autofocus]') || this.target; |
+ }, |
+ |
+ applyFocus: function() { |
+ var focusNode = this.getFocusNode(); |
+ if (this.opened) { |
+ focusNode.focus(); |
+ } else { |
+ focusNode.blur(); |
+ if (currentOverlay() == this) { |
+ console.warn('Current core-overlay is attempting to focus itself as next! (bug)'); |
+ } else { |
+ focusOverlay(); |
+ } |
+ } |
+ }, |
+ |
+ updateTargetDimensions: function() { |
+ this.positionTarget(); |
+ this.sizeTarget(); |
+ // |
+ if (this.layered) { |
+ var rect = this.target.getBoundingClientRect(); |
+ this.target.style.top = rect.top + 'px'; |
+ this.target.style.left = rect.left + 'px'; |
+ this.target.style.right = this.target.style.bottom = 'auto'; |
+ } |
+ }, |
+ |
+ sizeTarget: function() { |
+ var sizer = this.sizingTarget || this.target; |
+ var rect = sizer.getBoundingClientRect(); |
+ var mt = rect.top === this.margin ? this.margin : this.margin * 2; |
+ var ml = rect.left === this.margin ? this.margin : this.margin * 2; |
+ var h = window.innerHeight - rect.top - mt; |
+ var w = window.innerWidth - rect.left - ml; |
+ sizer.style.maxHeight = h + 'px'; |
+ sizer.style.maxWidth = w + 'px'; |
+ sizer.style.boxSizing = 'border-box'; |
+ }, |
+ |
+ positionTarget: function() { |
+ // vertically and horizontally center if not positioned |
+ if (this._shouldPosition.top) { |
+ var t = Math.max((window.innerHeight - |
+ this.target.offsetHeight - this.margin*2) / 2, this.margin); |
+ this.target.style.top = t + 'px'; |
+ } |
+ if (this._shouldPosition.left) { |
+ var l = Math.max((window.innerWidth - |
+ this.target.offsetWidth - this.margin*2) / 2, this.margin); |
+ this.target.style.left = l + 'px'; |
+ } |
+ }, |
+ |
+ resetTargetDimensions: function() { |
+ this.target.style.top = this.target.style.left = ''; |
+ this.target.style.right = this.target.style.bottom = ''; |
+ this.target.style.width = this.target.style.height = ''; |
+ this._shouldPosition = null; |
+ }, |
+ |
+ tapHandler: function(e) { |
+ // closeSelector takes precedence since closeAttribute has a default non-null value. |
+ if (e.target && |
+ (this.closeSelector && e.target.matches(this.closeSelector)) || |
+ (this.closeAttribute && e.target.hasAttribute(this.closeAttribute))) { |
+ this.toggle(); |
+ } else { |
+ if (this.autoCloseJob) { |
+ this.autoCloseJob.stop(); |
+ this.autoCloseJob = null; |
+ } |
+ } |
+ }, |
+ |
+ // We use the traditional approach of capturing events on document |
+ // to to determine if the overlay needs to close. However, due to |
+ // ShadowDOM event retargeting, the event target is not useful. Instead |
+ // of using it, we attempt to close asynchronously and prevent the close |
+ // if a tap event is immediately heard on the target. |
+ // TODO(sorvell): This approach will not work with modal. For |
+ // this we need a scrim. |
+ captureHandler: function(e) { |
+ if (!this.autoCloseDisabled && (currentOverlay() == this)) { |
+ this.autoCloseJob = this.job(this.autoCloseJob, function() { |
+ this.close(); |
+ }); |
+ } |
+ }, |
+ |
+ keydownHandler: function(e) { |
+ if (!this.autoCloseDisabled && (e.keyCode == this.keyHelper.ESCAPE_KEY)) { |
+ this.close(); |
+ e.stopPropagation(); |
+ } |
+ }, |
+ |
+ /** |
+ * Extensions of core-overlay should implement the `resizeHandler` |
+ * method to adjust the size and position of the overlay when the |
+ * browser window resizes. |
+ * @method resizeHandler |
+ */ |
+ resizeHandler: function() { |
+ this.updateTargetDimensions(); |
+ }, |
+ |
+ // TODO(sorvell): these utility methods should not be here. |
+ addElementListenerList: function(node, events) { |
+ for (var i in events) { |
+ this.addElementListener(node, i, events[i]); |
+ } |
+ }, |
+ |
+ removeElementListenerList: function(node, events) { |
+ for (var i in events) { |
+ this.removeElementListener(node, i, events[i]); |
+ } |
+ }, |
+ |
+ enableElementListener: function(enable, node, event, methodName, capture) { |
+ if (enable) { |
+ this.addElementListener(node, event, methodName, capture); |
+ } else { |
+ this.removeElementListener(node, event, methodName, capture); |
+ } |
+ }, |
+ |
+ addElementListener: function(node, event, methodName, capture) { |
+ var fn = this._makeBoundListener(methodName); |
+ if (node && fn) { |
+ Polymer.addEventListener(node, event, fn, capture); |
+ } |
+ }, |
+ |
+ removeElementListener: function(node, event, methodName, capture) { |
+ var fn = this._makeBoundListener(methodName); |
+ if (node && fn) { |
+ Polymer.removeEventListener(node, event, fn, capture); |
+ } |
+ }, |
+ |
+ _makeBoundListener: function(methodName) { |
+ var self = this, method = this[methodName]; |
+ if (!method) { |
+ return; |
+ } |
+ var bound = '_bound' + methodName; |
+ if (!this[bound]) { |
+ this[bound] = function(e) { |
+ method.call(self, e); |
+ } |
+ } |
+ return this[bound]; |
+ }, |
+ }); |
+ |
+ function forcePolyfillRender(target) { |
+ if (window.ShadowDOMPolyfill) { |
+ target.offsetHeight; |
+ } |
+ } |
+ |
+ // TODO(sorvell): This should be an element with private state so it can |
+ // be independent of overlay. |
+ // track overlays for z-index and focus managemant |
+ var overlays = []; |
+ function addOverlay(overlay) { |
+ var z0 = currentOverlayZ(); |
+ overlays.push(overlay); |
+ var z1 = currentOverlayZ(); |
+ if (z1 <= z0) { |
+ applyOverlayZ(overlay, z0); |
+ } |
+ } |
+ |
+ function removeOverlay(overlay) { |
+ var i = overlays.indexOf(overlay); |
+ if (i >= 0) { |
+ overlays.splice(i, 1); |
+ setZ(overlay, ''); |
+ } |
+ } |
+ |
+ function applyOverlayZ(overlay, aboveZ) { |
+ setZ(overlay.target, aboveZ + 2); |
+ } |
+ |
+ function setZ(element, z) { |
+ element.style.zIndex = z; |
+ } |
+ |
+ function currentOverlay() { |
+ return overlays[overlays.length-1]; |
+ } |
+ |
+ var DEFAULT_Z = 10; |
+ |
+ function currentOverlayZ() { |
+ var z; |
+ var current = currentOverlay(); |
+ if (current) { |
+ var z1 = window.getComputedStyle(current.target).zIndex; |
+ if (!isNaN(z1)) { |
+ z = Number(z1); |
+ } |
+ } |
+ return z || DEFAULT_Z; |
+ } |
+ |
+ function focusOverlay() { |
+ var current = currentOverlay(); |
+ // We have to be careful to focus the next overlay _after_ any current |
+ // transitions are complete (due to the state being toggled prior to the |
+ // transition). Otherwise, we risk infinite recursion when a transitioning |
+ // (closed) overlay becomes the current overlay. |
+ // |
+ // NOTE: We make the assumption that any overlay that completes a transition |
+ // will call into focusOverlay to kick the process back off. Currently: |
+ // transitionend -> applyFocus -> focusOverlay. |
+ if (current && !current.transitioning) { |
+ current.applyFocus(); |
+ } |
+ } |
+ |
+ var backdrops = []; |
+ function trackBackdrop(element) { |
+ if (element.opened) { |
+ backdrops.push(element); |
+ } else { |
+ var i = backdrops.indexOf(element); |
+ if (i >= 0) { |
+ backdrops.splice(i, 1); |
+ } |
+ } |
+ } |
+ |
+ function getBackdrops() { |
+ return backdrops; |
+ } |
+})(); |