PageSourceSearch

https://ikmk.at/layout/jquery.keyboard.js

js ikmk.at collected 2026-10-02 11:18:38 UTC 112,433 bytes, 3,561 lines download raw bytes

1/*! jQuery UI Virtual Keyboard v1.28.7 *//*
2Author: Jeremy Satterfield
3Maintained: Rob Garrison (Mottie on github)
4Licensed under the MIT License
5
6An on-screen virtual keyboard embedded within the browser window which
7will popup when a specified entry field is focused. The user can then
8type and preview their input before Accepting or Canceling.
9
10This plugin adds default class names to match jQuery UI theme styling.
11Bootstrap & custom themes may also be applied - See
12https://github.com/Mottie/Keyboard#themes
13
14Requires:
15	jQuery v1.4.3+
16	Caret plugin (included)
17Optional:
18	jQuery UI (position utility only) & CSS theme
19	jQuery mousewheel
20
21Setup/Usage:
22	Please refer to https://github.com/Mottie/Keyboard/wiki
23
24-----------------------------------------
25Caret code modified from jquery.caret.1.02.js
26Licensed under the MIT License:
27http://www.opensource.org/licenses/mit-license.php
28-----------------------------------------
29*/
30/*jshint browser:true, jquery:true, unused:false */
31/*global require:false, define:false, module:false */
32;(function (factory) {
33	if (typeof define === 'function' && define.amd) {
34		define(['jquery'], factory);
35	} else if (typeof module === 'object' && typeof module.exports === 'object') {
36		module.exports = factory(require('jquery'));
37	} else {
38		factory(jQuery);
39	}
40}(function ($) {
41	'use strict';
42	var $keyboard = $.keyboard = function (el, options) {
43	var o, base = this;
44
45	base.version = '1.28.7';
46
47	// Access to jQuery and DOM versions of element
48	base.$el = $(el);
49	base.el = el;
50
51	// Add a reverse reference to the DOM object
52	base.$el.data('keyboard', base);
53
54	base.init = function () {
55		base.initialized = false;
56		var k, position, tmp,
57			kbcss = $keyboard.css,
58			kbevents = $keyboard.events;
59		base.settings = options || {};
60		// shallow copy position to prevent performance issues; see #357
61		if (options && options.position) {
62			position = $.extend({}, options.position);
63			options.position = null;
64		}
65		base.options = o = $.extend(true, {}, $keyboard.defaultOptions, options);
66		if (position) {
67			o.position = position;
68			options.position = position;
69		}
70
71		// keyboard is active (not destroyed);
72		base.el.active = true;
73		// unique keyboard namespace
74		base.namespace = '.keyboard' + Math.random().toString(16).slice(2);
75		// extension namespaces added here (to unbind listeners on base.$el upon destroy)
76		base.extensionNamespace = [];
77		// Shift and Alt key toggles, sets is true if a layout has more than one keyset
78		// used for mousewheel message
79		base.shiftActive = base.altActive = base.metaActive = base.sets = base.capsLock = false;
80		// Class names of the basic key set - meta keysets are handled by the keyname
81		base.rows = ['', '-shift', '-alt', '-alt-shift'];
82
83		base.inPlaceholder = base.$el.attr('placeholder') || '';
84		// html 5 placeholder/watermark
85		base.watermark = $keyboard.watermark && base.inPlaceholder !== '';
86		// convert mouse repeater rate (characters per second) into a time in milliseconds.
87		base.repeatTime = 1000 / (o.repeatRate || 20);
88		// delay in ms to prevent mousedown & touchstart from both firing events at the same time
89		o.preventDoubleEventTime = o.preventDoubleEventTime || 100;
90		// flag indication that a keyboard is open
91		base.isOpen = false;
92		// is mousewheel plugin loaded?
93		base.wheel = $.isFunction($.fn.mousewheel);
94		// special character in regex that need to be escaped
95		base.escapeRegex = /[-\/\\^$*+?.()|[\]{}]/g;
96		// detect contenteditable
97		base.isContentEditable = !/(input|textarea)/i.test(base.el.nodeName) &&
98			base.el.isContentEditable;
99
100		// keyCode of keys always allowed to be typed
101		k = $keyboard.keyCodes;
102		// base.alwaysAllowed = [20,33,34,35,36,37,38,39,40,45,46];
103		base.alwaysAllowed = [
104			k.capsLock,
105			k.pageUp,
106			k.pageDown,
107			k.end,
108			k.home,
109			k.left,
110			k.up,
111			k.right,
112			k.down,
113			k.insert,
114			k.delete
115		];
116		base.$keyboard = [];
117		// keyboard enabled; set to false on destroy
118		base.enabled = true;
119
120		base.checkCaret = (o.lockInput || $keyboard.checkCaretSupport());
121
122		// disable problematic usePreview for contenteditable
123		if (base.isContentEditable) {
124			o.usePreview = false;
125		}
126
127		base.last = {
128			start: 0,
129			end: 0,
130			key: '',
131			val: '',
132			preVal: '',
133			layout: '',
134			virtual: true,
135			keyset: [false, false, false], // [shift, alt, meta]
136			wheel_$Keys: [],
137			wheelIndex: 0,
138			wheelLayers: []
139		};
140		// used when building the keyboard - [keyset element, row, index]
141		base.temp = ['', 0, 0];
142
143		// Callbacks
144		$.each([
145			kbevents.kbInit,
146			kbevents.kbBeforeVisible,
147			kbevents.kbVisible,
148			kbevents.kbHidden,
149			kbevents.inputCanceled,
150			kbevents.inputAccepted,
151			kbevents.kbBeforeClose,
152			kbevents.inputRestricted
153		], function (i, callback) {
154			if ($.isFunction(o[callback])) {
155				// bind callback functions within options to triggered events
156				base.$el.bind(callback + base.namespace + 'callbacks', o[callback]);
157			}
158		});
159
160		// Close with esc key & clicking outside
161		if (o.alwaysOpen) {
162			o.stayOpen = true;
163		}
164
165		tmp = $(document);
166		if (base.el.ownerDocument !== document) {
167			tmp = tmp.add(base.el.ownerDocument);
168		}
169
170		var bindings = 'keyup checkkeyboard mousedown touchstart ';
171		if (o.closeByClickEvent) {
172			bindings += 'click ';
173		}
174		// debounce bindings... see #542
175		tmp.bind(bindings.split(' ').join(base.namespace + ' '), function(e) {
176			clearTimeout(base.timer3);
177			base.timer3 = setTimeout(function() {
178				base.checkClose(e);
179			}, 1);
180		});
181
182		// Display keyboard on focus
183		base.$el
184			.addClass(kbcss.input + ' ' + o.css.input)
185			.attr({
186				'aria-haspopup': 'true',
187				'role': 'textbox'
188			});
189
190		// set lockInput if the element is readonly; or make the element readonly if lockInput is set
191		if (o.lockInput || base.el.readOnly) {
192			o.lockInput = true;
193			base.$el
194				.addClass(kbcss.locked)
195				.attr({
196					'readonly': 'readonly'
197				});
198		}
199		// add disabled/readonly class - dynamically updated on reveal
200		if (base.isUnavailable()) {
201			base.$el.addClass(kbcss.noKeyboard);
202		}
203		if (o.openOn) {
204			base.bindFocus();
205		}
206
207		// Add placeholder if not supported by the browser
208		if (
209			!base.watermark &&
210			base.getValue(base.$el) === '' &&
211			base.inPlaceholder !== '' &&
212			base.$el.attr('placeholder') !== ''
213		) {
214			// css watermark style (darker text)
215			base.$el.addClass(kbcss.placeholder);
216			base.setValue(base.inPlaceholder, base.$el);
217		}
218
219		base.$el.trigger(kbevents.kbInit, [base, base.el]);
220
221		// initialized with keyboard open
222		if (o.alwaysOpen) {
223			base.reveal();
224		}
225		base.initialized = true;
226	};
227
228	base.toggle = function () {
229		if (!base.hasKeyboard()) { return; }
230		var $toggle = base.$keyboard.find('.' + $keyboard.css.keyToggle),
231			locked = !base.enabled;
232		// prevent physical keyboard from working
233		base.preview.readonly = locked || base.options.lockInput;
234		// disable all buttons
235		base.$keyboard
236			.toggleClass($keyboard.css.keyDisabled, locked)
237			.find('.' + $keyboard.css.keyButton)
238			.not($toggle)
239			.attr('aria-disabled', locked)
240			.each(function() {
241				this.disabled = locked;
242			});
243		$toggle.toggleClass($keyboard.css.keyDisabled, locked);
244		// stop auto typing
245		if (locked && base.typing_options) {
246			base.typing_options.text = '';
247		}
248		// allow chaining
249		return base;
250	};
251
252	base.setCurrent = function () {
253		var kbcss = $keyboard.css,
254			// close any "isCurrent" keyboard (just in case they are always open)
255			$current = $('.' + kbcss.isCurrent),
256			kb = $current.data('keyboard');
257		// close keyboard, if not self
258		if (!$.isEmptyObject(kb) && kb.el !== base.el) {
259			kb.close(kb.options.autoAccept ? 'true' : false);
260		}
261		$current.removeClass(kbcss.isCurrent);
262		// ui-keyboard-has-focus is applied in case multiple keyboards have
263		// alwaysOpen = true and are stacked
264		$('.' + kbcss.hasFocus).removeClass(kbcss.hasFocus);
265
266		base.$el.addClass(kbcss.isCurrent);
267		base.$keyboard.addClass(kbcss.hasFocus);
268		base.isCurrent(true);
269		base.isOpen = true;
270	};
271
272	base.isUnavailable = function() {
273		return (
274			base.$el.is(':disabled') || (
275				!base.options.activeOnReadonly &&
276				base.$el.attr('readonly') &&
277				!base.$el.hasClass($keyboard.css.locked)
278			)
279		);
280	};
281
282	base.isCurrent = function (set) {
283		var cur = $keyboard.currentKeyboard || false;
284		if (set) {
285			cur = $keyboard.currentKeyboard = base.el;
286		} else if (set === false && cur === base.el) {
287			cur = $keyboard.currentKeyboard = '';
288		}
289		return cur === base.el;
290	};
291
292	base.hasKeyboard = function () {
293		return base.$keyboard && base.$keyboard.length > 0;
294	};
295
296	base.isVisible = function () {
297		return base.hasKeyboard() ? base.$keyboard.is(':visible') : false;
298	};
299
300	base.setFocus = function () {
301		var $el = base.$preview || base.$el;
302		if (!o.noFocus) {
303			$el.focus();
304		}
305		if (base.isContentEditable) {
306			$keyboard.setEditableCaret($el, base.last.start, base.last.end);
307		} else {
308			$keyboard.caret($el, base.last);
309		}
310	};
311
312	base.focusOn = function () {
313		if (!base && base.el.active) {
314			// keyboard was destroyed
315			return;
316		}
317		if (!base.isVisible()) {
318			clearTimeout(base.timer);
319			base.reveal();
320		} else {
321			// keyboard already open, make it the current keyboard
322			base.setCurrent();
323		}
324	};
325
326	// add redraw method to make API more clear
327	base.redraw = function (layout) {
328		if (layout) {
329			// allow updating the layout by calling redraw
330			base.options.layout = layout;
331		}
332		// update keyboard after a layout change
333		if (base.$keyboard.length) {
334
335			base.last.preVal = '' + base.last.val;
336			base.saveLastChange();
337			base.setValue(base.last.val, base.$el);
338
339			base.removeKeyboard();
340			base.shiftActive = base.altActive = base.metaActive = false;
341		}
342		base.isOpen = o.alwaysOpen;
343		base.reveal(true);
344		return base;
345	};
346
347	base.reveal = function (redraw) {
348		var temp,
349			alreadyOpen = base.isOpen,
350			kbcss = $keyboard.css;
351		base.opening = !alreadyOpen;
352		// remove all 'extra' keyboards by calling close function
353		$('.' + kbcss.keyboard).not('.' + kbcss.alwaysOpen).each(function(){
354			var kb = $(this).data('keyboard');
355			if (!$.isEmptyObject(kb)) {
356				// this closes previous keyboard when clicking another input - see #515
357				kb.close(kb.options.autoAccept ? 'true' : false);
358			}
359		});
360
361		// Don't open if disabled
362		if (base.isUnavailable()) {
363			return;
364		}
365		base.$el.removeClass(kbcss.noKeyboard);
366
367		// Unbind focus to prevent recursion - openOn may be empty if keyboard is opened externally
368		if (o.openOn) {
369			base.$el.unbind($.trim((o.openOn + ' ').split(/\s+/).join(base.namespace + ' ')));
370		}
371
372		// build keyboard if it doesn't exist; or attach keyboard if it was removed, but not cleared
373		if (!base.$keyboard || base.$keyboard &&
374			(!base.$keyboard.length || $.contains(base.el.ownerDocument.body, base.$keyboard[0]))) {
375			base.startup();
376		}
377
378		// clear watermark
379		if (!base.watermark && base.getValue() === base.inPlaceholder) {
380			base.$el.removeClass(kbcss.placeholder);
381			base.setValue('', base.$el);
382		}
383		// save starting content, in case we cancel
384		base.originalContent = base.isContentEditable ?
385			base.$el.html() :
386			base.getValue(base.$el);
387		if (base.el !== base.preview && !base.isContentEditable) {
388			base.setValue(base.originalContent);
389		}
390
391		// disable/enable accept button
392		if (o.acceptValid && o.checkValidOnInit) {
393			base.checkValid();
394		}
395
396		if (o.resetDefault) {
397			base.shiftActive = base.altActive = base.metaActive = false;
398		}
399		base.showSet();
400
401		// beforeVisible event
402		if (!base.isVisible()) {
403			base.$el.trigger($keyboard.events.kbBeforeVisible, [base, base.el]);
404		}
405		if (
406			base.initialized ||
407			o.initialFocus ||
408			( !o.initialFocus && base.$el.hasClass($keyboard.css.initialFocus) )
409		) {
410			base.setCurrent();
411		}
412		// update keyboard - enabled or disabled?
413		base.toggle();
414
415		// show keyboard
416		base.$keyboard.show();
417
418		// adjust keyboard preview window width - save width so IE won't keep expanding (fix issue #6)
419		if (o.usePreview && $keyboard.msie) {
420			if (typeof base.width === 'undefined') {
421				base.$preview.hide(); // preview is 100% browser width in IE7, so hide the damn thing
422				base.width = Math.ceil(base.$keyboard.width()); // set input width to match the widest keyboard row
423				base.$preview.show();
424			}
425			base.$preview.width(base.width);
426		}
427
428		base.reposition();
429
430		base.checkDecimal();
431
432		// get preview area line height
433		// add roughly 4px to get line height from font height, works well for font-sizes from 14-36px
434		// needed for textareas
435		base.lineHeight = parseInt(base.$preview.css('lineHeight'), 10) ||
436			parseInt(base.$preview.css('font-size'), 10) + 4;
437
438		if (o.caretToEnd) {
439			temp = base.isContentEditable ? $keyboard.getEditableLength(base.el) : base.originalContent.length;
440			base.saveCaret(temp, temp);
441		}
442
443		// IE caret haxx0rs
444		if ($keyboard.allie) {
445			// sometimes end = 0 while start is > 0
446			if (base.last.end === 0 && base.last.start > 0) {
447				base.last.end = base.last.start;
448			}
449			// IE will have start -1, end of 0 when not focused (see demo: https://jsfiddle.net/Mottie/fgryQ/3/)
450			if (base.last.start < 0) {
451				// ensure caret is at the end of the text (needed for IE)
452				base.last.start = base.last.end = base.originalContent.length;
453			}
454		}
455
456		if (alreadyOpen || redraw) {
457			// restore caret position (userClosed)
458			$keyboard.caret(base.$preview, base.last);
459			base.opening = false;
460			return base;
461		}
462
463		// opening keyboard flag; delay allows switching between keyboards without immediately closing
464		// the keyboard
465		base.timer2 = setTimeout(function () {
466			var undef;
467			base.opening = false;
468			// Number inputs don't support selectionStart and selectionEnd
469			// Number/email inputs don't support selectionStart and selectionEnd
470			if (!/(number|email)/i.test(base.el.type) && !o.caretToEnd) {
471				// caret position is always 0,0 in webkit; and nothing is focused at this point... odd
472				// save caret position in the input to transfer it to the preview
473				// inside delay to get correct caret position
474				base.saveCaret(undef, undef, base.$el);
475			}
476			if (o.initialFocus || base.$el.hasClass($keyboard.css.initialFocus)) {
477				$keyboard.caret(base.$preview, base.last);
478			}
479			// save event time for keyboards with stayOpen: true
480			base.last.eventTime = new Date().getTime();
481			base.$el.trigger($keyboard.events.kbVisible, [base, base.el]);
482			base.timer = setTimeout(function () {
483				// get updated caret information after visible event - fixes #331
484				if (base) { // Check if base exists, this is a case when destroy is called, before timers fire
485					base.saveCaret();
486				}
487			}, 200);
488		}, 10);
489		// return base to allow chaining in typing extension
490		return base;
491	};
492
493	base.updateLanguage = function () {
494		// change language if layout is named something like 'french-azerty-1'
495		var layouts = $keyboard.layouts,
496			lang = o.language || layouts[o.layout] && layouts[o.layout].lang &&
497				layouts[o.layout].lang || [o.language || 'en'],
498			kblang = $keyboard.language;
499
500		// some languages include a dash, e.g. 'en-gb' or 'fr-ca'
501		// allow o.language to be a string or array...
502		// array is for future expansion where a layout can be set for multiple languages
503		lang = ($.isArray(lang) ? lang[0] : lang);
504		base.language = lang;
505		lang = lang.split('-')[0];
506
507		// set keyboard language
508		o.display = $.extend(true, {},
509			kblang.en.display,
510			kblang[lang] && kblang[lang].display || {},
511			base.settings.display
512		);
513		o.combos = $.extend(true, {},
514			kblang.en.combos,
515			kblang[lang] && kblang[lang].combos || {},
516			base.settings.combos
517		);
518		o.wheelMessage = kblang[lang] && kblang[lang].wheelMessage || kblang.en.wheelMessage;
519		// rtl can be in the layout or in the language definition; defaults to false
520		o.rtl = layouts[o.layout] && layouts[o.layout].rtl || kblang[lang] && kblang[lang].rtl || false;
521
522		// save default regex (in case loading another layout changes it)
523		base.regex = kblang[lang] && kblang[lang].comboRegex || $keyboard.comboRegex;
524		// determine if US '.' or European ',' system being used
525		base.decimal = /^\./.test(o.display.dec);
526		base.$el
527			.toggleClass('rtl', o.rtl)
528			.css('direction', o.rtl ? 'rtl' : '');
529	};
530
531	base.startup = function () {
532		var kbcss = $keyboard.css;
533		// ensure base.$preview is defined; but don't overwrite it if keyboard is always visible
534		if (!((o.alwaysOpen || o.userClosed) && base.$preview)) {
535			base.makePreview();
536		}
537		if (!base.hasKeyboard()) {
538			// custom layout - create a unique layout name based on the hash
539			if (o.layout === 'custom') {
540				o.layoutHash = 'custom' + base.customHash();
541			}
542			base.layout = o.layout === 'custom' ? o.layoutHash : o.layout;
543			base.last.layout = base.layout;
544
545			base.updateLanguage();
546			if (typeof $keyboard.builtLayouts[base.layout] === 'undefined') {
547				if ($.isFunction(o.create)) {
548					// create must call buildKeyboard() function; or create it's own keyboard
549					base.$keyboard = o.create(base);
550				} else if (!base.$keyboard.length) {
551					base.buildKeyboard(base.layout, true);
552				}
553			}
554			base.$keyboard = $keyboard.builtLayouts[base.layout].$keyboard.clone();
555			base.$keyboard.data('keyboard', base);
556			if ((base.el.id || '') !== '') {
557				// add ID to keyboard for styling purposes
558				base.$keyboard.attr('id', base.el.id + $keyboard.css.idSuffix);
559			}
560
561			base.makePreview();
562		}
563
564		// Add layout and laguage data-attibutes
565		base.$keyboard
566			.attr('data-' + kbcss.keyboard + '-layout', o.layout)
567			.attr('data-' + kbcss.keyboard + '-language', base.language);
568
569		base.$decBtn = base.$keyboard.find('.' + kbcss.keyPrefix + 'dec');
570		// add enter to allowed keys; fixes #190
571		if (o.enterNavigation || base.el.nodeName === 'TEXTAREA') {
572			base.alwaysAllowed.push($keyboard.keyCodes.enter);
573		}
574
575		base.bindKeyboard();
576
577		base.$keyboard.appendTo(o.appendLocally ? base.$el.parent() : o.appendTo || 'body');
578
579		base.bindKeys();
580
581		// reposition keyboard on window resize
582		if (o.reposition && $.ui && $.ui.position && o.appendTo === 'body') {
583			$(window).bind('resize' + base.namespace, function () {
584				base.reposition();
585			});
586		}
587
588	};
589
590	base.reposition = function () {
591		base.position = $.isEmptyObject(o.position) ? false : o.position;
592		// position after keyboard is visible (required for UI position utility)
593		// and appropriately sized
594		if ($.ui && $.ui.position && base.position) {
595			base.position.of =
596				// get single target position
597				base.position.of ||
598				// OR target stored in element data (multiple targets)
599				base.$el.data('keyboardPosition') ||
600				// OR default @ element
601				base.$el;
602			base.position.collision = base.position.collision || 'flipfit flipfit';
603			base.position.at = o.usePreview ? o.position.at : o.position.at2;
604			if (base.isVisible()) {
605				base.$keyboard.position(base.position);
606			}
607		}
608		// make chainable
609		return base;
610	};
611
612	base.makePreview = function () {
613		if (o.usePreview) {
614			var indx, attrs, attr, removedAttr,
615				kbcss = $keyboard.css;
616			base.$preview = base.$el.clone(false)
617				.data('keyboard', base)
618				.removeClass(kbcss.placeholder + ' ' + kbcss.input)
619				.addClass(kbcss.preview + ' ' + o.css.input)
620				.attr('tabindex', '-1')
621				.show();
621 // for hidden inputs
622			base.preview = base.$preview[0];
623
624			// Switch the number input field to text so the caret positioning will work again
625			if (base.preview.type === 'number') {
626				base.preview.type = 'text';
627			}
628
629			// remove extraneous attributes.
630			removedAttr = /^(data-|id|aria-haspopup)/i;
631			attrs = base.$preview.get(0).attributes;
632			for (indx = attrs.length - 1; indx >= 0; indx--) {
633				attr = attrs[indx] && attrs[indx].name;
634				if (removedAttr.test(attr)) {
635					// remove data-attributes - see #351
636					base.preview.removeAttribute(attr);
637				}
638			}
639			// build preview container and append preview display
640			$('<div />')
641				.addClass(kbcss.wrapper)
642				.append(base.$preview)
643				.prependTo(base.$keyboard);
644		} else {
645			base.$preview = base.$el;
646			base.preview = base.el;
647		}
648	};
649
650	// Added in v1.26.8 to allow chaining of the caret function, e.g.
651	// keyboard.reveal().caret(4,5).insertText('test').caret('end');
652	base.caret = function(param1, param2) {
653		var result = $keyboard.caret(base.$preview, param1, param2),
654			wasSetCaret = result instanceof $;
655		// Caret was set, save last position & make chainable
656		if (wasSetCaret) {
657			base.saveCaret(result.start, result.end);
658			return base;
659		}
660		// return caret position if using .caret()
661		return result;
662	};
663
664	base.saveCaret = function (start, end, $el) {
665		if (base.isCurrent()) {
666			var p;
667			if (typeof start === 'undefined') {
668				// grab & save current caret position
669				p = $keyboard.caret($el || base.$preview);
670			} else {
671				p = $keyboard.caret($el || base.$preview, start, end);
672			}
673			base.last.start = typeof start === 'undefined' ? p.start : start;
674			base.last.end = typeof end === 'undefined' ? p.end : end;
675		}
676	};
677
678	base.saveLastChange = function (val) {
679		base.last.val = val || base.getValue(base.$preview || base.$el);
680		if (base.isContentEditable) {
681			base.last.elms = base.el.cloneNode(true);
682		}
683	};
684
685	base.setScroll = function () {
686		// Set scroll so caret & current text is in view
687		// needed for virtual keyboard typing, NOT manual typing - fixes #23
688		if (!base.isContentEditable && base.last.virtual) {
689
690			var scrollWidth, clientWidth, adjustment, direction,
691				isTextarea = base.preview.nodeName === 'TEXTAREA',
692				value = base.last.val.substring(0, Math.max(base.last.start, base.last.end));
693
694			if (!base.$previewCopy) {
695				// clone preview
696				base.$previewCopy = base.$preview.clone()
697					.removeAttr('id') // fixes #334
698					.css({
699						position: 'absolute',
700						left: 0,
701						zIndex: -10,
702						visibility: 'hidden'
703					})
704					.addClass($keyboard.css.inputClone);
705				// prevent submitting content on form submission
706				base.$previewCopy[0].disabled = true;
707				if (!isTextarea) {
708					// make input zero-width because we need an accurate scrollWidth
709					base.$previewCopy.css({
710						'white-space': 'pre',
711						'width': 0
712					});
713				}
714				if (o.usePreview) {
715					// add clone inside of preview wrapper
716					base.$preview.after(base.$previewCopy);
717				} else {
718					// just slap that thing in there somewhere
719					base.$keyboard.prepend(base.$previewCopy);
720				}
721			}
722
723			if (isTextarea) {
724				// need the textarea scrollHeight, so set the clone textarea height to be the line height
725				base.$previewCopy
726					.height(base.lineHeight)
727					.val(value);
728				// set scrollTop for Textarea
729				base.preview.scrollTop = base.lineHeight *
730					(Math.floor(base.$previewCopy[0].scrollHeight / base.lineHeight) - 1);
731			} else {
732				// add non-breaking spaces
733				base.$previewCopy.val(value.replace(/\s/g, '\xa0'));
734
735				// if scrollAdjustment option is set to "c" or "center" then center the caret
736				adjustment = /c/i.test(o.scrollAdjustment) ? base.preview.clientWidth / 2 : o.scrollAdjustment;
737				scrollWidth = base.$previewCopy[0].scrollWidth - 1;
738
739				// set initial state as moving right
740				if (typeof base.last.scrollWidth === 'undefined') {
741					base.last.scrollWidth = scrollWidth;
742					base.last.direction = true;
743				}
744				// if direction = true; we're scrolling to the right
745				direction = base.last.scrollWidth === scrollWidth ?
746					base.last.direction :
747					base.last.scrollWidth < scrollWidth;
748				clientWidth = base.preview.clientWidth - adjustment;
749
750				// set scrollLeft for inputs; try to mimic the inherit caret positioning + scrolling:
751				// hug right while scrolling right...
752				if (direction) {
753					if (scrollWidth < clientWidth) {
754						base.preview.scrollLeft = 0;
755					} else {
756						base.preview.scrollLeft = scrollWidth - clientWidth;
757					}
758				} else {
759					// hug left while scrolling left...
760					if (scrollWidth >= base.preview.scrollWidth - clientWidth) {
761						base.preview.scrollLeft = base.preview.scrollWidth - adjustment;
762					} else if (scrollWidth - adjustment > 0) {
763						base.preview.scrollLeft = scrollWidth - adjustment;
764					} else {
765						base.preview.scrollLeft = 0;
766					}
767				}
768
769				base.last.scrollWidth = scrollWidth;
770				base.last.direction = direction;
771			}
772		}
773	};
774
775	base.bindFocus = function () {
776		if (o.openOn) {
777			// make sure keyboard isn't destroyed
778			// Check if base exists, this is a case when destroy is called, before timers have fired
779			if (base && base.el.active) {
780				base.$el.bind(o.openOn + base.namespace, function () {
781					base.focusOn();
782				});
783				// remove focus from element (needed for IE since blur doesn't seem to work)
784				if ($(':focus')[0] === base.el) {
785					base.$el.blur();
786				}
787			}
788		}
789	};
790
791	base.bindKeyboard = function () {
792		var evt,
793			keyCodes = $keyboard.keyCodes,
794			layout = $keyboard.builtLayouts[base.layout],
795			namespace = base.namespace + 'keybindings';
796		base.$preview
797			.unbind(base.namespace)
798			.bind('click' + namespace + ' touchstart' + namespace, function () {
799				if (o.alwaysOpen && !base.isCurrent()) {
800					base.reveal();
801				}
802				// update last caret position after user click, use at least 150ms or it doesn't work in IE
803				base.timer2 = setTimeout(function () {
804					if (base){
805						base.saveCaret();
806					}
807				}, 150);
808
809			})
810			.bind('keypress' + namespace, function (e) {
811				if (o.lockInput) {
812					return false;
813				}
814				if (!base.isCurrent()) {
815					return;
816				}
817
818				var k = e.charCode || e.which,
819					// capsLock can only be checked while typing a-z
820					k1 = k >= keyCodes.A && k <= keyCodes.Z,
821					k2 = k >= keyCodes.a && k <= keyCodes.z,
822					str = base.last.key = String.fromCharCode(k);
823				// check, that keypress wasn't rise by functional key
824				// space is first typing symbol in UTF8 table
825				if (k < keyCodes.space) { //see #549
826					return;
827				}
828				base.last.virtual = false;
829				base.last.event = e;
830				base.last.$key = []; // not a virtual keyboard key
831				if (base.checkCaret) {
832					base.saveCaret();
833				}
834
835				// update capsLock
836				if (k !== keyCodes.capsLock && (k1 || k2)) {
837					base.capsLock = (k1 && !e.shiftKey) || (k2 && e.shiftKey);
838					// if shifted keyset not visible, then show it
839					if (base.capsLock && !base.shiftActive) {
840						base.shiftActive = true;
841						base.showSet();
842					}
843				}
844
845				// restrict input - keyCode in keypress special keys:
846				// see http://www.asquare.net/javascript/tests/KeyCode.html
847				if (o.restrictInput) {
848					// allow navigation keys to work - Chrome doesn't fire a keypress event (8 = bksp)
849					if ((e.which === keyCodes.backSpace || e.which === 0) &&
850						$.inArray(e.keyCode, base.alwaysAllowed)) {
851						return;
852					}
853					// quick key check
854					if ($.inArray(str, layout.acceptedKeys) === -1) {
855						e.preventDefault();
856						// copy event object in case e.preventDefault() breaks when changing the type
857						evt = $.extend({}, e);
858						evt.type = $keyboard.events.inputRestricted;
859						base.$el.trigger(evt, [base, base.el]);
860					}
861				} else if ((e.ctrlKey || e.metaKey) &&
862					(e.which === keyCodes.A || e.which === keyCodes.C || e.which === keyCodes.V ||
863						(e.which >= keyCodes.X && e.which <= keyCodes.Z))) {
864					// Allow select all (ctrl-a), copy (ctrl-c), paste (ctrl-v) & cut (ctrl-x) &
865					// redo (ctrl-y)& undo (ctrl-z); meta key for mac
866					return;
867				}
868				// Mapped Keys - allows typing on a regular keyboard and the mapped key is entered
869				// Set up a key in the layout as follows: 'm(a):label'; m = key to map, (a) = actual keyboard key
870				// to map to (optional), ':label' = title/tooltip (optional)
871				// example: \u0391 or \u0391(A) or \u0391:alpha or \u0391(A):alpha
872				if (layout.hasMappedKeys && layout.mappedKeys.hasOwnProperty(str)) {
873					base.last.key = layout.mappedKeys[str];
874					base.insertText(base.last.key);
875					e.preventDefault();
876				}
877				if (typeof o.beforeInsert === 'function') {
878					base.insertText(base.last.key);
879					e.preventDefault();
880				}
881				base.checkMaxLength();
882
883			})
884			.bind('keyup' + namespace, function (e) {
885				if (!base.isCurrent()) { return; }
886				base.last.virtual = false;
887				switch (e.which) {
888					// Insert tab key
889				case keyCodes.tab:
890					// Added a flag to prevent from tabbing into an input, keyboard opening, then adding the tab
891					// to the keyboard preview area on keyup. Sadly it still happens if you don't release the tab
892					// key immediately because keydown event auto-repeats
893					if (base.tab && !o.lockInput) {
894						base.shiftActive = e.shiftKey;
895						// when switching inputs, the tab keyaction returns false
896						var notSwitching = $keyboard.keyaction.tab(base);
897						base.tab = false;
898						if (!notSwitching) {
899							return false;
900						}
901					} else {
902						e.preventDefault();
903					}
904					break;
905
906					// Escape will hide the keyboard
907				case keyCodes.escape:
908					if (!o.ignoreEsc) {
909						base.close(o.autoAccept && o.autoAcceptOnEsc ? 'true' : false);
910					}
911					return false;
912				}
913
914				// throttle the check combo function because fast typers will have an incorrectly positioned caret
915				clearTimeout(base.throttled);
916				base.throttled = setTimeout(function () {
917					// fix error in OSX? see issue #102
918					if (base && base.isVisible()) {
919						base.checkCombos();
920					}
921				}, 100);
922
923				base.checkMaxLength();
924
925				base.last.preVal = '' + base.last.val;
926				base.saveLastChange();
927
928				// don't alter "e" or the "keyup" event never finishes processing; fixes #552
929				var event = $.Event( $keyboard.events.kbChange );
930				// base.last.key may be empty string (shift, enter, tab, etc) when keyboard is first visible
931				// use e.key instead, if browser supports it
932				event.action = base.last.key;
933				base.$el.trigger(event, [base, base.el]);
934
935				// change callback is no longer bound to the input element as the callback could be
936				// called during an external change event with all the necessary parameters (issue #157)
937				if ($.isFunction(o.change)) {
938					event.type = $keyboard.events.inputChange;
939					o.change(event, base, base.el);
940					return false;
941				}
942				if (o.acceptValid && o.autoAcceptOnValid) {
943					if (
944						$.isFunction(o.validate) &&
945						o.validate(base, base.getValue(base.$preview))
946					) {
947						base.$preview.blur();
948						base.accept();
949					}
950				}
951			})
952			.bind('keydown' + namespace, function (e) {
953				base.last.keyPress = e.which;
954				// ensure alwaysOpen keyboards are made active
955				if (o.alwaysOpen && !base.isCurrent()) {
956					base.reveal();
957				}
958				// prevent tab key from leaving the preview window
959				if (e.which === keyCodes.tab) {
960					// allow tab to pass through - tab to next input/shift-tab for prev
961					base.tab = true;
962					return false;
963				}
964				if (o.lockInput || e.timeStamp === base.last.timeStamp) {
965					return !o.lockInput;
966				}
967
968				base.last.timeStamp = e.timeStamp; // fixes #659
969				base.last.virtual = false;
970				switch (e.which) {
971
972				case keyCodes.backSpace:
973					$keyboard.keyaction.bksp(base, null, e);
974					e.preventDefault();
975					break;
976
977				case keyCodes.enter:
978					$keyboard.keyaction.enter(base, null, e);
979					break;
980
981					// Show capsLock
982				case keyCodes.capsLock:
983					base.shiftActive = base.capsLock = !base.capsLock;
984					base.showSet();
985					break;
986
987				case keyCodes.V:
988					// prevent ctrl-v/cmd-v
989					if (e.ctrlKey || e.metaKey) {
990						if (o.preventPaste) {
991							e.preventDefault();
992							return;
993						}
994						base.checkCombos(); // check pasted content
995					}
996					break;
997				}
998			})
999			.bind('mouseup touchend '.split(' ').join(namespace + ' '), function () {
1000				base.last.virtual = true;
1001				base.saveCaret();
1002			});
1003
1004		// prevent keyboard event bubbling
1005		base.$keyboard.bind('mousedown click touchstart '.split(' ').join(base.namespace + ' '), function (e) {
1006			e.stopPropagation();
1007			if (!base.isCurrent()) {
1008				base.reveal();
1009				$(base.el.ownerDocument).trigger('checkkeyboard' + base.namespace);
1010			}
1011			base.setFocus();
1012		});
1013
1014		// If preventing paste, block context menu (right click)
1015		if (o.preventPaste) {
1016			base.$preview.bind('contextmenu' + base.namespace, function (e) {
1017				e.preventDefault();
1018			});
1019			base.$el.bind('contextmenu' + base.namespace, function (e) {
1020				e.preventDefault();
1021			});
1022		}
1023
1024	};
1025
1026	base.bindButton = function(events, handler) {
1027		var button = '.' + $keyboard.css.keyButton,
1028			callback = function(e) {
1029				e.stopPropagation();
1030				// save closest keyboard wrapper/input to check in checkClose function
1031				e.$target = $(this).closest('.' + $keyboard.css.keyboard + ', .' + $keyboard.css.input);
1032				handler.call(this, e);
1033			};
1034		if ($.fn.on) {
1035			// jQuery v1.7+
1036			base.$keyboard.on(events, button, callback);
1037		} else if ($.fn.delegate) {
1038			// jQuery v1.4.2 - 3.0.0
1039			base.$keyboard.delegate(button, events, callback);
1040		}
1041		return base;
1042	};
1043
1044	base.unbindButton = function(namespace) {
1045		if ($.fn.off) {
1046			// jQuery v1.7+
1047			base.$keyboard.off(namespace);
1048		} else if ($.fn.undelegate) {
1049			// jQuery v1.4.2 - 3.0.0 (namespace only added in v1.6)
1050			base.$keyboard.undelegate('.' + $keyboard.css.keyButton, namespace);
1051		}
1052		return base;
1053	};
1054
1055	base.bindKeys = function () {
1056		var kbcss = $keyboard.css;
1057		base
1058			.unbindButton(base.namespace + ' ' + base.namespace + 'kb')
1059			// Change hover class and tooltip - moved this touchstart before option.keyBinding touchstart
1060			// to prevent mousewheel lag/duplication - Fixes #379 & #411
1061			.bindButton('mouseenter mouseleave touchstart '.split(' ').join(base.namespace + ' '), function (e) {
1062				if ((o.alwaysOpen || o.userClosed) && e.type !== 'mouseleave' && !base.isCurrent()) {
1063					base.reveal();
1064					base.setFocus();
1065				}
1066				if (!base.isCurrent() || this.disabled) {
1067					return;
1068				}
1069				var $keys, txt,
1070					last = base.last,
1071					$this = $(this),
1072					type = e.type;
1073
1074				if (o.useWheel && base.wheel) {
1075					$keys = base.getLayers($this);
1076					txt = ($keys.length ? $keys.map(function () {
1077							return $(this).attr('data-value') || '';
1078						})
1079						.get() : '') || [$this.text()];
1080					last.wheel_$Keys = $keys;
1081					last.wheelLayers = txt;
1082					last.wheelIndex = $.inArray($this.attr('data-value'), txt);
1083				}
1084
1085				if ((type === 'mouseenter' || type === 'touchstart') && base.el.type !== 'password' &&
1086					!$this.hasClass(o.css.buttonDisabled)) {
1087					$this.addClass(o.css.buttonHover);
1088					if (o.useWheel && base.wheel) {
1089						$this.attr('title', function (i, t) {
1090							// show mouse wheel message
1091							return (base.wheel && t === '' && base.sets && txt.length > 1 && type !== 'touchstart') ?
1092								o.wheelMessage : t;
1093						});
1094					}
1095				}
1096				if (type === 'mouseleave') {
1097					// needed or IE flickers really bad
1098					$this.removeClass((base.el.type === 'password') ? '' : o.css.buttonHover);
1099					if (o.useWheel && base.wheel) {
1100						last.wheelIndex = 0;
1101						last.wheelLayers = [];
1102						last.wheel_$Keys = [];
1103						$this
1104							.attr('title', function (i, t) {
1105								return (t === o.wheelMessage) ? '' : t;
1106							})
1107							.html($this.attr('data-html')); // restore original button text
1108					}
1109				}
1110			})
1111			// keyBinding = 'mousedown touchstart' by default
1112			.bindButton(o.keyBinding.split(' ').join(base.namespace + ' ') + base.namespace + ' ' +
1113				$keyboard.events.kbRepeater, function (e) {
1114				e.preventDefault();
1115				// prevent errors when external triggers attempt to 'type' - see issue #158
1116				if (!base.$keyboard.is(':visible') || this.disabled) {
1117					return false;
1118				}
1119				var action,
1120					last = base.last,
1121					$key = $(this),
1122					// prevent mousedown & touchstart from both firing events at the same time - see #184
1123					timer = new Date().getTime();
1124
1125				if (o.useWheel && base.wheel) {
1126					// get keys from other layers/keysets (shift, alt, meta, etc) that line up by data-position
1127					// target mousewheel selected key
1128					$key = last.wheel_$Keys.length && last.wheelIndex > -1 ? last.wheel_$Keys.eq(last.wheelIndex) : $key;
1129				}
1130				action = $key.attr('data-action');
1131				if (timer - (last.eventTime || 0) < o.preventDoubleEventTime) {
1132					return;
1133				}
1134				last.eventTime = timer;
1135				last.event = e;
1136				last.virtual = true;
1137				last.$key = $key;
1138				last.key = $key.attr('data-value');
1139				last.keyPress = '';
1140				// Start caret in IE when not focused (happens with each virtual keyboard button click
1141				base.setFocus();
1142				if (/^meta/.test(action)) {
1143					action = 'meta';
1144				}
1145				// keyaction is added as a string, override original action & text
1146				if (action === last.key && typeof $keyboard.keyaction[action] === 'string') {
1147					last.key = action = $keyboard.keyaction[action];
1148				}
1148 else if (action in $keyboard.keyaction && $.isFunction($keyboard.keyaction[action])) {
1149					// stop processing if action returns false (close & cancel)
1150					if ($keyboard.keyaction[action](base, this, e) === false) {
1151						return false;
1152					}
1153					action = null; // prevent inserting action name
1154				}
1155				// stop processing if keyboard closed and keyaction did not return false - see #536
1156				if (!base.hasKeyboard()) {
1157					return false;
1158				}
1159				if (typeof action !== 'undefined' && action !== null) {
1160					last.key = $(this).hasClass(kbcss.keyAction) ? action : last.key;
1161					base.insertText(last.key);
1162					if (!base.capsLock && !o.stickyShift && !e.shiftKey) {
1163						base.shiftActive = false;
1164						base.showSet($key.attr('data-name'));
1165					}
1166				}
1167				// set caret if caret moved by action function; also, attempt to fix issue #131
1168				$keyboard.caret(base.$preview, last);
1169				base.checkCombos();
1170				e = $.extend({}, e, $.Event($keyboard.events.kbChange));
1171				e.target = base.el;
1172				e.action = last.key;
1173				base.$el.trigger(e, [base, base.el]);
1174				last.preVal = '' + last.val;
1175				base.saveLastChange();
1176
1177				if ($.isFunction(o.change)) {
1178					e.type = $keyboard.events.inputChange;
1179					o.change(e, base, base.el);
1180					// return false to prevent reopening keyboard if base.accept() was called
1181					return false;
1182				}
1183
1184			})
1185			// using 'kb' namespace for mouse repeat functionality to keep it separate
1186			// I need to trigger a 'repeater.keyboard' to make it work
1187			.bindButton('mouseup' + base.namespace + ' ' + 'mouseleave touchend touchmove touchcancel '.split(' ')
1188				.join(base.namespace + 'kb '), function (e) {
1189				base.last.virtual = true;
1190				var offset,
1191					$this = $(this);
1192				if (e.type === 'touchmove') {
1193					// if moving within the same key, don't stop repeating
1194					offset = $this.offset();
1195					offset.right = offset.left + $this.outerWidth();
1196					offset.bottom = offset.top + $this.outerHeight();
1197					if (e.originalEvent.touches[0].pageX >= offset.left &&
1198						e.originalEvent.touches[0].pageX < offset.right &&
1199						e.originalEvent.touches[0].pageY >= offset.top &&
1200						e.originalEvent.touches[0].pageY < offset.bottom) {
1201						return true;
1202					}
1203				} else if (/(mouseleave|touchend|touchcancel)/i.test(e.type)) {
1204					$this.removeClass(o.css.buttonHover); // needed for touch devices
1205				} else {
1206					if (!o.noFocus && base.isCurrent() && base.isVisible()) {
1207						base.$preview.focus();
1208					}
1209					if (base.checkCaret) {
1210						$keyboard.caret(base.$preview, base.last);
1211					}
1212				}
1213				base.mouseRepeat = [false, ''];
1214				clearTimeout(base.repeater); // make sure key repeat stops!
1215				if (o.acceptValid && o.autoAcceptOnValid) {
1216					if (
1217						$.isFunction(o.validate) &&
1218						o.validate(base, base.getValue())
1219					) {
1220						base.$preview.blur();
1221						base.accept();
1222					}
1223				}
1224				return false;
1225			})
1226			// prevent form submits when keyboard is bound locally - issue #64
1227			.bindButton('click' + base.namespace, function () {
1228				return false;
1229			})
1230			// Allow mousewheel to scroll through other keysets of the same (non-action) key
1231			.bindButton('mousewheel' + base.namespace, base.throttleEvent(function (e, delta) {
1232				var $btn = $(this);
1233				// no mouse repeat for action keys (shift, ctrl, alt, meta, etc)
1234				if (!$btn || $btn.hasClass(kbcss.keyAction) || base.last.wheel_$Keys[0] !== this) {
1235					return;
1236				}
1237				if (o.useWheel && base.wheel) {
1238					// deltaY used by newer versions of mousewheel plugin
1239					delta = delta || e.deltaY;
1240					var n,
1241						txt = base.last.wheelLayers || [];
1242					if (txt.length > 1) {
1243						n = base.last.wheelIndex + (delta > 0 ? -1 : 1);
1244						if (n > txt.length - 1) {
1245							n = 0;
1246						}
1247						if (n < 0) {
1248							n = txt.length - 1;
1249						}
1250					} else {
1251						n = 0;
1252					}
1253					base.last.wheelIndex = n;
1254					$btn.html(txt[n]);
1255					return false;
1256				}
1257			}, 30))
1258			.bindButton('mousedown touchstart '.split(' ').join(base.namespace + 'kb '), function () {
1259				var $btn = $(this);
1260				// no mouse repeat for action keys (shift, ctrl, alt, meta, etc)
1261				if (
1262					!$btn || (
1263						$btn.hasClass(kbcss.keyAction) &&
1264						// mouse repeated action key exceptions
1265						!$btn.is('.' + kbcss.keyPrefix + ('tab bksp space enter'.split(' ').join(',.' + kbcss.keyPrefix)))
1266					)
1267				) {
1268					return;
1269				}
1270				if (o.repeatRate !== 0) {
1271					// save the key, make sure we are repeating the right one (fast typers)
1272					base.mouseRepeat = [true, $btn];
1273					setTimeout(function () {
1274						// don't repeat keys if it is disabled - see #431
1275						if (base && base.mouseRepeat[0] && base.mouseRepeat[1] === $btn && !$btn[0].disabled) {
1276							base.repeatKey($btn);
1277						}
1278					}, o.repeatDelay);
1279				}
1280				return false;
1281			});
1282	};
1283
1284	// No call on tailing event
1285	base.throttleEvent = function(cb, time) {
1286		var interm;
1287		return function() {
1288			if (!interm) {
1289				cb.apply(this, arguments);
1290				interm = true;
1291				setTimeout(function() {
1292					interm = false;
1293				}, time);
1294			}
1295		};
1296	};
1297
1298	base.execCommand = function(cmd, str) {
1299		base.el.ownerDocument.execCommand(cmd, false, str);
1300		base.el.normalize();
1301		if (o.reposition) {
1302			base.reposition();
1303		}
1304	};
1305
1306	base.getValue = function ($el) {
1307		$el = $el || base.$preview;
1308		return $el[base.isContentEditable ? 'text' : 'val']();
1309	};
1310
1311	base.setValue = function (txt, $el) {
1312		$el = $el || base.$preview;
1313		if (base.isContentEditable) {
1314			if (txt !== $el.text()) {
1315				$keyboard.replaceContent($el, txt);
1316				base.saveCaret();
1317			}
1318		} else {
1319			$el.val(txt);
1320		}
1321		return base;
1322	};
1323
1324	// Insert text at caret/selection - thanks to Derek Wickwire for fixing this up!
1325	base.insertText = function (txt) {
1326		if (!base.$preview) { return base; }
1327		if (typeof o.beforeInsert === 'function') {
1328			txt = o.beforeInsert(base.last.event, base, base.el, txt);
1329		}
1330		if (typeof txt === 'undefined' || txt === false) {
1331			base.last.key = '';
1332			return base;
1333		}
1334		if (base.isContentEditable) {
1335			return base.insertContentEditable(txt);
1336		}
1337		var t,
1338			bksp = false,
1339			isBksp = txt === '\b',
1340			// use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
1341			val = base.getValue(),
1342			pos = $keyboard.caret(base.$preview),
1343			len = val.length; // save original content length
1344
1345		// silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
1346		// is still difficult
1347		// in IE, pos.end can be zero after input loses focus
1348		if (pos.end < pos.start) {
1349			pos.end = pos.start;
1350		}
1351		if (pos.start > len) {
1352			pos.end = pos.start = len;
1353		}
1354
1355		if (base.preview.nodeName === 'TEXTAREA') {
1356			// This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
1357			if ($keyboard.msie && val.substr(pos.start, 1) === '\n') {
1358				pos.start += 1;
1359				pos.end += 1;
1360			}
1361		}
1362
1363		t = pos.start;
1364		if (txt === '{d}') {
1365			txt = '';
1366			pos.end += 1;
1367		}
1368
1369		if (isBksp) {
1370			txt = '';
1371			bksp = isBksp && t === pos.end && t > 0;
1372		}
1373		val = val.substr(0, t - (bksp ? 1 : 0)) + txt + val.substr(pos.end);
1374		t += bksp ? -1 : txt.length;
1375
1376		base.setValue(val);
1377		base.saveCaret(t, t); // save caret in case of bksp
1378		base.setScroll();
1379		// see #506.. allow chaining of insertText
1380		return base;
1381	};
1382
1383	base.insertContentEditable = function (txt) {
1384		base.$preview.focus();
1385		base.execCommand('insertText', txt);
1386		base.saveCaret();
1387		return base;
1388	};
1389
1390	// check max length
1391	base.checkMaxLength = function () {
1392		if (!base.$preview) { return; }
1393		var start, caret,
1394			val = base.getValue(),
1395			len = base.isContentEditable ? $keyboard.getEditableLength(base.el) : val.length;
1396		if (o.maxLength !== false && len > o.maxLength) {
1397			start = $keyboard.caret(base.$preview).start;
1398			caret = Math.min(start, o.maxLength);
1399
1400			// prevent inserting new characters when maxed #289
1401			if (!o.maxInsert) {
1402				val = base.last.val;
1403				caret = start - 1; // move caret back one
1404			}
1405			base.setValue(val.substring(0, o.maxLength));
1406			// restore caret on change, otherwise it ends up at the end.
1407			base.saveCaret(caret, caret);
1408		}
1409		if (base.$decBtn.length) {
1410			base.checkDecimal();
1411		}
1412		// allow chaining
1413		return base;
1414	};
1415
1416	// mousedown repeater
1417	base.repeatKey = function (key) {
1418		key.trigger($keyboard.events.kbRepeater);
1419		if (base.mouseRepeat[0]) {
1420			base.repeater = setTimeout(function () {
1421				if (base){
1422					base.repeatKey(key);
1423				}
1424			}, base.repeatTime);
1425		}
1426	};
1427
1428	base.getKeySet = function () {
1429		var sets = [];
1430		if (base.altActive) {
1431			sets.push('alt');
1432		}
1433		if (base.shiftActive) {
1434			sets.push('shift');
1435		}
1436		if (base.metaActive) {
1437			// base.metaActive contains the string name of the
1438			// current meta keyset
1439			sets.push(base.metaActive);
1440		}
1441		return sets.length ? sets.join('+') : 'normal';
1442	};
1443
1444	// make it easier to switch keysets via API
1445	// showKeySet('shift+alt+meta1')
1446	base.showKeySet = function (str) {
1447		if (typeof str === 'string') {
1448			base.last.keyset = [base.shiftActive, base.altActive, base.metaActive];
1449			base.shiftActive = /shift/i.test(str);
1450			base.altActive = /alt/i.test(str);
1451			if (/\bmeta/.test(str)) {
1452				base.metaActive = true;
1453				base.showSet(str.match(/\bmeta[\w-]+/i)[0]);
1454			} else {
1455				base.metaActive = false;
1456				base.showSet();
1457			}
1458		} else {
1459			base.showSet(str);
1460		}
1461		// allow chaining
1462		return base;
1463	};
1464
1465	base.showSet = function (name) {
1466		if (!base.hasKeyboard()) { return; }
1467		o = base.options; // refresh options
1468		var kbcss = $keyboard.css,
1469			prefix = '.' + kbcss.keyPrefix,
1470			active = o.css.buttonActive,
1471			key = '',
1472			toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
1473		if (!base.shiftActive) {
1474			base.capsLock = false;
1475		}
1476		// check meta key set
1477		if (base.metaActive) {
1478			// remove "-shift" and "-alt" from meta name if it exists
1479			if (base.shiftActive) {
1480				name = (name || '').replace('-shift', '');
1481			}
1482			if (base.altActive) {
1483				name = (name || '').replace('-alt', '');
1484			}
1485			// the name attribute contains the meta set name 'meta99'
1486			key = (/^meta/i.test(name)) ? name : '';
1487			// save active meta keyset name
1488			if (key === '') {
1489				key = (base.metaActive === true) ? '' : base.metaActive;
1490			} else {
1491				base.metaActive = key;
1492			}
1493			// if meta keyset doesn't have a shift or alt keyset, then show just the meta key set
1494			if ((!o.stickyShift && base.last.keyset[2] !== base.metaActive) ||
1495				((base.shiftActive || base.altActive) &&
1496				!base.$keyboard.find('.' + kbcss.keySet + '-' + key + base.rows[toShow]).length)) {
1497				base.shiftActive = base.altActive = false;
1498			}
1499		} else if (!o.stickyShift && base.last.keyset[2] !== base.metaActive && base.shiftActive) {
1500			// switching from meta key set back to default, reset shift & alt if using stickyShift
1501			base.shiftActive = base.altActive = false;
1502		}
1503		toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
1504		key = (toShow === 0 && !base.metaActive) ? '-normal' : (key === '') ? '' : '-' + key;
1505		if (!base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow]).length) {
1506			// keyset doesn't exist, so restore last keyset settings
1507			base.shiftActive = base.last.keyset[0];
1508			base.altActive = base.last.keyset[1];
1509			base.metaActive = base.last.keyset[2];
1510			return;
1511		}
1512		base.$keyboard
1513			.find(prefix + 'alt,' + prefix + 'shift,.' + kbcss.keyAction + '[class*=meta]')
1514			.removeClass(active)
1515			.end()
1516			.find(prefix + 'alt')
1517			.toggleClass(active, base.altActive)
1518			.end()
1519			.find(prefix + 'shift')
1520			.toggleClass(active, base.shiftActive)
1521			.end()
1522			.find(prefix + 'lock')
1523			.toggleClass(active, base.capsLock)
1524			.end()
1525			.find('.' + kbcss.keySet)
1526			.hide()
1527			.end()
1528			.find('.' + (kbcss.keyAction + prefix + key).replace('--', '-'))
1529			.addClass(active);
1530
1531		// show keyset using inline-block ( extender layout will then line up )
1532		base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow])[0].style.display = 'inline-block';
1533		if (base.metaActive) {
1534			base.$keyboard.find(prefix + base.metaActive)
1535				// base.metaActive contains the string "meta#" or false
1536				// without the !== false, jQuery UI tries to transition the classes
1537				.toggleClass(active, base.metaActive !== false);
1538		}
1539		base.last.keyset = [base.shiftActive, base.altActive, base.metaActive];
1540		base.$el.trigger($keyboard.events.kbKeysetChange, [base, base.el]);
1541		if (o.reposition) {
1542			base.reposition();
1543		}
1544	};
1545
1546	// check for key combos (dead keys)
1547	base.checkCombos = function () {
1548		// return val for close function
1549		if ( !(
1550			base.isVisible() || (
1551				base.hasKeyboard() &&
1552				base.$keyboard.hasClass( $keyboard.css.hasFocus )
1553			)
1554		) ) {
1555			return base.getValue(base.$preview || base.$el);
1556		}
1557		var r, t, t2, repl,
1558			// use base.$preview.val() instead of base.preview.value
1559			// (val.length includes carriage returns in IE).
1560			val = base.getValue(),
1561			pos = $keyboard.caret(base.$preview),
1562			layout = $keyboard.builtLayouts[base.layout],
1563			max = base.isContentEditable ? $keyboard.getEditableLength(base.el) : val.length,
1564			// save original content length
1565			len = max;
1566		// return if val is empty; fixes #352
1567		if (val === '') {
1568			// check valid on empty string - see #429
1569			if (o.acceptValid) {
1570				base.checkValid();
1571			}
1572			return val;
1573		}
1574
1575		// silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
1576		// is still difficult
1577		// in IE, pos.end can be zero after input loses focus
1578		if (pos.end < pos.start) {
1579			pos.end = pos.start;
1580		}
1581		if (pos.start > len) {
1582			pos.end = pos.start = len;
1583		}
1584		// This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
1585		if ($keyboard.msie && val.substr(pos.start, 1) === '\n') {
1586			pos.start += 1;
1587			pos.end += 1;
1588		}
1589
1590		if (o.useCombos) {
1591			// keep 'a' and 'o' in the regex for ae and oe ligature (æ,œ)
1592			// thanks to KennyTM: http://stackoverflow.com/q/4275077
1593			// original regex /([`\'~\^\"ao])([a-z])/mig moved to $.keyboard.comboRegex
1594			if ($keyboard.msie) {
1595				// old IE may not have the caret positioned correctly, so just check the whole thing
1596				val = val.replace(base.regex, function (s, accent, letter) {
1597					return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
1598				});
1599				// prevent combo replace error, in case the keyboard closes - see issue #116
1600			} else if (base.$preview.length) {
1601				// Modern browsers - check for combos from last two characters left of the caret
1602				t = pos.start - (pos.start - 2 >= 0 ? 2 : 0);
1603				// target last two characters
1604				$keyboard.caret(base.$preview, t, pos.end);
1605				// do combo replace
1606				t = $keyboard.caret(base.$preview);
1607				repl = function (txt) {
1608					return (txt || '').replace(base.regex, function (s, accent, letter) {
1609						return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
1610					});
1611				};
1612				t2 = repl(t.text);
1613				// add combo back
1614				// prevent error if caret doesn't return a function
1615				if (t && t.replaceStr && t2 !== t.text) {
1616					if (base.isContentEditable) {
1617						$keyboard.replaceContent(el, repl);
1618					} else {
1619						base.setValue(t.replaceStr(t2));
1620					}
1621				}
1622				val = base.getValue();
1623			}
1624		}
1625
1626		// check input restrictions - in case content was pasted
1627		if (o.restrictInput && val !== '') {
1628			t = layout.acceptedKeys.length;
1629
1630			r = layout.acceptedKeysRegex;
1631			if (!r) {
1632				t2 = $.map(layout.acceptedKeys, function (v) {
1633					// escape any special characters
1634					return v.replace(base.escapeRegex, '\\$&');
1635				});
1636				if (base.alwaysAllowed.indexOf($keyboard.keyCodes.enter) > -1) {
1637					t2.push('\\n'); // Fixes #686
1638				}
1639				r = layout.acceptedKeysRegex = new RegExp('(' + t2.join('|') + ')', 'g');
1640			}
1641			// only save matching keys
1642			t2 = val.match(r);
1643			if (t2) {
1644				val = t2.join('');
1645			} else {
1646				// no valid characters
1647				val = '';
1648				len = 0;
1649			}
1650		}
1651
1652		// save changes, then reposition caret
1653		pos.start += max - len;
1654		pos.end += max - len;
1655
1656		base.setValue(val);
1657		base.saveCaret(pos.start, pos.end);
1658		// set scroll to keep caret in view
1659		base.setScroll();
1660		base.checkMaxLength();
1661
1662		if (o.acceptValid) {
1663			base.checkValid();
1664		}
1665		return val; // return text, used for keyboard closing section
1666	};
1667
1668	// Toggle accept button classes, if validating
1669	base.checkValid = function () {
1670		var kbcss = $keyboard.css,
1671			$accept = base.$keyboard.find('.' + kbcss.keyPrefix + 'accept'),
1672			valid = true;
1673		if ($.isFunction(o.validate)) {
1674			valid = o.validate(base, base.getValue(), false);
1675		}
1676		// toggle accept button classes; defined in the css
1677		$accept
1678			.toggleClass(kbcss.inputInvalid, !valid)
1679			.toggleClass(kbcss.inputValid, valid)
1680			// update title to indicate that the entry is valid or invalid
1681			.attr('title', $accept.attr('data-title') + ' (' + o.display[valid ? 'valid' : 'invalid'] + ')');
1682	};
1683
1684	// Decimal button for num pad - only allow one (not used by default)
1685	base.checkDecimal = function () {
1686		// Check US '.' or European ',' format
1687		if ((base.decimal && /\./g.test(base.preview.value)) ||
1688			(!base.decimal && /\,/g.test(base.preview.value))) {
1689			base.$decBtn
1690				.attr({
1691					'disabled': 'disabled',
1692					'aria-disabled': 'true'
1693				})
1694				.removeClass(o.css.buttonHover)
1695				.addClass(o.css.buttonDisabled);
1696		} else {
1697			base.$decBtn
1698				.removeAttr('disabled')
1699				.attr({
1700					'aria-disabled': 'false'
1701				})
1702				.addClass(o.css.buttonDefault)
1703				.removeClass(o.css.buttonDisabled);
1704		}
1705	};
1706
1707	// get other layer values for a specific key
1708	base.getLayers = function ($el) {
1709		var kbcss = $keyboard.css,
1710			key = $el.attr('data-pos'),
1711			$keys = $el.closest('.' + kbcss.keyboard)
1712			.find('button[data-pos="' + key + '"]');
1713		return $keys.filter(function () {
1714			return $(this)
1715				.find('.' + kbcss.keyText)
1716				.text() !== '';
1717		})
1718		.add($el);
1719	};
1720
1721	// Go to next or prev inputs
1722	// goToNext = true, then go to next input; if false go to prev
1723	// isAccepted is from autoAccept option or true if user presses shift+enter
1724	base.switchInput = function (goToNext, isAccepted) {
1725		if ($.isFunction(o.switchInput)) {
1726			o.switchInput(base, goToNext, isAccepted);
1727		} else {
1728			// base.$keyboard may be an empty array - see #275 (apod42)
1729			if (base.$keyboard.length) {
1730				base.$keyboard.hide();
1731			}
1732			var kb,
1733				stopped = false,
1734				all = $('button, input, select, textarea, a, [contenteditable]')
1735					.filter(':visible')
1736					.not(':disabled'),
1737				indx = all.index(base.$el) + (goToNext ? 1 : -1);
1738			if (base.$keyboard.length) {
1739				base.$keyboard.show();
1740			}
1741			if (indx > all.length - 1) {
1742				stopped = o.stopAtEnd;
1743				indx = 0; // go to first input
1744			}
1745			if (indx < 0) {
1746				stopped = o.stopAtEnd;
1747				indx = all.length - 1; // stop or go to last
1748			}
1749			if (!stopped) {
1750				isAccepted = base.close(isAccepted);
1751				if (!isAccepted) {
1752					return;
1753				}
1754				kb = all.eq(indx).data('keyboard');
1755				if (kb && kb.options.openOn.length) {
1756					kb.focusOn();
1757				} else {
1758					all.eq(indx).focus();
1759				}
1760			}
1761		}
1762		return false;
1763	};
1764
1765	// Close the keyboard, if visible. Pass a status of true, if the content was accepted
1766	// (for the event trigger).
1767	base.close = function (accepted) {
1768		if (base.isOpen && base.$keyboard.length) {
1769			clearTimeout(base.throttled);
1770			var kbcss = $keyboard.css,
1771				kbevents = $keyboard.events,
1772				val = accepted ? base.checkCombos() : base.originalContent;
1773			// validate input if accepted
1774			if (accepted && $.isFunction(o.validate) && !o.validate(base, val, true)) {
1775				val = base.originalContent;
1776				accepted = false;
1777				if (o.cancelClose) {
1778					return;
1779				}
1780			}
1781			base.isCurrent(false);
1782			base.isOpen = o.alwaysOpen || o.userClosed;
1783			if (base.isContentEditable && !accepted) {
1784				// base.originalContent stores the HTML
1785				base.$el.html(val);
1786			} else {
1787				base.setValue(val, base.$el);
1788			}
1789			base.$el
1790				.removeClass(kbcss.isCurrent + ' ' + kbcss.inputAutoAccepted)
1791				// add 'ui-keyboard-autoaccepted' to inputs - see issue #66
1792				.addClass((accepted || false) ? accepted === true ? '' : kbcss.inputAutoAccepted : '')
1793				// trigger default change event - see issue #146
1794				.trigger(kbevents.inputChange);
1795			// don't trigger an empty event - see issue #463
1796			if (!o.alwaysOpen) {
1797				// don't trigger beforeClose if keyboard is always open
1798				base.$el.trigger(kbevents.kbBeforeClose, [base, base.el, (accepted || false)]);
1799			}
1800			// save caret after updating value (fixes userClosed issue with changing focus)
1801			$keyboard.caret(base.$preview, base.last);
1802
1803			base.$el
1804				.trigger(((accepted || false) ? kbevents.inputAccepted : kbevents.inputCanceled), [base, base.el])
1805				.trigger((o.alwaysOpen) ? kbevents.kbInactive : kbevents.kbHidden, [base, base.el])
1806				.blur();
1807
1808			// base is undefined if keyboard was destroyed - fixes #358
1809			if (base) {
1810				// add close event time
1811				base.last.eventTime = new Date().getTime();
1812				if (!(o.alwaysOpen || o.userClosed && accepted === 'true') && base.$keyboard.length) {
1813					// free up memory
1814					base.removeKeyboard();
1815					// rebind input focus - delayed to fix IE issue #72
1816					base.timer = setTimeout(function () {
1817						if (base) {
1818							base.bindFocus();
1819						}
1820					}, 200);
1821				}
1822				if (!base.watermark && base.el.value === '' && base.inPlaceholder !== '') {
1823					base.$el.addClass(kbcss.placeholder);
1824					base.setValue(base.inPlaceholder, base.$el);
1825				}
1826			}
1827		}
1828		return !!accepted;
1829	};
1830
1831	base.accept = function () {
1832		return base.close(true);
1833	};
1834
1835	base.checkClose = function (e) {
1836		if (base.opening) {
1837			return;
1838		}
1839		var kbcss = $.keyboard.css,
1840			$target = e.$target || $(e.target).closest('.' + $keyboard.css.keyboard + ', .' + $keyboard.css.input);
1841		if (!$target.length) {
1842			$target = $(e.target);
1843		}
1844		// needed for IE to allow switching between keyboards smoothly
1845		if ($target.length && $target.hasClass(kbcss.keyboard)) {
1846			var kb = $target.data('keyboard');
1847			// only trigger on self
1848			if (
1849				kb !== base &&
1850				!kb.$el.hasClass(kbcss.isCurrent) &&
1851				kb.options.openOn &&
1852				e.type === o.openOn
1853			) {
1854				kb.focusOn();
1855			}
1856		} else {
1857			base.escClose(e, $target);
1858		}
1859	};
1860
1861	// callback functions called to check if the keyboard needs to be closed
1862	// e.g. on escape or clicking outside the keyboard
1863	base.escCloseCallback = {
1864		// keep keyboard open if alwaysOpen or stayOpen is true - fixes mutliple
1865		// always open keyboards or single stay open keyboard
1866		keepOpen: function() {
1867			return !base.isOpen;
1868		}
1869	};
1870
1871	base.escClose = function (e, $el) {
1872		if (!base.isOpen) {
1873			return;
1874		}
1875		if (e && e.type === 'keyup') {
1876			return (e.which === $keyboard.keyCodes.escape && !o.ignoreEsc) ?
1877				base.close(o.autoAccept && o.autoAcceptOnEsc ? 'true' : false) :
1878				'';
1879		}
1880		var shouldStayOpen = false,
1881			$target = $el.length && $el || $(e.target);
1882		$.each(base.escCloseCallback, function(i, callback) {
1883			if (typeof callback === 'function') {
1884				shouldStayOpen = shouldStayOpen || callback($target);
1885			}
1886		});
1887		if (shouldStayOpen) {
1888			return;
1889		}
1890		// ignore autoaccept if using escape - good idea?
1891		if (!base.isCurrent() && base.isOpen || base.isOpen && $target[0] !== base.el) {
1892			// don't close if stayOpen is set; but close if a different keyboard is being opened
1893			if ((o.stayOpen || o.userClosed) && !$target.hasClass($keyboard.css.input)) {
1894				return;
1895			}
1896			// stop propogation in IE - an input getting focus doesn't open a keyboard if one is already open
1897			if ($keyboard.allie) {
1898				e.preventDefault();
1899			}
1900			if (o.closeByClickEvent) {
1901				// only close the keyboard if the user is clicking on an input or if they cause a click
1902				// event (touchstart/mousedown will not force the close with this setting)
1903				var name = $target[0] && $target[0].nodeName.toLowerCase();
1904				if (name === 'input' || name === 'textarea' || e.type === 'click') {
1905					base.close(o.autoAccept ? 'true' : false);
1906				}
1907			} else {
1908				// send 'true' instead of a true (boolean), the input won't get a 'ui-keyboard-autoaccepted'
1909				// class name - see issue #66
1910				base.close(o.autoAccept ? 'true' : false);
1911			}
1912		}
1913	};
1914
1915	// Build default button
1916	base.keyBtn = $('<button />')
1917		.attr({
1918			'role': 'button',
1919			'type': 'button',
1920			'aria-disabled': 'false',
1921			'tabindex': '-1'
1922		})
1923		.addClass($keyboard.css.keyButton);
1924
1925	// convert key names into a class name
1926	base.processName = function (name) {
1927		var index, n,
1928			process = (name || '').replace(/[^a-z0-9-_]/gi, ''),
1929			len = process.length,
1930			newName = [];
1931		if (len > 1 && name === process) {
1932			// return name if basic text
1933			return name;
1934		}
1935		// return character code sequence
1936		len = name.length;
1937		if (len) {
1938			for (index = 0; index < len; index++) {
1939				n = name[index];
1940				// keep '-' and '_'... so for dash, we get two dashes in a row
1941				newName.push(/[a-z0-9-_]/i.test(n) ?
1942					(/[-_]/.test(n) && index !== 0 ? '' : n) :
1943					(index === 0 ? '' : '-') + n.charCodeAt(0)
1944				);
1945			}
1946			return newName.join('');
1947		}
1948		return name;
1949	};
1950
1951	base.processKeys = function (name) {
1952		var tmp,
1953			// Don't split colons followed by //, e.g. https://; Fixes #555
1954			parts = name.split(/:(?!\/\/)/),
1955			data = {
1956				name: null,
1957				map: '',
1958				title: ''
1959			};
1960		/* map defined keys
1961		format 'key(A):Label_for_key_(ignore_parentheses_here)'
1962			'key' = key that is seen (can any character(s); but it might need to be escaped using '\'
1963			or entered as unicode '\u####'
1964			'(A)' = the actual key on the real keyboard to remap
1965			':Label_for_key' ends up in the title/tooltip
1966		Examples:
1967			'\u0391(A):alpha', 'x(y):this_(might)_cause_problems
1968			or edge cases of ':(x)', 'x(:)', 'x(()' or 'x())'
1969		Enhancement (if I can get alt keys to work):
1970			A mapped key will include the mod key, e.g. 'x(alt-x)' or 'x(alt-shift-x)'
1971		*/
1972		if (/\(.+\)/.test(parts[0]) || /^:\(.+\)/.test(name) || /\([(:)]\)/.test(name)) {
1973			// edge cases 'x(:)', 'x(()' or 'x())'
1974			if (/\([(:)]\)/.test(name)) {
1975				tmp = parts[0].match(/([^(]+)\((.+)\)/);
1976				if (tmp && tmp.length) {
1977					data.name = tmp[1];
1978					data.map = tmp[2];
1979					data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
1980				} else {
1981					// edge cases 'x(:)', ':(x)' or ':(:)'
1982					data.name = name.match(/([^(]+)/)[0];
1983					if (data.name === ':') {
1984						// ':(:):test' => parts = [ '', '(', ')', 'title' ] need to slice 1
1985						parts = parts.slice(1);
1986					}
1987					if (tmp === null) {
1988						// 'x(:):test' => parts = [ 'x(', ')', 'title' ] need to slice 2
1989						data.map = ':';
1990						parts = parts.slice(2);
1991					}
1992					data.title = parts.length ? parts.join(':') : '';
1993				}
1994			} else {
1995				// example: \u0391(A):alpha; extract 'A' from '(A)'
1996				data.map = name.match(/\(([^()]+?)\)/)[1];
1997				// remove '(A)', left with '\u0391:alpha'
1998				name = name.replace(/\(([^()]+)\)/, '');
1999				tmp = name.split(':');
2000				// get '\u0391' from '\u0391:alpha'
2001				if (tmp[0] === '') {
2002					data.name = ':';
2003					parts = parts.slice(1);
2004				} else {
2005					data.name = tmp[0];
2006				}
2007				data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
2008			}
2009		} else {
2010			// find key label
2011			// corner case of '::;' reduced to ':;', split as ['', ';']
2012			if (name !== '' && parts[0] === '') {
2013				data.name = ':';
2014				parts = parts.slice(1);
2015			} else {
2016				data.name = parts[0];
2017			}
2018			data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
2019		}
2020		data.title = $.trim(data.title).replace(/_/g, ' ');
2021		return data;
2022	};
2023
2024	// Add key function
2025	// keyName = the name of the function called in $.keyboard.keyaction when the button is clicked
2026	// name = name added to key, or cross-referenced in the display options
2027	// base.temp[0] = keyset to attach the new button
2028	// regKey = true when it is not an action key
2029	base.addKey = function (keyName, action, regKey) {
2030		var keyClass, tmp, keys,
2031			data = {},
2032			txt = base.processKeys(regKey ? keyName : action),
2033			kbcss = $keyboard.css;
2034
2035		if (!regKey && o.display[txt.name]) {
2036			keys = base.processKeys(o.display[txt.name]);
2037			// action contained in "keyName" (e.g. keyName = "accept",
2038			// action = "a" (use checkmark instead of text))
2039			keys.action = base.processKeys(keyName).name;
2040		} else {
2041			// when regKey is true, keyName is the same as action
2042			keys = txt;
2043			keys.action = txt.name;
2044		}
2045
2046		data.name = base.processName(txt.name);
2047		if (keys.name !== '') {
2048			if (keys.map !== '') {
2049				$keyboard.builtLayouts[base.layout].mappedKeys[keys.map] = keys.name;
2050				$keyboard.builtLayouts[base.layout].acceptedKeys.push(keys.name);
2051			} else if (regKey) {
2052				$keyboard.builtLayouts[base.layout].acceptedKeys.push(keys.name);
2053			}
2054		}
2055
2056		if (regKey) {
2057			keyClass = data.name === '' ? '' : kbcss.keyPrefix + data.name;
2058		} else {
2059			// Action keys will have the 'ui-keyboard-actionkey' class
2060			keyClass = kbcss.keyAction + ' ' + kbcss.keyPrefix + keys.action;
2061		}
2062		// '\u2190'.length = 1 because the unicode is converted, so if more than one character,
2063		// add the wide class
2064		keyClass += (keys.name.length > 2 ? ' ' + kbcss.keyWide : '') + ' ' + o.css.buttonDefault;
2065
2066		data.html = '<span class="' + kbcss.keyText + '">' +
2067			// this prevents HTML from being added to the key
2068			keys.name.replace(/[\u00A0-\u9999]/gim, function (i) {
2069				return '&#' + i.charCodeAt(0) + ';';
2070			}) +
2071			'</span>';
2072
2073		data.$key = base.keyBtn
2074			.clone()
2075			.attr({
2076				'data-value': regKey ? keys.name : keys.action, // value
2077				'data-name': keys.action,
2078				'data-pos': base.temp[1] + ',' + base.temp[2],
2079				'data-action': keys.action,
2080				'data-html': data.html
2081			})
2082			// add 'ui-keyboard-' + data.name for all keys
2083			//  (e.g. 'Bksp' will have 'ui-keyboard-bskp' class)
2084			// any non-alphanumeric characters will be replaced with
2085			//  their decimal unicode value
2086			//  (e.g. '~' is a regular key, class = 'ui-keyboard-126'
2087			//  (126 is the unicode decimal value - same as &#126;)
2088			//  See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
2089			.addClass(keyClass)
2090			.html(data.html)
2091			.appendTo(base.temp[0]);
2092
2093		if (keys.map) {
2094			data.$key.attr('data-mapped', keys.map);
2095		}
2096		if (keys.title || txt.title) {
2097			data.$key.attr({
2098				'data-title': txt.title || keys.title, // used to allow adding content to title
2099				'title': txt.title || keys.title
2100			});
2101		}
2102
2103		if (typeof o.buildKey === 'function') {
2104			data = o.buildKey(base, data);
2105			// copy html back to attributes
2106			tmp = data.$key.html();
2107			data.$key.attr('data-html', tmp);
2108		}
2109		return data.$key;
2110	};
2111
2112	base.customHash = function (layout) {
2113		/*jshint bitwise:false */
2114		var i, array, hash, character, len,
2115			arrays = [],
2116			merged = [];
2117		// pass layout to allow for testing
2118		layout = typeof layout === 'undefined' ? o.customLayout : layout;
2119		// get all layout arrays
2120		for (array in layout) {
2121			if (layout.hasOwnProperty(array)) {
2122				arrays.push(layout[array]);
2123			}
2124		}
2125		// flatten array
2126		merged = merged.concat.apply(merged, arrays).join(' ');
2127		// produce hash name - http://stackoverflow.com/a/7616484/145346
2128		hash = 0;
2129		len = merged.length;
2130		if (len === 0) {
2131			return hash;
2132		}
2133		for (i = 0; i < len; i++) {
2134			character = merged.charCodeAt(i);
2135			hash = ((hash << 5) - hash) + character;
2136			hash = hash & hash; // Convert to 32bit integer
2137		}
2138		return hash;
2139	};
2140
2141	base.buildKeyboard = function (name, internal) {
2142		// o.display is empty when this is called from the scramble extension (when alwaysOpen:true)
2143		if ($.isEmptyObject(o.display)) {
2144			// set keyboard language
2145			base.updateLanguage();
2146		}
2147		var index, row, $row, currentSet,
2148			kbcss = $keyboard.css,
2149			sets = 0,
2150			layout = $keyboard.builtLayouts[name || base.layout || o.layout] = {
2151				mappedKeys: {},
2152				acceptedKeys: []
2153			},
2154			acceptedKeys = layout.acceptedKeys = o.restrictInclude ?
2155				('' + o.restrictInclude).split(/\s+/) || [] :
2156				[],
2157			// using $layout temporarily to hold keyboard popup classnames
2158			$layout = kbcss.keyboard + ' ' + o.css.popup + ' ' + o.css.container +
2159				(o.alwaysOpen || o.userClosed ? ' ' + kbcss.alwaysOpen : ''),
2160
2161			container = $('<div />')
2162				.addClass($layout)
2163				.attr({
2164					'role': 'textbox'
2165				})
2166				.hide();
2167
2168		// allow adding "{space}" as an accepted key - Fixes #627
2169		index = $.inArray('{space}', acceptedKeys);
2170		if (index > -1) {
2171			acceptedKeys[index] = ' ';
2172		}
2173
2174		// verify layout or setup custom keyboard
2175		if ((internal && o.layout === 'custom') || !$keyboard.layouts.hasOwnProperty(o.layout)) {
2176			o.layout = 'custom';
2177			$layout = $keyboard.layouts.custom = o.customLayout || {
2178				'normal': ['{cancel}']
2179			};
2180		} else {
2181			$layout = $keyboard.layouts[internal ? o.layout : name || base.layout || o.layout];
2182		}
2183
2184		// Main keyboard building loop
2185		$.each($layout, function (set, keySet) {
2186			// skip layout name & lang settings
2187			if (set !== '' && !/^(name|lang|rtl)$/i.test(set)) {
2188				// keep backwards compatibility for change from default to normal naming
2189				if (set === 'default') {
2190					set = 'normal';
2191				}
2192				sets++;
2193				$row = $('<div />')
2194					.attr('name', set) // added for typing extension
2195					.addClass(kbcss.keySet + ' ' + kbcss.keySet + '-' + set)
2196					.appendTo(container)
2197					.toggle(set === 'normal');
2198
2199				for (row = 0; row < keySet.length; row++) {
2200					// remove extra spaces before spliting (regex probably could be improved)
2201					currentSet = $.trim(keySet[row]).replace(/\{(\.?)[\s+]?:[\s+]?(\.?)\}/g, '{$1:$2}');
2202					base.buildRow($row, row, currentSet.split(/\s+/), acceptedKeys);
2203					$row.find('.' + kbcss.keyButton + ',.' + kbcss.keySpacer)
2204						.filter(':last')
2205						.after('<br class="' + kbcss.endRow + '"/>');
2206				}
2207			}
2208		});
2209
2210		if (sets > 1) {
2211			base.sets = true;
2212		}
2213		layout.hasMappedKeys = !($.isEmptyObject(layout.mappedKeys));
2214		layout.$keyboard = container;
2215		return container;
2216	};
2217
2218	base.buildRow = function ($row, row, keys, acceptedKeys) {
2219		var t, txt, key, isAction, action, margin,
2220			kbcss = $keyboard.css;
2221		for (key = 0; key < keys.length; key++) {
2222			// used by addKey function
2223			base.temp = [$row, row, key];
2224			isAction = false;
2225
2226			// ignore empty keys
2227			if (keys[key].length === 0) {
2228				continue;
2229			}
2230
2231			// process here if it's an action key
2232			if (/^\{\S+\}$/.test(keys[key])) {
2233				action = keys[key].match(/^\{(\S+)\}$/)[1];
2234				// add active class if there are double exclamation points in the name
2235				if (/\!\!/.test(action)) {
2236					action = action.replace('!!', '');
2237					isAction = true;
2238				}
2239
2240				// add empty space
2241				if (/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i.test(action)) {
2242					// not perfect globalization, but allows you to use {sp:1,1em}, {sp:1.2em} or {sp:15px}
2243					margin = parseFloat(action
2244						.replace(/,/, '.')
2245						.match(/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
2246					);
2247					$('<span class="' + kbcss.keyText + '"></span>')
2248						// previously {sp:1} would add 1em margin to each side of a 0 width span
2249						// now Firefox doesn't seem to render 0px dimensions, so now we set the
2250						// 1em margin x 2 for the width
2251						.width((action.match(/px/i) ? margin + 'px' : (margin * 2) + 'em'))
2252						.addClass(kbcss.keySpacer)
2253						.appendTo($row);
2254				}
2255
2256				// add empty button
2257				if (/^empty(:((\d+)?([\.|,]\d+)?)(em|px)?)?$/i.test(action)) {
2258					margin = (/:/.test(action)) ? parseFloat(action
2259						.replace(/,/, '.')
2260						.match(/^empty:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
2261					) : '';
2262					base
2263						.addKey('', ' ', true)
2264						.addClass(o.css.buttonDisabled + ' ' + o.css.buttonEmpty)
2265						.attr('aria-disabled', true)
2266						.width(margin ? (action.match('px') ? margin + 'px' : (margin * 2) + 'em') : '');
2267					continue;
2268				}
2269
2270				// meta keys
2271				if (/^meta[\w-]+\:?(\w+)?/i.test(action)) {
2272					base
2273						.addKey(action.split(':')[0], action)
2274						.addClass(kbcss.keyHasActive);
2275					continue;
2276				}
2277
2278				// switch needed for action keys with multiple names/shortcuts or
2279				// default will catch all others
2280				txt = action.split(':');
2281				switch (txt[0].toLowerCase()) {
2282
2283				case 'a':
2284				case 'accept':
2285					base
2286						.addKey('accept', action)
2287						.addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
2288					break;
2289
2290				case 'alt':
2291				case 'altgr':
2292					base
2293						.addKey('alt', action)
2294						.addClass(kbcss.keyHasActive);
2295					break;
2296
2297				case 'b':
2298				case 'bksp':
2299					base.addKey('bksp', action);
2300					break;
2301
2302				case 'c':
2303				case 'cancel':
2304					base
2305						.addKey('cancel', action)
2306						.addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
2307					break;
2308
2309					// toggle combo/diacritic key
2310					/*jshint -W083 */
2311				case 'combo':
2312					base
2313						.addKey('combo', action)
2314						.addClass(kbcss.keyHasActive)
2315						.attr('title', function (indx, title) {
2316							// add combo key state to title
2317							return title + ' ' + o.display[o.useCombos ? 'active' : 'disabled'];
2318						})
2319						.toggleClass(o.css.buttonActive, o.useCombos);
2320					break;
2321
2322					// Decimal - unique decimal point (num pad layout)
2323				case 'dec':
2324					acceptedKeys.push((base.decimal) ? '.' : ',');
2325					base.addKey('dec', action);
2326					break;
2327
2328				case 'e':
2329				case 'enter':
2330					base
2331						.addKey('enter', action)
2332						.addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
2333					break;
2334
2335				case 'lock':
2336					base
2337						.addKey('lock', action)
2338						.addClass(kbcss.keyHasActive);
2339					break;
2340
2341				case 's':
2342				case 'shift':
2343					base
2344						.addKey('shift', action)
2345						.addClass(kbcss.keyHasActive);
2346					break;
2347
2348					// Change sign (for num pad layout)
2349				case 'sign':
2350					acceptedKeys.push('-');
2351					base.addKey('sign', action);
2352					break;
2353
2354				case 'space':
2355					acceptedKeys.push(' ');
2356					base.addKey('space', action);
2357					break;
2358
2359				case 't':
2360				case 'tab':
2361					base.addKey('tab', action);
2362					break;
2363
2364				default:
2365					if ($keyboard.keyaction.hasOwnProperty(txt[0])) {
2366						base
2367							.addKey(txt[0], action)
2368							.toggleClass(o.css.buttonAction + ' ' + kbcss.keyAction, isAction);
2369					}
2370
2371				}
2372
2373			} else {
2374
2375				// regular button (not an action key)
2376				t = keys[key];
2377				base.addKey(t, t, true);
2378			}
2379		}
2380	};
2381
2382	base.removeBindings = function (namespace) {
2383		$(document).unbind(namespace);
2384		if (base.el.ownerDocument !== document) {
2385			$(base.el.ownerDocument).unbind(namespace);
2386		}
2387		$(window).unbind(namespace);
2388		base.$el.unbind(namespace);
2389	};
2390
2391	base.removeKeyboard = function () {
2392		base.$decBtn = [];
2393		// base.$preview === base.$el when o.usePreview is false - fixes #442
2394		if (o.usePreview) {
2395			base.$preview.removeData('keyboard');
2396		}
2397		base.$preview.unbind(base.namespace + 'keybindings');
2398		base.preview = null;
2399		base.$preview = null;
2400		base.$previewCopy = null;
2401		base.$keyboard.removeData('keyboard');
2402		base.$keyboard.remove();
2403		base.$keyboard = [];
2404		base.isOpen = false;
2405		base.isCurrent(false);
2406	};
2407
2408	base.destroy = function (callback) {
2409		var index,
2410			kbcss = $keyboard.css,
2411			len = base.extensionNamespace.length,
2412			tmp = [
2413				kbcss.input,
2414				kbcss.locked,
2415				kbcss.placeholder,
2416				kbcss.noKeyboard,
2417				kbcss.alwaysOpen,
2418				o.css.input,
2419				kbcss.isCurrent
2420			].join(' ');
2421		clearTimeout(base.timer);
2422		clearTimeout(base.timer2);
2423		clearTimeout(base.timer3);
2424		if (base.$keyboard.length) {
2425			base.removeKeyboard();
2426		}
2427		base.removeBindings(base.namespace);
2428		base.removeBindings(base.namespace + 'callbacks');
2429		for (index = 0; index < len; index++) {
2430			base.removeBindings(base.extensionNamespace[index]);
2431		}
2432		base.el.active = false;
2433
2434		base.$el
2435			.removeClass(tmp)
2436			.removeAttr('aria-haspopup')
2437			.removeAttr('role')
2438			.removeData('keyboard');
2439		base = null;
2440
2441		if (typeof callback === 'function') {
2442			callback();
2443		}
2444	};
2445
2446	// Run initializer
2447	base.init();
2448
2449	}; // end $.keyboard definition
2450
2451	// event.which & ASCII values
2452	$keyboard.keyCodes = {
2453		backSpace: 8,
2454		tab: 9,
2455		enter: 13,
2456		capsLock: 20,
2457		escape: 27,
2458		space: 32,
2459		pageUp: 33,
2460		pageDown: 34,
2461		end: 35,
2462		home: 36,
2463		left: 37,
2464		up: 38,
2465		right: 39,
2466		down: 40,
2467		insert: 45,
2468		delete: 46,
2469		// event.which keyCodes (uppercase letters)
2470		A: 65,
2471		Z: 90,
2472		V: 86,
2473		C: 67,
2474		X: 88,
2475
2476		// ASCII lowercase a & z
2477		a: 97,
2478		z: 122
2479	};
2480
2481	$keyboard.css = {
2482		// keyboard id suffix
2483		idSuffix: '_keyboard',
2484		// class name to set initial focus
2485		initialFocus: 'keyboard-init-focus',
2486		// element class names
2487		input: 'ui-keyboard-input',
2488		inputClone: 'ui-keyboard-preview-clone',
2489		wrapper: 'ui-keyboard-preview-wrapper',
2490		preview: 'ui-keyboard-preview',
2491		keyboard: 'ui-keyboard',
2492		keySet: 'ui-keyboard-keyset',
2493		keyButton: 'ui-keyboard-button',
2494		keyWide: 'ui-keyboard-widekey',
2495		keyPrefix: 'ui-keyboard-',
2496		keyText: 'ui-keyboard-text', // span with button text
2497		keyHasActive: 'ui-keyboard-hasactivestate',
2498		keyAction: 'ui-keyboard-actionkey',
2499		keySpacer: 'ui-keyboard-spacer', // empty keys
2500		keyToggle: 'ui-keyboard-toggle',
2501		keyDisabled: 'ui-keyboard-disabled',
2502		// Class for BRs with a div wrapper inside of contenteditable
2503		divWrapperCE: 'ui-keyboard-div-wrapper',
2504		// states
2505		locked: 'ui-keyboard-lockedinput',
2506		alwaysOpen: 'ui-keyboard-always-open',
2507		noKeyboard: 'ui-keyboard-nokeyboard',
2508		placeholder: 'ui-keyboard-placeholder',
2509		hasFocus: 'ui-keyboard-has-focus',
2510		isCurrent: 'ui-keyboard-input-current',
2511		// validation & autoaccept
2512		inputValid: 'ui-keyboard-valid-input',
2513		inputInvalid: 'ui-keyboard-invalid-input',
2514		inputAutoAccepted: 'ui-keyboard-autoaccepted',
2515		endRow: 'ui-keyboard-button-endrow' // class added to <br>
2516	};
2517
2518	$keyboard.events = {
2519		// keyboard events
2520		kbChange: 'keyboardChange',
2521		kbBeforeClose: 'beforeClose',
2522		kbBeforeVisible: 'beforeVisible',
2523		kbVisible: 'visible',
2524		kbInit: 'initialized',
2525		kbInactive: 'inactive',
2526		kbHidden: 'hidden',
2527		kbRepeater: 'repeater',
2528		kbKeysetChange: 'keysetChange',
2529		// input events
2530		inputAccepted: 'accepted',
2531		inputCanceled: 'canceled',
2532		inputChange: 'change',
2533		inputRestricted: 'restricted'
2534	};
2535
2536	// Action key function list
2537	$keyboard.keyaction = {
2538		accept: function (base) {
2539			base.close(true); // same as base.accept();
2540			return false; // return false prevents further processing
2541		},
2542		alt: function (base) {
2543			base.altActive = !base.altActive;
2544			base.showSet();
2545		},
2546		bksp: function (base) {
2547			if (base.isContentEditable) {
2548				base.execCommand('delete');
2549				// save new caret position
2550				base.saveCaret();
2551			} else {
2552				// the script looks for the '\b' string and initiates a backspace
2553				base.insertText('\b');
2554			}
2555		},
2556		cancel: function (base) {
2557			base.close();
2558			return false; // return false prevents further processing
2559		},
2560		clear: function (base) {
2561			base.$preview[base.isContentEditable ? 'text' : 'val']('');
2562			if (base.$decBtn.length) {
2563				base.checkDecimal();
2564			}
2565		},
2566		combo: function (base) {
2567			var o = base.options,
2568				c = !o.useCombos,
2569				$combo = base.$keyboard.find('.' + $keyboard.css.keyPrefix + 'combo');
2570			o.useCombos = c;
2571			$combo
2572				.toggleClass(o.css.buttonActive, c)
2573				// update combo key state
2574				.attr('title', $combo.attr('data-title') + ' (' + o.display[c ? 'active' : 'disabled'] + ')');
2575			if (c) {
2576				base.checkCombos();
2577			}
2578			return false;
2579		},
2580		dec: function (base) {
2581			base.insertText((base.decimal) ? '.' : ',');
2582		},
2583		del: function (base) {
2584			if (base.isContentEditable) {
2585				base.execCommand('forwardDelete');
2586			} else {
2587				// the script looks for the '{d}' string and initiates a delete
2588				base.insertText('{d}');
2589			}
2590		},
2591		// resets to base keyset (deprecated because "default" is a reserved word)
2592		'default': function (base) {
2593			base.shiftActive = base.altActive = base.metaActive = false;
2594			base.showSet();
2595		},
2596		// el is the pressed key (button) object; it is null when the real keyboard enter is pressed
2597		enter: function (base, el, e) {
2598			var tag = base.el.nodeName,
2599				o = base.options;
2600			// shift+enter in textareas
2601			if (e.shiftKey) {
2602				// textarea, input & contenteditable - enterMod + shift + enter = accept,
2603				//  then go to prev; base.switchInput(goToNext, autoAccept)
2604				// textarea & input - shift + enter = accept (no navigation)
2605				return (o.enterNavigation) ? base.switchInput(!e[o.enterMod], true) : base.close(true);
2606			}
2607			// input only - enterMod + enter to navigate
2608			if (o.enterNavigation && (tag !== 'TEXTAREA' || e[o.enterMod])) {
2609				return base.switchInput(!e[o.enterMod], o.autoAccept ? 'true' : false);
2610			}
2611			// pressing virtual enter button inside of a textarea - add a carriage return
2612			// e.target is span when clicking on text and button at other times
2613			if (tag === 'TEXTAREA' && $(e.target).closest('button').length) {
2614				// IE8 fix (space + \n) - fixes #71 thanks Blookie!
2615				base.insertText(($keyboard.msie ? ' ' : '') + '\n');
2616			}
2617			if (base.isContentEditable && !o.enterNavigation) {
2618				base.execCommand('insertHTML', '<div><br class="' + $keyboard.css.divWrapperCE + '"></div>');
2619				// Using backspace on wrapped BRs will now shift the textnode inside of the wrapped BR
2620				// Although not ideal, the caret is moved after the block - see the wiki page for
2621				// more details: https://github.com/Mottie/Keyboard/wiki/Contenteditable#limitations
2622				// move caret after a delay to allow rendering of HTML
2623				setTimeout(function() {
2624					$keyboard.keyaction.right(base);
2625					base.saveCaret();
2626				}, 0);
2627			}
2628		},
2629		// caps lock key
2630		lock: function (base) {
2631			base.last.keyset[0] = base.shiftActive = base.capsLock = !base.capsLock;
2632			base.showSet();
2633		},
2634		left: function (base) {
2635			var p = $keyboard.caret(base.$preview);
2636			if (p.start - 1 >= 0) {
2637				// move both start and end of caret (prevents text selection) & save caret position
2638				base.last.start = base.last.end = p.start - 1;
2639				$keyboard.caret(base.$preview, base.last);
2640				base.setScroll();
2641			}
2642		},
2643		meta: function (base, el) {
2644			var $el = $(el);
2645			base.metaActive = !$el.hasClass(base.options.css.buttonActive);
2646			base.showSet($el.attr('data-name'));
2647		},
2648		next: function (base) {
2649			base.switchInput(true, base.options.autoAccept);
2650			return false;
2651		},
2652		// same as 'default' - resets to base keyset
2653		normal: function (base) {
2654			base.shiftActive = base.altActive = base.metaActive = false;
2655			base.showSet();
2656		},
2657		prev: function (base) {
2658			base.switchInput(false, base.options.autoAccept);
2659			return false;
2660		},
2661		right: function (base) {
2662			var p = $keyboard.caret(base.$preview),
2663				len = base.isContentEditable ? $keyboard.getEditableLength(base.el) : base.getValue().length;
2664			if (p.end + 1 <= len) {
2665				// move both start and end of caret to end position
2666				// (prevents text selection) && save caret position
2667				base.last.start = base.last.end = p.end + 1;
2668				$keyboard.caret(base.$preview, base.last);
2669				base.setScroll();
2670			}
2671		},
2672		shift: function (base) {
2673			base.last.keyset[0] = base.shiftActive = !base.shiftActive;
2674			base.showSet();
2675		},
2676		sign: function (base) {
2677			if (/^[+-]?\d*\.?\d*$/.test(base.getValue())) {
2678				var caret,
2679					p = $keyboard.caret(base.$preview),
2680					val = base.getValue(),
2681					len = base.isContentEditable ? $keyboard.getEditableLength(base.el) : val.length;
2682				base.setValue(val * -1);
2683				caret = len - val.length;
2684				base.last.start = p.start + caret;
2685				base.last.end = p.end + caret;
2686				$keyboard.caret(base.$preview, base.last);
2687				base.setScroll();
2688			}
2689		},
2690		space: function (base) {
2691			base.insertText(' ');
2692		},
2693		tab: function (base) {
2694			var tag = base.el.nodeName,
2695				o = base.options;
2696			if (tag !== 'TEXTAREA') {
2697				if (o.tabNavigation) {
2698					return base.switchInput(!base.shiftActive, true);
2699				} else if (tag === 'INPUT') {
2700					// ignore tab key in input
2701					return false;
2702				}
2703			}
2704			base.insertText('\t');
2705		},
2706		toggle: function (base) {
2707			base.enabled = !base.enabled;
2708			base.toggle();
2709		},
2710		// *** Special action keys: NBSP & zero-width characters ***
2711		// Non-breaking space
2712		NBSP: '\u00a0',
2713		// zero width space
2714		ZWSP: '\u200b',
2715		// Zero width non-joiner
2716		ZWNJ: '\u200c',
2717		// Zero width joiner
2718		ZWJ: '\u200d',
2719		// Left-to-right Mark
2720		LRM: '\u200e',
2721		// Right-to-left Mark
2722		RLM: '\u200f'
2723	};
2724
2725	// Default keyboard layouts
2726	$keyboard.builtLayouts = {};
2727	$keyboard.layouts = {
2728		'alpha': {
2729			'normal': [
2730				'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2731				'{tab} a b c d e f g h i j [ ] \\',
2732				'k l m n o p q r s ; \' {enter}',
2733				'{shift} t u v w x y z , . / {shift}',
2734				'{accept} {space} {cancel}'
2735			],
2736			'shift': [
2737				'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2738				'{tab} A B C D E F G H I J { } |',
2739				'K L M N O P Q R S : " {enter}',
2740				'{shift} T U V W X Y Z < > ? {shift}',
2741				'{accept} {space} {cancel}'
2742			]
2743		},
2744		'qwerty': {
2745			'normal': [
2746				'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2747				'{tab} q w e r t y u i o p [ ] \\',
2748				'a s d f g h j k l ; \' {enter}',
2749				'{shift} z x c v b n m , . / {shift}',
2750				'{accept} {space} {cancel}'
2751			],
2752			'shift': [
2753				'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2754				'{tab} Q W E R T Y U I O P { } |',
2755				'A S D F G H J K L : " {enter}',
2756				'{shift} Z X C V B N M < > ? {shift}',
2757				'{accept} {space} {cancel}'
2758			]
2759		},
2760		'international': {
2761			'normal': [
2762				'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2763				'{tab} q w e r t y u i o p [ ] \\',
2764				'a s d f g h j k l ; \' {enter}',
2765				'{shift} z x c v b n m , . / {shift}',
2766				'{accept} {alt} {space} {alt} {cancel}'
2767			],
2768			'shift': [
2769				'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2770				'{tab} Q W E R T Y U I O P { } |',
2771				'A S D F G H J K L : " {enter}',
2772				'{shift} Z X C V B N M < > ? {shift}',
2773				'{accept} {alt} {space} {alt} {cancel}'
2774			],
2775			'alt': [
2776				'~ \u00a1 \u00b2 \u00b3 \u00a4 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00d7 {bksp}',
2777				'{tab} \u00e4 \u00e5 \u00e9 \u00ae \u00fe \u00fc \u00fa \u00ed \u00f3 \u00f6 \u00ab \u00bb \u00ac',
2778				'\u00e1 \u00df \u00f0 f g h j k \u00f8 \u00b6 \u00b4 {enter}',
2779				'{shift} \u00e6 x \u00a9 v b \u00f1 \u00b5 \u00e7 > \u00bf {shift}',
2780				'{accept} {alt} {space} {alt} {cancel}'
2781			],
2782			'alt-shift': [
2783				'~ \u00b9 \u00b2 \u00b3 \u00a3 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00f7 {bksp}',
2784				'{tab} \u00c4 \u00c5 \u00c9 \u00ae \u00de \u00dc \u00da \u00cd \u00d3 \u00d6 \u00ab \u00bb \u00a6',
2785				'\u00c4 \u00a7 \u00d0 F G H J K \u00d8 \u00b0 \u00a8 {enter}',
2786				'{shift} \u00c6 X \u00a2 V B \u00d1 \u00b5 \u00c7 . \u00bf {shift}',
2787				'{accept} {alt} {space} {alt} {cancel}'
2788			]
2789		},
2790		'colemak': {
2791			'normal': [
2792				'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2793				'{tab} q w f p g j l u y ; [ ] \\',
2794				'{bksp} a r s t d h n e i o \' {enter}',
2795				'{shift} z x c v b k m , . / {shift}',
2796				'{accept} {space} {cancel}'
2797			],
2798			'shift': [
2799				'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2800				'{tab} Q W F P G J L U Y : { } |',
2801				'{bksp} A R S T D H N E I O " {enter}',
2802				'{shift} Z X C V B K M < > ? {shift}',
2803				'{accept} {space} {cancel}'
2804			]
2805		},
2806		'dvorak': {
2807			'normal': [
2808				'` 1 2 3 4 5 6 7 8 9 0 [ ] {bksp}',
2809				'{tab} \' , . p y f g c r l / = \\',
2810				'a o e u i d h t n s - {enter}',
2811				'{shift} ; q j k x b m w v z {shift}',
2812				'{accept} {space} {cancel}'
2813			],
2814			'shift': [
2815				'~ ! @ # $ % ^ & * ( ) { } {bksp}',
2816				'{tab} " < > P Y F G C R L ? + |',
2817				'A O E U I D H T N S _ {enter}',
2818				'{shift} : Q J K X B M W V Z {shift}',
2819				'{accept} {space} {cancel}'
2820			]
2821		},
2822		'num': {
2823			'normal': [
2824				'= ( ) {b}',
2825				'{clear} / * -',
2826				'7 8 9 +',
2827				'4 5 6 {sign}',
2828				'1 2 3 %',
2829				'0 {dec} {a} {c}'
2830			]
2831		}
2832	};
2833
2834	$keyboard.language = {
2835		en: {
2836			display: {
2837				// check mark - same action as accept
2838				'a': '\u2714:Accept (Shift+Enter)',
2839				'accept': 'Accept:Accept (Shift+Enter)',
2840				// other alternatives \u2311
2841				'alt': 'Alt:\u2325 AltGr',
2842				// Left arrow (same as &larr;)
2843				'b': '\u232b:Backspace',
2844				'bksp': 'Bksp:Backspace',
2845				// big X, close - same action as cancel
2846				'c': '\u2716:Cancel (Esc)',
2847				'cancel': 'Cancel:Cancel (Esc)',
2848				// clear num pad
2849				'clear': 'C:Clear',
2850				'combo': '\u00f6:Toggle Combo Keys',
2851				// decimal point for num pad (optional), change '.' to ',' for European format
2852				'dec': '.:Decimal',
2853				// down, then left arrow - enter symbol
2854				'e': '\u23ce:Enter',
2855				'empty': '\u00a0',
2856				'enter': 'Enter:Enter \u23ce',
2857				// left arrow (move caret)
2858				'left': '\u2190',
2859				// caps lock
2860				'lock': 'Lock:\u21ea Caps Lock',
2861				'next': 'Next \u21e8',
2862				'prev': '\u21e6 Prev',
2863				// right arrow (move caret)
2864				'right': '\u2192',
2865				// thick hollow up arrow
2866				's': '\u21e7:Shift',
2867				'shift': 'Shift:Shift',
2868				// +/- sign for num pad
2869				'sign': '\u00b1:Change Sign',
2870				'space': '\u00a0:Space',
2871				// right arrow to bar (used since this virtual keyboard works with one directional tabs)
2872				't': '\u21e5:Tab',
2873				// \u21b9 is the true tab symbol (left & right arrows)
2874				'tab': '\u21e5 Tab:Tab',
2875				// replaced by an image
2876				'toggle': ' ',
2877
2878				// added to titles of keys
2879				// accept key status when acceptValid:true
2880				'valid': 'valid',
2881				'invalid': 'invalid',
2882				// combo key states
2883				'active': 'active',
2884				'disabled': 'disabled'
2885			},
2886
2887			// Message added to the key title while hovering, if the mousewheel plugin exists
2888			wheelMessage: 'Use mousewheel to see other keys',
2889
2890			comboRegex: /([`\'~\^\"ao])([a-z])/mig,
2891			combos: {
2892				// grave
2893				'`': { a: '\u00e0', A: '\u00c0', e: '\u00e8', E: '\u00c8', i: '\u00ec', I: '\u00cc', o: '\u00f2',
2894						O: '\u00d2', u: '\u00f9', U: '\u00d9', y: '\u1ef3', Y: '\u1ef2' },
2895				// acute & cedilla
2896				"'": { a: '\u00e1', A: '\u00c1', e: '\u00e9', E: '\u00c9', i: '\u00ed', I: '\u00cd', o: '\u00f3',
2897						O: '\u00d3', u: '\u00fa', U: '\u00da', y: '\u00fd', Y: '\u00dd' },
2898				// umlaut/trema
2899				'"': { a: '\u00e4', A: '\u00c4', e: '\u00eb', E: '\u00cb', i: '\u00ef', I: '\u00cf', o: '\u00f6',
2900						O: '\u00d6', u: '\u00fc', U: '\u00dc', y: '\u00ff', Y: '\u0178' },
2901				// circumflex
2902				'^': { a: '\u00e2', A: '\u00c2', e: '\u00ea', E: '\u00ca', i: '\u00ee', I: '\u00ce', o: '\u00f4',
2903						O: '\u00d4', u: '\u00fb', U: '\u00db', y: '\u0177', Y: '\u0176' },
2904				// tilde
2905				'~': { a: '\u00e3', A: '\u00c3', e: '\u1ebd', E: '\u1ebc', i: '\u0129', I: '\u0128', o: '\u00f5',
2906						O: '\u00d5', u: '\u0169', U: '\u0168', y: '\u1ef9', Y: '\u1ef8', n: '\u00f1', N: '\u00d1' }
2907			}
2908		}
2909	};
2910
2911	$keyboard.defaultOptions = {
2912		// set this to ISO 639-1 language code to override language set by the layout
2913		// http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
2914		// language defaults to 'en' if not found
2915		language: null,
2916		rtl: false,
2917
2918		// *** choose layout & positioning ***
2919		layout: 'qwerty',
2920		customLayout: null,
2921
2922		position: {
2923			// optional - null (attach to input/textarea) or a jQuery object (attach elsewhere)
2924			of: null,
2925			my: 'center top',
2926			at: 'center top',
2927			// used when 'usePreview' is false (centers the keyboard at the bottom of the input/textarea)
2928			at2: 'center bottom'
2929		},
2930
2931		// allow jQuery position utility to reposition the keyboard on window resize
2932		reposition: true,
2933
2934		// preview added above keyboard if true, original input/textarea used if false
2935		usePreview: true,
2936
2937		// if true, the keyboard will always be visible
2938		alwaysOpen: false,
2939
2940		// give the preview initial focus when the keyboard becomes visible
2941		initialFocus: true,
2942
2943		// avoid changing the focus (hardware keyboard probably won't work)
2944		noFocus: false,
2945
2946		// if true, keyboard will remain open even if the input loses focus, but closes on escape
2947		// or when another keyboard opens.
2948		stayOpen: false,
2949
2950		// Prevents the keyboard from closing when the user clicks or presses outside the keyboard
2951		// the `autoAccept` option must also be set to true when this option is true or changes are lost
2952		userClosed: false,
2953
2954		// if true, keyboard will not close if you press escape.
2955		ignoreEsc: false,
2956
2957		// if true, keyboard will only closed on click event instead of mousedown and touchstart
2958		closeByClickEvent: false,
2959
2960		css: {
2961			// input & preview
2962			input: 'ui-widget-content ui-corner-all',
2963			// keyboard container
2964			container: 'ui-widget-content ui-widget ui-corner-all ui-helper-clearfix',
2965			// keyboard container extra class (same as container, but separate)
2966			popup: '',
2967			// default state
2968			buttonDefault: 'ui-state-default ui-corner-all',
2969			// hovered button
2970			buttonHover: 'ui-state-hover',
2971			// Action keys (e.g. Accept, Cancel, Tab, etc); this replaces 'actionClass' option
2972			buttonAction: 'ui-state-active',
2973			// Active keys (e.g. shift down, meta keyset active, combo keys active)
2974			buttonActive: 'ui-state-active',
2975			// used when disabling the decimal button {dec} when a decimal exists in the input area
2976			buttonDisabled: 'ui-state-disabled',
2977			buttonEmpty: 'ui-keyboard-empty'
2978		},
2979
2980		// *** Useability ***
2981		// Auto-accept content when clicking outside the keyboard (popup will close)
2982		autoAccept: false,
2983		// Auto-accept content even if the user presses escape (only works if `autoAccept` is `true`)
2984		autoAcceptOnEsc: false,
2985
2986		// Prevents direct input in the preview window when true
2987		lockInput: false,
2988
2989		// Prevent keys not in the displayed keyboard from being typed in
2990		restrictInput: false,
2991		// Additional allowed characters while restrictInput is true
2992		restrictInclude: '', // e.g. 'a b foo \ud83d\ude38'
2993
2994		// Check input against validate function, if valid the accept button gets a class name of
2995		// 'ui-keyboard-valid-input'. If invalid, the accept button gets a class name of
2996		// 'ui-keyboard-invalid-input'
2997		acceptValid: false,
2998		// Auto-accept when input is valid; requires `acceptValid` set `true` & validate callback
2999		autoAcceptOnValid: false,
3000		// Check validation on keyboard initialization. If false, the "Accept" key state (color)
3001		// will not change to show if the content is valid, or not
3002		checkValidOnInit: true,
3003
3004		// if acceptValid is true & the validate function returns a fal
3004se, this option will cancel
3005		// a keyboard close only after the accept button is pressed
3006		cancelClose: true,
3007
3008		// tab to go to next, shift-tab for previous (default behavior)
3009		tabNavigation: false,
3010
3011		// enter for next input; shift+enter accepts content & goes to next
3012		// shift + 'enterMod' + enter ('enterMod' is the alt as set below) will accept content and go
3013		// to previous in a textarea
3014		enterNavigation: false,
3015		// mod key options: 'ctrlKey', 'shiftKey', 'altKey', 'metaKey' (MAC only)
3016		enterMod: 'altKey', // alt-enter to go to previous; shift-alt-enter to accept & go to previous
3017
3018		// if true, the next button will stop on the last keyboard input/textarea; prev button stops at first
3019		// if false, the next button will wrap to target the first input/textarea; prev will go to the last
3020		stopAtEnd: true,
3021
3022		// Set this to append the keyboard after the input/textarea (appended to the input/textarea parent).
3023		// This option works best when the input container doesn't have a set width & when the 'tabNavigation'
3024		// option is true.
3025		appendLocally: false,
3026		// When appendLocally is false, the keyboard will be appended to this object
3027		appendTo: 'body',
3028
3029		// Wrap all <br>s inside of a contenteditable in a div; without wrapping, the caret
3030		// position will not be accurate
3031		wrapBRs: true,
3032
3033		// If false, the shift key will remain active until the next key is (mouse) clicked on; if true it will
3034		// stay active until pressed again
3035		stickyShift: true,
3036
3037		// Prevent pasting content into the area
3038		preventPaste: false,
3039
3040		// caret placed at the end of any text when keyboard becomes visible
3041		caretToEnd: false,
3042
3043		// caret stays this many pixels from the edge of the input while scrolling left/right;
3044		// use "c" or "center" to center the caret while scrolling
3045		scrollAdjustment: 10,
3046
3047		// Set the max number of characters allowed in the input, setting it to false disables this option
3048		maxLength: false,
3049		// allow inserting characters @ caret when maxLength is set
3050		maxInsert: true,
3051
3052		// Mouse repeat delay - when clicking/touching a virtual keyboard key, after this delay the key will
3053		// start repeating
3054		repeatDelay: 500,
3055
3056		// Mouse repeat rate - after the repeatDelay, this is the rate (characters per second) at which the
3057		// key is repeated Added to simulate holding down a real keyboard key and having it repeat. I haven't
3058		// calculated the upper limit of this rate, but it is limited to how fast the javascript can process
3059		// the keys. And for me, in Firefox, it's around 20.
3060		repeatRate: 20,
3061
3062		// resets the keyboard to the default keyset when visible
3063		resetDefault: true,
3064
3065		// Event (namespaced) on the input to reveal the keyboard. To disable it, just set it to ''.
3066		openOn: 'focus',
3067
3068		// enable the keyboard on readonly inputs
3069		activeOnReadonly: false,
3070
3071		// Event (namepaced) for when the character is added to the input (clicking on the keyboard)
3072		keyBinding: 'mousedown touchstart',
3073
3074		// enable/disable mousewheel functionality
3075		// enabling still depends on the mousewheel plugin
3076		useWheel: true,
3077
3078		// combos (emulate dead keys : http://en.wikipedia.org/wiki/Keyboard_layout#US-International)
3079		// if user inputs `a the script converts it to à, ^o becomes ô, etc.
3080		useCombos: true,
3081
3082		/*
3083			// *** Methods ***
3084			// commenting these out to reduce the size of the minified version
3085			// Callbacks - attach a function to any of these callbacks as desired
3086			initialized   : function(e, keyboard, el) {},
3087			beforeVisible : function(e, keyboard, el) {},
3088			visible       : function(e, keyboard, el) {},
3089			beforeInsert  : function(e, keyboard, el, textToAdd) { return textToAdd; },
3090			change        : function(e, keyboard, el) {},
3091			beforeClose   : function(e, keyboard, el, accepted) {},
3092			accepted      : function(e, keyboard, el) {},
3093			canceled      : function(e, keyboard, el) {},
3094			restricted    : function(e, keyboard, el) {},
3095			hidden        : function(e, keyboard, el) {},
3096			// called instead of base.switchInput
3097			switchInput   : function(keyboard, goToNext, isAccepted) {},
3098			// used if you want to create a custom layout or modify the built-in keyboard
3099			create        : function(keyboard) { return keyboard.buildKeyboard(); },
3100
3101			// build key callback
3102			buildKey : function( keyboard, data ) {
3103				/ *
3104				data = {
3105				// READ ONLY
3106				isAction : [boolean] true if key is an action key
3107				name     : [string]  key class name suffix ( prefix = 'ui-keyboard-' );
3108														 may include decimal ascii value of character
3109				value    : [string]  text inserted (non-action keys)
3110				title    : [string]  title attribute of key
3111				action   : [string]  keyaction name
3112				html     : [string]  HTML of the key; it includes a <span> wrapping the text
3113				// use to modify key HTML
3114				$key     : [object]  jQuery selector of key which is already appended to keyboard
3115				}
3116				* /
3117				return data;
3118			},
3119		*/
3120
3121		// this callback is called, if the acceptValid is true, and just before the 'beforeClose' to check
3122		// the value if the value is valid, return true and the keyboard will continue as it should
3123		// (close if not always open, etc). If the value is not valid, return false and clear the keyboard
3124		// value ( like this "keyboard.$preview.val('');" ), if desired. The validate function is called after
3125		// each input, the 'isClosing' value will be false; when the accept button is clicked,
3126		// 'isClosing' is true
3127		validate: function (/* keyboard, value, isClosing */) {
3128			return true;
3129		}
3130
3131	};
3132
3133	// for checking combos
3134	$keyboard.comboRegex = /([`\'~\^\"ao])([a-z])/mig;
3135
3136	// store current keyboard element; used by base.isCurrent()
3137	$keyboard.currentKeyboard = '';
3138
3139	$('<!--[if lte IE 8]><script>jQuery("body").addClass("oldie");</script><![endif]--><!--[if IE]>' +
3140			'<script>jQuery("body").addClass("ie");</script><![endif]-->')
3141		.appendTo('body')
3142		.remove();
3143	$keyboard.msie = $('body').hasClass('oldie'); // Old IE flag, used for caret positioning
3144	$keyboard.allie = $('body').hasClass('ie');
3145
3146	$keyboard.watermark = (typeof (document.createElement('input').placeholder) !== 'undefined');
3147
3148	$keyboard.checkCaretSupport = function () {
3149		if (typeof $keyboard.checkCaret !== 'boolean') {
3150			// Check if caret position is saved when input is hidden or loses focus
3151			// (*cough* all versions of IE and I think Opera has/had an issue as well
3152			var $temp = $('<div style="height:0px;width:0px;overflow:hidden;position:fixed;top:0;left:-100px;">' +
3153				'<input type="text" value="testing"/></div>').prependTo('body'); // stop page scrolling
3154			$keyboard.caret($temp.find('input'), 3, 3);
3155			// Also save caret position of the input if it is locked
3156			$keyboard.checkCaret = $keyboard.caret($temp.find('input').hide().show()).start !== 3;
3157			$temp.remove();
3158		}
3159		return $keyboard.checkCaret;
3160	};
3161
3162	$keyboard.caret = function($el, param1, param2) {
3163		if (!$el || !$el.length || $el.is(':hidden') || $el.css('visibility') === 'hidden') {
3164			return {};
3165		}
3166		var start, end, txt, pos,
3167			kb = $el.data( 'keyboard' ),
3168			noFocus = kb && kb.options.noFocus,
3169			formEl = /(textarea|input)/i.test($el[0].nodeName);
3170		if (!noFocus) { $el.focus(); }
3171		// set caret position
3172		if (typeof param1 !== 'undefined') {
3173			// allow setting caret using ( $el, { start: x, end: y } )
3174			if (typeof param1 === 'object' && 'start' in param1 && 'end' in param1) {
3175				start = param1.start;
3176				end = param1.end;
3177			} else if (typeof param2 === 'undefined') {
3178				param2 = param1; // set caret using start position
3179			}
3180			// set caret using ( $el, start, end );
3181			if (typeof param1 === 'number' && typeof param2 === 'number') {
3182				start = param1;
3183				end = param2;
3184			} else if ( param1 === 'start' ) {
3185				start = end = 0;
3186			} else if ( typeof param1 === 'string' ) {
3187				// unknown string setting, move caret to end
3188				start = end = 'end';
3189			}
3190
3191			// *** SET CARET POSITION ***
3192			// modify the line below to adapt to other caret plugins
3193			return formEl ?
3194				$el.caret( start, end, noFocus ) :
3195				$keyboard.setEditableCaret( $el, start, end );
3196		}
3197		// *** GET CARET POSITION ***
3198		// modify the line below to adapt to other caret plugins
3199		if (formEl) {
3200			// modify the line below to adapt to other caret plugins
3201			pos = $el.caret();
3202		} else {
3203			// contenteditable
3204			pos = $keyboard.getEditableCaret($el[0]);
3205		}
3206		start = pos.start;
3207		end = pos.end;
3208
3209		// *** utilities ***
3210		txt = formEl && $el[0].value || $el.text() || '';
3211		return {
3212			start : start,
3213			end : end,
3214			// return selected text
3215			text : txt.substring( start, end ),
3216			// return a replace selected string method
3217			replaceStr : function( str ) {
3218				return txt.substring( 0, start ) + str + txt.substring( end, txt.length );
3219			}
3220		};
3221	};
3222
3223	$keyboard.isTextNode = function(el) {
3224		return el && el.nodeType === 3;
3225	};
3226
3227	$keyboard.isBlock = function(el, node) {
3228		var win = el.ownerDocument.defaultView;
3229		if (
3230			node && node.nodeType === 1 && node !== el &&
3231			win.getComputedStyle(node).display === 'block'
3232		) {
3233			return 1;
3234		}
3235		return 0;
3236	};
3237
3238	// Wrap all BR's inside of contenteditable
3239	$keyboard.wrapBRs = function(container) {
3240		var $el = $(container).find('br:not(.' + $keyboard.css.divWrapperCE + ')');
3241		if ($el.length) {
3242			$.each($el, function(i, el) {
3243				var len = el.parentNode.childNodes.length;
3244				if (
3245					// wrap BRs if not solo child
3246					len !== 1 ||
3247					// Or if BR is wrapped by a span
3248					len === 1 && !$keyboard.isBlock(container, el.parentNode)
3249				) {
3250					$(el).addClass($keyboard.css.divWrapperCE).wrap('<div>');
3251				}
3252			});
3253		}
3254	};
3255
3256	$keyboard.getEditableCaret = function(container) {
3257		container = $(container)[0];
3258		if (!container.isContentEditable) { return {}; }
3259		var end, text,
3260			options = ($(container).data('keyboard') || {}).options,
3261			doc = container.ownerDocument,
3262			range = doc.getSelection().getRangeAt(0),
3263			result = pathToNode(range.startContainer, range.startOffset),
3264			start = result.position;
3265		if (options.wrapBRs !== false) {
3266			$keyboard.wrapBRs(container);
3267		}
3268		function pathToNode(endNode, offset) {
3269			var node, adjust,
3270				txt = '',
3271				done = false,
3272				position = 0,
3273				nodes = $.makeArray(container.childNodes);
3274
3275			function checkBlock(val) {
3276				if (val) {
3277					position += val;
3278					txt += options && options.replaceCR || '\n';
3279				}
3280			}
3281
3282			while (!done && nodes.length) {
3283				node = nodes.shift();
3284				if (node === endNode) {
3285					done = true;
3286				}
3287
3288				// Add one if previous sibling was a block node (div, p, etc)
3289				adjust = $keyboard.isBlock(container, node.previousSibling);
3290				checkBlock(adjust);
3291
3292				if ($keyboard.isTextNode(node)) {
3293					position += done ? offset : node.length;
3294					txt += node.textContent;
3295					if (done) {
3296						return {position: position, text: txt};
3297					}
3298				} else if (!done && node.childNodes) {
3299					nodes = $.makeArray(node.childNodes).concat(nodes);
3300				}
3301				// Add one if we're inside a block node (div, p, etc)
3302				// and previous sibling was a text node
3303				adjust = $keyboard.isTextNode(node.previousSibling) && $keyboard.isBlock(container, node);
3304				checkBlock(adjust);
3305			}
3306			return {position: position, text: txt};
3307		}
3308		// check of start and end are the same
3309		if (range.endContainer === range.startContainer && range.endOffset === range.startOffset) {
3310			end = start;
3311			text = '';
3312		} else {
3313			result = pathToNode(range.endContainer, range.endOffset);
3314			end = result.position;
3315			text = result.text.substring(start, end);
3316		}
3317		return {
3318			start: start,
3319			end: end,
3320			text: text
3321		};
3322	};
3323
3324	$keyboard.getEditableLength = function(container) {
3325		var result = $keyboard.setEditableCaret(container, 'getMax');
3326		// if not a number, the container is not a contenteditable element
3327		return typeof result === 'number' ? result : null;
3328	};
3329
3330	$keyboard.setEditableCaret = function(container, start, end) {
3331		container = $(container)[0];
3332		if (!container.isContentEditable) { return {}; }
3333		var doc = container.ownerDocument,
3334			range = doc.createRange(),
3335			sel = doc.getSelection(),
3336			options = ($(container).data('keyboard') || {}).options,
3337			s = start,
3338			e = end,
3339			text = '',
3340			result = findNode(start === 'getMax' ? 'end' : start);
3341		function findNode(offset) {
3342			if (offset === 'end') {
3343				// Set some value > content length; but return max
3344				offset = container.innerHTML.length;
3345			} else if (offset < 0) {
3346				offset = 0;
3347			}
3348			var node, check,
3349				txt = '',
3350				done = false,
3351				position = 0,
3352				last = 0,
3353				max = 0,
3354				nodes = $.makeArray(container.childNodes);
3355			function updateText(val) {
3356				txt += val ? options && options.replaceCR || '\n' : '';
3357				return val > 0;
3358			}
3359			function checkDone(adj) {
3360				var val = position + adj;
3361				last = max;
3362				max += adj;
3363				if (offset - val >= 0) {
3364					position = val;
3365					return offset - position <= 0;
3366				}
3367				return offset - val <= 0;
3368			}
3369			while (!done && nodes.length) {
3370				node = nodes.shift();
3371				// Add one if the previous sibling was a block node (div, p, etc)
3372				check = $keyboard.isBlock(container, node.previousSibling);
3373				if (updateText(check) && checkDone(check)) {
3374					done = true;
3375				}
3376				// Add one if we're inside a block node (div, p, etc)
3377				check = $keyboard.isTextNode(node.previousSibling) && $keyboard.isBlock(container, node);
3378				if (updateText(check) && checkDone(check)) {
3379					done = true;
3380				}
3381				if ($keyboard.isTextNode(node)) {
3382					txt += node.textContent;
3383					if (checkDone(node.length)) {
3384						check = offset - position === 0 && position - last >= 1 ? node.length : offset - position;
3385						return {
3386							node: node,
3387							offset: check,
3388							position: offset,
3389							text: txt
3390						};
3391					}
3392				} else if (!done && node.childNodes) {
3393					nodes = $.makeArray(node.childNodes).concat(nodes);
3394				}
3395			}
3396			return nodes.length ?
3397				{node: node, offset: offset - position, position: offset, text: txt} :
3398				// Offset is larger than content, return max
3399				{node: node, offset: node && node.length || 0, position: max, text: txt};
3400		}
3401		if (result.node) {
3402			s = result.position; // Adjust if start > content length
3403			if (start === 'getMax') {
3404				return s;
3405			}
3406			range.setStart(result.node, result.offset);
3407			// Only find end if > start and is defined... this allows passing
3408			// setEditableCaret(el, 'end') or setEditableCaret(el, 10, 'end');
3409			if (typeof end !== 'undefined' && end !== start) {
3410				result = findNode(end);
3411			}
3412			if (result.node) {
3413				e = result.position; // Adjust if end > content length
3414				range.setEnd(result.node, result.offset);
3415				text = s === e ? '' : result.text.substring(s, e);
3416			}
3417			sel.removeAllRanges();
3418			sel.addRange(range);
3419		}
3420		return {
3421			start: s,
3422			end: e,
3423			text: text
3424		};
3425	};
3426
3427	$keyboard.replaceContent = function (el, param) {
3428		el = $(el)[0];
3429		var node, i, str,
3430			type = typeof param,
3431			caret = $keyboard.getEditableCaret(el).start,
3432			charIndex = 0,
3433			nodeStack = [el];
3434		while ((node = nodeStack.pop())) {
3435			if ($keyboard.isTextNode(node)) {
3436				if (type === 'function') {
3437					if (caret >
3437= charIndex && caret <= charIndex + node.length) {
3438						node.textContent = param(node.textContent);
3439					}
3440				} else if (type === 'string') {
3441					// maybe not the best method, but it works for simple changes
3442					str = param.substring(charIndex, charIndex + node.length);
3443					if (str !== node.textContent) {
3444						node.textContent = str;
3445					}
3446				}
3447				charIndex += node.length;
3448			} else if (node && node.childNodes) {
3449				i = node.childNodes.length;
3450				while (i--) {
3451					nodeStack.push(node.childNodes[i]);
3452				}
3453			}
3454		}
3455		i = $keyboard.getEditableCaret(el);
3456		$keyboard.setEditableCaret(el, i.start, i.start);
3457	};
3458
3459	$.fn.keyboard = function (options) {
3460		return this.each(function () {
3461			if (!$(this).data('keyboard')) {
3462				/*jshint nonew:false */
3463				(new $.keyboard(this, options));
3464			}
3465		});
3466	};
3467
3468	$.fn.getkeyboard = function () {
3469		return this.data('keyboard');
3470	};
3471
3472	/* Copyright (c) 2010 C. F., Wong (<a href="http://cloudgen.w0ng.hk">Cloudgen Examplet Store</a>)
3473	 * Licensed under the MIT License:
3474	 * http://www.opensource.org/licenses/mit-license.php
3475	 * Highly modified from the original
3476	 */
3477
3478	$.fn.caret = function (start, end, noFocus) {
3479		if (
3480			typeof this[0] === 'undefined' ||
3481			this.is(':hidden') ||
3482			this.css('visibility') === 'hidden' ||
3483			!/(INPUT|TEXTAREA)/.test(this[0].nodeName)
3484		) {
3485			return this;
3486		}
3487		var selRange, range, stored_range, txt, val,
3488			$el = this,
3489			el = $el[0],
3490			selection = el.ownerDocument.selection,
3491			sTop = el.scrollTop,
3492			ss = false,
3493			supportCaret = true;
3494		try {
3495			ss = 'selectionStart' in el;
3496		} catch (err) {
3497			supportCaret = false;
3498		}
3499		if (supportCaret && typeof start !== 'undefined') {
3500			if (!/(email|number)/i.test(el.type)) {
3501				if (ss) {
3502					el.selectionStart = start;
3503					el.selectionEnd = end;
3504				} else {
3505					selRange = el.createTextRange();
3506					selRange.collapse(true);
3507					selRange.moveStart('character', start);
3508					selRange.moveEnd('character', end - start);
3509					selRange.select();
3510				}
3511			}
3512			// must be visible or IE8 crashes; IE9 in compatibility mode works fine - issue #56
3513			if (!noFocus && ($el.is(':visible') || $el.css('visibility') !== 'hidden')) {
3514				el.focus();
3515			}
3516			el.scrollTop = sTop;
3517			return this;
3518		}
3519		if (/(email|number)/i.test(el.type)) {
3520			// fix suggested by raduanastase (https://github.com/Mottie/Keyboard/issues/105#issuecomment-40456535)
3521			start = end = $el.val().length;
3522		} else if (ss) {
3523			start = el.selectionStart;
3524			end = el.selectionEnd;
3525		} else if (selection) {
3526			if (el.nodeName === 'TEXTAREA') {
3527				val = $el.val();
3528				range = selection.createRange();
3529				stored_range = range.duplicate();
3530				stored_range.moveToElementText(el);
3531				stored_range.setEndPoint('EndToEnd', range);
3532				// thanks to the awesome comments in the rangy plugin
3533				start = stored_range.text.replace(/\r/g, '\n').length;
3534				end = start + range.text.replace(/\r/g, '\n').length;
3535			} else {
3536				val = $el.val().replace(/\r/g, '\n');
3537				range = selection.createRange().duplicate();
3538				range.moveEnd('character', val.length);
3539				start = (range.text === '' ? val.length : val.lastIndexOf(range.text));
3540				range = selection.createRange().duplicate();
3541				range.moveStart('character', -val.length);
3542				end = range.text.length;
3543			}
3544		} else {
3545			// caret positioning not supported
3546			start = end = (el.value || '').length;
3547		}
3548		txt = (el.value || '');
3549		return {
3550			start: start,
3551			end: end,
3552			text: txt.substring(start, end),
3553			replace: function (str) {
3554				return txt.substring(0, start) + str + txt.substring(end, txt.length);
3555			}
3556		};
3557	};
3558
3559	return $keyboard;
3560
3561}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.