blob: 7bb4c74428a7c319fb5e330bfad707593ddfc5ee [file] [log] [blame]
<!--
@license
Copyright (c) 2015 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
-->
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
<link rel="import" href="../iron-behaviors/iron-control-state.html">
<link rel="import" href="../iron-dropdown/iron-dropdown.html">
<link rel="import" href="../neon-animation/animations/fade-in-animation.html">
<link rel="import" href="../neon-animation/animations/fade-out-animation.html">
<link rel="import" href="../paper-styles/default-theme.html">
<link rel="import" href="../paper-styles/shadow.html">
<link rel="import" href="paper-menu-button-animations.html">
<!--
Material design: [Dropdown buttons](https://www.google.com/design/spec/components/buttons.html#buttons-dropdown-buttons)
`paper-menu-button` allows one to compose a designated "trigger" element with
another element that represents "content", to create a dropdown menu that
displays the "content" when the "trigger" is clicked.
The child element with the class `dropdown-trigger` will be used as the
"trigger" element. The child element with the class `dropdown-content` will be
used as the "content" element.
The `paper-menu-button` is sensitive to its content's `iron-select` events. If
the "content" element triggers an `iron-select` event, the `paper-menu-button`
will close automatically.
Example:
<paper-menu-button>
<paper-icon-button icon="menu" class="dropdown-trigger"></paper-icon-button>
<paper-menu class="dropdown-content">
<paper-item>Share</paper-item>
<paper-item>Settings</paper-item>
<paper-item>Help</paper-item>
</paper-menu>
</paper-menu-button>
### Styling
The following custom properties and mixins are also available for styling:
Custom property | Description | Default
----------------|-------------|----------
`--paper-menu-button-dropdown-background` | Background color of the paper-menu-button dropdown | `--primary-background-color`
`--paper-menu-button` | Mixin applied to the paper-menu-button | `{}`
`--paper-menu-button-disabled` | Mixin applied to the paper-menu-button when disabled | `{}`
`--paper-menu-button-dropdown` | Mixin applied to the paper-menu-button dropdown | `{}`
`--paper-menu-button-content` | Mixin applied to the paper-menu-button content | `{}`
@hero hero.svg
@demo demo/index.html
-->
<dom-module id="paper-menu-button">
<template>
<style>
:host {
display: inline-block;
position: relative;
padding: 8px;
outline: none;
@apply(--paper-menu-button);
}
:host([disabled]) {
cursor: auto;
color: var(--disabled-text-color);
@apply(--paper-menu-button-disabled);
}
iron-dropdown {
@apply(--paper-menu-button-dropdown);
}
.dropdown-content {
@apply(--shadow-elevation-2dp);
position: relative;
border-radius: 2px;
background-color: var(--paper-menu-button-dropdown-background, --primary-background-color);
@apply(--paper-menu-button-content);
}
:host([vertical-align="top"]) .dropdown-content {
margin-bottom: 20px;
margin-top: -10px;
top: 10px;
}
:host([vertical-align="bottom"]) .dropdown-content {
bottom: 10px;
margin-bottom: -10px;
margin-top: 20px;
}
#trigger {
cursor: pointer;
}
</style>
<div id="trigger" on-tap="toggle">
<content select=".dropdown-trigger"></content>
</div>
<iron-dropdown
id="dropdown"
opened="{{opened}}"
horizontal-align="[[horizontalAlign]]"
vertical-align="[[verticalAlign]]"
dynamic-align="[[dynamicAlign]]"
horizontal-offset="[[horizontalOffset]]"
vertical-offset="[[verticalOffset]]"
no-overlap="[[noOverlap]]"
open-animation-config="[[openAnimationConfig]]"
close-animation-config="[[closeAnimationConfig]]"
no-animations="[[noAnimations]]"
focus-target="[[_dropdownContent]]"
allow-outside-scroll="[[allowOutsideScroll]]"
restore-focus-on-close="[[restoreFocusOnClose]]"
on-iron-overlay-canceled="__onIronOverlayCanceled">
<div class="dropdown-content">
<content id="content" select=".dropdown-content"></content>
</div>
</iron-dropdown>
</template>
<script>
(function() {
'use strict';
var config = {
ANIMATION_CUBIC_BEZIER: 'cubic-bezier(.3,.95,.5,1)',
MAX_ANIMATION_TIME_MS: 400
};
var PaperMenuButton = Polymer({
is: 'paper-menu-button',
/**
* Fired when the dropdown opens.
*
* @event paper-dropdown-open
*/
/**
* Fired when the dropdown closes.
*
* @event paper-dropdown-close
*/
behaviors: [
Polymer.IronA11yKeysBehavior,
Polymer.IronControlState
],
properties: {
/**
* True if the content is currently displayed.
*/
opened: {
type: Boolean,
value: false,
notify: true,
observer: '_openedChanged'
},
/**
* The orientation against which to align the menu dropdown
* horizontally relative to the dropdown trigger.
*/
horizontalAlign: {
type: String,
value: 'left',
reflectToAttribute: true
},
/**
* The orientation against which to align the menu dropdown
* vertically relative to the dropdown trigger.
*/
verticalAlign: {
type: String,
value: 'top',
reflectToAttribute: true
},
/**
* If true, the `horizontalAlign` and `verticalAlign` properties will
* be considered preferences instead of strict requirements when
* positioning the dropdown and may be changed if doing so reduces
* the area of the dropdown falling outside of `fitInto`.
*/
dynamicAlign: {
type: Boolean
},
/**
* A pixel value that will be added to the position calculated for the
* given `horizontalAlign`. Use a negative value to offset to the
* left, or a positive value to offset to the right.
*/
horizontalOffset: {
type: Number,
value: 0,
notify: true
},
/**
* A pixel value that will be added to the position calculated for the
* given `verticalAlign`. Use a negative value to offset towards the
* top, or a positive value to offset towards the bottom.
*/
verticalOffset: {
type: Number,
value: 0,
notify: true
},
/**
* If true, the dropdown will be positioned so that it doesn't overlap
* the button.
*/
noOverlap: {
type: Boolean
},
/**
* Set to true to disable animations when opening and closing the
* dropdown.
*/
noAnimations: {
type: Boolean,
value: false
},
/**
* Set to true to disable automatically closing the dropdown after
* a selection has been made.
*/
ignoreSelect: {
type: Boolean,
value: false
},
/**
* Set to true to enable automatically closing the dropdown after an
* item has been activated, even if the selection did not change.
*/
closeOnActivate: {
type: Boolean,
value: false
},
/**
* An animation config. If provided, this will be used to animate the
* opening of the dropdown.
*/
openAnimationConfig: {
type: Object,
value: function() {
return [{
name: 'fade-in-animation',
timing: {
delay: 100,
duration: 200
}
}, {
name: 'paper-menu-grow-width-animation',
timing: {
delay: 100,
duration: 150,
easing: config.ANIMATION_CUBIC_BEZIER
}
}, {
name: 'paper-menu-grow-height-animation',
timing: {
delay: 100,
duration: 275,
easing: config.ANIMATION_CUBIC_BEZIER
}
}];
}
},
/**
* An animation config. If provided, this will be used to animate the
* closing of the dropdown.
*/
closeAnimationConfig: {
type: Object,
value: function() {
return [{
name: 'fade-out-animation',
timing: {
duration: 150
}
}, {
name: 'paper-menu-shrink-width-animation',
timing: {
delay: 100,
duration: 50,
easing: config.ANIMATION_CUBIC_BEZIER
}
}, {
name: 'paper-menu-shrink-height-animation',
timing: {
duration: 200,
easing: 'ease-in'
}
}];
}
},
/**
* By default, the dropdown will constrain scrolling on the page
* to itself when opened.
* Set to true in order to prevent scroll from being constrained
* to the dropdown when it opens.
*/
allowOutsideScroll: {
type: Boolean,
value: false
},
/**
* Whether focus should be restored to the button when the menu closes.
*/
restoreFocusOnClose: {
type: Boolean,
value: true
},
/**
* This is the element intended to be bound as the focus target
* for the `iron-dropdown` contained by `paper-menu-button`.
*/
_dropdownContent: {
type: Object
}
},
hostAttributes: {
role: 'group',
'aria-haspopup': 'true'
},
listeners: {
'iron-activate': '_onIronActivate',
'iron-select': '_onIronSelect'
},
/**
* The content element that is contained by the menu button, if any.
*/
get contentElement() {
return Polymer.dom(this.$.content).getDistributedNodes()[0];
},
/**
* Toggles the drowpdown content between opened and closed.
*/
toggle: function() {
if (this.opened) {
this.close();
} else {
this.open();
}
},
/**
* Make the dropdown content appear as an overlay positioned relative
* to the dropdown trigger.
*/
open: function() {
if (this.disabled) {
return;
}
this.$.dropdown.open();
},
/**
* Hide the dropdown content.
*/
close: function() {
this.$.dropdown.close();
},
/**
* When an `iron-select` event is received, the dropdown should
* automatically close on the assumption that a value has been chosen.
*
* @param {CustomEvent} event A CustomEvent instance with type
* set to `"iron-select"`.
*/
_onIronSelect: function(event) {
if (!this.ignoreSelect) {
this.close();
}
},
/**
* Closes the dropdown when an `iron-activate` event is received if
* `closeOnActivate` is true.
*
* @param {CustomEvent} event A CustomEvent of type 'iron-activate'.
*/
_onIronActivate: function(event) {
if (this.closeOnActivate) {
this.close();
}
},
/**
* When the dropdown opens, the `paper-menu-button` fires `paper-open`.
* When the dropdown closes, the `paper-menu-button` fires `paper-close`.
*
* @param {boolean} opened True if the dropdown is opened, otherwise false.
* @param {boolean} oldOpened The previous value of `opened`.
*/
_openedChanged: function(opened, oldOpened) {
if (opened) {
// TODO(cdata): Update this when we can measure changes in distributed
// children in an idiomatic way.
// We poke this property in case the element has changed. This will
// cause the focus target for the `iron-dropdown` to be updated as
// necessary:
this._dropdownContent = this.contentElement;
this.fire('paper-dropdown-open');
} else if (oldOpened != null) {
this.fire('paper-dropdown-close');
}
},
/**
* If the dropdown is open when disabled becomes true, close the
* dropdown.
*
* @param {boolean} disabled True if disabled, otherwise false.
*/
_disabledChanged: function(disabled) {
Polymer.IronControlState._disabledChanged.apply(this, arguments);
if (disabled && this.opened) {
this.close();
}
},
__onIronOverlayCanceled: function(event) {
var uiEvent = event.detail;
var target = Polymer.dom(uiEvent).rootTarget;
var trigger = this.$.trigger;
var path = Polymer.dom(uiEvent).path;
if (path.indexOf(trigger) > -1) {
event.preventDefault();
}
}
});
Object.keys(config).forEach(function (key) {
PaperMenuButton[key] = config[key];
});
Polymer.PaperMenuButton = PaperMenuButton;
})();
</script>
</dom-module>