| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of html; | 5 part of html; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Internal class that does the actual calculations to determine keyCode and | 8 * Internal class that does the actual calculations to determine keyCode and |
| 9 * charCode for keydown, keypress, and keyup events for all browsers. | 9 * charCode for keydown, keypress, and keyup events for all browsers. |
| 10 */ | 10 */ |
| 11 class _KeyboardEventHandler extends EventStreamProvider<KeyEvent> { | 11 class _KeyboardEventHandler extends EventStreamProvider<KeyEvent> { |
| 12 // This code inspired by Closure's KeyHandling library. | 12 // This code inspired by Closure's KeyHandling library. |
| 13 // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandl
er.js.source.html | 13 // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandl
er.js.source.html |
| 14 | 14 |
| 15 /** | 15 /** |
| 16 * The set of keys that have been pressed down without seeing their | 16 * The set of keys that have been pressed down without seeing their |
| 17 * corresponding keyup event. | 17 * corresponding keyup event. |
| 18 */ | 18 */ |
| 19 final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[]; | 19 final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[]; |
| 20 | 20 |
| 21 /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */ | 21 /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */ |
| 22 final String _type; | 22 final String _type; |
| 23 | 23 |
| 24 /** The element we are watching for events to happen on. */ | 24 /** The element we are watching for events to happen on. */ |
| 25 final EventTarget _target; | 25 final EventTarget _target; |
| 26 | 26 |
| 27 // The distance to shift from upper case alphabet Roman letters to lower case. | 27 // The distance to shift from upper case alphabet Roman letters to lower case. |
| 28 static final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0]; | 28 static final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0]; |
| 29 | 29 |
| 30 /** Controller to produce KeyEvents for the stream. */ | 30 /** Custom Stream (Controller) to produce KeyEvents for the stream. */ |
| 31 final StreamController _controller = new StreamController(sync: true); | 31 _CustomKeyEventStreamImpl _stream; |
| 32 | 32 |
| 33 static const _EVENT_TYPE = 'KeyEvent'; | 33 static const _EVENT_TYPE = 'KeyEvent'; |
| 34 | 34 |
| 35 /** | 35 /** |
| 36 * An enumeration of key identifiers currently part of the W3C draft for DOM3 | 36 * An enumeration of key identifiers currently part of the W3C draft for DOM3 |
| 37 * and their mappings to keyCodes. | 37 * and their mappings to keyCodes. |
| 38 * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set | 38 * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set |
| 39 */ | 39 */ |
| 40 static const Map<String, int> _keyIdentifier = const { | 40 static const Map<String, int> _keyIdentifier = const { |
| 41 'Up': KeyCode.UP, | 41 'Up': KeyCode.UP, |
| (...skipping 15 matching lines...) Expand all Loading... |
| 57 'F12': KeyCode.F12, | 57 'F12': KeyCode.F12, |
| 58 'U+007F': KeyCode.DELETE, | 58 'U+007F': KeyCode.DELETE, |
| 59 'Home': KeyCode.HOME, | 59 'Home': KeyCode.HOME, |
| 60 'End': KeyCode.END, | 60 'End': KeyCode.END, |
| 61 'PageUp': KeyCode.PAGE_UP, | 61 'PageUp': KeyCode.PAGE_UP, |
| 62 'PageDown': KeyCode.PAGE_DOWN, | 62 'PageDown': KeyCode.PAGE_DOWN, |
| 63 'Insert': KeyCode.INSERT | 63 'Insert': KeyCode.INSERT |
| 64 }; | 64 }; |
| 65 | 65 |
| 66 /** Return a stream for KeyEvents for the specified target. */ | 66 /** Return a stream for KeyEvents for the specified target. */ |
| 67 Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) { | 67 // Note: this actually functions like a factory constructor. |
| 68 return new _KeyboardEventHandler.initializeAllEventListeners( | 68 CustomStream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) { |
| 69 _type, e).stream; | 69 var handler = new _KeyboardEventHandler.initializeAllEventListeners( |
| 70 } | 70 _type, e); |
| 71 | 71 return handler._stream; |
| 72 /** | |
| 73 * Accessor to the stream associated with a particular KeyboardEvent | |
| 74 * EventTarget. | |
| 75 * | |
| 76 * [forTarget] must be called to initialize this stream to listen to a | |
| 77 * particular EventTarget. | |
| 78 */ | |
| 79 Stream<KeyEvent> get stream { | |
| 80 if(_target != null) { | |
| 81 return _controller.stream; | |
| 82 } else { | |
| 83 throw new StateError("Not initialized. Call forTarget to access a stream " | |
| 84 "initialized with a particular EventTarget."); | |
| 85 } | |
| 86 } | 72 } |
| 87 | 73 |
| 88 /** | 74 /** |
| 89 * General constructor, performs basic initialization for our improved | 75 * General constructor, performs basic initialization for our improved |
| 90 * KeyboardEvent controller. | 76 * KeyboardEvent controller. |
| 91 */ | 77 */ |
| 92 _KeyboardEventHandler(this._type) : | 78 _KeyboardEventHandler(this._type): super(_EVENT_TYPE), |
| 93 _target = null, super(_EVENT_TYPE) { | 79 _stream = new _CustomKeyEventStreamImpl('event'); |
| 94 } | |
| 95 | 80 |
| 96 /** | 81 /** |
| 97 * Hook up all event listeners under the covers so we can estimate keycodes | 82 * Hook up all event listeners under the covers so we can estimate keycodes |
| 98 * and charcodes when they are not provided. | 83 * and charcodes when they are not provided. |
| 99 */ | 84 */ |
| 100 _KeyboardEventHandler.initializeAllEventListeners(this._type, this._target) : | 85 _KeyboardEventHandler.initializeAllEventListeners(this._type, this._target) : |
| 101 super(_EVENT_TYPE) { | 86 super(_EVENT_TYPE) { |
| 102 Element.keyDownEvent.forTarget(_target, useCapture: true).listen( | 87 Element.keyDownEvent.forTarget(_target, useCapture: true).listen( |
| 103 processKeyDown); | 88 processKeyDown); |
| 104 Element.keyPressEvent.forTarget(_target, useCapture: true).listen( | 89 Element.keyPressEvent.forTarget(_target, useCapture: true).listen( |
| 105 processKeyPress); | 90 processKeyPress); |
| 106 Element.keyUpEvent.forTarget(_target, useCapture: true).listen( | 91 Element.keyUpEvent.forTarget(_target, useCapture: true).listen( |
| 107 processKeyUp); | 92 processKeyUp); |
| 108 } | 93 _stream = new _CustomKeyEventStreamImpl(_type); |
| 109 | |
| 110 /** | |
| 111 * Notify all callback listeners that a KeyEvent of the relevant type has | |
| 112 * occurred. | |
| 113 */ | |
| 114 bool _dispatch(KeyEvent event) { | |
| 115 if (event.type == _type) | |
| 116 _controller.add(event); | |
| 117 } | 94 } |
| 118 | 95 |
| 119 /** Determine if caps lock is one of the currently depressed keys. */ | 96 /** Determine if caps lock is one of the currently depressed keys. */ |
| 120 bool get _capsLockOn => | 97 bool get _capsLockOn => |
| 121 _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK); | 98 _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK); |
| 122 | 99 |
| 123 /** | 100 /** |
| 124 * Given the previously recorded keydown key codes, see if we can determine | 101 * Given the previously recorded keydown key codes, see if we can determine |
| 125 * the keycode of this keypress [event]. (Generally browsers only provide | 102 * the keycode of this keypress [event]. (Generally browsers only provide |
| 126 * charCode information for keypress events, but with a little | 103 * charCode information for keypress events, but with a little |
| (...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 297 // before we've caught a key-up event. If the last-key was one of these | 274 // before we've caught a key-up event. If the last-key was one of these |
| 298 // we reset the state. | 275 // we reset the state. |
| 299 if (_keyDownList.length > 0 && | 276 if (_keyDownList.length > 0 && |
| 300 (_keyDownList.last.keyCode == KeyCode.CTRL && !e.ctrlKey || | 277 (_keyDownList.last.keyCode == KeyCode.CTRL && !e.ctrlKey || |
| 301 _keyDownList.last.keyCode == KeyCode.ALT && !e.altKey || | 278 _keyDownList.last.keyCode == KeyCode.ALT && !e.altKey || |
| 302 Device.userAgent.contains('Mac') && | 279 Device.userAgent.contains('Mac') && |
| 303 _keyDownList.last.keyCode == KeyCode.META && !e.metaKey)) { | 280 _keyDownList.last.keyCode == KeyCode.META && !e.metaKey)) { |
| 304 _keyDownList.clear(); | 281 _keyDownList.clear(); |
| 305 } | 282 } |
| 306 | 283 |
| 307 var event = new KeyEvent(e); | 284 var event = new KeyEvent.wrap(e); |
| 308 event._shadowKeyCode = _normalizeKeyCodes(event); | 285 event._shadowKeyCode = _normalizeKeyCodes(event); |
| 309 // Technically a "keydown" event doesn't have a charCode. This is | 286 // Technically a "keydown" event doesn't have a charCode. This is |
| 310 // calculated nonetheless to provide us with more information in giving | 287 // calculated nonetheless to provide us with more information in giving |
| 311 // as much information as possible on keypress about keycode and also | 288 // as much information as possible on keypress about keycode and also |
| 312 // charCode. | 289 // charCode. |
| 313 event._shadowCharCode = _findCharCodeKeyDown(event); | 290 event._shadowCharCode = _findCharCodeKeyDown(event); |
| 314 if (_keyDownList.length > 0 && event.keyCode != _keyDownList.last.keyCode && | 291 if (_keyDownList.length > 0 && event.keyCode != _keyDownList.last.keyCode && |
| 315 !_firesKeyPressEvent(event)) { | 292 !_firesKeyPressEvent(event)) { |
| 316 // Some browsers have quirks not firing keypress events where all other | 293 // Some browsers have quirks not firing keypress events where all other |
| 317 // browsers do. This makes them more consistent. | 294 // browsers do. This makes them more consistent. |
| 318 processKeyPress(e); | 295 processKeyPress(e); |
| 319 } | 296 } |
| 320 _keyDownList.add(event); | 297 _keyDownList.add(event); |
| 321 _dispatch(event); | 298 _stream.add(event); |
| 322 } | 299 } |
| 323 | 300 |
| 324 /** Handle keypress events. */ | 301 /** Handle keypress events. */ |
| 325 void processKeyPress(KeyboardEvent event) { | 302 void processKeyPress(KeyboardEvent event) { |
| 326 var e = new KeyEvent(event); | 303 var e = new KeyEvent.wrap(event); |
| 327 // IE reports the character code in the keyCode field for keypress events. | 304 // IE reports the character code in the keyCode field for keypress events. |
| 328 // There are two exceptions however, Enter and Escape. | 305 // There are two exceptions however, Enter and Escape. |
| 329 if (Device.isIE) { | 306 if (Device.isIE) { |
| 330 if (e.keyCode == KeyCode.ENTER || e.keyCode == KeyCode.ESC) { | 307 if (e.keyCode == KeyCode.ENTER || e.keyCode == KeyCode.ESC) { |
| 331 e._shadowCharCode = 0; | 308 e._shadowCharCode = 0; |
| 332 } else { | 309 } else { |
| 333 e._shadowCharCode = e.keyCode; | 310 e._shadowCharCode = e.keyCode; |
| 334 } | 311 } |
| 335 } else if (Device.isOpera) { | 312 } else if (Device.isOpera) { |
| 336 // Opera reports the character code in the keyCode field. | 313 // Opera reports the character code in the keyCode field. |
| 337 e._shadowCharCode = KeyCode.isCharacterKey(e.keyCode) ? e.keyCode : 0; | 314 e._shadowCharCode = KeyCode.isCharacterKey(e.keyCode) ? e.keyCode : 0; |
| 338 } | 315 } |
| 339 // Now we guestimate about what the keycode is that was actually | 316 // Now we guestimate about what the keycode is that was actually |
| 340 // pressed, given previous keydown information. | 317 // pressed, given previous keydown information. |
| 341 e._shadowKeyCode = _determineKeyCodeForKeypress(e); | 318 e._shadowKeyCode = _determineKeyCodeForKeypress(e); |
| 342 | 319 |
| 343 // Correct the key value for certain browser-specific quirks. | 320 // Correct the key value for certain browser-specific quirks. |
| 344 if (e._shadowKeyIdentifier != null && | 321 if (e._shadowKeyIdentifier != null && |
| 345 _keyIdentifier.containsKey(e._shadowKeyIdentifier)) { | 322 _keyIdentifier.containsKey(e._shadowKeyIdentifier)) { |
| 346 // This is needed for Safari Windows because it currently doesn't give a | 323 // This is needed for Safari Windows because it currently doesn't give a |
| 347 // keyCode/which for non printable keys. | 324 // keyCode/which for non printable keys. |
| 348 e._shadowKeyCode = _keyIdentifier[e._shadowKeyIdentifier]; | 325 e._shadowKeyCode = _keyIdentifier[e._shadowKeyIdentifier]; |
| 349 } | 326 } |
| 350 e._shadowAltKey = _keyDownList.any((var element) => element.altKey); | 327 e._shadowAltKey = _keyDownList.any((var element) => element.altKey); |
| 351 _dispatch(e); | 328 _stream.add(e); |
| 352 } | 329 } |
| 353 | 330 |
| 354 /** Handle keyup events. */ | 331 /** Handle keyup events. */ |
| 355 void processKeyUp(KeyboardEvent event) { | 332 void processKeyUp(KeyboardEvent event) { |
| 356 var e = new KeyEvent(event); | 333 var e = new KeyEvent.wrap(event); |
| 357 KeyboardEvent toRemove = null; | 334 KeyboardEvent toRemove = null; |
| 358 for (var key in _keyDownList) { | 335 for (var key in _keyDownList) { |
| 359 if (key.keyCode == e.keyCode) { | 336 if (key.keyCode == e.keyCode) { |
| 360 toRemove = key; | 337 toRemove = key; |
| 361 } | 338 } |
| 362 } | 339 } |
| 363 if (toRemove != null) { | 340 if (toRemove != null) { |
| 364 _keyDownList.removeWhere((element) => element == toRemove); | 341 _keyDownList.removeWhere((element) => element == toRemove); |
| 365 } else if (_keyDownList.length > 0) { | 342 } else if (_keyDownList.length > 0) { |
| 366 // This happens when we've reached some international keyboard case we | 343 // This happens when we've reached some international keyboard case we |
| 367 // haven't accounted for or we haven't correctly eliminated all browser | 344 // haven't accounted for or we haven't correctly eliminated all browser |
| 368 // inconsistencies. Filing bugs on when this is reached is welcome! | 345 // inconsistencies. Filing bugs on when this is reached is welcome! |
| 369 _keyDownList.removeLast(); | 346 _keyDownList.removeLast(); |
| 370 } | 347 } |
| 371 _dispatch(e); | 348 _stream.add(e); |
| 372 } | 349 } |
| 373 } | 350 } |
| 374 | 351 |
| 375 | 352 |
| 376 /** | 353 /** |
| 377 * Records KeyboardEvents that occur on a particular element, and provides a | 354 * Records KeyboardEvents that occur on a particular element, and provides a |
| 378 * stream of outgoing KeyEvents with cross-browser consistent keyCode and | 355 * stream of outgoing KeyEvents with cross-browser consistent keyCode and |
| 379 * charCode values despite the fact that a multitude of browsers that have | 356 * charCode values despite the fact that a multitude of browsers that have |
| 380 * varying keyboard default behavior. | 357 * varying keyboard default behavior. |
| 381 * | 358 * |
| 382 * Example usage: | 359 * Example usage: |
| 383 * | 360 * |
| 384 * KeyboardEventStream.onKeyDown(document.body).listen( | 361 * KeyboardEventStream.onKeyDown(document.body).listen( |
| 385 * keydownHandlerTest); | 362 * keydownHandlerTest); |
| 386 * | 363 * |
| 387 * This class is very much a work in progress, and we'd love to get information | 364 * This class is very much a work in progress, and we'd love to get information |
| 388 * on how we can make this class work with as many international keyboards as | 365 * on how we can make this class work with as many international keyboards as |
| 389 * possible. Bugs welcome! | 366 * possible. Bugs welcome! |
| 390 */ | 367 */ |
| 391 class KeyboardEventStream { | 368 class KeyboardEventStream { |
| 392 | 369 |
| 393 /** Named constructor to produce a stream for onKeyPress events. */ | 370 /** Named constructor to produce a stream for onKeyPress events. */ |
| 394 static Stream<KeyEvent> onKeyPress(EventTarget target) => | 371 static CustomStream<KeyEvent> onKeyPress(EventTarget target) => |
| 395 new _KeyboardEventHandler('keypress').forTarget(target); | 372 new _KeyboardEventHandler('keypress').forTarget(target); |
| 396 | 373 |
| 397 /** Named constructor to produce a stream for onKeyUp events. */ | 374 /** Named constructor to produce a stream for onKeyUp events. */ |
| 398 static Stream<KeyEvent> onKeyUp(EventTarget target) => | 375 static CustomStream<KeyEvent> onKeyUp(EventTarget target) => |
| 399 new _KeyboardEventHandler('keyup').forTarget(target); | 376 new _KeyboardEventHandler('keyup').forTarget(target); |
| 400 | 377 |
| 401 /** Named constructor to produce a stream for onKeyDown events. */ | 378 /** Named constructor to produce a stream for onKeyDown events. */ |
| 402 static Stream<KeyEvent> onKeyDown(EventTarget target) => | 379 static CustomStream<KeyEvent> onKeyDown(EventTarget target) => |
| 403 new _KeyboardEventHandler('keydown').forTarget(target); | 380 new _KeyboardEventHandler('keydown').forTarget(target); |
| 404 } | 381 } |
| OLD | NEW |