313 lines
10 KiB
HTML
313 lines
10 KiB
HTML
<!--
|
|
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>
|