Home | History | Annotate | Download | only in iron-behaviors
      1 <!--
      2 @license
      3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
      4 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
      5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
      6 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
      7 Code distributed by Google as part of the polymer project is also
      8 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
      9 -->
     10 
     11 <link rel="import" href="../polymer/polymer.html">
     12 <link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
     13 <link rel="import" href="iron-control-state.html">
     14 
     15 <script>
     16 
     17   /**
     18    * @demo demo/index.html
     19    * @polymerBehavior Polymer.IronButtonState
     20    */
     21   Polymer.IronButtonStateImpl = {
     22 
     23     properties: {
     24 
     25       /**
     26        * If true, the user is currently holding down the button.
     27        */
     28       pressed: {
     29         type: Boolean,
     30         readOnly: true,
     31         value: false,
     32         reflectToAttribute: true,
     33         observer: '_pressedChanged'
     34       },
     35 
     36       /**
     37        * If true, the button toggles the active state with each tap or press
     38        * of the spacebar.
     39        */
     40       toggles: {
     41         type: Boolean,
     42         value: false,
     43         reflectToAttribute: true
     44       },
     45 
     46       /**
     47        * If true, the button is a toggle and is currently in the active state.
     48        */
     49       active: {
     50         type: Boolean,
     51         value: false,
     52         notify: true,
     53         reflectToAttribute: true
     54       },
     55 
     56       /**
     57        * True if the element is currently being pressed by a "pointer," which
     58        * is loosely defined as mouse or touch input (but specifically excluding
     59        * keyboard input).
     60        */
     61       pointerDown: {
     62         type: Boolean,
     63         readOnly: true,
     64         value: false
     65       },
     66 
     67       /**
     68        * True if the input device that caused the element to receive focus
     69        * was a keyboard.
     70        */
     71       receivedFocusFromKeyboard: {
     72         type: Boolean,
     73         readOnly: true
     74       },
     75 
     76       /**
     77        * The aria attribute to be set if the button is a toggle and in the
     78        * active state.
     79        */
     80       ariaActiveAttribute: {
     81         type: String,
     82         value: 'aria-pressed',
     83         observer: '_ariaActiveAttributeChanged'
     84       }
     85     },
     86 
     87     listeners: {
     88       down: '_downHandler',
     89       up: '_upHandler',
     90       tap: '_tapHandler'
     91     },
     92 
     93     observers: [
     94       '_detectKeyboardFocus(focused)',
     95       '_activeChanged(active, ariaActiveAttribute)'
     96     ],
     97 
     98     keyBindings: {
     99       'enter:keydown': '_asyncClick',
    100       'space:keydown': '_spaceKeyDownHandler',
    101       'space:keyup': '_spaceKeyUpHandler',
    102     },
    103 
    104     _mouseEventRe: /^mouse/,
    105 
    106     _tapHandler: function() {
    107       if (this.toggles) {
    108        // a tap is needed to toggle the active state
    109         this._userActivate(!this.active);
    110       } else {
    111         this.active = false;
    112       }
    113     },
    114 
    115     _detectKeyboardFocus: function(focused) {
    116       this._setReceivedFocusFromKeyboard(!this.pointerDown && focused);
    117     },
    118 
    119     // to emulate native checkbox, (de-)activations from a user interaction fire
    120     // 'change' events
    121     _userActivate: function(active) {
    122       if (this.active !== active) {
    123         this.active = active;
    124         this.fire('change');
    125       }
    126     },
    127 
    128     _downHandler: function(event) {
    129       this._setPointerDown(true);
    130       this._setPressed(true);
    131       this._setReceivedFocusFromKeyboard(false);
    132     },
    133 
    134     _upHandler: function() {
    135       this._setPointerDown(false);
    136       this._setPressed(false);
    137     },
    138 
    139     /**
    140      * @param {!KeyboardEvent} event .
    141      */
    142     _spaceKeyDownHandler: function(event) {
    143       var keyboardEvent = event.detail.keyboardEvent;
    144       var target = Polymer.dom(keyboardEvent).localTarget;
    145 
    146       // Ignore the event if this is coming from a focused light child, since that
    147       // element will deal with it.
    148       if (this.isLightDescendant(/** @type {Node} */(target)))
    149         return;
    150 
    151       keyboardEvent.preventDefault();
    152       keyboardEvent.stopImmediatePropagation();
    153       this._setPressed(true);
    154     },
    155 
    156     /**
    157      * @param {!KeyboardEvent} event .
    158      */
    159     _spaceKeyUpHandler: function(event) {
    160       var keyboardEvent = event.detail.keyboardEvent;
    161       var target = Polymer.dom(keyboardEvent).localTarget;
    162 
    163       // Ignore the event if this is coming from a focused light child, since that
    164       // element will deal with it.
    165       if (this.isLightDescendant(/** @type {Node} */(target)))
    166         return;
    167 
    168       if (this.pressed) {
    169         this._asyncClick();
    170       }
    171       this._setPressed(false);
    172     },
    173 
    174     // trigger click asynchronously, the asynchrony is useful to allow one
    175     // event handler to unwind before triggering another event
    176     _asyncClick: function() {
    177       this.async(function() {
    178         this.click();
    179       }, 1);
    180     },
    181 
    182     // any of these changes are considered a change to button state
    183 
    184     _pressedChanged: function(pressed) {
    185       this._changedButtonState();
    186     },
    187 
    188     _ariaActiveAttributeChanged: function(value, oldValue) {
    189       if (oldValue && oldValue != value && this.hasAttribute(oldValue)) {
    190         this.removeAttribute(oldValue);
    191       }
    192     },
    193 
    194     _activeChanged: function(active, ariaActiveAttribute) {
    195       if (this.toggles) {
    196         this.setAttribute(this.ariaActiveAttribute,
    197                           active ? 'true' : 'false');
    198       } else {
    199         this.removeAttribute(this.ariaActiveAttribute);
    200       }
    201       this._changedButtonState();
    202     },
    203 
    204     _controlStateChanged: function() {
    205       if (this.disabled) {
    206         this._setPressed(false);
    207       } else {
    208         this._changedButtonState();
    209       }
    210     },
    211 
    212     // provide hook for follow-on behaviors to react to button-state
    213 
    214     _changedButtonState: function() {
    215       if (this._buttonStateChanged) {
    216         this._buttonStateChanged(); // abstract
    217       }
    218     }
    219 
    220   };
    221 
    222   /** @polymerBehavior */
    223   Polymer.IronButtonState = [
    224     Polymer.IronA11yKeysBehavior,
    225     Polymer.IronButtonStateImpl
    226   ];
    227 
    228 </script>
    229