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 |