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