blob: e0f0663b6fffd8963f8ca347d81c872497667075 [file] [log] [blame]
<!--
-- Copyright 2013 The Chromium Authors. All rights reserved.
-- Use of this source code is governed by a BSD-style license that can be
-- found in the LICENSE file.
-->
<polymer-element name="kb-keyboard" on-key-over="{{keyOver}}"
on-key-up="{{keyUp}}" on-key-down="{{keyDown}}"
on-key-longpress="{{keyLongpress}}" on-pointerup="{{up}}"
on-pointerdown="{{down}}" on-pointerout="{{out}}"
on-enable-sel="{{enableSel}}" on-enable-dbl="{{enableDbl}}"
on-key-out="{{keyOut}}" on-show-options="{{showOptions}}"
on-set-layout="{{setLayout}}"
attributes="keyset layout inputType inputTypeToLayoutMap">
<template>
<style>
@host {
* {
position: relative;
}
}
</style>
<!-- The ID for a keyset follows the naming convention of combining the
-- layout name with a base keyset name. This convention is used to
-- allow multiple layouts to be loaded (enablign fast switching) while
-- allowing the shift and spacebar keys to be common across multiple
-- keyboard layouts.
-->
<content id="content" select="#{{layout}}-{{keyset}}"></content>
<kb-keyboard-overlay id="overlay" hidden></kb-keyboard-overlay>
<kb-key-codes id="keyCodeMetadata"></kb-key-codes>
</template>
<script>
/**
* The repeat delay in milliseconds before a key starts repeating. Use the
* same rate as Chromebook.
* (See chrome/browser/chromeos/language_preferences.cc)
* @const
* @type {number}
*/
var REPEAT_DELAY_MSEC = 500;
/**
* The repeat interval or number of milliseconds between subsequent
* keypresses. Use the same rate as Chromebook.
* @const
* @type {number}
*/
var REPEAT_INTERVAL_MSEC = 50;
/**
* The double click/tap interval.
* @const
* @type {number}
*/
var DBL_INTERVAL_MSEC = 300;
/**
* The index of the name of the keyset when searching for all keysets.
* @const
* @type {number}
*/
var REGEX_KEYSET_INDEX = 1;
/**
* The integer number of matches when searching for keysets.
* @const
* @type {number}
*/
var REGEX_MATCH_COUNT = 2;
/**
* The boolean to decide if keyboard should transit to upper case keyset
* when spacebar is pressed. If a closing punctuation is followed by a
* spacebar, keyboard should automatically transit to upper case.
* @type {boolean}
*/
var enterUpperOnSpace = false;
/**
* A structure to track the currently repeating key on the keyboard.
*/
var repeatKey = {
/**
* The timer for the delay before repeating behaviour begins.
* @type {number|undefined}
*/
timer: undefined,
/**
* The interval timer for issuing keypresses of a repeating key.
* @type {number|undefined}
*/
interval: undefined,
/**
* The key which is currently repeating.
* @type {BaseKey|undefined}
*/
key: undefined,
/**
* Cancel the repeat timers of the currently active key.
*/
cancel: function() {
clearTimeout(this.timer);
clearInterval(this.interval);
this.timer = undefined;
this.interval = undefined;
this.key = undefined;
}
};
/**
* The minimum movement interval needed to trigger cursor move on
* horizontal and vertical way.
* @const
* @type {number}
*/
var MIN_SWIPE_DIST_X = 50;
var MIN_SWIPE_DIST_Y = 20;
/**
* The maximum swipe distance that will trigger hintText of a key
* to be typed.
* @const
* @type {number}
*/
var MAX_SWIPE_FLICK_DIST = 60;
/**
* The boolean to decide if it is swipe in process or finished.
* @type {boolean}
*/
var swipeInProgress = false;
// Flag values for ctrl, alt and shift as defined by EventFlags
// in "event_constants.h".
// @enum {number}
var Modifier = {
NONE: 0,
ALT: 8,
CONTROL: 4,
SHIFT: 2
};
/**
* The enumeration of swipe directions.
* @const
* @type {Enum}
*/
var SWIPE_DIRECTION = {
RIGHT: 0x1,
LEFT: 0x2,
UP: 0x4,
DOWN: 0x8
};
/**
* A structure to track the current swipe status.
*/
var swipeTracker = {
/**
* The latest PointerMove event in the swipe.
* @type {Object}
*/
currentEvent: undefined,
/**
* Whether or not a swipe changes direction.
* @type {false}
*/
isComplex: false,
/**
* The count of horizontal and vertical movement.
* @type {number}
*/
offset_x : 0,
offset_y : 0,
/**
* Last touch coordinate.
* @type {number}
*/
pre_x : 0,
pre_y : 0,
/**
* The PointerMove event which triggered the swipe.
* @type {Object}
*/
startEvent: undefined,
/**
* The flag of current modifier key.
* @type {number}
*/
swipeFlags : 0,
/**
* Current swipe direction.
* @type {number}
*/
swipeDirection : 0,
/**
* The number of times we've swiped within a single swipe.
* @type {number}
*/
swipeIndex: 0,
/**
* Returns the combined direction of the x and y offsets.
* @return {number} The latest direction.
*/
getOffsetDirection: function() {
// TODO (rsadam): Use angles to figure out the direction.
var direction = 0;
// Checks for horizontal swipe.
if (Math.abs(this.offset_x) > MIN_SWIPE_DIST_X) {
if (this.offset_x > 0) {
direction |= SWIPE_DIRECTION.RIGHT;
} else {
direction |= SWIPE_DIRECTION.LEFT;
}
}
// Checks for vertical swipe.
if (Math.abs(this.offset_y) > MIN_SWIPE_DIST_Y) {
if (this.offset_y < 0) {
direction |= SWIPE_DIRECTION.UP;
} else {
direction |= SWIPE_DIRECTION.DOWN;
}
}
return direction;
},
/**
* Populates the swipe update details.
* @param {boolean} endSwipe Whether this is the final event for this
* swipe.
* @return {Object} The current state of the swipeTracker.
*/
populateDetails: function(endSwipe) {
var detail = {};
detail.direction = this.swipeDirection;
detail.index = this.swipeIndex;
detail.status = this.swipeStatus;
detail.endSwipe = endSwipe;
detail.startEvent = this.startEvent;
detail.currentEvent = this.currentEvent;
detail.isComplex = this.isComplex;
return detail;
},
/**
* Reset all the values when swipe finished.
*/
resetAll: function() {
this.offset_x = 0;
this.offset_y = 0;
this.pre_x = 0;
this.pre_y = 0;
this.swipeFlags = 0;
this.swipeDirection = 0;
this.swipeIndex = 0;
this.startEvent = undefined;
this.currentEvent = undefined;
this.isComplex = false;
},
/**
* Updates the swipe path with the current event.
* @param {Object} event The PointerEvent that triggered this update.
* @return {boolean} Whether or not to notify swipe observers.
*/
update: function(event) {
if(!event.isPrimary)
return false;
// Update priors.
this.offset_x += event.screenX - this.pre_x;
this.offset_y += event.screenY - this.pre_y;
this.pre_x = event.screenX;
this.pre_y = event.screenY;
// Check if movement crosses minimum thresholds in each direction.
var direction = this.getOffsetDirection();
if (direction == 0)
return false;
// If swipeIndex is zero the current event is triggering the swipe.
if (this.swipeIndex == 0) {
this.startEvent = event;
} else if (direction != this.swipeDirection) {
// Toggle the isComplex flag.
this.isComplex = true;
}
// Update the swipe tracker.
this.swipeDirection = direction;
this.offset_x = 0;
this.offset_y = 0;
this.currentEvent = event;
this.swipeIndex++;
return true;
},
};
Polymer('kb-keyboard', {
alt: null,
control: null,
dblDetail_: null,
dblTimer_: null,
inputType: null,
lastPressedKey: null,
shift: null,
swipeHandler: null,
voiceInput_: null,
/**
* The default input type to keyboard layout map. The key must be one of
* the input box type values.
* @type {object}
*/
inputTypeToLayoutMap: {
number: "numeric",
text: "qwerty",
password: "qwerty"
},
/**
* Changes the current keyset.
* @param {Object} detail The detail of the event that called this
* function.
*/
changeKeyset: function(detail) {
if (detail.relegateToShift && this.shift) {
this.keyset = this.shift.textKeyset;
this.activeKeyset.nextKeyset = undefined;
return true;
}
var toKeyset = detail.toKeyset;
if (toKeyset) {
this.keyset = toKeyset;
this.activeKeyset.nextKeyset = detail.nextKeyset;
return true;
}
return false;
},
ready: function() {
this.voiceInput_ = new VoiceInput(this);
this.swipeHandler = this.move.bind(this);
},
/**
* Registers a callback for state change events. Lazy initializes a
* mutation observer used to detect when the keyset selection is changed.
* @param{!Function} callback Callback function to register.
*/
addKeysetChangedObserver: function(callback) {
if (!this.keysetChangedObserver) {
var target = this.$.content;
var self = this;
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
if (m.type == 'attributes' && m.attributeName == 'select') {
var value = m.target.getAttribute('select');
self.fire('stateChange', {
state: 'keysetChanged',
value: value
});
}
});
});
observer.observe(target, {
attributes: true,
childList: true,
subtree: true
});
this.keysetChangedObserver = observer;
}
this.addEventListener('stateChange', callback);
},
/**
* Called when the type of focused input box changes. If a keyboard layout
* is defined for the current input type, that layout will be loaded.
* Otherwise, the keyboard layout for 'text' type will be loaded.
*/
inputTypeChanged: function() {
// TODO(bshe): Toggle visibility of some keys in a keyboard layout
// according to the input type.
var layout = this.inputTypeToLayoutMap[this.inputType];
if (!layout)
layout = this.inputTypeToLayoutMap.text;
this.layout = layout;
},
/**
* When double click/tap event is enabled, the second key-down and key-up
* events on the same key should be skipped. Return true when the event
* with |detail| should be skipped.
* @param {Object} detail The detail of key-up or key-down event.
*/
skipEvent: function(detail) {
if (this.dblDetail_) {
if (this.dblDetail_.char != detail.char) {
// The second key down is not on the same key. Double click/tap
// should be ignored.
this.dblDetail_ = null;
clearTimeout(this.dblTimer_);
} else if (this.dblDetail_.clickCount == 1) {
return true;
}
}
return false;
},
/**
* Handles a swipe update.
* param {Object} detail The swipe update details.
*/
onSwipeUpdate: function(detail) {
var direction = detail.direction;
if (!direction)
console.error("Swipe direction cannot be: " + direction);
// Triggers swipe editting if it's a purely horizontal swipe.
if (!(direction & (SWIPE_DIRECTION.UP | SWIPE_DIRECTION.DOWN))) {
// Nothing to do if the swipe has ended.
if (detail.endSwipe)
return;
var modifiers = 0;
// TODO (rsadam): This doesn't take into account index shifts caused
// by vertical swipes.
if (detail.index % 2 != 0) {
modifiers |= Modifier.SHIFT;
modifiers |= Modifier.CONTROL;
}
MoveCursor(direction, modifiers);
return;
}
// Triggers swipe hintText if it's a purely vertical swipe.
if (!(direction & (SWIPE_DIRECTION.LEFT | SWIPE_DIRECTION.RIGHT))) {
// Check if event is relevant to us.
if ((!detail.endSwipe) ||
(direction & SWIPE_DIRECTION.DOWN) ||
(detail.isComplex)) {
return;
}
// Too long a swipe.
var distance = Math.abs(detail.startEvent.screenY -
detail.currentEvent.screenY);
if (distance > MAX_SWIPE_FLICK_DIST)
return;
var triggerKey = detail.startEvent.target;
// Swipe-up on a key without hintText.
if (!triggerKey || !triggerKey.hintTextValue)
return;
var detail = {};
detail.char = triggerKey.hintTextValue;
this.keyTyped(detail);
return;
}
},
/**
* This function is bound to swipeHandler. Updates the current swipe
* status so that PointerEvents can be converted to Swipe events.
* @param {PointerEvent} event.
*/
move: function(event) {
if (!swipeTracker.update(event))
return;
// Conversion was successful, swipe is now in progress.
swipeInProgress = true;
if (this.lastPressedKey) {
this.lastPressedKey.classList.remove('active');
this.lastPressedKey = null;
}
this.onSwipeUpdate(swipeTracker.populateDetails(false));
},
/**
* Handles key-down event that is sent by kb-key-base.
* @param {CustomEvent} event The key-down event dispatched by
* kb-key-base.
* @param {Object} detail The detail of pressed kb-key.
*/
keyDown: function(event, detail) {
if (this.skipEvent(detail))
return;
if (this.lastPressedKey) {
this.lastPressedKey.classList.remove('active');
this.lastPressedKey.autoRelease();
}
this.lastPressedKey = event.target;
this.lastPressedKey.classList.add('active');
repeatKey.cancel();
var char = detail.char;
switch(char) {
case 'Shift':
this.classList.remove('caps-locked');
break;
case 'Alt':
case 'Ctrl':
var modifier = char.toLowerCase() + "-active";
// Removes modifier if already active.
if (this.classList.contains(modifier))
this.classList.remove(modifier);
break;
default:
// Notify shift key.
if (this.shift)
this.shift.onNonControlKeyDown();
if (this.ctrl)
this.ctrl.onNonControlKeyDown();
if (this.alt)
this.alt.onNonControlKeyDown();
break;
}
if(this.changeKeyset(detail))
return;
if (detail.repeat) {
this.keyTyped(detail);
this.onNonControlKeyTyped();
repeatKey.key = this.lastPressedKey;
var self = this;
repeatKey.timer = setTimeout(function() {
repeatKey.timer = undefined;
repeatKey.interval = setInterval(function() {
self.keyTyped(detail);
}, REPEAT_INTERVAL_MSEC);
}, Math.max(0, REPEAT_DELAY_MSEC - REPEAT_INTERVAL_MSEC));
}
},
/**
* Handles key-out event that is sent by kb-shift-key.
* @param {CustomEvent} event The key-out event dispatched by
* kb-shift-key.
* @param {Object} detail The detail of pressed kb-shift-key.
*/
keyOut: function(event, detail) {
this.changeKeyset(detail);
},
/**
* Enable/start double click/tap event recognition.
* @param {CustomEvent} event The enable-dbl event dispatched by
* kb-shift-key.
* @param {Object} detail The detail of pressed kb-shift-key.
*/
enableDbl: function(event, detail) {
if (!this.dblDetail_) {
this.dblDetail_ = detail;
this.dblDetail_.clickCount = 0;
var self = this;
this.dblTimer_ = setTimeout(function() {
self.dblDetail_.callback = null;
self.dblDetail_ = null;
}, DBL_INTERVAL_MSEC);
}
},
/**
* Enable the selection while swipe.
* @param {CustomEvent} event The enable-dbl event dispatched by
* kb-shift-key.
*/
enableSel: function(event) {
// TODO(rsadam): Disabled for now. May come back if we revert swipe
// selection to not do word selection.
},
/**
* Handles pointerdown event. This is used for swipe selection process.
* to get the start pre_x and pre_y. And also add a pointermove handler
* to start handling the swipe selection event.
* @param {PointerEvent} event The pointerup event that received by
* kb-keyboard.
*/
down: function(event) {
if (event.isPrimary) {
swipeTracker.pre_x = event.screenX;
swipeTracker.pre_y = event.screenY;
this.addEventListener("pointermove", this.swipeHandler, false);
}
},
/**
* Handles pointerup event. This is used for double tap/click events.
* @param {PointerEvent} event The pointerup event that bubbled to
* kb-keyboard.
*/
up: function(event) {
// When touch typing, it is very possible that finger moves slightly out
// of the key area before releases. The key should not be dropped in
// this case.
if (this.lastPressedKey &&
this.lastPressedKey.pointerId == event.pointerId) {
this.lastPressedKey.autoRelease();
}
if (this.dblDetail_) {
this.dblDetail_.clickCount++;
if (this.dblDetail_.clickCount == 2) {
this.dblDetail_.callback();
this.changeKeyset(this.dblDetail_);
clearTimeout(this.dblTimer_);
this.classList.add('caps-locked');
this.dblDetail_ = null;
}
}
// TODO(zyaozhujun): There are some edge cases to deal with later.
// (for instance, what if a second finger trigger a down and up
// event sequence while swiping).
// When pointer up from the screen, a swipe selection session finished,
// all the data should be reset to prepare for the next session.
if (event.isPrimary && swipeInProgress) {
swipeInProgress = false;
this.onSwipeUpdate(swipeTracker.populateDetails(true))
swipeTracker.resetAll();
}
this.removeEventListener('pointermove', this.swipeHandler, false);
},
/**
* Handles PointerOut event. This is used for when a swipe gesture goes
* outside of the keyboard window.
* @param {Object} event The pointerout event that bubbled to the
* kb-keyboard.
*/
out: function(event) {
// Ignore if triggered from one of the keys.
if (this.compareDocumentPosition(event.relatedTarget) &
Node.DOCUMENT_POSITION_CONTAINED_BY)
return;
if (swipeInProgress)
this.onSwipeUpdate(swipeTracker.populateDetails(true))
// Touched outside of the keyboard area, so disables swipe.
swipeInProgress = false;
swipeTracker.resetAll();
this.removeEventListener('pointermove', this.swipeHandler, false);
},
/**
* Handles key-up event that is sent by kb-key-base.
* @param {CustomEvent} event The key-up event dispatched by kb-key-base.
* @param {Object} detail The detail of pressed kb-key.
*/
keyUp: function(event, detail) {
if (this.skipEvent(detail))
return;
if (swipeInProgress)
return;
if (detail.activeModifier) {
var modifier = detail.activeModifier.toLowerCase() + "-active";
this.classList.add(modifier);
}
// Adds the current keyboard modifiers to the detail.
if (this.ctrl)
detail.controlModifier = this.ctrl.isActive();
if (this.alt)
detail.altModifier = this.alt.isActive();
if (this.lastPressedKey)
this.lastPressedKey.classList.remove('active');
// Keyset transition key. This is needed to transition from upper
// to lower case when we are not in caps mode, as well as when
// we're ending chording.
this.changeKeyset(detail);
if (this.lastPressedKey &&
this.lastPressedKey.charValue != event.target.charValue) {
return;
}
if (repeatKey.key == event.target) {
repeatKey.cancel();
this.lastPressedKey = null;
return;
}
var toLayoutId = detail.toLayout;
// Layout transition key.
if (toLayoutId)
this.layout = toLayoutId;
var char = detail.char;
this.lastPressedKey = null;
// Characters that should not be typed.
switch(char) {
case 'Invalid':
case 'Shift':
case 'Ctrl':
case 'Alt':
enterUpperOnSpace = false;
swipeTracker.swipeFlags = 0;
return;
case 'Microphone':
this.voiceInput_.onDown();
return;
default:
break;
}
// Tries to type the character. Resorts to insertText if that fails.
if(!this.keyTyped(detail))
insertText(char);
// Post-typing logic.
switch(char) {
case ' ':
if(enterUpperOnSpace) {
enterUpperOnSpace = false;
if (this.shift) {
var shiftDetail = this.shift.onSpaceAfterPunctuation();
// Check if transition defined.
this.changeKeyset(shiftDetail);
} else {
console.error('Capitalization on space after punctuation \
enabled, but cannot find target keyset.');
}
// Immediately return to maintain shift-state. Space is a
// non-control key and would otherwise trigger a reset of the
// shift key, causing a transition to lower case.
// TODO(rsadam): Add unit test after Polymer uprev complete.
return;
}
break;
case '.':
case '?':
case '!':
enterUpperOnSpace = this.shouldUpperOnSpace();
break;
default:
break;
}
// Reset control keys.
this.onNonControlKeyTyped();
},
/*
* Handles key-longpress event that is sent by kb-key-base.
* @param {CustomEvent} event The key-longpress event dispatched by
* kb-key-base.
* @param {Object} detail The detail of pressed key.
*/
keyLongpress: function(event, detail) {
// If the gesture is long press, remove the pointermove listener.
this.removeEventListener('pointermove', this.swipeHandler, false);
// Keyset transtion key.
if (this.changeKeyset(detail)) {
// Locks the keyset before removing active to prevent flicker.
this.classList.add('caps-locked');
// Makes last pressed key inactive if transit to a new keyset on long
// press.
if (this.lastPressedKey)
this.lastPressedKey.classList.remove('active');
}
},
/**
* Whether we should transit to upper case when seeing a space after
* punctuation.
* @return {boolean}
*/
shouldUpperOnSpace: function() {
// TODO(rsadam): Add other input types in which we should not
// transition to upper after a space.
return this.inputTypeValue != 'password';
},
/**
* Show menu for selecting a keyboard layout.
* @param {!Event} event The triggering event.
* @param {{left: number, top: number, width: number}} details Location of
* the button that triggered the popup.
*/
showOptions: function(event, details) {
var overlay = this.$.overlay;
if (!overlay) {
console.error('Missing overlay.');
return;
}
var menu = overlay.$.options;
if (!menu) {
console.error('Missing options menu.');
}
menu.hidden = false;
overlay.hidden = false;
var left = details.left + details.width - menu.clientWidth;
var top = details.top - menu.clientHeight;
menu.style.left = left + 'px';
menu.style.top = top + 'px';
},
/**
* Handler for the 'set-layout' event.
* @param {!Event} event The triggering event.
* @param {{layout: string}} details Details of the event, which contains
* the name of the layout to activate.
*/
setLayout: function(event, details) {
this.layout = details.layout;
},
/**
* Handles a change in the keyboard layout. Auto-selects the default
* keyset for the new layout.
*/
layoutChanged: function() {
if (!this.selectDefaultKeyset()) {
this.fire('stateChange', {state: 'loadingKeyset'});
// Keyset selection fails if the keysets have not been loaded yet.
var keysets = document.querySelector('#' + this.layout);
if (keysets && keysets.content) {
var content = flattenKeysets(keysets.content);
this.appendChild(content);
this.selectDefaultKeyset();
} else {
// Add link for the keysets if missing from the document. Force
// a layout change after resolving the import of the link.
var query = 'link[id=' + this.layout + ']';
if (!document.querySelector(query)) {
// Layout has not beeen loaded yet.
var link = document.createElement('link');
link.id = this.layout;
link.setAttribute('rel', 'import');
link.setAttribute('href', 'layouts/' + this.layout + '.html');
document.head.appendChild(link);
// Load content for the new link element.
var self = this;
HTMLImports.importer.load(document, function() {
HTMLImports.parser.parseLink(link);
self.layoutChanged();
});
}
}
}
},
/**
* Notifies the modifier keys that a non-control key was typed. This
* lets them reset sticky behaviour. A non-control key is defined as
* any key that is not Control, Alt, or Shift.
*/
onNonControlKeyTyped: function() {
if (this.shift)
this.shift.onNonControlKeyTyped();
if (this.ctrl)
this.ctrl.onNonControlKeyTyped();
if (this.alt)
this.alt.onNonControlKeyTyped();
this.classList.remove('ctrl-active');
this.classList.remove('alt-active');
},
/**
* Id for the active keyset.
* @type {string}
*/
get activeKeysetId() {
return this.layout + '-' + this.keyset;
},
/**
* The active keyset DOM object.
* @type {kb-keyset}
*/
get activeKeyset() {
return this.querySelector('#' + this.activeKeysetId);
},
/**
* The current input type.
* @type {string}
*/
get inputTypeValue() {
return this.inputType;
},
/**
* Changes the input type if it's different from the current
* type, else resets the keyset to the default keyset.
* @type {string}
*/
set inputTypeValue(value) {
if (value == this.inputType)
this.selectDefaultKeyset();
else
this.inputType = value;
},
/**
* The keyboard is ready for input once the target keyset appears
* in the distributed nodes for the keyboard.
* @return {boolean} Indicates if the keyboard is ready for input.
*/
isReady: function() {
var keyset = this.activeKeyset;
if (!keyset)
return false;
var content = this.$.content.getDistributedNodes()[0];
return content == keyset;
},
/**
* Generates fabricated key events to simulate typing on a
* physical keyboard.
* @param {Object} detail Attributes of the key being typed.
* @return {boolean} Whether the key type succeeded.
*/
keyTyped: function(detail) {
var builder = this.$.keyCodeMetadata;
if (this.shift)
detail.shiftModifier = this.shift.isActive();
if (this.ctrl)
detail.controlModifier = this.ctrl.isActive();
if (this.alt)
detail.altModifier = this.alt.isActive();
var downEvent = builder.createVirtualKeyEvent(detail, "keydown");
if (downEvent) {
sendKeyEvent(downEvent);
sendKeyEvent(builder.createVirtualKeyEvent(detail, "keyup"));
return true;
}
return false;
},
/**
* Selects the default keyset for a layout.
* @return {boolean} True if successful. This method can fail if the
* keysets corresponding to the layout have not been injected.
*/
selectDefaultKeyset: function() {
var keysets = this.querySelectorAll('kb-keyset');
// Full name of the keyset is of the form 'layout-keyset'.
var regex = new RegExp('^' + this.layout + '-(.+)');
var keysetsLoaded = false;
for (var i = 0; i < keysets.length; i++) {
var matches = keysets[i].id.match(regex);
if (matches && matches.length == REGEX_MATCH_COUNT) {
keysetsLoaded = true;
// Without both tests for a default keyset, it is possible to get
// into a state where multiple layouts are displayed. A
// reproducable test case is do the following set of keyset
// transitions: qwerty -> system -> dvorak -> qwerty.
// TODO(kevers): Investigate why this is the case.
if (keysets[i].isDefault ||
keysets[i].getAttribute('isDefault') == 'true') {
this.keyset = matches[REGEX_KEYSET_INDEX];
this.classList.remove('caps-locked');
this.classList.remove('alt-active');
this.classList.remove('ctrl-active');
// Caches shift key.
this.shift = this.querySelector('kb-shift-key');
if (this.shift)
this.shift.reset();
// Caches control key.
this.ctrl = this.querySelector('kb-modifier-key[char=Ctrl]');
if (this.ctrl)
this.ctrl.reset();
// Caches alt key.
this.alt = this.querySelector('kb-modifier-key[char=Alt]');
if (this.alt)
this.alt.reset();
this.fire('stateChange', {
state: 'keysetLoaded',
value: this.keyset,
});
keyboardLoaded();
return true;
}
}
}
if (keysetsLoaded)
console.error('No default keyset found for ' + this.layout);
return false;
}
});
</script>
</polymer-element>