| <!-- |
| Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
| The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
| Code distributed by Google as part of the polymer project is also |
| subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
| --> |
| |
| <!-- |
| |
| `core-dropdown` is an element that is initially hidden and is positioned relatively to another |
| element, usually the element that triggers the dropdown. The dropdown and the triggering element |
| should be children of the same offsetParent, e.g. the same `<div>` with `position: relative`. |
| It can be used to implement dropdown menus, menu buttons, etc.. |
| |
| Example: |
| |
| <template is="auto-binding"> |
| <div relative> |
| <core-icon-button id="trigger" icon="menu"></core-icon-button> |
| <core-dropdown relatedTarget="{{$.trigger}}"> |
| <core-menu> |
| <core-item>Cut</core-item> |
| <core-item>Copy</core-item> |
| <core-item>Paste</core-item> |
| </core-menu> |
| </core-dropdown> |
| </div> |
| </template> |
| |
| Positioning |
| ----------- |
| |
| By default, the dropdown is absolutely positioned on top of the `relatedTarget` with the top and |
| left edges aligned. The `halign` and `valign` properties controls the various alignments. The size |
| of the dropdown is automatically restrained such that it is entirely visible on the screen. Use the |
| `margin` |
| |
| If you need more control over the dropdown's position, use CSS. The `halign` and `valign` properties are |
| ignored if the dropdown is positioned with CSS. |
| |
| Example: |
| |
| <style> |
| /* manually position the dropdown below the trigger */ |
| core-dropdown { |
| position: absolute; |
| top: 38px; |
| left: 0; |
| } |
| </style> |
| |
| <template is="auto-binding"> |
| <div relative> |
| <core-icon-button id="trigger" icon="menu"></core-icon-button> |
| <core-dropdown relatedTarget="{{$.trigger}}"> |
| <core-menu> |
| <core-item>Cut</core-item> |
| <core-item>Copy</core-item> |
| <core-item>Paste</core-item> |
| </core-menu> |
| </core-dropdown> |
| </div> |
| </template> |
| |
| The `layered` property |
| ---------------------- |
| |
| Sometimes you may need to render the dropdown in a separate layer. For example, |
| it may be nested inside an element that needs to be `overflow: hidden`, or |
| its parent may be overlapped by elements above it in stacking order. |
| |
| The `layered` property will place the dropdown in a separate layer to ensure |
| it appears on top of everything else. Note that this implies the dropdown will |
| not scroll with its container. |
| |
| @group Polymer Core Elements |
| @element core-dropdown |
| @extends core-overlay |
| @homepage github.io |
| --> |
| <link href="../polymer/polymer.html" rel="import"> |
| <link href="../core-overlay/core-overlay.html" rel="import"> |
| |
| <style shim-shadowdom> |
| html /deep/ core-dropdown { |
| position: absolute; |
| overflow: auto; |
| background-color: #fff; |
| } |
| </style> |
| |
| <polymer-element name="core-dropdown" extends="core-overlay"> |
| <script> |
| |
| (function() { |
| |
| function docElem(property) { |
| var t; |
| return ((t = document.documentElement) || (t = document.body.parentNode)) && (typeof t[property] === 'number') ? t : document.body; |
| } |
| |
| // View width and height excluding any visible scrollbars |
| // http://www.highdots.com/forums/javascript/faq-topic-how-do-i-296669.html |
| // 1) document.client[Width|Height] always reliable when available, including Safari2 |
| // 2) document.documentElement.client[Width|Height] reliable in standards mode DOCTYPE, except for Safari2, Opera<9.5 |
| // 3) document.body.client[Width|Height] is gives correct result when #2 does not, except for Safari2 |
| // 4) When document.documentElement.client[Width|Height] is unreliable, it will be size of <html> element either greater or less than desired view size |
| // https://bugzilla.mozilla.org/show_bug.cgi?id=156388#c7 |
| // 5) When document.body.client[Width|Height] is unreliable, it will be size of <body> element less than desired view size |
| function viewSize() { |
| // This algorithm avoids creating test page to determine if document.documentElement.client[Width|Height] is greater then view size, |
| // will succeed where such test page wouldn't detect dynamic unreliability, |
| // and will only fail in the case the right or bottom edge is within the width of a scrollbar from edge of the viewport that has visible scrollbar(s). |
| var doc = docElem('clientWidth'); |
| var body = document.body; |
| var w, h; |
| return typeof document.clientWidth === 'number' ? |
| {w: document.clientWidth, h: document.clientHeight} : |
| doc === body || (w = Math.max( doc.clientWidth, body.clientWidth )) > self.innerWidth || (h = Math.max( doc.clientHeight, body.clientHeight )) > self.innerHeight ? |
| {w: body.clientWidth, h: body.clientHeight} : {w: w, h: h }; |
| } |
| |
| Polymer({ |
| |
| publish: { |
| |
| /** |
| * The element associated with this dropdown, usually the element that triggers |
| * the menu. If unset, this property will default to the target's parent node |
| * or shadow host. |
| * |
| * @attribute relatedTarget |
| * @type Node |
| */ |
| relatedTarget: null, |
| |
| /** |
| * The horizontal alignment of the popup relative to `relatedTarget`. `left` |
| * means the left edges are aligned together. `right` means the right edges |
| * are aligned together. |
| * |
| * Accepted values: 'left', 'right' |
| * |
| * @attribute halign |
| * @type String |
| * @default 'left' |
| */ |
| halign: 'left', |
| |
| /** |
| * The vertical alignment of the popup relative to `relatedTarget`. `top` means |
| * the top edges are aligned together. `bottom` means the bottom edges are |
| * aligned together. |
| * |
| * Accepted values: 'top', 'bottom' |
| * |
| * @attribute valign |
| * @type String |
| * @default 'top' |
| */ |
| valign: 'top' |
| |
| }, |
| |
| measure: function() { |
| var target = this.target; |
| // remember position, because core-overlay may have set the property |
| var pos = target.style.position; |
| |
| // get the size of the target as if it's positioned in the top left |
| // corner of the screen |
| target.style.position = 'fixed'; |
| target.style.left = '0px'; |
| target.style.top = '0px'; |
| |
| var rect = target.getBoundingClientRect(); |
| |
| target.style.position = pos; |
| target.style.left = null; |
| target.style.top = null; |
| |
| return rect; |
| }, |
| |
| resetTargetDimensions: function() { |
| var dims = this.dimensions; |
| var style = this.target.style; |
| if (dims.position.h_by === this.localName) { |
| style[dims.position.h] = null; |
| dims.position.h_by = null; |
| } |
| if (dims.position.v_by === this.localName) { |
| style[dims.position.v] = null; |
| dims.position.v_by = null; |
| } |
| |
| var style = this.sizingTarget.style; |
| style.width = null; |
| style.height = null; |
| this.super(); |
| }, |
| |
| positionTarget: function() { |
| if (!this.relatedTarget) { |
| this.relatedTarget = this.target.parentElement || (this.target.parentNode && this.target.parentNode.host); |
| if (!this.relatedTarget) { |
| this.super(); |
| return; |
| } |
| } |
| |
| // explicitly set width/height, because we don't want it constrained |
| // to the offsetParent |
| var target = this.sizingTarget; |
| var rect = this.measure(); |
| target.style.width = Math.ceil(rect.width) + 'px'; |
| target.style.height = Math.ceil(rect.height) + 'px'; |
| |
| if (this.layered) { |
| this.positionLayeredTarget(); |
| } else { |
| this.positionNestedTarget(); |
| } |
| }, |
| |
| positionLayeredTarget: function() { |
| var target = this.target; |
| var rect = this.relatedTarget.getBoundingClientRect(); |
| |
| var dims = this.dimensions; |
| var margin = dims.margin; |
| var vp = viewSize(); |
| |
| if (!dims.position.h) { |
| if (this.halign === 'right') { |
| target.style.right = vp.w - rect.right - margin.right + 'px'; |
| dims.position.h = 'right'; |
| } else { |
| target.style.left = rect.left - margin.left + 'px'; |
| dims.position.h = 'left'; |
| } |
| dims.position.h_by = this.localName; |
| } |
| |
| if (!dims.position.v) { |
| if (this.valign === 'bottom') { |
| target.style.bottom = vp.h - rect.bottom - margin.bottom + 'px'; |
| dims.position.v = 'bottom'; |
| } else { |
| target.style.top = rect.top - margin.top + 'px'; |
| dims.position.v = 'top'; |
| } |
| dims.position.v_by = this.localName; |
| } |
| |
| if (dims.position.h_by || dims.position.v_by) { |
| target.style.position = 'fixed'; |
| } |
| }, |
| |
| positionNestedTarget: function() { |
| var target = this.target; |
| var related = this.relatedTarget; |
| |
| var t_op = target.offsetParent; |
| var r_op = related.offsetParent; |
| if (window.ShadowDOMPolyfill) { |
| t_op = wrap(t_op); |
| r_op = wrap(r_op); |
| } |
| |
| if (t_op !== r_op && t_op !== related) { |
| console.warn('core-dropdown-overlay: dropdown\'s offsetParent must be the relatedTarget or the relatedTarget\'s offsetParent!'); |
| } |
| |
| // Don't use CSS to handle halign/valign so we can use |
| // dimensions.position to detect custom positioning |
| |
| var dims = this.dimensions; |
| var margin = dims.margin; |
| var inside = t_op === related; |
| |
| if (!dims.position.h) { |
| if (this.halign === 'right') { |
| target.style.right = ((inside ? 0 : t_op.offsetWidth - related.offsetLeft - related.offsetWidth) - margin.right) + 'px'; |
| dims.position.h = 'right'; |
| } else { |
| target.style.left = ((inside ? 0 : related.offsetLeft) - margin.left) + 'px'; |
| dims.position.h = 'left'; |
| } |
| dims.position.h_by = this.localName; |
| } |
| |
| if (!dims.position.v) { |
| if (this.valign === 'bottom') { |
| target.style.bottom = ((inside ? 0 : t_op.offsetHeight - related.offsetTop - related.offsetHeight) - margin.bottom) + 'px'; |
| dims.position.v = 'bottom'; |
| } else { |
| target.style.top = ((inside ? 0 : related.offsetTop) - margin.top) + 'px'; |
| dims.position.v = 'top'; |
| } |
| dims.position.v_by = this.localName; |
| } |
| } |
| |
| }); |
| |
| })(); |
| |
| </script> |
| </polymer-element> |