Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1006)

Unified Diff: sdk/lib/html/dart2js/html_dart2js.dart

Issue 12419011: Modern-ify KeyEvent handling. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
Download patch
« no previous file with comments | « no previous file | sdk/lib/html/dartium/html_dartium.dart » ('j') | tests/html/interactive_test.dart » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sdk/lib/html/dart2js/html_dart2js.dart
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index edf1fe7a485bcbe2bf884b5f0e566a7f71056348..b2650d1ddfa3c352e74209f3bd25578a404e83df 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -31181,17 +31181,10 @@ class _CustomEventStreamProvider<T extends Event>
/**
- * Works with KeyboardEvent and KeyEvent to determine how to expose information
- * about Key(board)Events. This class functions like an EventListenerList, and
- * provides a consistent interface for the Dart
- * user, despite the fact that a multitude of browsers that have varying
- * keyboard default behavior.
- *
- * This class is very much a work in progress, and we'd love to get information
- * on how we can make this class work with as many international keyboards as
- * possible. Bugs welcome!
+ * Internal class that does the actual calculations to determine keyCode and
+ * charCode for keydown, keypress, and keyup events for all browsers.
*/
-class KeyboardEventController {
+class _KeyboardEventHandler implements EventStreamProvider<KeyEvent> {
// This code inspired by Closure's KeyHandling library.
// http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandler.js.source.html
@@ -31213,8 +31206,8 @@ class KeyboardEventController {
// The distance to shift from upper case alphabet Roman letters to lower case.
final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0];
- StreamSubscription _keyUpSubscription, _keyDownSubscription,
- _keyPressSubscription;
+ /** Controller to produce KeyEvents for the stream. */
+ StreamController _controller;
/**
* An enumeration of key identifiers currently part of the W3C draft for DOM3
@@ -31247,53 +31240,41 @@ class KeyboardEventController {
'Insert': KeyCode.INSERT
};
- /** Named constructor to add an onKeyPress event listener to our handler. */
- KeyboardEventController.keypress(EventTarget target) {
- _KeyboardEventController(target, 'keypress');
- }
-
- /** Named constructor to add an onKeyUp event listener to our handler. */
- KeyboardEventController.keyup(EventTarget target) {
- _KeyboardEventController(target, 'keyup');
- }
+ /**
+ * Gets the type of the event which this would listen for on the specified
+ * event target.
+ */
+ String getEventType(EventTarget target) => 'KeyEvent';
- /** Named constructor to add an onKeyDown event listener to our handler. */
- KeyboardEventController.keydown(EventTarget target) {
- _KeyboardEventController(target, 'keydown');
+ /** Return a stream for KeyEvents for the specified target. */
+ Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) {
+ _initializeAllEventListeners(e);
+ return _controller.stream;
}
/**
* General constructor, performs basic initialization for our improved
* KeyboardEvent controller.
*/
- _KeyboardEventController(EventTarget target, String type) {
- _callbacks = [];
+ _KeyboardEventHandler(String type) {
_type = type;
- _target = target;
+ _controller = new StreamController.broadcast();
+ _callbacks = [];
}
/**
* Hook up all event listeners under the covers so we can estimate keycodes
* and charcodes when they are not provided.
*/
- void _initializeAllEventListeners() {
+ _initializeAllEventListeners(EventTarget target) {
+ _target = target;
_keyDownList = [];
- if (_keyDownSubscription == null) {
- _keyDownSubscription = Element.keyDownEvent.forTarget(
- _target, useCapture: true).listen(processKeyDown);
- _keyPressSubscription = Element.keyPressEvent.forTarget(
- _target, useCapture: true).listen(processKeyUp);
- _keyUpSubscription = Element.keyUpEvent.forTarget(
- _target, useCapture: true).listen(processKeyPress);
- }
- }
-
- /** Add a callback that wishes to be notified when a KeyEvent occurs. */
- void add(void callback(KeyEvent)) {
- if (_callbacks.length == 0) {
- _initializeAllEventListeners();
- }
- _callbacks.add(callback);
+ Element.keyDownEvent.forTarget(_target, useCapture: true).listen(
+ processKeyDown);
+ Element.keyPressEvent.forTarget(_target, useCapture: true).listen(
+ processKeyPress);
+ Element.keyUpEvent.forTarget(_target, useCapture: true).listen(
+ processKeyUp);
}
/**
@@ -31301,31 +31282,8 @@ class KeyboardEventController {
* occurred.
*/
bool _dispatch(KeyEvent event) {
- if (event.type == _type) {
- // Make a copy of the listeners in case a callback gets removed while
- // dispatching from the list.
- List callbacksCopy = new List.from(_callbacks);
- for(var callback in callbacksCopy) {
- callback(event);
- }
- }
- }
-
- /** Remove the given callback from the listeners list. */
- void remove(void callback(KeyEvent)) {
- var index = _callbacks.indexOf(callback);
- if (index != -1) {
- _callbacks.removeAt(index);
- }
- if (_callbacks.length == 0) {
- // If we have no listeners, don't bother keeping track of keypresses.
- _keyDownSubscription.cancel();
- _keyDownSubscription = null;
- _keyPressSubscription.cancel();
- _keyPressSubscription = null;
- _keyUpSubscription.cancel();
- _keyUpSubscription = null;
- }
+ if (event.type == _type)
+ _controller.add(event);
}
/** Determine if caps lock is one of the currently depressed keys. */
@@ -31584,6 +31542,363 @@ class KeyboardEventController {
_dispatch(e);
}
}
+
+
+/**
+ * Records KeyboardEvents that occur on a particular element, and provides a
+ * stream of outgoing KeyEvents with cross-browser consistent keyCode and
+ * charCode values despite the fact that a multitude of browsers that have
+ * varying keyboard default behavior.
+ *
+ * Example usage:
+ *
+ * new KeyboardEventStream.onKeyDown(document.body).listen(
+ * keydownHandlerTest);
+ *
+ * This class is very much a work in progress, and we'd love to get information
+ * on how we can make this class work with as many international keyboards as
+ * possible. Bugs welcome!
+ */
+class KeyboardEventStream implements Stream<KeyEvent> {
+ _KeyboardEventHandler _handler;
+ Stream<KeyEvent> _stream;
+
+ /** Named constructor to produce a stream for onKeyPress events. */
+ KeyboardEventStream.onKeyPress(EventTarget target) {
+ _handler = new _KeyboardEventHandler('keypress');
+ _stream = _handler.forTarget(target);
+ }
+
+ /** Named constructor to produce a stream for onKeyUp events. */
+ KeyboardEventStream.onKeyUp(EventTarget target) {
+ _handler = new _KeyboardEventHandler('keyup');
+ _stream = _handler.forTarget(target);
+ }
+
+ /** Named constructor to produce a stream for onKeyDown events. */
+ KeyboardEventStream.onKeyDown(EventTarget target) {
+ _handler = new _KeyboardEventHandler('keydown');
+ _stream = _handler.forTarget(target);
+ }
+
+ /**
+ * Unlike regular KeyboardEvents, you can programmatically add KeyEvents to
+ * this stream. Be careful, though! If you add a keyDown event without a
+ * corresponding keyUp event later, the stream may have difficulty estimating
+ * future key codes.
+ */
+ void addKeyDown(KeyEvent e) {
+ _handler.processKeyDown(e);
+ }
+
+ /**
+ * Unlike regular KeyboardEvents, you can programmatically add KeyEvents to
+ * this stream. Be careful, though! If you add a keyUp event without a
+ * corresponding keyDown event previously, the stream may have difficulty
+ * estimating future key codes.
+ */
+ void addKeyUp(KeyEvent e) {
+ _handler.processKeyUp(e);
+ }
+
+ /**
+ * Unlike regular KeyboardEvents, you can programmatically add KeyEvents to
+ * this stream.
+ */
+ void addKeyPress(KeyEvent e) {
+ _handler.processKeyPress(e);
+ }
+
+ // ---------------- Stream implementation methods: ---------------
+ /**
+ * Adds a subscription to this stream.
+ *
+ * On each data event from this stream, the subscriber's [onData] handler
+ * is called. If [onData] is null, nothing happens.
+ *
+ * On errors from this stream, the [onError] handler is given a
+ * [AsyncError] object describing the error.
+ *
+ * If this stream closes, the [onDone] handler is called.
+ *
+ * If [unsubscribeOnError] is true, the subscription is ended when
+ * the first error is reported. The default is false.
+ */
+ StreamSubscription<KeyEvent> listen(void onData(KeyEvent event),
+ {void onError(AsyncError error), void onDone(),
+ bool unsubscribeOnError}) => _stream.listen(onData, onError:
+ onError, onDone: onDone, unsubscribeOnError: unsubscribeOnError);
+
+ /**
+ * Reports whether this stream is a broadcast stream.
+ */
+ bool get isBroadcast => _stream.isBroadcast;
+
+ /** Counts the elements in the stream. */
+ Future<int> get length => _stream.length;
+
+ /** Reports whether this stream contains any elements. */
+ Future<bool> get isEmpty => _stream.isEmpty;
+
+ /**
+ * Returns the first element.
+ *
+ * If [this] is empty throws a [StateError]. Otherwise this method is
+ * equivalent to [:this.elementAt(0):]
+ */
+ Future<KeyEvent> get first => _stream.first;
+
+ /**
+ * Returns the last element.
+ *
+ * If [this] is empty throws a [StateError].
+ */
+ Future<KeyEvent> get last => _stream.last;
+
+ /**
+ * Returns the single element.
+ *
+ * If [this] is empty or has more than one element throws a [StateError].
+ */
+ Future<KeyEvent> get single => _stream.single;
+
+ /**
+ * Creates a new stream from this stream that converts each element
+ * into zero or more events.
+ *
+ * Each incoming event is converted to an [Iterable] of new events,
+ * and each of these new events are then sent by the returned stream
+ * in order.
+ */
+ Stream<dynamic> expand(Iterable convert(KeyEvent value)) =>
+ _stream.expand(convert);
+
+ /**
+ * Chains this stream as the input of the provided [StreamTransformer].
+ *
+ * Returns the result of [:streamTransformer.bind:] itself.
+ */
+ Stream<dynamic> transform(StreamTransformer<KeyEvent, dynamic>
+ streamTransformer) => _stream.transform(streamTransformer);
+
+ /**
+ * Checks whether [test] accepts any element provided by this stream.
+ *
+ * Completes the [Future] when the answer is known.
+ * If this stream reports an error, the [Future] will report that error.
+ */
+ Future<bool> any(bool test(KeyEvent element)) => _stream.any(test);
+
+ /**
+ * Returns a multi-subscription stream that produces the same events as this.
+ *
+ * If this stream is single-subscription, return a new stream that allows
+ * multiple subscribers. It will subscribe to this stream when its first
+ * subscriber is added, and unsubscribe again when the last subscription is
+ * cancelled.
+ *
+ * If this stream is already a broadcast stream, it is returned unmodified.
+ */
+ Stream<KeyEvent> asBroadcastStream() => _stream;
+
+ /**
+ * Checks whether [match] occurs in the elements provided by this stream.
+ *
+ * Completes the [Future] when the answer is known.
+ * If this stream reports an error, the [Future] will report that error.
+ */
+ Future<bool> contains(KeyEvent match) => _stream.contains(match);
+
+ /**
+ * Skips data events if they are equal to the previous data event.
+ *
+ * The returned stream provides the same events as this stream, except
+ * that it never provides two consequtive data events that are equal.
+ *
+ * Equality is determined by the provided [equals] method. If that is
+ * omitted, the '==' operator on the last provided data element is used.
+ */
+ Stream<KeyEvent> distinct([bool equals(KeyEvent previous, KeyEvent next)]) =>
+ _stream.distinct(equals);
+
+ /**
+ * Returns the value of the [index]th data event of this stream.
+ *
+ * If an error event occurs, the future will end with this error.
+ *
+ * If this stream provides fewer than [index] elements before closing,
+ * an error is reported.
+ */
+ Future<KeyEvent> elementAt(int index) => _stream.elementAt(index);
+
+ /**
+ * Checks whether [test] accepts all elements provided by this stream.
+ *
+ * Completes the [Future] when the answer is known.
+ * If this stream reports an error, the [Future] will report that error.
+ */
+ Future<bool> every(bool test(KeyEvent element)) =>
+ _stream.every(test);
+
+ /**
+ * Finds the first element of this stream matching [test].
+ *
+ * Returns a future that is filled with the first element of this stream
+ * that [test] returns true for.
+ *
+ * If no such element is found before this stream is done, and a
+ * [defaultValue] function is provided, the result of calling [defaultValue]
+ * becomes the value of the future.
+ *
+ * If an error occurs, or if this stream ends without finding a match and
+ * with no [defaultValue] function provided, the future will receive an
+ * error.
+ */
+ Future<KeyEvent> firstWhere(bool test(KeyEvent value),
+ {KeyEvent defaultValue()}) => _stream.firstWhere(test);
+
+ /**
+ * Creates a wrapper Stream that intercepts some errors from this stream.
+ *
+ * If this stream sends an error that matches [test], then it is intercepted
+ * by the [handle] function.
+ *
+ * An [AsyncError] [:e:] is matched by a test function if [:test(e):] returns
+ * true. If [test] is omitted, every error is considered matching.
+ *
+ * If the error is intercepted, the [handle] function can decide what to do
+ * with it. It can throw if it wants to raise a new (or the same) error,
+ * or simply return to make the stream forget the error.
+ *
+ * If you need to transform an error into a data event, use the more generic
+ * [Stream.transformEvent] to handle the event by writing a data event to
+ * the output sink
+ */
+ Stream<KeyEvent> handleError(void handle(AsyncError error),
+ {bool test(error)}) => _stream.handleError(handle);
+
+ /**
+ * Finds the last element in this stream matching [test].
+ *
+ * As [firstWhere], except that the last matching element is found.
+ * That means that the result cannot be provided before this stream
+ * is done.
+ */
+ Future<KeyEvent> lastWhere(bool test(KeyEvent value),
+ {KeyEvent defaultValue()}) => _stream.lastWhere(test);
+
+ /**
+ * Creates a new stream that converts each element of this stream
+ * to a new value using the [convert] function.
+ */
+ Stream map(convert(KeyEvent event)) => _stream.map(convert);
+
+ /**
+ * Finds the largest element in the stream.
+ *
+ * If the stream is empty, the result is [:null:].
+ * Otherwise the result is an value from the stream that is not smaller
+ * than any other value from the stream (according to [compare], which must
+ * be a [Comparator]).
+ *
+ * If [compare] is omitted, it defaults to [Comparable.compare].
+ *
+ * *Deprecated*. Use [reduce] with a binary max method if needed.
+ */
+ Future<KeyEvent> max([int compare(KeyEvent a, KeyEvent b)]) =>
+ _stream.max(compare);
+
+ /**
+ * Finds the least element in the stream.
+ *
+ * If the stream is empty, the result is [:null:].
+ * Otherwise the result is a value from the stream that is not greater
+ * than any other value from the stream (according to [compare], which must
+ * be a [Comparator]).
+ *
+ * If [compare] is omitted, it defaults to [Comparable.compare].
+ *
+ * *Deprecated*. Use [reduce] with a binary min method if needed.
+ */
+ Future<KeyEvent> min([int compare(KeyEvent a, KeyEvent b)]) =>
+ _stream.min(compare);
+
+ /**
+ * Binds this stream as the input of the provided [StreamConsumer].
+ */
+ Future pipe(StreamConsumer<KeyEvent, dynamic> streamConsumer) =>
+ _stream.pipe(streamConsumer);
+
+ Future pipeInto(EventSink<KeyEvent> sink, {void onError(AsyncError error),
+ bool unsubscribeOnError}) => _stream.pipeInto(sink, onError:
+ onError, unsubscribeOnError: unsubscribeOnError);
+
+ /** Reduces a sequence of values by repeatedly applying [combine]. */
+ Future reduce(initialValue, combine(previous, KeyEvent element)) =>
+ _stream.reduce(initialValue, combine);
+
+ /**
+ * Finds the single element in this stream matching [test].
+ *
+ * Like [lastMatch], except that it is an error if more than one
+ * matching element occurs in the stream.
+ */
+ Future<KeyEvent> singleWhere(bool test(KeyEvent value)) =>
+ _stream.singleWhere(test);
+
+ /**
+ * Skips the first [count] data events from this stream.
+ */
+ Stream<KeyEvent> skip(int count) => _stream.skip(count);
+
+ /**
+ * Skip data events from this stream while they are matched by [test].
+ *
+ * Error and done events are provided by the returned stream unmodified.
+ *
+ * Starting with the first data event where [test] returns true for the
+ * event data, the returned stream will have the same events as this stream.
+ */
+ Stream<KeyEvent> skipWhile(bool test(KeyEvent value)) =>
+ _stream.skipWhile(test);
+
+ /**
+ * Provides at most the first [n] values of this stream.
+ *
+ * Forwards the first [n] data events of this stream, and all error
+ * events, to the returned stream, and ends with a done event.
+ *
+ * If this stream produces fewer than [count] values before it's done,
+ * so will the returned stream.
+ */
+ Stream<KeyEvent> take(int count) => _stream.take(count);
+
+ /**
+ * Forwards data events while [test] is successful.
+ *
+ * The returned stream provides the same events as this stream as long
+ * as [test] returns [:true:] for the event data. The stream is done
+ * when either this stream is done, or when this stream first provides
+ * a value that [test] doesn't accept.
+ */
+ Stream<KeyEvent> takeWhile(bool test(KeyEvent value)) =>
+ _stream.takeWhile(test);
+
+ /** Collects the data of this stream in a [List]. */
+ Future<List<KeyEvent>> toList() => _stream.toList();
+
+ /** Collects the data of this stream in a [Set]. */
+ Future<Set<KeyEvent>> toSet() => _stream.toSet();
+
+ /**
+ * Creates a new stream from this stream that discards some data events.
+ *
+ * The new stream sends the same error and done events as this stream,
+ * but it only sends the data events that satisfy the [test].
+ */
+ Stream<KeyEvent> where(bool test(KeyEvent event)) =>
+ _stream.where(test);
+}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@@ -33671,6 +33986,9 @@ class _HistoryCrossFrame implements HistoryBase {
* inconsistencies, and also provide both keyCode and charCode information
* for all key events (when such information can be determined).
*
+ * KeyEvent tries to provide a higher level, more polished keyboard event
+ * information on top of the "raw" [KeyboardEvent].
+ *
* This class is very much a work in progress, and we'd love to get information
* on how we can make this class work with as many international keyboards as
* possible. Bugs welcome!
@@ -33709,7 +34027,7 @@ class KeyEvent implements KeyboardEvent {
/** Accessor to the underlying altKey value is the parent event. */
bool get _realAltKey => JS('int', '#.altKey', _parent);
- /** Construct a KeyEvent with [parent] as event we're emulating. */
+ /** Construct a KeyEvent with [parent] as the event we're emulating. */
KeyEvent(KeyboardEvent parent) {
_parent = parent;
_shadowAltKey = _realAltKey;
@@ -33717,6 +34035,18 @@ class KeyEvent implements KeyboardEvent {
_shadowKeyCode = _realKeyCode;
}
+ // TODO(efortuna): If KeyEvent is sufficiently successful that we want to make
+ // it the default keyboard event handling, move these methods over to Element.
+ /** Accessor to provide a stream of KeyEvents on the desired target. */
+ static EventStreamProvider<KeyEvent> keyDownEvent =
+ new _KeyboardEventHandler('keydown');
+ /** Accessor to provide a stream of KeyEvents on the desired target. */
+ static EventStreamProvider<KeyEvent> keyUpEvent =
+ new _KeyboardEventHandler('keyup');
+ /** Accessor to provide a stream of KeyEvents on the desired target. */
+ static EventStreamProvider<KeyEvent> keyPressEvent =
+ new _KeyboardEventHandler('keypress');
+
/** True if the altGraphKey is pressed during this event. */
bool get altGraphKey => _parent.altGraphKey;
bool get bubbles => _parent.bubbles;
« no previous file with comments | « no previous file | sdk/lib/html/dartium/html_dartium.dart » ('j') | tests/html/interactive_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698