OLD | NEW |
(Empty) | |
| 1 <!-- |
| 2 Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 3 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 4 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 5 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 6 Code distributed by Google as part of the polymer project is also |
| 7 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 8 --> |
| 9 |
| 10 <!-- |
| 11 |
| 12 `core-dropdown` is an element that is initially hidden and is positioned relativ
ely to another |
| 13 element, usually the element that triggers the dropdown. The dropdown and the tr
iggering element |
| 14 should be children of the same offsetParent, e.g. the same `<div>` with `positio
n: relative`. |
| 15 It can be used to implement dropdown menus, menu buttons, etc.. |
| 16 |
| 17 Example: |
| 18 |
| 19 <template is="auto-binding"> |
| 20 <div relative> |
| 21 <core-icon-button id="trigger" icon="menu"></core-icon-button> |
| 22 <core-dropdown relatedTarget="{{$.trigger}}"> |
| 23 <core-menu> |
| 24 <core-item>Cut</core-item> |
| 25 <core-item>Copy</core-item> |
| 26 <core-item>Paste</core-item> |
| 27 </core-menu> |
| 28 </core-dropdown> |
| 29 </div> |
| 30 </template> |
| 31 |
| 32 Positioning |
| 33 ----------- |
| 34 |
| 35 By default, the dropdown is absolutely positioned on top of the `relatedTarget`
with the top and |
| 36 left edges aligned. The `halign` and `valign` properties controls the various al
ignments. The size |
| 37 of the dropdown is automatically restrained such that it is entirely visible on
the screen. Use the |
| 38 `margin` |
| 39 |
| 40 If you need more control over the dropdown's position, use CSS. The `halign` and
`valign` properties are |
| 41 ignored if the dropdown is positioned with CSS. |
| 42 |
| 43 Example: |
| 44 |
| 45 <style> |
| 46 /* manually position the dropdown below the trigger */ |
| 47 core-dropdown { |
| 48 position: absolute; |
| 49 top: 38px; |
| 50 left: 0; |
| 51 } |
| 52 </style> |
| 53 |
| 54 <template is="auto-binding"> |
| 55 <div relative> |
| 56 <core-icon-button id="trigger" icon="menu"></core-icon-button> |
| 57 <core-dropdown relatedTarget="{{$.trigger}}"> |
| 58 <core-menu> |
| 59 <core-item>Cut</core-item> |
| 60 <core-item>Copy</core-item> |
| 61 <core-item>Paste</core-item> |
| 62 </core-menu> |
| 63 </core-dropdown> |
| 64 </div> |
| 65 </template> |
| 66 |
| 67 The `layered` property |
| 68 ---------------------- |
| 69 |
| 70 Sometimes you may need to render the dropdown in a separate layer. For example, |
| 71 it may be nested inside an element that needs to be `overflow: hidden`, or |
| 72 its parent may be overlapped by elements above it in stacking order. |
| 73 |
| 74 The `layered` property will place the dropdown in a separate layer to ensure |
| 75 it appears on top of everything else. Note that this implies the dropdown will |
| 76 not scroll with its container. |
| 77 |
| 78 @group Polymer Core Elements |
| 79 @element core-dropdown |
| 80 @extends core-overlay |
| 81 @homepage github.io |
| 82 --> |
| 83 <link href="../polymer/polymer.html" rel="import"> |
| 84 <link href="../core-overlay/core-overlay.html" rel="import"> |
| 85 |
| 86 <style shim-shadowdom> |
| 87 html /deep/ core-dropdown { |
| 88 position: absolute; |
| 89 overflow: auto; |
| 90 background-color: #fff; |
| 91 } |
| 92 </style> |
| 93 |
| 94 <polymer-element name="core-dropdown" extends="core-overlay"> |
| 95 <script> |
| 96 |
| 97 (function() { |
| 98 |
| 99 function docElem(property) { |
| 100 var t; |
| 101 return ((t = document.documentElement) || (t = document.body.parentNode)) &&
(typeof t[property] === 'number') ? t : document.body; |
| 102 } |
| 103 |
| 104 // View width and height excluding any visible scrollbars |
| 105 // http://www.highdots.com/forums/javascript/faq-topic-how-do-i-296669.html |
| 106 // 1) document.client[Width|Height] always reliable when available, includi
ng Safari2 |
| 107 // 2) document.documentElement.client[Width|Height] reliable in standards m
ode DOCTYPE, except for Safari2, Opera<9.5 |
| 108 // 3) document.body.client[Width|Height] is gives correct result when #2 do
es not, except for Safari2 |
| 109 // 4) When document.documentElement.client[Width|Height] is unreliable, it
will be size of <html> element either greater or less than desired view size |
| 110 // https://bugzilla.mozilla.org/show_bug.cgi?id=156388#c7 |
| 111 // 5) When document.body.client[Width|Height] is unreliable, it will be siz
e of <body> element less than desired view size |
| 112 function viewSize() { |
| 113 // This algorithm avoids creating test page to determine if document.documen
tElement.client[Width|Height] is greater then view size, |
| 114 // will succeed where such test page wouldn't detect dynamic unreliability, |
| 115 // and will only fail in the case the right or bottom edge is within the wid
th of a scrollbar from edge of the viewport that has visible scrollbar(s). |
| 116 var doc = docElem('clientWidth'); |
| 117 var body = document.body; |
| 118 var w, h; |
| 119 return typeof document.clientWidth === 'number' ? |
| 120 {w: document.clientWidth, h: document.clientHeight} : |
| 121 doc === body || (w = Math.max( doc.clientWidth, body.clientWidth )) > self
.innerWidth || (h = Math.max( doc.clientHeight, body.clientHeight )) > self.inne
rHeight ? |
| 122 {w: body.clientWidth, h: body.clientHeight} : {w: w, h: h }; |
| 123 } |
| 124 |
| 125 Polymer({ |
| 126 |
| 127 publish: { |
| 128 |
| 129 /** |
| 130 * The element associated with this dropdown, usually the element that tri
ggers |
| 131 * the menu. If unset, this property will default to the target's parent n
ode |
| 132 * or shadow host. |
| 133 * |
| 134 * @attribute relatedTarget |
| 135 * @type Node |
| 136 */ |
| 137 relatedTarget: null, |
| 138 |
| 139 /** |
| 140 * The horizontal alignment of the popup relative to `relatedTarget`. `lef
t` |
| 141 * means the left edges are aligned together. `right` means the right edge
s |
| 142 * are aligned together. |
| 143 * |
| 144 * @attribute halign |
| 145 * @type 'left' | 'right' |
| 146 * @default 'left' |
| 147 */ |
| 148 halign: 'left', |
| 149 |
| 150 /** |
| 151 * The vertical alignment of the popup relative to `relatedTarget`. `top`
means |
| 152 * the top edges are aligned together. `bottom` means the bottom edges are |
| 153 * aligned together. |
| 154 * |
| 155 * @attribute valign |
| 156 * @type 'top' | 'bottom' |
| 157 * @default 'top' |
| 158 */ |
| 159 valign: 'top', |
| 160 |
| 161 }, |
| 162 |
| 163 measure: function() { |
| 164 var target = this.target; |
| 165 // remember position, because core-overlay may have set the property |
| 166 var pos = target.style.position; |
| 167 |
| 168 // get the size of the target as if it's positioned in the top left |
| 169 // corner of the screen |
| 170 target.style.position = 'fixed'; |
| 171 target.style.left = '0px'; |
| 172 target.style.top = '0px'; |
| 173 |
| 174 var rect = target.getBoundingClientRect(); |
| 175 |
| 176 target.style.position = pos; |
| 177 target.style.left = null; |
| 178 target.style.top = null; |
| 179 |
| 180 return rect; |
| 181 }, |
| 182 |
| 183 resetTargetDimensions: function() { |
| 184 var dims = this.dimensions; |
| 185 var style = this.target.style; |
| 186 if (dims.position.h_by === this.localName) { |
| 187 style[dims.position.h] = null; |
| 188 dims.position.h_by = null; |
| 189 } |
| 190 if (dims.position.v_by === this.localName) { |
| 191 style[dims.position.v] = null; |
| 192 dims.position.v_by = null; |
| 193 } |
| 194 style.width = null; |
| 195 style.height = null; |
| 196 this.super(); |
| 197 }, |
| 198 |
| 199 positionTarget: function() { |
| 200 if (!this.relatedTarget) { |
| 201 this.relatedTarget = this.target.parentElement || (this.target.parentNod
e && this.target.parentNode.host); |
| 202 if (!this.relatedTarget) { |
| 203 this.super(); |
| 204 return; |
| 205 } |
| 206 } |
| 207 |
| 208 // explicitly set width/height, because we don't want it constrained |
| 209 // to the offsetParent |
| 210 var target = this.sizingTarget; |
| 211 var rect = this.measure(); |
| 212 target.style.width = Math.ceil(rect.width) + 'px'; |
| 213 target.style.height = Math.ceil(rect.height) + 'px'; |
| 214 |
| 215 if (this.layered) { |
| 216 this.positionLayeredTarget(); |
| 217 } else { |
| 218 this.positionNestedTarget(); |
| 219 } |
| 220 }, |
| 221 |
| 222 positionLayeredTarget: function() { |
| 223 var target = this.target; |
| 224 var rect = this.relatedTarget.getBoundingClientRect(); |
| 225 |
| 226 var dims = this.dimensions; |
| 227 var margin = dims.margin; |
| 228 var vp = viewSize(); |
| 229 |
| 230 if (!dims.position.h) { |
| 231 if (this.halign === 'right') { |
| 232 target.style.right = vp.w - rect.right - margin.right + 'px'; |
| 233 dims.position.h = 'right'; |
| 234 } else { |
| 235 target.style.left = rect.left - margin.left + 'px'; |
| 236 dims.position.h = 'left'; |
| 237 } |
| 238 dims.position.h_by = this.localName; |
| 239 } |
| 240 |
| 241 if (!dims.position.v) { |
| 242 if (this.valign === 'bottom') { |
| 243 target.style.bottom = vp.h - rect.bottom - margin.bottom + 'px'; |
| 244 dims.position.v = 'bottom'; |
| 245 } else { |
| 246 target.style.top = rect.top - margin.top + 'px'; |
| 247 dims.position.v = 'top'; |
| 248 } |
| 249 dims.position.v_by = this.localName; |
| 250 } |
| 251 |
| 252 if (dims.position.h_by || dims.position.v_by) { |
| 253 target.style.position = 'fixed'; |
| 254 } |
| 255 }, |
| 256 |
| 257 positionNestedTarget: function() { |
| 258 var target = this.target; |
| 259 var related = this.relatedTarget; |
| 260 |
| 261 var t_op = target.offsetParent; |
| 262 var r_op = related.offsetParent; |
| 263 if (window.ShadowDOMPolyfill) { |
| 264 t_op = wrap(t_op); |
| 265 r_op = wrap(r_op); |
| 266 } |
| 267 |
| 268 if (t_op !== r_op && t_op !== related) { |
| 269 console.warn('core-dropdown-overlay: dropdown\'s offsetParent must be th
e relatedTarget or the relatedTarget\'s offsetParent!'); |
| 270 } |
| 271 |
| 272 // Don't use CSS to handle halign/valign so we can use |
| 273 // dimensions.position to detect custom positioning |
| 274 |
| 275 var dims = this.dimensions; |
| 276 var margin = dims.margin; |
| 277 var inside = t_op === related; |
| 278 |
| 279 if (!dims.position.h) { |
| 280 if (this.halign === 'right') { |
| 281 target.style.right = ((inside ? 0 : t_op.offsetWidth - related.offsetL
eft - related.offsetWidth) - margin.right) + 'px'; |
| 282 dims.position.h = 'right'; |
| 283 } else { |
| 284 target.style.left = ((inside ? 0 : related.offsetLeft) - margin.left)
+ 'px'; |
| 285 dims.position.h = 'left'; |
| 286 } |
| 287 dims.position.h_by = this.localName; |
| 288 } |
| 289 |
| 290 if (!dims.position.v) { |
| 291 if (this.valign === 'bottom') { |
| 292 target.style.bottom = ((inside ? 0 : t_op.offsetHeight - related.offse
tTop - related.offsetHeight) - margin.bottom) + 'px'; |
| 293 dims.position.v = 'bottom'; |
| 294 } else { |
| 295 target.style.top = ((inside ? 0 : related.offsetTop) - margin.top) + '
px'; |
| 296 dims.position.v = 'top'; |
| 297 } |
| 298 dims.position.v_by = this.localName; |
| 299 } |
| 300 } |
| 301 |
| 302 }); |
| 303 |
| 304 })(); |
| 305 |
| 306 </script> |
| 307 </polymer-element> |
OLD | NEW |