diff --git a/examples/keyboard-shortcuts/app.js b/examples/keyboard-shortcuts/app.js
new file mode 100644
index 000000000..a458eb2d9
--- /dev/null
+++ b/examples/keyboard-shortcuts/app.js
@@ -0,0 +1,61 @@
+requirejs.config({
+ paths: {
+ 'rx.all': '../../dist/rx.all',
+ 'jquery': 'https://code.jquery.com/jquery-3.1.0.min'
+ }
+});
+
+// Load the main app module to start the app
+requirejs(['jquery', 'keyboard-shortcuts'], function($, kbShortcuts) {
+
+ var shortuctSequences = Rx.Observable
+ .fromEvent(document.querySelector("button"), 'click')
+ .map( click => document.querySelector("input").value )
+ .startWith('Ctrl+Alt+D', 'Ctrl+Shift+S', 'Trash')
+ .map( text => {
+ return {
+ id: text.replace(/\+/g,'_'),
+ text: text
+ }
+ });
+
+ var validShortcuts = shortuctSequences.filter( seq => kbShortcuts.validate(seq.text) );
+
+ var invalidShortcuts = shortuctSequences.filter( seq => !kbShortcuts.validate(seq.text) );
+
+ var shortCutPrompts = validShortcuts
+ .flatMap( obj => {
+ return kbShortcuts.create(obj.text)
+ .scan((acc, x, seed) => acc + 1, 0)
+ .map( count => {
+ return {
+ id: obj.id,
+ count: count
+ }
+ })
+ }
+ );
+
+
+ // Subscriptions for Side Effects
+
+ validShortcuts.subscribe(
+ seq => {
+ var tmpl = '
' + seq.text + ': 0';
+ var li = $(tmpl);
+ $('ul').append(li);
+ }
+ );
+
+ invalidShortcuts.subscribe( seq => {
+ var tmpl = 'Invalid sequence ' + seq.text + '';
+ var li = $(tmpl);
+ $('ul').append(li);
+ }
+ );
+
+ shortCutPrompts.subscribe(
+ obj => $('ul > li#' + obj.id + ' > span').eq(1).text(obj.count)
+ );
+
+});
diff --git a/examples/keyboard-shortcuts/keyCodeMap.js b/examples/keyboard-shortcuts/keyCodeMap.js
new file mode 100644
index 000000000..08ef5d8fa
--- /dev/null
+++ b/examples/keyboard-shortcuts/keyCodeMap.js
@@ -0,0 +1,107 @@
+define([], function (Rx) {
+
+ var map = {
+ 'backspace': 8,
+ 'tab': 9,
+ 'enter': 13,
+ 'shift': 16,
+ 'ctrl': 17,
+ 'alt': 18,
+ 'pause/break': 19,
+ 'caps lock': 20,
+ 'escape': 27,
+ 'page up': 33,
+ 'page down': 34,
+ 'end': 35,
+ 'home': 36,
+ 'left arrow': 37,
+ 'up arrow': 38,
+ 'right arrow': 39,
+ 'down arrow': 40,
+ 'insert': 45,
+ 'delete': 46,
+ '0': 48,
+ '1': 49,
+ '2': 50,
+ '3': 51,
+ '4': 52,
+ '5': 53,
+ '6': 54,
+ '7': 55,
+ '8': 56,
+ '9': 57,
+ 'a': 65,
+ 'b': 66,
+ 'c': 67,
+ 'd': 68,
+ 'e': 69,
+ 'f': 70,
+ 'g': 71,
+ 'h': 72,
+ 'i': 73,
+ 'j': 74,
+ 'k': 75,
+ 'l': 76,
+ 'm': 77,
+ 'n': 78,
+ 'o': 79,
+ 'p': 80,
+ 'q': 81,
+ 'r': 82,
+ 's': 83,
+ 't': 84,
+ 'u': 85,
+ 'v': 86,
+ 'w': 87,
+ 'x': 88,
+ 'y': 89,
+ 'z': 90,
+ 'left window key': 91,
+ 'right window key': 92,
+ 'select key': 93,
+ 'numpad 0': 96,
+ 'numpad 1': 97,
+ 'numpad 2': 98,
+ 'numpad 3': 99,
+ 'numpad 4': 100,
+ 'numpad 5': 101,
+ 'numpad 6': 102,
+ 'numpad 7': 103,
+ 'numpad 8': 104,
+ 'numpad 9': 105,
+ 'multiply': 106,
+ 'add': 107,
+ 'subtract': 109,
+ 'decimal point': 110,
+ 'divide': 111,
+ 'f1': 112,
+ 'f2': 113,
+ 'f3': 114,
+ 'f4': 115,
+ 'f5': 116,
+ 'f6': 117,
+ 'f7': 118,
+ 'f8': 119,
+ 'f9': 120,
+ 'f10': 121,
+ 'f11': 122,
+ 'f12': 123,
+ 'num lock': 144,
+ 'scroll lock': 145,
+ 'semi-colon': 186,
+ 'equal sign': 187,
+ 'comma': 188,
+ 'dash': 189,
+ 'period': 190,
+ 'forward slash': 191,
+ 'grave accent': 192,
+ 'open bracket': 219,
+ 'back slash': 220,
+ 'close braket': 221,
+ 'single quote': 222,
+ };
+
+ return map;
+
+});
+
diff --git a/examples/keyboard-shortcuts/keyboard-shortcuts.html b/examples/keyboard-shortcuts/keyboard-shortcuts.html
new file mode 100644
index 000000000..f1e6da36c
--- /dev/null
+++ b/examples/keyboard-shortcuts/keyboard-shortcuts.html
@@ -0,0 +1,15 @@
+
+
+
+ Keyboarb Shortcuts
+
+
+
+
+
+
+ Keyboard shortcuts:
+
+
+
diff --git a/examples/keyboard-shortcuts/keyboard-shortcuts.js b/examples/keyboard-shortcuts/keyboard-shortcuts.js
new file mode 100644
index 000000000..21d74a967
--- /dev/null
+++ b/examples/keyboard-shortcuts/keyboard-shortcuts.js
@@ -0,0 +1,71 @@
+define(['rx.all', 'keyCodeMap'], function (Rx, keyCodeMap) {
+
+ var keyDowns = Rx.Observable.fromEvent(document, 'keydown');
+
+ var keyUps = Rx.Observable.fromEvent(document, 'keyup');
+
+ var keyEvents = Rx.Observable
+ .merge(keyDowns, keyUps)
+ .distinctUntilChanged(
+ null,
+ (a,b) => {
+ return a.keyCode === b.keyCode && a.type === b.type;
+ }
+ )
+ .share();
+
+ var createKeyPressStream = (charCode) => {
+ return {
+ char: charCode,
+ stream: keyEvents
+ .filter((event) => event.keyCode === charCode)
+ .map(function(e) {
+ return e.type;
+ })
+ };
+ }
+
+ var createShortcutStream = (text) => {
+
+ return Rx.Observable
+ .from(text.split('+'))
+ .map( c => {
+ var code = keyCodeMap[c.toLowerCase()];
+ if(code === undefined) {
+ throw new Error('Invalid sequence ' + text);
+ }
+ return code;
+ })
+ .map(createKeyPressStream)
+ .map( obj => obj.stream)
+ .toArray()
+ .flatMap( arr => {
+ return Rx.Observable.combineLatest(arr);
+ })
+ .filter( arr => {
+ var isDown = true;
+ for (var i = 0; i < arr.length; i++) {
+ isDown = isDown && (arr[i] === 'keydown');
+ }
+ return isDown;
+ })
+ .map( x => text);
+
+ }
+
+ var validateSeq = text => {
+ var arr = text.split('+');
+ for (var i = 0; i < arr.length; i++) {
+ if(keyCodeMap[arr[i].toLowerCase()] === undefined) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return {
+ create: createShortcutStream,
+ validate: validateSeq
+ };
+
+});
diff --git a/examples/keyboard-shortcuts/readme.md b/examples/keyboard-shortcuts/readme.md
new file mode 100644
index 000000000..781b0f744
--- /dev/null
+++ b/examples/keyboard-shortcuts/readme.md
@@ -0,0 +1,5 @@
+Keyboard shorcuts is an Observable that emit events when a specified sequence of characters are pressed at the same time.
+
+There is a function to create those Observables, which receives a text as parameter.
+
+Also it has a simple UI to create a and display keyboard shortcut occurrences, which shows usage of several Observables operators.
\ No newline at end of file