PageSourceSearch

https://arabus.me/js/jquery.keyboard.js

js arabus.me collected 2026-10-10 02:22:15 UTC 90,652 bytes, 2,617 lines download raw bytes

1/*! jQuery UI Virtual Keyboard v1.25.12 *//*
2
3Author: Jeremy Satterfield
4Modified: Rob Garrison (Mottie on github)
5-----------------------------------------
6Licensed under the MIT License
7
8Caret code modified from jquery.caret.1.02.js
9Licensed under the MIT License:
10http://www.opensource.org/licenses/mit-license.php
11-----------------------------------------
12
13An on-screen virtual keyboard embedded within the browser window which
14will popup when a specified entry field is focused. The user can then
15type and preview their input before Accepting or Canceling.
16
17As a plugin to jQuery UI styling and theme will automatically
18match that used by jQuery UI with the exception of the required CSS.
19
20Requires: jQuery v1.4.3+
21Optional:
22 jQuery UI (position utility only) & CSS theme
23 jQuery mousewheel
24
25Setup/Usage:
26	Please refer to https://github.com/Mottie/Keyboard/wiki
27*/
28/*jshint browser:true, jquery:true, unused:false */
29/*global require:false, define:false, module:false */
30;(function(factory) {
31	if (typeof define === 'function' && define.amd) {
32		define(['jquery'], factory);
33	} else if (typeof module === 'object' && typeof module.exports === 'object') {
34		module.exports = factory(require('jquery'));
35	} else {
36		factory(jQuery);
37	}
38}(function($) {
39'use strict';
40var $keyboard = $.keyboard = function(el, options){
41	var base = this, o;
42
43	base.version = '1.25.12';
44
45	// Access to jQuery and DOM versions of element
46	base.$el = $(el);
47	base.el = el;
48
49	// Add a reverse reference to the DOM object
50	base.$el.data('keyboard', base);
51
52	base.init = function() {
53		var k, position, tmp,
54			kbcss = $keyboard.css,
55			kbevents = $keyboard.events;
56		base.settings = options || {};
57		// shallow copy position to prevent performance issues; see #357
58		if ( options && options.position ) {
59			position = $.extend( {}, options.position );
60			options.position = null;
61		}
62		base.options = o = $.extend( true, {}, $keyboard.defaultOptions, options );
63		if ( position ) {
64			o.position = position;
65			options.position = position;
66		}
67
68		// keyboard is active (not destroyed);
69		base.el.active = true;
70		// unique keyboard namespace
71		base.namespace = '.keyboard' + Math.random().toString(16).slice(2);
72		// extension namespaces added here (to unbind listeners on base.$el upon destroy)
73		base.extensionNamespace = [];
74		// Shift and Alt key toggles, sets is true if a layout has more than one keyset
75		// used for mousewheel message
76		base.shiftActive = base.altActive = base.metaActive = base.sets = base.capsLock = false;
77		// Class names of the basic key set - meta keysets are handled by the keyname
78		base.rows = [ '', '-shift', '-alt', '-alt-shift' ];
79
80		base.inPlaceholder = base.$el.attr('placeholder') || '';
81		// html 5 placeholder/watermark
82		base.watermark = $keyboard.watermark && base.inPlaceholder !== '';
83		// convert mouse repeater rate (characters per second) into a time in milliseconds.
84		base.repeatTime = 1000/(o.repeatRate || 20);
85		// delay in ms to prevent mousedown & touchstart from both firing events at the same time
86		o.preventDoubleEventTime = o.preventDoubleEventTime || 100;
87		// flag indication that a keyboard is open
88		base.isOpen = false;
89		// is mousewheel plugin loaded?
90		base.wheel = $.isFunction( $.fn.mousewheel );
91		// keyCode of keys always allowed to be typed
92		k = $keyboard.keyCodes;
93
94		// special character in regex that need to be escaped
95		base.escapeRegex = /[-\/\\^$*+?.()|[\]{}]/g;
96
97		// base.alwaysAllowed = [20,33,34,35,36,37,38,39,40,45,46];
98		base.alwaysAllowed = [
99			k.capsLock,
100			k.pageUp,
101			k.pageDown,
102			k.end,
103			k.home,
104			k.left,
105			k.up,
106			k.right,
107			k.down,
108			k.insert,
109			k.delete
110		];
111		base.$keyboard = [];
112		// keyboard enabled; set to false on destroy
113		base.enabled = true;
114		// make a copy of the original keyboard position
115		if (!$.isEmptyObject(o.position)) {
116			o.position.orig_at = o.position.at;
117		}
118
119		base.checkCaret = ( o.lockInput || $keyboard.checkCaretSupport() );
120
121		// [shift, alt, meta]
122		base.last = {
123			start: 0,
124			end: 0,
125			key: '',
126			val: '',
127			preVal: '',
128			layout: '',
129			virtual: true,
130			keyset: [ false, false, false ],
131			wheel_$Keys : null,
132			wheelIndex : 0,
133			wheelLayers: []
134		};
135		base.temp = [ '', 0, 0 ]; // used when building the keyboard - [keyset element, row, index]
136
137		// Callbacks
138		$.each([
139			kbevents.kbInit,
140			kbevents.kbBeforeVisible,
141			kbevents.kbVisible,
142			kbevents.kbHidden,
143			kbevents.inputCanceled,
144			kbevents.inputAccepted,
145			kbevents.kbBeforeClose
146		], function( i, callback ) {
147			if ( $.isFunction( o[ callback ] ) ) {
148				// bind callback functions within options to triggered events
149				base.$el.bind( callback + base.namespace, o[ callback ] );
150			}
151		});
152
153		// Close with esc key & clicking outside
154		if (o.alwaysOpen) { o.stayOpen = true; }
155
156		tmp = $( document );
157		if ( base.el.ownerDocument !== document ) {
158			tmp = tmp.add( base.el.ownerDocument );
159		}
160		tmp.bind( 'mousedown keyup touchstart checkkeyboard '.split( ' ' ).join( base.namespace + ' ' ), base.checkClose );
161
162		// Display keyboard on focus
163		base.$el
164			.addClass( kbcss.input + ' ' + o.css.input )
165			.attr({ 'aria-haspopup' : 'true', 'role' : 'textbox' });
166
167		// set lockInput if the element is readonly; or make the element readonly if lockInput is set
168		if (o.lockInput || base.el.readOnly) {
169			o.lockInput = true;
170			base.$el.addClass(kbcss.locked).attr({ 'readonly': 'readonly'});
171		}
172
173		// add disabled/readonly class - dynamically updated on reveal
174		if (base.$el.is(':disabled') || (base.$el.attr('readonly') &&
175			!base.$el.hasClass(kbcss.locked))) {
176			base.$el.addClass(kbcss.noKeyboard);
177		}
178		if (o.openOn) {
179			base.$el.bind(o.openOn + base.namespace, function(){
180				base.focusOn();
181			});
182		}
183
184		// Add placeholder if not supported by the browser
185		if (!base.watermark && base.$el.val() === '' && base.inPlaceholder !== '' &&
186			base.$el.attr('placeholder') !== '') {
187			base.$el
188				.addClass(kbcss.placeholder) // css watermark style (darker text)
189				.val( base.inPlaceholder );
190		}
191
192		base.$el.trigger( kbevents.kbInit, [ base, base.el ] );
193
194		// initialized with keyboard open
195		if (o.alwaysOpen) {
196			base.reveal();
197		}
198
199	};
200
201	base.toggle = function(){
202		var $toggle = base.$keyboard.find( '.' + $keyboard.css.keyToggle ),
203			locked = !base.enabled;
204		// prevent physical keyboard from working
205		base.$preview.prop( 'readonly', locked || base.options.lockInput );
206		// disable all buttons
207		base.$keyboard
208			.toggleClass( $keyboard.css.keyDisabled, locked )
209			.find( '.' + $keyboard.css.keyButton )
210			.not( $toggle )
211			.prop( 'disabled', locked )
212			.attr( 'aria-disabled', locked );
213		$toggle.toggleClass( $keyboard.css.keyDisabled, locked );
214		// stop auto typing
215		if ( locked && base.typing_options ) {
216			base.typing_options.text = '';
217		}
218	};
219
220	base.setCurrent = function(){
221		var kbcss = $keyboard.css,
222			// close any "isCurrent" keyboard (just in case they are always open)
223			$current = $( '.' + kbcss.isCurrent ),
224			kb = $current.data( 'keyboard' );
225		if ( !$.isEmptyObject( kb ) ) {
226			kb.close( kb.options.autoAccept ? 'true' : false );
227		}
228		$current.removeClass( kbcss.isCurrent );
229		// ui-keyboard-has-focus is applied in case multiple keyboards have alwaysOpen = true and are stacked
230		$( '.' + kbcss.hasFocus ).removeClass( kbcss.hasFocus );
231
232		base.$el.addClass(kbcss.isCurrent);
233		base.$keyboard.addClass(kbcss.hasFocus);
234		base.isCurrent(true);
235		base.isOpen = true;
236	};
237
238	base.isCurrent = function(set){
239		var cur = $keyboard.currentKeyboard || false;
240		if (set) {
241			cur = $keyboard.currentKeyboard = base.el;
242		} else if (set === false && cur === base.el) {
243			cur = $keyboard.currentKeyboard = '';
244		}
245		return cur === base.el;
246	};
247
248	base.isVisible = function() {
249		return base.$keyboard && base.$keyboard.length ? base.$keyboard.is(':visible') : false;
250	};
251
252	base.focusOn = function(){
253		if ( !base && base.el.active ) {
254			// keyboard was destroyed
255			return;
256		}
257		if ( !base.isVisible() ) {
258			clearTimeout( base.timer );
259			base.reveal();
260		}
261	};
262
263	base.reveal = function(refresh){
264		if (base.isOpen) {
265			refresh = true;
266		}
267		var kbcss = $keyboard.css;
268		base.opening = true;
269		// remove all 'extra' keyboards
270		$('.' + kbcss.keyboard).not('.' + kbcss.alwaysOpen).remove();
271
272		// update keyboard after a layout change
273		if (refresh) {
274			base.isOpen = o.alwaysOpen;
275			base.last.preVal = '' + base.last.val;
276			base.last.val = base.$preview && base.$preview.val() || '';
277			if (base.$keyboard.length) {
278				base.$keyboard.remove();
279				base.$keyboard = [];
280				base.shiftActive = base.altActive = base.metaActive = false;
281			}
282		}
283
284		// Don't open if disabled
285		if (base.$el.is(':disabled') || (base.$el.attr('readonly') &&
286			!base.$el.hasClass(kbcss.locked))) {
287			base.$el.addClass(kbcss.noKeyboard);
288			return;
289		} else {
290			base.$el.removeClass(kbcss.noKeyboard);
291		}
292
293		// Unbind focus to prevent recursion - openOn may be empty if keyboard is opened externally
294		if (o.openOn) {
295			base.$el.unbind( $.trim( ( o.openOn + ' ' ).split( /\s+/ ).join( base.namespace + ' ' ) ) );
296		}
297
298		// build keyboard if it doesn't exist; or attach keyboard if it was removed, but not cleared
299		if ( !base.$keyboard || base.$keyboard && ( !base.$keyboard.length || $.contains(document.body, base.$keyboard[0]) ) ) {
300			base.startup();
301		}
302
303		// clear watermark
304		if (!base.watermark && base.el.value === base.inPlaceholder) {
305			base.$el
306				.removeClass(kbcss.placeholder)
307				.val('');
308		}
309		// save starting content, in case we cancel
310		base.originalContent = base.$el.val();
311		base.$preview.val( refresh ? base.last.val : base.originalContent );
312
313		// disable/enable accept button
314		if (o.acceptValid) { base.checkValid(); }
315
316		if (o.resetDefault) {
317			base.shiftActive = base.altActive = base.metaActive = false;
318		}
319		base.showSet();
320
321		// beforeVisible event
322		if ( !base.isVisible() ) {
323			base.$el.trigger( $keyboard.events.kbBeforeVisible, [ base, base.el ] );
324		}
325		base.setCurrent();
326		// update keyboard - enabled or disabled?
327		base.toggle();
328
329		// show keyboard
330		base.$keyboard.show();
331
332		// adjust keyboard preview window width - save width so IE won't keep expanding (fix issue #6)
333		if (o.usePreview && $keyboard.msie) {
334			if (typeof base.width === 'undefined') {
335				base.$preview.hide(); // preview is 100% browser width in IE7, so hide the damn thing
336				base.width = Math.ceil(base.$keyboard.width()); // set input width to match the widest keyboard row
337				base.$preview.show();
338			}
339			base.$preview.width(base.width);
340		}
341
342		base.position = $.isEmptyObject(o.position) ? false : o.position;
343
344		// position after keyboard is visible (required for UI position utility) and appropriately sized
345		if ($.ui && $.ui.position && base.position) {
346			// get single target position || target stored in element data (multiple targets) || default @ element
347			base.position.of = base.position.of || base.$el.data('keyboardPosition') || base.$el;
348			base.position.collision = base.position.collision || 'flipfit flipfit';
349			o.position.at = o.usePreview ? o.position.orig_at : o.position.at2;
350			base.$keyboard.position(base.position);
351		}
352
353		base.checkDecimal();
354
355		// get preview area line height
356		// add roughly 4px to get line height from font height, works well for font-sizes from 14-36px
357		// needed for textareas
358		base.lineHeight = parseInt( base.$preview.css('lineHeight'), 10) ||
359			parseInt(base.$preview.css('font-size') ,10) + 4;
360
361		if (o.caretToEnd) {
362			base.saveCaret( base.originalContent.length, base.originalContent.length );
363		}
364
365		// IE caret haxx0rs
366		if ($keyboard.allie){
367			// sometimes end = 0 while start is > 0
368			if (base.last.end === 0 && base.last.start > 0) {
369				base.last.end = base.last.start;
370			}
371			// IE will have start -1, end of 0 when not focused (see demo: http://jsfiddle.net/Mottie/fgryQ/3/)
372			if (base.last.start < 0) {
373				// ensure caret is at the end of the text (needed for IE)
374				base.last.start = base.last.end = base.originalContent.length;
375			}
376		}
377
378		if ( !refresh ) {
379			// opening keyboard flag; delay allows switching between keyboards without immediately closing
380			// the keyboard
381			base.timer2 = setTimeout(function() {
382				var undef;
383				base.opening = false;
384				// Number inputs don't support selectionStart and selectionEnd
385				// Number/email inputs don't support selectionStart and selectionEnd
386				if ( !/(number|email)/i.test( base.el.type ) && !o.caretToEnd ) {
387					// caret position is always 0,0 in webkit; and nothing is focused at this point... odd
388					// save caret position in the input to transfer it to the preview
389					// inside delay to get correct caret position
390					base.saveCaret( undef, undef, base.$el );
391				}
392				if (o.initialFocus) {
393					$keyboard.caret( base.$preview, base.last );
394				}
395				// save event time for keyboards with stayOpen: true
396				base.last.eventTime = new Date().getTime();
397				base.$el.trigger( $keyboard.events.kbVisible, [ base, base.el ] );
398				base.timer = setTimeout(function(){
399					// get updated caret information after visible event - fixes #331
400					if (base) { // Check if base exists, this is a case when destroy is called, before timers have fired
401						base.saveCaret();
402					}
403				}, 200);
404			}, 10);
405		}
406		// return base to allow chaining in typing extension
407		return base;
408	};
409
410	base.updateLanguage = function(){
411		// change language if layout is named something like 'french-azerty-1'
412		var layouts =  $keyboard.layouts,
413			lang = o.language || layouts[ o.layout ] && layouts[ o.layout ].lang && layouts[ o.layout ].lang || [ o.language || 'en' ],
414			kblang = $keyboard.language;
415
416		// some languages include a dash, e.g. 'en-gb' or 'fr-ca'
417		// allow o.language to be a string or array...
418		// array is for future expansion where a layout can be set for multiple languages
419		lang = ( $.isArray(lang) ? lang[0] : lang ).split('-')[0];
420
421		// set keyboard language
422		o.display = $.extend( true, {}, kblang.en.display, kblang[ lang ] && kblang[ lang ].display || {}, base.settings.display );
423
424		o.combos = $.extend( true, {}, kblang.en.combos, kblang[ lang ] && kblang[ lang ].combos || {}, base.settings.combos );
425		o.wheelMessage = kblang[ lang ] && kblang[ lang ].wheelMessage || 
425kblang.en.wheelMessage;
426		// rtl can be in the layout or in the language definition; defaults to false
427		o.rtl = layouts[ o.layout ] && layouts[ o.layout ].rtl || kblang[ lang ] && kblang[ lang ].rtl  || false;
428
429		// save default regex (in case loading another layout changes it)
430		base.regex = kblang[ lang ] && kblang[ lang ].comboRegex || $keyboard.comboRegex;
431		// determine if US '.' or European ',' system being used
432		base.decimal = /^\./.test(o.display.dec);
433		base.$el
434			.toggleClass('rtl', o.rtl)
435			.css('direction', o.rtl ? 'rtl' : '');
436	};
437
438	base.startup = function(){
439		var kbcss = $keyboard.css;
440		// ensure base.$preview is defined; but don't overwrite it if keyboard is always visible
441		if ( !( o.alwaysOpen && base.$preview ) ) {
442			base.makePreview();
443		}
444		if ( !(base.$keyboard && base.$keyboard.length) ) {
445			// custom layout - create a unique layout name based on the hash
446			if (o.layout === 'custom') { o.layoutHash = 'custom' + base.customHash(); }
447			base.layout = o.layout === 'custom' ? o.layoutHash : o.layout;
448			base.last.layout = base.layout;
449
450			base.updateLanguage();
451			if (typeof $keyboard.builtLayouts[base.layout] === 'undefined') {
452				if ($.isFunction(o.create)) {
453					// create must call buildKeyboard() function; or create it's own keyboard
454					base.$keyboard = o.create(base);
455				} else if (!base.$keyboard.length) {
456					base.buildKeyboard(base.layout, true);
457				}
458			}
459			base.$keyboard = $keyboard.builtLayouts[base.layout].$keyboard.clone();
460			base.$keyboard.data( 'keyboard', base );
461			if ( ( base.el.id || '' ) !== '' ) {
462				// add ID to keyboard for styling purposes
463				base.$keyboard.attr( 'id', base.el.id + $keyboard.css.idSuffix );
464			}
465
466			base.makePreview();
467			// build preview display
468			if (o.usePreview) {
469				// restore original positioning (in case usePreview option is altered)
470				if (!$.isEmptyObject(o.position)) {
471					o.position.at = o.position.orig_at;
472				}
473			} else {
474				// No preview display, use element and reposition the keyboard under it.
475				if (!$.isEmptyObject(o.position)) {
476					o.position.at = o.position.at2;
477				}
478			}
479
480		}
481
482		base.$decBtn = base.$keyboard.find('.' + kbcss.keyPrefix + 'dec');
483		// add enter to allowed keys; fixes #190
484		if (o.enterNavigation || base.el.nodeName === 'TEXTAREA') { base.alwaysAllowed.push(13); }
485
486		base.bindKeyboard();
487
488		base.$keyboard.appendTo( o.appendLocally ? base.$el.parent() : o.appendTo || 'body' );
489
490		base.bindKeys();
491
492		// adjust with window resize; don't check base.position
493		// here in case it is changed dynamically
494		if (o.reposition && $.ui && $.ui.position && o.appendTo == 'body') {
495			$(window).bind('resize' + base.namespace, function(){
496				if (base.position && base.isVisible()) {
497					base.$keyboard.position(base.position);
498				}
499			});
500		}
501
502	};
503
504	base.makePreview = function() {
505		if ( o.usePreview ) {
506			var indx, attrs, attr, removedAttr,
507				kbcss = $keyboard.css;
508			base.$preview = base.$el.clone( false )
509				.data( 'keyboard', base )
510				.removeClass( kbcss.placeholder + ' ' + kbcss.input )
511				.addClass( kbcss.preview + ' ' + o.css.input )
512				.attr( 'tabindex', '-1' )
513				.show(); // for hidden inputs
514			base.preview = base.$preview[0];
515
516			// Switch the number input field to text so the caret positioning will work again
517			if ( base.preview.type === 'number' ) {
518				base.preview.type = 'text';
519			}
520
521			// remove extraneous attributes.
522			removedAttr = /^(data-|id|aria-haspopup)/i;
523			attrs = base.$preview.get(0).attributes;
524			for ( indx = attrs.length - 1; indx >= 0; indx-- ) {
525				attr = attrs[ indx ] && attrs[ indx ].name;
526				if ( removedAttr.test( attr ) ) {
527					// remove data-attributes - see #351
528					base.preview.removeAttribute( attr );
529				}
530			}
531			// build preview container and append preview display
532			$('<div />')
533				.addClass( kbcss.wrapper )
534				.append( base.$preview )
535				.prependTo( base.$keyboard );
536		} else {
537			base.$preview = base.$el;
538			base.preview = base.el;
539		}
540	};
541
542	base.saveCaret = function(start, end, $el){
543		var p = $keyboard.caret( $el || base.$preview, start, end );
544		base.last.start = typeof start === 'undefined' ? p.start : start;
545		base.last.end = typeof end === 'undefined' ? p.end : end;
546	};
547
548	base.setScroll = function(){
549		// Set scroll so caret & current text is in view
550		// needed for virtual keyboard typing, NOT manual typing - fixes #23
551		if ( base.last.virtual ) {
552
553			var scrollWidth, clientWidth, adjustment, direction,
554				isTextarea = base.preview.nodeName === 'TEXTAREA',
555				value = base.last.val.substring( 0, Math.max( base.last.start, base.last.end ) );
556
557			if ( !base.$previewCopy ) {
558				// clone preview
559				base.$previewCopy = base.$preview.clone()
560					.removeAttr('id') // fixes #334
561					.css({
562						position : 'absolute',
563						left: 0,
564						zIndex : -10,
565						visibility : 'hidden'
566					})
567					.addClass('ui-keyboard-preview-clone');
568				if ( !isTextarea ) {
569					// make input zero-width because we need an accurate scrollWidth
570					base.$previewCopy.css({ 'white-space' : 'pre', 'width' : 0 });
571				}
572				if (o.usePreview) {
573					// add clone inside of preview wrapper
574					base.$preview.after( base.$previewCopy );
575				} else {
576					// just slap that thing in there somewhere
577					base.$keyboard.prepend( base.$previewCopy );
578				}
579			}
580
581			if ( isTextarea ) {
582				// need the textarea scrollHeight, so set the clone textarea height to be the line height
583				base.$previewCopy
584					.height( base.lineHeight )
585					.val( value );
586				// set scrollTop for Textarea
587				base.preview.scrollTop = base.lineHeight * ( Math.floor( base.$previewCopy[0].scrollHeight / base.lineHeight ) - 1 );
588			} else {
589				// add non-breaking spaces
590				base.$previewCopy.val( value.replace(/\s/g, '\xa0') );
591
592				// if scrollAdjustment option is set to "c" or "center" then center the caret
593				adjustment = /c/i.test( o.scrollAdjustment ) ? base.preview.clientWidth / 2 : o.scrollAdjustment;
594				scrollWidth = base.$previewCopy[0].scrollWidth - 1;
595
596				// set initial state as moving right
597				if ( typeof base.last.scrollWidth === 'undefined' ) {
598					base.last.scrollWidth = scrollWidth;
599					base.last.direction = true;
600				}
601				// if direction = true; we're scrolling to the right
602				direction = base.last.scrollWidth === scrollWidth ? base.last.direction : base.last.scrollWidth < scrollWidth;
603				clientWidth = base.preview.clientWidth - adjustment;
604
605				// set scrollLeft for inputs; try to mimic the inherit caret positioning + scrolling:
606				// hug right while scrolling right...
607				if ( direction ) {
608					if ( scrollWidth < clientWidth ) {
609						base.preview.scrollLeft = 0;
610					} else {
611						base.preview.scrollLeft = scrollWidth - clientWidth;
612					}
613				} else {
614					// hug left while scrolling left...
615					if ( scrollWidth >= base.preview.scrollWidth - clientWidth ) {
616						base.preview.scrollLeft = base.preview.scrollWidth - adjustment;
617					} else if ( scrollWidth - adjustment > 0 ) {
618						base.preview.scrollLeft = scrollWidth - adjustment;
619					} else {
620						base.preview.scrollLeft = 0;
621					}
622				}
623
624				base.last.scrollWidth = scrollWidth;
625				base.last.direction = direction;
626			}
627		}
628	};
629
630	base.bindKeyboard = function() {
631		var evt,
632			keyCodes = $keyboard.keyCodes,
633			layout = $keyboard.builtLayouts[base.layout];
634		base.$preview
635			.unbind('keypress keyup keydown mouseup touchend '.split(' ').join(base.namespace + ' '))
636			.bind('click' + base.namespace, function(){
637				// update last caret position after user click, use at least 150ms or it doesn't work in IE
638				base.timer2 = setTimeout(function(){
639					base.saveCaret();
640				}, 150);
641			})
642			.bind('keypress' + base.namespace, function(e){
643				if (o.lockInput) { return false; }
644				var k = e.charCode || e.which,
645					// capsLock can only be checked while typing a-z
646					k1 = k >= keyCodes.A && k <= keyCodes.Z,
647					k2 = k >= keyCodes.a && k <= keyCodes.z,
648					str = base.last.key = String.fromCharCode(k);
649				base.last.virtual = false;
650				base.last.event = e;
651				base.last.$key = []; // not a virtual keyboard key
652				if (base.checkCaret) {
653					base.saveCaret();
654				}
655
656				// update capsLock
657				if ( k !== keyCodes.capsLock && ( k1 || k2 ) ) {
658					base.capsLock = ( k1 && !e.shiftKey ) || ( k2 && e.shiftKey );
659					// if shifted keyset not visible, then show it
660					if ( base.capsLock && !base.shiftActive ) {
661						base.shiftActive = true;
662						base.showSet();
663					}
664				}
665
666				// restrict input - keyCode in keypress special keys:
667				// see http://www.asquare.net/javascript/tests/KeyCode.html
668				if (o.restrictInput) {
669					// allow navigation keys to work - Chrome doesn't fire a keypress event (8 = bksp)
670					if ( ( e.which === keyCodes.backSpace || e.which === 0 ) && $.inArray( e.keyCode, base.alwaysAllowed ) ) {
671						return;
672					}
673					// quick key check
674					if ($.inArray(str, layout.acceptedKeys) === -1) {
675						e.preventDefault();
676						// copy event object in case e.preventDefault() breaks when changing the type
677						evt = $.extend({}, e);
678						evt.type = $keyboard.events.inputRestricted;
679						base.$el.trigger( evt, [ base, base.el ] );
680						if ( $.isFunction(o.restricted) ) {
681							o.restricted( evt, base, base.el );
682						}
683					}
684				} else if ( ( e.ctrlKey || e.metaKey ) &&
685						( e.which === keyCodes.A || e.which === keyCodes.C || e.which === keyCodes.V ||
686						( e.which >= keyCodes.X && e.which <= keyCodes.Z ) ) ) {
687					// Allow select all (ctrl-a), copy (ctrl-c), paste (ctrl-v) & cut (ctrl-x) &
688					// redo (ctrl-y)& undo (ctrl-z); meta key for mac
689					return;
690				}
691				// Mapped Keys - allows typing on a regular keyboard and the mapped key is entered
692				// Set up a key in the layout as follows: 'm(a):label'; m = key to map, (a) = actual keyboard key
693				// to map to (optional), ':label' = title/tooltip (optional)
694				// example: \u0391 or \u0391(A) or \u0391:alpha or \u0391(A):alpha
695				if (layout.hasMappedKeys && layout.mappedKeys.hasOwnProperty(str)){
696					base.last.key = layout.mappedKeys[str];
697					base.insertText( base.last.key );
698					e.preventDefault();
699				}
700				base.checkMaxLength();
701
702			})
703			.bind('keyup' + base.namespace, function(e){
704				base.last.virtual = false;
705				switch (e.which) {
706					// Insert tab key
707					case keyCodes.tab :
708						// Added a flag to prevent from tabbing into an input, keyboard opening, then adding the tab to the keyboard preview
709						// area on keyup. Sadly it still happens if you don't release the tab key immediately because keydown event auto-repeats
710						if (base.tab && o.tabNavigation && !o.lockInput) {
711							base.shiftActive = e.shiftKey;
712							// when switching inputs, the tab keyaction returns false
713							var notSwitching = $keyboard.keyaction.tab(base);
714							base.tab = false;
715							if (!notSwitching) { return false; }
716						} else {
717							e.preventDefault();
718						}
719						break;
720
721					// Escape will hide the keyboard
722					case keyCodes.escape :
723						if (!o.ignoreEsc) {
724							base.close( o.autoAccept && o.autoAcceptOnEsc ? 'true' : false );
725						}
726						return false;
727				}
728
729				// throttle the check combo function because fast typers will have an incorrectly positioned caret
730				clearTimeout(base.throttled);
731				base.throttled = setTimeout(function(){
732					// fix error in OSX? see issue #102
733					if (base.isVisible()) {
734						base.checkCombos();
735					}
736				}, 100);
737
738				base.checkMaxLength();
739				// change callback is no longer bound to the input element as the callback could be
740				// called during an external change event with all the necessary parameters (issue #157)
741				base.$el.trigger( $keyboard.events.kbChange, [ base, base.el ] );
742				base.last.preVal = '' + base.last.val;
743				base.last.val = base.$preview.val();
744
745				if ($.isFunction(o.change)){
746					o.change( $.Event( $keyboard.events.inputChange ), base, base.el );
747					return false;
748				}
749			})
750			.bind('keydown' + base.namespace, function(e){
751				// ensure alwaysOpen keyboards are made active
752				if ( o.alwaysOpen && !base.isCurrent() ) {
753					base.reveal();
754				}
755				// prevent tab key from leaving the preview window
756				if ( e.which === keyCodes.tab ) {
757					// allow tab to pass through - tab to next input/shift-tab for prev
758					base.tab = true;
759					return false;
760				}
761
762				if ( o.lockInput ) { return false; }
763
764				base.last.virtual = false;
765				switch (e.which) {
766
767					case keyCodes.backSpace :
768						$keyboard.keyaction.bksp(base, null, e);
769						e.preventDefault();
770						break;
771
772					case keyCodes.enter :
773						$keyboard.keyaction.enter(base, null, e);
774						break;
775
776					// Show capsLock
777					case keyCodes.capsLock :
778						base.shiftActive = base.capsLock = !base.capsLock;
779						base.showSet();
780						break;
781
782					case keyCodes.V :
783						// prevent ctrl-v/cmd-v
784						if (e.ctrlKey || e.metaKey) {
785							if (o.preventPaste) { e.preventDefault(); return; }
786							base.checkCombos(); // check pasted content
787						}
788						break;
789				}
790			})
791			.bind('mouseup touchend '.split(' ').join(base.namespace + ' '), function(){
792				base.last.virtual = true;
793				if (base.checkCaret) {
794					base.saveCaret();
795				}
796			});
797
798		// prevent keyboard event bubbling
799		base.$keyboard.bind('mousedown click touchstart '.split(' ').join(base.namespace + ' '), function(e){
800			e.stopPropagation();
801			if (!base.isCurrent()) {
802				base.reveal();
803				$(document).trigger('checkkeyboard' + base.namespace);
804			}
805			if (!o.noFocus) { base.$preview.focus(); }
806		});
807
808		// If preventing paste, block context menu (right click)
809		if (o.preventPaste){
810			base.$preview.bind('contextmenu' + base.namespace, function(e){ e.preventDefault(); });
811			base.$el.bind('contextmenu' + base.namespace, function(e){ e.preventDefault(); });
812		}
813
814	};
815
816	base.bindKeys = function(){
817		var kbcss = $keyboard.css;
818		base.$allKeys = base.$keyboard.find('button.' + kbcss.keyButton)
819			.unbind(base.namespace + ' ' + base.namespace + 'kb')
820			// Change hover class and tooltip - moved this touchstart before option.keyBinding touchstart
821			// to prevent mousewheel lag/duplication - Fixes #379 & #411
822			.bind('mouseenter mouseleave touchstart '.split(' ').join(base.namespace + ' '), function( e ) {
823				if ( o.alwaysOpen && e.type !== 'mouseleave' && !base.isCurrent() ) {
824					base.reveal();
825				}
826				if (!base.isCurrent()) { return; }
827				var $keys, txt,
828					last = base.last,
829					$this = $(this),
830					type = e.type;
831
832				if ( o.useWheel && base.wheel ) {
833					$keys = base.getLayers( $this );
834					txt = ( $keys.length ? $keys.map( function() {
835						return $( this ).attr( 'data-value' ) || '';
836					}).get() : '' ) || [ $this.text() ];
837					last.wheel_$Keys = $keys;
838					last.wheelLayers = txt;
839					last.wheelIndex = $.inArray( $this.attr( 'data-value' ), txt );
840				}
841
842				if ((type === 'mouseenter' || type === 'touchstart') && base.el.type !== 'password' &&
843					!$this.hasClass(o.css.buttonDisabled) ) {
844					$this.addClass(o.css.buttonHover);
845					if ( o.useWheel && base.wheel ) {
846						$this.attr( 'title', function( i, t ) {
847							// show mouse wheel message
848							return ( base.wheel && t === '' && base.sets && txt.length > 1 && type !== 'touchstart' ) ?
849								o.wheelMessage : t;
850						});
851					}
852				}
853				if ( type === 'mouseleave' ) {
854					// needed or IE flickers really bad
855					$this.removeClass( (base.el.type === 'password') ? '' : o.css.buttonHover);
856					if ( o.useWheel && base.wheel ) {
857						last.wheelIndex = 0;
858						last.wheelLayers = [];
859						last.wheel_$Keys = null;
860						$this
861							.attr( 'title', function( i, t ){ return ( t === o.wheelMessage ) ? '' : t; })
862							.html( $this.attr('data-html') ); // restore original button text
863					}
864				}
865			})
866			.bind(o.keyBinding.split(' ').join(base.namespace + ' ') + base.namespace + ' ' + $keyboard.events.kbRepeater, function(e){
867				e.preventDefault();
868				// prevent errors when external triggers attempt to 'type' - see issue #158
869				if (!base.$keyboard.is(':visible')){ return false; }
870				var action, $keys,
871					last = base.last,
872					key = this,
873					$key = $(key),
874					// prevent mousedown & touchstart from both firing events at the same time - see #184
875					timer = new Date().getTime();
876
877				if ( o.useWheel && base.wheel ) {
878					// get keys from other layers/keysets (shift, alt, meta, etc) that line up by data-position
879					$keys = last.wheel_$Keys;
880					// target mousewheel selected key
881					$key = $keys && last.wheelIndex > -1 ? $keys.eq( last.wheelIndex ) : $key;
882				}
883				action = $key.attr( 'data-action' );
884				// don't split colon key. Fixes #264
885				action = action === ':' ? ':' : (action || '').split(':')[0];
886				if ( timer - ( last.eventTime || 0 ) < o.preventDoubleEventTime ) { return; }
887				last.eventTime = timer;
888				last.event = e;
889				last.virtual = true;
890				if (!o.noFocus) { base.$preview.focus(); }
891				last.$key = $key;
892				last.key = $key.attr('data-value');
893				// Start caret in IE when not focused (happens with each virtual keyboard button click
894				if (base.checkCaret) {
895					$keyboard.caret( base.$preview, last );
896				}
897				if (action.match('meta')) { action = 'meta'; }
898				// keyaction is added as a string, override original action & text
899				if (action === last.key && typeof $keyboard.keyaction[ action ] === 'string' ) {
900					last.key = action = $keyboard.keyaction[ action ];
901				}
901 else if (action in $keyboard.keyaction && $.isFunction($keyboard.keyaction[action])) {
902					// stop processing if action returns false (close & cancel)
903					if ( $keyboard.keyaction[ action ]( base, this, e ) === false ) { return false; }
904					action = null; // prevent inserting action name
905				}
906				if (typeof action !== 'undefined' && action !== null) {
907					last.key = $(this).hasClass(kbcss.keyAction) ? action : last.key;
908					base.insertText( last.key );
909					if (!base.capsLock && !o.stickyShift && !e.shiftKey) {
910						base.shiftActive = false;
911						base.showSet( $key.attr('data-name') );
912					}
913				}
914				// set caret if caret moved by action function; also, attempt to fix issue #131
915				$keyboard.caret( base.$preview, last );
916				base.checkCombos();
917				base.$el.trigger( $keyboard.events.kbChange, [ base, base.el ] );
918				last.preVal = '' + last.val;
919				last.val = base.$preview.val();
920
921				if ($.isFunction(o.change)){
922					o.change( $.Event( $keyboard.events.inputChange ), base, base.el );
923					// return false to prevent reopening keyboard if base.accept() was called
924					return false;
925				}
926
927			})
928			// using 'kb' namespace for mouse repeat functionality to keep it separate
929			// I need to trigger a 'repeater.keyboard' to make it work
930			.bind('mouseup' + base.namespace + ' ' + 'mouseleave touchend touchmove touchcancel '.split(' ').join(base.namespace + 'kb '), function(e){
931				base.last.virtual = true;
932				var offset,
933					$this = $(this);
934				if (e.type === 'touchmove') {
935					// if moving within the same key, don't stop repeating
936					offset = $this.offset();
937					offset.right = offset.left + $this.outerWidth();
938					offset.bottom = offset.top + $this.outerHeight();
939					if (e.originalEvent.touches[0].pageX >= offset.left &&
940						e.originalEvent.touches[0].pageX < offset.right &&
941						e.originalEvent.touches[0].pageY >= offset.top &&
942						e.originalEvent.touches[0].pageY < offset.bottom) {
943						return true;
944					}
945				} else if (/(mouseleave|touchend|touchcancel)/i.test(e.type)) {
946					$this.removeClass(o.css.buttonHover); // needed for touch devices
947				} else {
948					if (!o.noFocus && base.isVisible() && base.isCurrent()) { base.$preview.focus(); }
949					if (base.checkCaret) {
950						$keyboard.caret( base.$preview, base.last );
951					}
952				}
953				base.mouseRepeat = [false,''];
954				clearTimeout(base.repeater); // make sure key repeat stops!
955				return false;
956			})
957			// prevent form submits when keyboard is bound locally - issue #64
958			.bind('click' + base.namespace, function(){
959				return false;
960			})
961			// no mouse repeat for action keys (shift, ctrl, alt, meta, etc)
962			.not( '.' + kbcss.keyAction )
963			// Allow mousewheel to scroll through other keysets of the same (non-action) key
964			.bind( 'mousewheel' + base.namespace, function( e, delta ) {
965				if ( o.useWheel && base.wheel ) {
966					// deltaY used by newer versions of mousewheel plugin
967					delta = delta || e.deltaY;
968					var n,
969						txt = base.last.wheelLayers || [];
970					if ( txt.length > 1 ) {
971						n = base.last.wheelIndex + ( delta > 0 ? -1 : 1 );
972						if ( n > txt.length-1) { n = 0; }
973						if ( n < 0 ) { n = txt.length-1; }
974					} else {
975						n = 0;
976					}
977					base.last.wheelIndex = n;
978					$( this ).html( txt[ n ] );
979					return false;
980				}
981			})
982			// mouse repeated action key exceptions
983			.add('.' + kbcss.keyPrefix + ('tab bksp space enter'.split(' ').join(',.' + kbcss.keyPrefix)), base.$keyboard)
984			.bind('mousedown touchstart '.split(' ').join(base.namespace + 'kb '), function(){
985				if (o.repeatRate !== 0) {
986					var key = $(this);
987					base.mouseRepeat = [true, key]; // save the key, make sure we are repeating the right one (fast typers)
988					setTimeout(function() {
989						if (base.mouseRepeat[0] && base.mouseRepeat[1] === key) { base.repeatKey(key); }
990					}, o.repeatDelay);
991				}
992				return false;
993			});
994	};
995
996	// Insert text at caret/selection - thanks to Derek Wickwire for fixing this up!
997	base.insertText = function(txt){
998		if ( typeof txt === 'undefined' ) { return; }
999		var bksp, t,
1000			isBksp = txt === '\b',
1001			// use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
1002			val = base.$preview.val(),
1003			pos = $keyboard.caret( base.$preview ),
1004			len = val.length; // save original content length
1005
1006		// silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
1007		// is still difficult
1008		// in IE, pos.end can be zero after input loses focus
1009		if (pos.end < pos.start) { pos.end = pos.start; }
1010		if (pos.start > len) { pos.end = pos.start = len; }
1011
1012		if (base.preview.nodeName === 'TEXTAREA') {
1013			// This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
1014			if ($keyboard.msie && val.substr(pos.start, 1) === '\n') { pos.start += 1; pos.end += 1; }
1015		}
1016
1017		if (txt === '{d}') {
1018			txt = '';
1019			t = pos.start;
1020			pos.end += 1;
1021		}
1022
1023		bksp = isBksp && pos.start === pos.end;
1024		txt = isBksp ? '' : txt;
1025		val = val.substr(0, pos.start - (bksp ? 1 : 0)) + txt + val.substr(pos.end);
1026		t = pos.start + (bksp ? -1 : txt.length);
1027
1028		base.$preview.val( val );
1029		base.saveCaret( t, t ); // save caret in case of bksp
1030		base.setScroll();
1031	};
1032
1033	// check max length
1034	base.checkMaxLength = function(){
1035		var start, caret,
1036			val = base.$preview.val();
1037		if (o.maxLength !== false && val.length > o.maxLength) {
1038			start = $keyboard.caret( base.$preview ).start;
1039			caret = Math.min(start, o.maxLength);
1040
1041			// prevent inserting new characters when maxed #289
1042			if (!o.maxInsert) {
1043				val = base.last.val;
1044				caret = start - 1; // move caret back one
1045			}
1046
1047			base.$preview.val( val.substring(0, o.maxLength) );
1048			// restore caret on change, otherwise it ends up at the end.
1049			base.saveCaret( caret, caret );
1050		}
1051		if (base.$decBtn.length) {
1052			base.checkDecimal();
1053		}
1054	};
1055
1056	// mousedown repeater
1057	base.repeatKey = function(key){
1058		key.trigger( $keyboard.events.kbRepeater );
1059		if (base.mouseRepeat[0]) {
1060			base.repeater = setTimeout(function() {
1061				base.repeatKey(key);
1062			}, base.repeatTime);
1063		}
1064	};
1065
1066	// make it easier to switch keysets via API
1067	// showKeySet('shift+alt+meta1')
1068	base.showKeySet = function(str) {
1069		if (typeof str === 'string') {
1070			base.last.keyset = [ base.shiftActive, base.altActive, base.metaActive ];
1071			base.shiftActive = /shift/i.test(str);
1072			base.altActive = /alt/i.test(str);
1073			if (/meta/.test(str)) {
1074				base.metaActive = true;
1075				base.showSet( str.match(/meta\d+/i)[0] );
1076			} else {
1077				base.metaActive = false;
1078				base.showSet();
1079			}
1080		} else {
1081			base.showSet( str );
1082		}
1083	};
1084
1085	base.showSet = function( name ) {
1086		o = base.options; // refresh options
1087		var kbcss = $keyboard.css,
1088			prefix = '.' + kbcss.keyPrefix,
1089			active = o.css.buttonActive,
1090			key = '',
1091			toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
1092		if (!base.shiftActive) { base.capsLock = false; }
1093		// check meta key set
1094		if (base.metaActive) {
1095			// the name attribute contains the meta set # 'meta99'
1096			key = (/meta/i.test(name)) ? name : '';
1097			// save active meta keyset name
1098			if (key === '') {
1099				key = (base.metaActive === true) ? '' : base.metaActive;
1100			} else {
1101				base.metaActive = key;
1102			}
1103			// if meta keyset doesn't have a shift or alt keyset, then show just the meta key set
1104			if ( (!o.stickyShift && base.last.keyset[2] !== base.metaActive) ||
1105				( (base.shiftActive || base.altActive) && !base.$keyboard.find('.' + kbcss.keySet + '-' + key +
1106					base.rows[toShow]).length) ) {
1107				base.shiftActive = base.altActive = false;
1108			}
1109		} else if (!o.stickyShift && base.last.keyset[2] !== base.metaActive && base.shiftActive) {
1110			// switching from meta key set back to default, reset shift & alt if using stickyShift
1111			base.shiftActive = base.altActive = false;
1112		}
1113		toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
1114		key = (toShow === 0 && !base.metaActive) ? '-normal' : (key === '') ? '' : '-' + key;
1115		if (!base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow]).length) {
1116			// keyset doesn't exist, so restore last keyset settings
1117			base.shiftActive = base.last.keyset[0];
1118			base.altActive = base.last.keyset[1];
1119			base.metaActive = base.last.keyset[2];
1120			return;
1121		}
1122		base.$keyboard
1123			.find( prefix + 'alt,' + prefix + 'shift,.' + kbcss.keyAction + '[class*=meta]' )
1124				.removeClass( active ).end()
1125			.find( prefix + 'alt' ).toggleClass( active, base.altActive ).end()
1126			.find( prefix + 'shift' ).toggleClass( active, base.shiftActive ).end()
1127			.find( prefix + 'lock' ).toggleClass( active, base.capsLock ).end()
1128			.find( '.' + kbcss.keySet ).hide().end()
1129			.find( '.' + kbcss.keyAction + prefix + key ).addClass( active );
1130
1131		// show keyset using inline-block ( extender layout will then line up )
1132		base.$keyboard.find( '.' + kbcss.keySet + key + base.rows[ toShow ] )[ 0 ].style.display = 'inline-block';
1133
1134		if ( base.metaActive ) {
1135			base.$keyboard.find( prefix + base.metaActive )
1136				// base.metaActive contains the string "meta#" or false
1137				// without the !== false, jQuery UI tries to transition the classes
1138				.toggleClass( active, base.metaActive !== false );
1139		}
1140		base.last.keyset = [ base.shiftActive, base.altActive, base.metaActive ];
1141		base.$el.trigger( $keyboard.events.kbKeysetChange, [ base, base.el ] );
1142	};
1143
1144	// check for key combos (dead keys)
1145	base.checkCombos = function(){
1146		if (!base.isVisible()) { return base.$preview.val(); }
1147		var r, t, t2,
1148			// use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
1149			val = base.$preview.val(),
1150			pos = $keyboard.caret( base.$preview ),
1151			layout = $keyboard.builtLayouts[base.layout],
1152			len = val.length; // save original content length
1153		// return if val is empty; fixes #352
1154		if (val === '') { return val; }
1155
1156		// silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
1157		// is still difficult
1158		// in IE, pos.end can be zero after input loses focus
1159		if (pos.end < pos.start) { pos.end = pos.start; }
1160		if (pos.start > len) { pos.end = pos.start = len; }
1161		// This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
1162		if ($keyboard.msie && val.substr(pos.start, 1) === '\n') { pos.start += 1; pos.end += 1; }
1163
1164		if (o.useCombos) {
1165			// keep 'a' and 'o' in the regex for ae and oe ligature (æ,œ)
1166			// thanks to KennyTM: http://stackoverflow.com/q/4275077
1167			// original regex /([`\'~\^\"ao])([a-z])/mig moved to $.keyboard.comboRegex
1168			if ($keyboard.msie) {
1169				// old IE may not have the caret positioned correctly, so just check the whole thing
1170				val = val.replace(base.regex, function(s, accent, letter){
1171					return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
1172				});
1173			// prevent combo replace error, in case the keyboard closes - see issue #116
1174			} else if (base.$preview.length) {
1175				// Modern browsers - check for combos from last two characters left of the caret
1176				t = pos.start - (pos.start - 2 >= 0 ? 2 : 0);
1177				// target last two characters
1178				$keyboard.caret( base.$preview, t, pos.end );
1179				// do combo replace
1180				t2 = ($keyboard.caret( base.$preview ).text || '').replace(base.regex, function(s, accent, letter){
1181					return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
1182				});
1183				// add combo back
1184				base.$preview.val( $keyboard.caret( base.$preview ).replaceStr(t2) );
1185				val = base.$preview.val();
1186			}
1187		}
1188
1189		// check input restrictions - in case content was pasted
1190		if (o.restrictInput && val !== '') {
1191			t = layout.acceptedKeys.length;
1192
1193			r = layout.acceptedKeysRegex;
1194			if ( !r ) {
1195				t2 = $.map( layout.acceptedKeys, function( v ) {
1196					// escape any special characters
1197					return v.replace( base.escapeRegex, '\\$&' );
1198				});
1199				r = layout.acceptedKeysRegex = new RegExp( '(' + t2.join( '|' ) + ')', 'g' );
1200			}
1201
1202			// only save matching keys
1203			t2 = val.match( r );
1204			if ( t2 ) {
1205				val = t2.join( '' );
1206			} else {
1207				// no valid characters
1208				val = '';
1209				len = 0;
1210			}
1211		}
1212
1213		// save changes, then reposition caret
1214		pos.start += val.length - len;
1215		pos.end += val.length - len;
1216		base.$preview.val(val);
1217		base.saveCaret( pos.start, pos.end );
1218		// set scroll to keep caret in view
1219		base.setScroll();
1220
1221		base.checkMaxLength();
1222
1223		if (o.acceptValid) { base.checkValid(); }
1224
1225		return val; // return text, used for keyboard closing section
1226	};
1227
1228	// Toggle accept button classes, if validating
1229	base.checkValid = function(){
1230		var kbcss = $keyboard.css,
1231			$accept = base.$keyboard.find('.' + kbcss.keyPrefix + 'accept'),
1232			valid = true;
1233		if ($.isFunction(o.validate)) {
1234			valid = o.validate(base, base.$preview.val(), false);
1235		}
1236		// toggle accept button classes; defined in the css
1237		$accept
1238			.toggleClass( kbcss.inputInvalid, !valid )
1239			.toggleClass( kbcss.inputValid, valid )
1240			// update title to indicate that the entry is valid or invalid
1241			.attr( 'title', $accept.attr('data-title') + ' (' + o.display[ valid ? 'valid' : 'invalid' ] + ')' );
1242	};
1243
1244	// Decimal button for num pad - only allow one (not used by default)
1245	base.checkDecimal = function(){
1246		// Check US '.' or European ',' format
1247		if ( ( base.decimal && /\./g.test(base.preview.value) ) ||
1248			( !base.decimal && /\,/g.test(base.preview.value) ) ) {
1249			base.$decBtn
1250				.attr({ 'disabled': 'disabled', 'aria-disabled': 'true' })
1251				.removeClass(o.css.buttonHover)
1252				.addClass(o.css.buttonDisabled);
1253		} else {
1254			base.$decBtn
1255				.removeAttr('disabled')
1256				.attr({ 'aria-disabled': 'false' })
1257				.addClass(o.css.buttonDefault)
1258				.removeClass(o.css.buttonDisabled);
1259		}
1260	};
1261
1262	// get other layer values for a specific key
1263	base.getLayers = function($el){
1264		var kbcss = $keyboard.css,
1265			key = $el.attr('data-pos'),
1266			$keys = $el.closest('.' + kbcss.keyboard).find('button[data-pos="' + key + '"]');
1267		return $keys.filter(function(){ return $(this).find('.' + kbcss.keyText).text() !== ''; }).add($el);
1268	};
1269
1270	// Go to next or prev inputs
1271	// goToNext = true, then go to next input; if false go to prev
1272	// isAccepted is from autoAccept option or true if user presses shift+enter
1273	base.switchInput = function(goToNext, isAccepted){
1274		if ($.isFunction(o.switchInput)) {
1275			o.switchInput(base, goToNext, isAccepted);
1276		} else {
1277			// base.$keyboard may be an empty array - see #275 (apod42)
1278			if (base.$keyboard.length) {
1279				base.$keyboard.hide();
1280			}
1281			var kb, stopped = false,
1282				all = $('button, input, textarea, a').filter(':visible').not(':disabled'),
1283				indx = all.index(base.$el) + (goToNext ? 1 : -1);
1284			if (base.$keyboard.length) {
1285				base.$keyboard.show();
1286			}
1287			if (indx > all.length - 1) {
1288				stopped = o.stopAtEnd;
1289				indx = 0; // go to first input
1290			}
1291			if (indx < 0) {
1292				stopped = o.stopAtEnd;
1293				indx = all.length - 1; // stop or go to last
1294			}
1295			if (!stopped) {
1296				isAccepted = base.close(isAccepted);
1297				if (!isAccepted) { return; }
1298				kb = all.eq(indx).data('keyboard');
1299				if (kb && kb.options.openOn.length) {
1300					kb.focusOn();
1301				} else {
1302					all.eq(indx).focus();
1303				}
1304			}
1305		}
1306		return false;
1307	};
1308
1309	// Close the keyboard, if visible. Pass a status of true, if the content was accepted
1310	// (for the event trigger).
1311	base.close = function(accepted){
1312		if (base.isOpen) {
1313			clearTimeout(base.throttled);
1314			var kbcss = $keyboard.css,
1315				kbevents = $keyboard.events,
1316				val = (accepted) ?  base.checkCombos() : base.originalContent;
1317			// validate input if accepted
1318			if (accepted && $.isFunction(o.validate) && !o.validate(base, val, true)) {
1319				val = base.originalContent;
1320				accepted = false;
1321				if (o.cancelClose) { return; }
1322			}
1323			base.isCurrent(false);
1324			base.isOpen = o.alwaysOpen;
1325			// update value for always open keyboards
1326			base.$preview.val(val);
1327
1328			base.$el
1329				.removeClass(kbcss.isCurrent + ' ' + kbcss.inputAutoAccepted)
1330				// add 'ui-keyboard-autoaccepted' to inputs - see issue #66
1331				.addClass( (accepted || false) ? accepted === true ? '' : kbcss.inputAutoAccepted : '' )
1332				.val( val )
1333				// trigger default change event - see issue #146
1334				.trigger(kbevents.inputChange)
1335				// don't trigger beforeClose if keyboard is always open
1336				.trigger( (o.alwaysOpen) ? '' : kbevents.kbBeforeClose, [ base, base.el, (accepted || false) ] )
1337
1338				.trigger( ((accepted || false) ? kbevents.inputAccepted : kbevents.inputCanceled), [ base, base.el ] )
1339				.trigger( (o.alwaysOpen) ? kbevents.kbInactive : kbevents.kbHidden, [ base, base.el ] )
1340				.blur();
1341
1342			// base is undefined if keyboard was destroyed - fixes #358
1343			if ( base ) {
1344				// add close event time
1345				base.last.eventTime = new Date().getTime();
1346				if (!o.alwaysOpen && base.$keyboard) {
1347					if (o.openOn) {
1348						// rebind input focus - delayed to fix IE issue #72
1349						base.timer = setTimeout(function(){
1350							// make sure keyboard isn't destroyed
1351							// Check if base exists, this is a case when destroy is called, before timers have fired
1352							if ( base && base.el.active ) {
1353								base.$el.bind( o.openOn + base.namespace, function(){ base.focusOn(); });
1354								// remove focus from element (needed for IE since blur doesn't seem to work)
1355								if ($(':focus')[0] === base.el) { base.$el.blur(); }
1356							}
1357						}, 500);
1358					}
1359					// free up memory
1360					base.$keyboard.remove();
1361					base.$keyboard = [];
1362					base.$previewCopy = null;
1363				}
1364				if (!base.watermark && base.el.value === '' && base.inPlaceholder !== '') {
1365					base.$el
1366						.addClass(kbcss.placeholder)
1367						.val(base.inPlaceholder);
1368				}
1369			}
1370		}
1371		return !!accepted;
1372	};
1373
1374	base.accept = function(){
1375		return base.close(true);
1376	};
1377
1378	base.checkClose = function( e ) {
1379		if ( base.opening ) { return; }
1380		base.escClose( e );
1381		var kbcss = $.keyboard.css,
1382			$target = $( e.target );
1383		// needed for IE to allow switching between keyboards smoothly
1384		if ( $target.hasClass( kbcss.input ) ) {
1385			var kb = $target.data( 'keyboard' );
1386			// only trigger on self
1387			if ( kb === base && !kb.$el.hasClass( kbcss.isCurrent ) && e.type === kb.options.openOn ) {
1388				kb.focusOn();
1389			}
1390		}
1391	};
1392
1393	base.escClose = function(e){
1394		if ( e && e.type === 'keyup' ) {
1395			return ( e.which === $keyboard.keyCodes.escape && !o.ignoreEsc ) ?
1396				base.close( o.autoAccept && o.autoAcceptOnEsc ? 'true' : false ) :
1397				'';
1398		}
1399		// keep keyboard open if alwaysOpen or stayOpen is true - fixes mutliple always open keyboards or
1400		// single stay open keyboard
1401		if ( !base.isOpen ) { return; }
1402		// ignore autoaccept if using escape - good idea?
1403		if ( !base.isCurrent() && base.isOpen || base.isOpen && e.target !== base.el ) {
1404			// don't close if stayOpen is set; but close if a different keyboard is being opened
1405			if (o.stayOpen && !$(e.target).hasClass('ui-keyboard-input')) {
1406				return;
1407			}
1408			// stop propogation in IE - an input getting focus doesn't open a keyboard if one is already open
1409			if ( $keyboard.allie ) {
1410				e.preventDefault();
1411			}
1412			// send 'true' instead of a true (boolean), the input won't get a 'ui-keyboard-autoaccepted'
1413			// class name - see issue #66
1414			base.close( o.autoAccept ? 'true' : false );
1415		}
1416	};
1417
1418	// Build default button
1419	base.keyBtn = $('<button />')
1420		.attr({ 'role': 'button', 'type': 'button', 'aria-disabled': 'false', 'tabindex' : '-1' })
1421		.addClass( $keyboard.css.keyButton );
1422
1423	base.processName = function( name ) {
1424		var index, n,
1425			process = ( name || '' ).replace( /[^a-z0-9-_]/gi, '' ),
1426			len = process.length,
1427			newName = [];
1428		if ( len > 1 && name === process ) {
1429			// return name if basic text
1430			return name;
1431		}
1432		// return character code sequence
1433		len = name.length;
1434		if ( len ) {
1435			for ( index = 0; index < len; index++ ) {
1436				n = name[ index ];
1437				// keep '-' as a dash, but don't add it or we get two dashes in a row
1438				newName.push( /[a-z0-9-_]/i.test( n ) ? ( /[-_]/.test(n) ? '' : n ) :
1439					( index === 0 ? '' : '-' ) + n.charCodeAt( 0 ) );
1440			}
1441			return newName.join( '' );
1442		} else {
1443			return name;
1444		}
1445	};
1446
1447	// Add key function
1448	// keyName = the name of the function called in $.keyboard.keyaction when the button is clicked
1449	// name = name added to key, or cross-referenced in the display options
1450	// base.temp[0] = keyset to attach the new button
1451	// regKey = true when it is not an action key
1452	base.addKey = function( keyName, name, regKey ) {
1453		var keyClass, m, map, nm,
1454			kbcss = $keyboard.css,
1455			txt = name.split(':'),
1456			len = txt.length - 1,
1457			n = (regKey === true) ? keyName : o.display[txt[0]] || keyName,
1458			data = {
1459				isAction : !regKey,
1460				action   : keyName,
1461				name     : base.processName( keyName.split(/[(:]/)[0] )
1462			};
1463		// map defined keys - format 'key(A):Label_for_key_(ignore_parentheses_here)'
1464		// 'key' = key that is seen (can any character; but it might need to be escaped using '\'
1465		//  or entered as unicode '\u####'
1466		// '(A)' = the actual key on the real keyboard to remap, ':Label_for_key' ends up in the title/tooltip
1467		if ( /\(.+\)/.test( n.split( ':' )[0] ) || /^:\(.+\)/.test( n ) ) { // n = '\u0391(A):alpha' or n = ':(x)'
1468			map = n.replace(/\(([^()]+)\)/, ''); // remove '(A)', left with '\u0391:alpha'
1469			m = n.match( /\(([^()]+)\)/ )[1]; // extract 'A' from '(A)'
1470			n = map;
1471			nm = map.split(':');
1472			map = (nm[0] !== '' && nm.length > 1) ? nm[0] : map; // get '\u0391' from '\u0391:alpha'
1473			$keyboard.builtLayouts[base.layout].mappedKeys[m] = map;
1474			$keyboard.builtLayouts[base.layout].acceptedKeys.push(m);
1475		} else if (regKey) {
1476			$keyboard.builtLayouts[base.layout].acceptedKeys.push(n);
1477		}
1478
1479		// find key label
1480		nm = n.split(':');
1481		// corner case of ':(:):;' reduced to '::;', split as ['', '', ';']
1482		if (nm[0] === '' && nm[1] === '') { n = ':'; }
1483		n = (nm[0] !== '' && nm.length > 1) ? nm[0] : n;
1484		// allow alt naming of action keys
1485		data.value = $.trim( regKey ? n : txt[1] || n );
1486		// added to title
1487		data.title = (nm.length > 1) ? $.trim(nm[1]).replace(/_/g, ' ') || '' : len > 0 ? txt[len] || '' : '';
1488
1489		// Action keys will have the 'ui-keyboard-actionkey' class
1490		// '\u2190'.length = 1 because the unicode is converted, so if more than one character,
1491		// add the wide class
1492		keyClass = ( data.value.length > 2 ) ? ' ' + kbcss.keyWide : '';
1493		keyClass += ( regKey ) ? '' : ' ' + kbcss.keyAction;
1494
1495		data.html = '<span class="' + kbcss.keyText + '">' +
1496			// this prevents HTML from being added to the key
1497			data.value.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
1498				return '&#'+i.charCodeAt(0)+';';
1499			}) +
1500			'</span>';
1501
1502		data.$key = base.keyBtn
1503			.clone()
1504			.attr({
1505				'data-value'  : data.value, // value
1506				'data-name'   : data.name,
1507				'data-pos'    : base.temp[1] + ',' + base.temp[2],
1508				'data-title'  : data.title, // used to allow adding content to title
1509				'title'       : data.title,
1510				'data-action' : data.action,
1511				'data-html'   : data.html
1512			})
1513			// add 'ui-keyboard-' + data.name for all keys
1514			//  (e.g. 'Bksp' will have 'ui-keyboard-bskp' class)
1515			// any non-alphanumeric characters will be replaced with
1516			//  their decimal unicode value
1517			//  (e.g. '~' is a regular key, class = 'ui-keyboard-126'
1518			//  (126 is the unicode decimal value - same as &#126;)
1519			//  See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
1520			.addClass( ( data.name === '' ? '' : kbcss.keyPrefix + data.name + keyClass + ' ' ) + o.css.buttonDefault )
1521			.html( data.html )
1522			.appendTo( base.temp[0] );
1523
1524		if ( typeof o.buildKey === 'function' ) {
1525			data = o.buildKey( base, data );
1526			// copy html back to attributes
1527			txt = data.$key.html();
1528			data.$key.attr( 'data-html', txt );
1529		}
1530		return data.$key;
1531	};
1532
1533	base.customHash = function( layout ) {
1534		/*jshint bitwise:false */
1535		var i, array, hash, character, len,
1536			arrays = [], merged = [];
1537		// pass layout to allow for testing
1538		layout = typeof layout === 'undefined' ? o.customLayout : layout;
1539		// get all layout arrays
1540		for ( array in layout ) {
1541			if ( layout.hasOwnProperty( array ) ) {
1542				arrays.push( layout[ array ] );
1543			}
1544		}
1545		// flatten array
1546		merged = merged.concat.apply( merged, arrays ).join( ' ' );
1547		// produce hash name - http://stackoverflow.com/a/7616484/145346
1548		hash = 0;
1549		len = merged.length;
1550		if ( len === 0 ) { return hash; }
1551		for ( i = 0; i < len; i++ ) {
1552			character = merged.charCodeAt( i );
1553			hash = ( ( hash<<5 ) - hash ) + character;
1554			hash = hash & hash; // Convert to 32bit integer
1555		}
1556		return hash;
1557	};
1558
1559	base.buildKeyboard = function(name, internal) {
1560		// o.display is empty when this is called from the scramble extension (when alwaysOpen:true)
1561		if ( $.isEmptyObject(o.display) ) {
1562			// set keyboard language
1563			base.updateLanguage();
1564		}
1565		var row, $row, currentSet,
1566			kbcss = $keyboard.css,
1567			sets = 0,
1568			layout = $keyboard.builtLayouts[name || base.layout || o.layout] = {
1569				mappedKeys   : {},
1570				acceptedKeys : []
1571			},
1572			acceptedKeys = layout.acceptedKeys = o.restrictInclude ? ( '' + o.restrictInclude ).split( /\s+/ ) || [] : [],
1573			// using $layout temporarily to hold keyboard popup classnames
1574			$layout = kbcss.keyboard + ' ' + o.css.popup + ' ' + o.css.container +
1575				( o.alwaysOpen ? ' ' + kbcss.alwaysOpen : '' ),
1576
1577		container = $('<div />')
1578			.addClass( $layout )
1579			.attr({ 'role': 'textbox' })
1580			.hide();
1581		// verify layout or setup custom keyboard
1582		if ( ( internal && o.layout === 'custom' ) || !$keyboard.layouts.hasOwnProperty(o.layout) ) {
1583			o.layout = 'custom';
1584			$layout = $keyboard.layouts.custom = o.customLayout || { 'normal' : ['{cancel}'] };
1585		} else {
1586			$layout = $keyboard.layouts[ internal ? o.layout : name || base.layout || o.layout ];
1587		}
1588
1589		// Main keyboard building loop
1590		$.each($layout, function(set, keySet) {
1591			// skip layout name & lang settings
1592			if (set !== '' && !/^(name|lang|rtl)$/i.test(set)) {
1593				// keep backwards compatibility for change from default to normal naming
1594				if (set === 'default') { set = 'normal'; }
1595				sets++;
1596				$row = $('<div />')
1597					.attr('name', set) // added for typing extension
1598					.addClass( kbcss.keySet + ' ' + kbcss.keySet + '-' + set)
1599					.appendTo(container)
1600					.toggle( set === 'normal' );
1601
1602				for ( row = 0; row < keySet.length; row++ ) {
1603					// remove extra spaces before spliting (regex probably could be improved)
1604					currentSet = $.trim(keySet[row]).replace(/\{(\.?)[\s+]?:[\s+]?(\.?)\}/g,'{$1:$2}');
1605					base.buildRow( $row, row, currentSet.split(/\s+/), acceptedKeys );
1606					$row.find('.' + kbcss.keyButton + ',.' + kbcss.keySpacer )
1607						.filter(':last')
1608						.after('<br class="' + kbcss.endRow + '"/>');
1609				}
1610			}
1611		});
1612
1613		if (sets > 1) { base.sets = true; }
1614		layout.hasMappedKeys = !( $.isEmptyObject(layout.mappedKeys) ); // $.isEmptyObject() requires jQuery 1.4+
1615		layout.$keyboard = container;
1616		return container;
1617	};
1618
1619	base.buildRow = function( $row, row, keys, acceptedKeys ) {
1620		var t, txt, key, isAction, action, margin,
1621			kbcss = $keyboard.css;
1622		for ( key = 0; key < keys.length; key++ ) {
1623			// used by addKey function
1624			base.temp = [ $row, row, key ];
1625			isAction = false;
1626
1627			// ignore empty keys
1628			if (keys[key].length === 0) { continue; }
1629
1630			// process here if it's an action key
1631			if (/^\{\S+\}$/.test(keys[key])) {
1632				action = keys[key].match(/^\{(\S+)\}$/)[1];
1633				// add active class if there are double exclamation points in the name
1634				if (/\!\!/.test(action)) {
1635					action = action.replace('!!', '');
1636					isAction = true;
1637				}
1638
1639				// add empty space
1640				if (/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i.test(action)) {
1641					// not perfect globalization, but allows you to use {sp:1,1em}, {sp:1.2em} or {sp:15px}
1642					margin = parseFloat( action
1643						.replace(/,/, '.')
1644						.match(/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
1645					);
1646					$('<span class="' + kbcss.keyText + '"></span>')
1647						// previously {sp:1} would add 1em margin to each side of a 0 width span
1648						// now Firefox doesn't seem to render 0px dimensions, so now we set the
1649						// 1em margin x 2 for the width
1650						.width( (action.match(/px/i) ? margin + 'px' : (margin * 2) + 'em') )
1651						.addClass( kbcss.keySpacer )
1652						.appendTo($row);
1653				}
1654
1655				// add empty button
1656				if (/^empty(:((\d+)?([\.|,]\d+)?)(em|px)?)?$/i.test(action)) {
1657					margin = (/:/.test(action)) ? parseFloat( action
1658						.replace(/,/,'.')
1659						.match(/^empty:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
1660					) : '';
1661					base
1662						.addKey('', ' ')
1663						.addClass(o.css.buttonDisabled + ' ' + o.css.buttonEmpty)
1664						.attr('aria-disabled', true)
1665						.width( margin ? (action.match('px') ? margin + 'px' : (margin * 2) + 'em') : '' );
1666				}
1667
1668				// meta keys
1669				if (/^meta\d+\:?(\w+)?/i.test(action)) {
1670					base
1671						.addKey(action.split(':')[0], action)
1672						.addClass( kbcss.keyHasActive );
1673					continue;
1674				}
1675
1676				// switch needed for action keys with multiple names/shortcuts or
1677				// default will catch all others
1678				txt = action.split(':');
1679				switch(txt[0].toLowerCase()) {
1680
1681					case 'a':
1682					case 'accept':
1683						base
1684							.addKey('accept', action)
1685							.addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
1686						break;
1687
1688					case 'alt':
1689					case 'altgr':
1690						base
1691							.addKey('alt', action)
1692							.addClass( kbcss.keyHasActive );
1693						break;
1694
1695					case 'b':
1696					case 'bksp':
1697						base.addKey('bksp', action);
1698						break;
1699
1700					case 'c':
1701					case 'cancel':
1702						base
1703							.addKey('cancel', action)
1704							.addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
1705						break;
1706
1707					// toggle combo/diacritic key
1708					/*jshint -W083 */
1709					case 'combo':
1710						base
1711							.addKey('combo', action)
1712							.addClass( kbcss.keyHasActive )
1713							.attr('title', function( indx, title ) {
1714								// add combo key state to title
1715								return title + ' (' + o.display[ o.useCombos ? 'active' : 'disabled' ] + ')';
1716							})
1717							.toggleClass(o.css.buttonActive, o.useCombos);
1718						break;
1719
1720					// Decimal - unique decimal point (num pad layout)
1721					case 'dec':
1722						acceptedKeys.push((base.decimal) ? '.' : ',');
1723						base.addKey('dec', action);
1724						break;
1725
1726					case 'e':
1727					case 'enter':
1728						base
1729							.addKey('enter', action)
1730							.addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
1731						break;
1732
1733					case 'lock':
1734						base
1735							.addKey('lock', action)
1736							.addClass( kbcss.keyHasActive );
1737						break;
1738
1739					case 's':
1740					case 'shift':
1741						base
1742							.addKey('shift', action)
1743							.addClass( kbcss.keyHasActive );
1744						break;
1745
1746					// Change sign (for num pad layout)
1747					case 'sign':
1748						acceptedKeys.push('-');
1749						base.addKey('sign', action);
1750						break;
1751
1752					case 'space':
1753						acceptedKeys.push(' ');
1754						base.addKey('space', action);
1755						break;
1756
1757					case 't':
1758					case 'tab':
1759						base.addKey('tab', action);
1760						break;
1761
1762					default:
1763						if ($keyboard.keyaction.hasOwnProperty(txt[0])){
1764							// acceptedKeys.push(action);
1765							base
1766								.addKey(txt[0], action)
1767								.toggleClass( o.css.buttonAction + ' ' + kbcss.keyAction, isAction );
1768						}
1769
1770				}
1771
1772			} else {
1773
1774				// regular button (not an action key)
1775				t = keys[key];
1776				base.addKey(t, t, true);
1777			}
1778		}
1779	};
1780
1781	base.removeBindings = function( namespace ) {
1782		$(document).unbind( namespace );
1783		if ( base.el.ownerDocument !== document ) {
1784			$( base.el.ownerDocument ).unbind( namespace );
1785		}
1786		$(window).unbind( namespace );
1787		base.$el.unbind( namespace );
1788	};
1789
1790	base.destroy = function( callback ) {
1791		var index,
1792			kbcss = $keyboard.css,
1793			len = base.extensionNamespace.length,
1794			tmp = [ kbcss.input, kbcss.locked, kbcss.placeholder, kbcss.noKeyboard,
1795				kbcss.alwaysOpen, o.css.input, kbcss.isCurrent ].join(' ');
1796		clearTimeout(base.timer);
1797		clearTimeout(base.timer2);
1798		base.removeBindings( base.namespace );
1799		for ( index = 0; index < len; index++ ) {
1800			base.removeBindings( base.extensionNamespace[ index ] );
1801		}
1802		base.el.active = false;
1803		if (base.$keyboard.length) {
1804			base.$keyboard.remove();
1805		}
1806
1807		base.$el
1808			.removeClass( tmp )
1809			.removeAttr('aria-haspopup')
1810			.removeAttr('role')
1811			.removeData('keyboard');
1812		base = null;
1813
1814		if ( typeof callback === 'function' ) {
1815			callback();
1816		}
1817	};
1818
1819		// Run initializer
1820		base.init();
1821	};
1822
1823	// event.which & ASCII values
1824	$keyboard.keyCodes = {
1825		backSpace: 8,
1826		tab: 9,
1827		enter: 13,
1828		capsLock: 20,
1829		escape: 27,
1830		space: 32,
1831		pageUp: 33,
1832		pageDown: 34,
1833		end: 35,
1834		home: 36,
1835		left: 37,
1836		up: 38,
1837		right: 39,
1838		down: 40,
1839		insert: 45,
1840		delete: 46,
1841		// event.which keyCodes (uppercase letters)
1842		A: 65,
1843		Z: 90,
1844		V: 86,
1845		C: 67,
1846		X: 88,
1847
1848		// ASCII lowercase a & z
1849		a: 97,
1850		z: 122
1851	};
1852
1853	$keyboard.css = {
1854		// keyboard id suffix
1855		idSuffix: '_keyboard',
1856		// element class names
1857		input: 'ui-keyboard-input',
1858		wrapper: 'ui-keyboard-preview-wrapper',
1859		preview: 'ui-keyboard-preview',
1860		keyboard: 'ui-keyboard',
1861		keySet: 'ui-keyboard-keyset',
1862		keyButton: 'ui-keyboard-button',
1863		keyWide: 'ui-keyboard-widekey',
1864		keyPrefix: 'ui-keyboard-',
1865		keyText: 'ui-keyboard-text', // span with button text
1866		keyHasActive: 'ui-keyboard-hasactivestate',
1867		keyAction: 'ui-keyboard-actionkey',
1868		keySpacer: 'ui-keyboard-spacer', // empty keys
1869		keyToggle: 'ui-keyboard-toggle',
1870		keyDisabled: 'ui-keyboard-disabled',
1871		// states
1872		locked: 'ui-keyboard-lockedinput',
1873		alwaysOpen: 'ui-keyboard-always-open',
1874		noKeyboard: 'ui-keyboard-nokeyboard',
1875		placeholder: 'ui-keyboard-placeholder',
1876		hasFocus: 'ui-keyboard-has-focus',
1877		isCurrent: 'ui-keyboard-input-current',
1878		// validation & autoaccept
1879		inputValid: 'ui-keyboard-valid-input',
1880		inputInvalid: 'ui-keyboard-invalid-input',
1881		inputAutoAccepted: 'ui-keyboard-autoaccepted',
1882		endRow: 'ui-keyboard-button-endrow' // class added to <br>
1883	};
1884
1885	$keyboard.events = {
1886		// keyboard events
1887		kbChange: 'keyboardChange',
1888		kbBeforeClose: 'beforeClose',
1889		kbBeforeVisible: 'beforeVisible',
1890		kbVisible: 'visible',
1891		kbInit: 'initialized',
1892		kbInactive: 'inactive',
1893		kbHidden: 'hidden',
1894		kbRepeater: 'repeater',
1895		kbKeysetChange: 'keysetChange',
1896		// input events
1897		inputAccepted: 'accepted',
1898		inputCanceled: 'canceled',
1899		inputChange: 'change',
1900		inputRestricted: 'restricted'
1901	};
1902
1903	// Action key function list
1904	$keyboard.keyaction = {
1905		accept : function(base) {
1906			base.close(true); // same as base.accept();
1907			return false;     // return false prevents further processing
1908		},
1909		alt : function(base) {
1910			base.altActive = !base.altActive;
1911			base.showSet();
1912		},
1913		bksp : function(base) {
1914			// the script looks for the '\b' string and initiates a backspace
1915			base.insertText('\b');
1916		},
1917		cancel : function(base) {
1918			base.close();
1919			return false; // return false prevents further processing
1920		},
1921		clear : function(base) {
1922			base.$preview.val('');
1923			if (base.$decBtn.length) {
1924				base.checkDecimal();
1925			}
1926		},
1927		combo : function( base ) {
1928			var o = base.options,
1929				c = !o.useCombos,
1930				$combo = base.$keyboard.find( '.' + $keyboard.css.keyPrefix + 'combo' );
1931			o.useCombos = c;
1932			$combo
1933				.toggleClass( o.css.buttonActive, c )
1934				// update combo key state
1935				.attr( 'title', $combo.attr( 'data-title' ) + ' (' + o.display[ c ? 'active' : 'disabled' ] + ')' );
1936			if ( c ) { base.checkCombos(); }
1937			return false;
1938		},
1939		dec : function(base) {
1940			base.insertText((base.decimal) ? '.' : ',');
1941		},
1942		del : function(base) {
1943			// the script looks for the '{d}' string and initiates a delete
1944			base.insertText('{d}');
1945		},
1946		// resets to base keyset (deprecated because "default" is a reserved word)
1947		'default' : function(base) {
1948			base.shiftActive = base.altActive = base.metaActive = false;
1949			base.showSet();
1950		},
1951		// el is the pressed key (button) object; it is null when the real keyboard enter is pressed
1952		enter : function(base, el, e) {
1953			var tag = base.el.nodeName, o = base.options;
1954			// shift+enter in textareas
1955			if (e.shiftKey) {
1956				// textarea & input - enterMod + shift + enter = accept, then go to prev;
1957				//  base.switchInput(goToNext, autoAccept)
1958				// textarea & input - shift + enter = accept (no navigation)
1959				return (o.enterNavigation) ? base.switchInput(!e[o.enterMod], true) : base.close(true);
1960			}
1961			// input only - enterMod + enter to navigate
1962			if (o.enterNavigation && (tag !== 'TEXTAREA' || e[o.enterMod])) {
1963				return base.switchInput(!e[o.enterMod], o.autoAccept ? 'true' : false);
1964			}
1965			// pressing virtual enter button inside of a textarea - add a carriage return
1966			// e.target is span when clicking on text and button at other times
1967			if (tag === 'TEXTAREA' && $(e.target).closest('button').length) {
1968				// IE8 fix (space + \n) - fixes #71 thanks Blookie!
1969				base.insertText( ( $keyboard.msie ? ' ' : '' ) + '\n' );
1970			}
1971		},
1972		// caps lock key
1973		lock : function(base) {
1974			base.last.keyset[0] = base.shiftActive = base.capsLock = !base.capsLock;
1975			base.showSet();
1976		},
1977		left : function(base) {
1978			var p = $keyboard.caret( base.$preview );
1979			if (p.start - 1 >= 0) {
1980				// move both start and end of caret (prevents text selection) & save caret position
1981				base.last.start = base.last.end = p.start - 1;
1982				$keyboard.caret( base.$preview, base.last );
1983				base.setScroll();
1984			}
1985		},
1986		meta : function(base, el) {
1987			var $el = $(el);
1988			base.metaActive = !$el.hasClass( base.options.css.buttonActive );
1989			base.showSet( $el.attr( 'data-name' ) );
1990		},
1991		next : function(base) {
1992			base.switchInput(true, base.options.autoAccept);
1993			return false;
1994		},
1995		// same as 'default' - resets to base keyset
1996		normal : function(base) {
1997			base.shiftActive = base.altActive = base.metaActive = false;
1998			base.showSet();
1999		},
2000		prev : function(base) {
2001			base.switchInput(false, base.options.autoAccept);
2002			return false;
2003		},
2004		right : function(base) {
2005			var p = $keyboard.caret( base.$preview );
2006			if (p.start + 1 <= base.$preview.val().length) {
2007				// move both start and end of caret (prevents text selection) && save caret position
2008				base.last.start = base.last.end = p.start + 1;
2009				$keyboard.caret( base.$preview, base.last );
2010				base.setScroll();
2011			}
2012		},
2013		shift : function(base) {
2014			base.last.keyset[0] = base.shiftActive = !base.shiftActive;
2015			base.showSet();
2016		},
2017		sign : function(base) {
2018			if(/^\-?\d*\.?\d*$/.test( base.$preview.val() )) {
2019				base.$preview.val( (base.$preview.val() * -1) );
2020			}
2021		},
2022		space : function(base) {
2023			base.insertText(' ');
2024		},
2025		tab : function(base) {
2026			var tag = base.el.nodeName,
2027				o = base.options;
2028			if (tag === 'INPUT') {
2029				if (o.tabNavigation) {
2030					return base.switchInput(!base.shiftActive, true);
2031				} else {
2032					// ignore tab key in input
2033					return false;
2034				}
2035			}
2036			base.insertText('\t');
2037		},
2038		toggle : function(base) {
2039			base.enabled = !base.enabled;
2040			base.toggle();
2041		},
2042		// *** Special action keys: NBSP & zero-width characters ***
2043		// Non-breaking space
2044		NBSP : '\u00a0',
2045		// zero width space
2046		ZWSP : '\u200b',
2047		// Zero width non-joiner
2048		ZWNJ : '\u200c',
2049		// Zero width joiner
2050		ZWJ : '\u200d',
2051		// Left-to-right Mark
2052		LRM : '\u200e',
2053		// Right-to-left Mark
2054		RLM : '\u200f'
2055	};
2056
2057	// Default keyboard layouts
2058	$keyboard.builtLayouts = {};
2059	$keyboard.layouts = {
2060		'alpha' : {
2061			'normal': [
2062				'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2063				'{tab} a b c d e f g h i j [ ] \\',
2064				'k l m n o p q r s ; \' {enter}',
2065				'{shift} t u v w x y z , . / {shift}',
2066				'{accept} {space} {cancel}'
2067			],
2068			'shift': [
2069				'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2070				'{tab} A B C D E F G H I J { } |',
2071				'K L M N O P Q R S : " {enter}',
2072				'{shift} T U V W X Y Z < > ? {shift}',
2073				'{accept} {space} {cancel}'
2074			]
2075		},
2076		'qwerty' : {
2077			'normal': [
2078				'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2079				'{tab} q w e r t y u i o p [ ] \\',
2080				'a s d f g h j k l ; \' {enter}',
2081				'{shift} z x c v b n m , . / {shift}',
2082				'{accept} {space} {cancel}'
2083			],
2084			'shift': [
2085				'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2086				'{tab} Q W E R T Y U I O P { } |',
2087				'A S D F G H J K L : " {enter}',
2088				'{shift} Z X C V B N M < > ? {shift}',
2089				'{accept} {space} {cancel}'
2090			]
2091		},
2092		'international' : {
2093			'normal': [
2094				'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2095				'{tab} q w e r t y u i o p [ ] \\',
2096				'a s d f g h j k l ; \' {enter}',
2097				'{shift} z x c v b n m , . / {shift}',
2098				'{accept} {alt} {space} {alt} {cancel}'
2099			],
2100			'shift': [
2101				'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2102				'{tab} Q W E R T Y U I O P { } |',
2103				'A S D F G H J K L : " {enter}',
2104				'{shift} Z X C V B N M < > ? {shift}',
2105				'{accept} {alt} {space} {alt} {cancel}'
2106			],
2107			'alt': [
2108				'~ \u00a1 \u00b2 \u00b3 \u00a4 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00d7 {bksp}',
2109				'{tab} \u00e4 \u00e5 \u00e9 \u00ae \u00fe \u00fc \u00fa \u00ed \u00f3 \u00f6 \u00ab \u00bb \u00ac',
2110				'\u00e1 \u00df \u00f0 f g h j k \u00f8 \u00b6 \u00b4 {enter}',
2111				'{shift} \u00e6 x \u00a9 v b \u00f1 \u00b5 \u00e7 > \u00bf {shift}',
2112				'{accept} {alt} {space} {alt} {cancel}'
2113			],
2114			'alt-shift': [
2115				'~ \u00b9 \u00b2 \u00b3 \u00a3 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00f7 {bksp}',
2116				'{tab} \u00c4 \u00c5 \u00c9 \u00ae \u00de \u00dc \u00da \u00cd \u00d3 \u00d6 \u00ab \u00bb \u00a6',
2117				'\u00c4 \u00a7 \u00d0 F G H J K \u00d8 \u00b0 \u00a8 {enter}',
2118				'{shift} \u00c6 X \u00a2 V B \u00d1 \u00b5 \u00c7 . \u00bf {shift}',
2119				'{accept} {alt} {space} {alt} {cancel}'
2120			]
2121		},
2122		'colemak' : {
2123			'normal': [
2124				'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2125				'{tab} q w f p g j l u y ; [ ] \\',
2126				'{bksp} a r s t d h n e i o \' {enter}',
2127				'{shift} z x c v b k m , . / {shift}',
2128				'{accept} {space} {cancel}'
2129			],
2130			'shift' : [
2131				'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2132				'{tab} Q W F P G J L U Y : { } |',
2133				'{bksp} A R S T D H N E I O " {enter}',
2134				'{shift} Z X C V B K M < > ? {shift}',
2135				'{accept} {space} {cancel}'
2136			]
2137		},
2138		'dvorak' : {
2139			'normal': [
2140				'` 1 2 3 4 5 6 7 8 9 0 [ ] {bksp}',
2141				'{tab} \' , . p y f g c r l / = \\',
2142				'a o e u i d h t n s - {enter}',
2143				'{shift} ; q j k x b m w v z {shift}',
2144				'{accept} {space} {cancel}'
2145			],
2146			'shift' : [
2147				'~ ! @ # $ % ^ & * ( ) { } {bksp}',
2148				'{tab} " < > P Y F G C R L ? + |',
2149				'A O E U I D H T N S _ {enter}',
2150				'{shift} : Q J K X B M W V Z {shift}',
2151				'{accept} {space} {cancel}'
2152			]
2153		},
2154		'num' : {
2155			'normal' : [
2156				'= ( ) {b}',
2157				'{clear} / * -',
2158				'7 8 9 +',
2159				'4 5 6 {sign}',
2160				'1 2 3 %',
2161				'0 {dec} {a} {c}'
2162			]
2163		}
2164	};
2165
2166	$keyboard.language = $.extend({}, $keyboard.language, {
2167		en : {
2168			display : {
2169				// check mark - same action as accept
2170				'a'      : '\u2714:Accept (Shift+Enter)',
2171				'accept' : 'Accept:Accept (Shift+Enter)',
2172				// other alternatives \u2311
2173				'alt'    : 'Alt:\u2325 AltGr',
2174				// Left arrow (same as &larr;)
2175				'b'      : '\u232b:Backspace',
2176				'bksp'   : 'Bksp:Backspace',
2177				// big X, close - same action as cancel
2178				'c'      : '\u2716:Cancel (Esc)',
2179				'cancel' : 'Cancel:Cancel (Esc)',
2180				// clear num pad
2181				'clear'  : 'C:Clear',
2182				'combo'  : '\u00f6:Toggle Combo Keys',
2183				// decimal point for num pad (optional), change '.' to ',' for European format
2184				'dec'    : '.:Decimal',
2185				// down, then left arrow - enter symbol
2186				'e'      : '\u23ce:Enter',
2187				'empty'  : '\u00a0',
2188				'enter'  : 'Enter:Enter \u23ce',
2189				// left arrow (move caret)
2190				'left'   : '\u2190',
2191				// caps lock
2192				'lock'   : 'Lock:\u21ea Caps Lock',
2193				'next'   : 'Next \u21e8',
2194				'prev'   : '\u21e6 Prev',
2195				// right arrow (move caret)
2196				'right'  : '\u2192',
2197				// thick hollow up arrow
2198				's'      : '\u21e7:Shift',
2199				'shift'  : 'Shift:Shift',
2200				// +/- sign for num pad
2201				'sign'   : '\u00b1:Change Sign',
2202				'space'  : '\u00a0:Space',
2203				// right arrow to bar (used since this virtual keyboard works with one directional tabs)
2204				't'      : '\u21e5:Tab',
2205				// \u21b9 is the true tab symbol (left & right arrows)
2206				'tab'    : '\u21e5 Tab:Tab',
2207				// replaced by an image
2208				'toggle' : ' ',
2209
2210				// added to titles of keys
2211				// accept key status when acceptValid:true
2212				'valid'    : 'valid',
2213				'invalid'  : 'invalid',
2214				// combo key states
2215				'active'   : 'active',
2216				'disabled' : 'disabled'
2217			},
2218
2219			// Message added to the key title while hovering, if the mousewheel plugin exists
2220			wheelMessage : 'Use mousewheel to see other keys',
2221
2222			comboRegex : /([`\'~\^\"ao])([a-z])/mig,
2223			combos    : {
2224				// grave
2225				'`' : { a:'\u00e0', A:'\u00c0', e:'\u00e8', E:'\u00c8', i:'\u00ec', I:'\u00cc', o:'\u00f2', O:'\u00d2',
2226						u:'\u00f9', U:'\u00d9', y:'\u1ef3', Y:'\u1ef2' },
2227				// acute & cedilla
2228				"'" : { a:'\u00e1', A:'\u00c1', e:'\u00e9', E:'\u00c9', i:'\u00ed', I:'\u00cd', o:'\u00f3', O:'\u00d3',
2229						u:'\u00fa', U:'\u00da', y:'\u00fd', Y:'\u00dd' },
2230				// umlaut/trema
2231				'"' : { a:'\u00e4', A:'\u00c4', e:'\u00eb', E:'\u00cb', i:'\u00ef', I:'\u00cf', o:'\u00f6', O:'\u00d6',
2232						u:'\u00fc', U:'\u00dc', y:'\u00ff', Y:'\u0178' },
2233				// circumflex
2234				'^' : { a:'\u00e2', A:'\u00c2', e:'\u00ea', E:'\u00ca', i:'\u00ee', I:'\u00ce', o:'\u00f4', O:'\u00d4',
2235						u:'\u00fb', U:'\u00db', y:'\u0177', Y:'\u0176' },
2236				// tilde
2237				'~' : { a:'\u00e3', A:'\u00c3', e:'\u1ebd', E:'\u1ebc', i:'\u0129', I:'\u0128', o:'\u00f5', O:'\u00d5',
2238						u:'\u0169', U:'\u0168', y:'\u1ef9', Y:'\u1ef8', n:'\u00f1', N:'\u00d1' }
2239			}
2240		}
2241	});
2242
2243	$keyboard.defaultOptions = {
2244		// set this to ISO 639-1 language code to override language set by the layout
2245		// http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
2246		// language defaults to 'en' if not found
2247		language     : null,
2248		rtl          : false,
2249
2250		// *** choose layout & positioning ***
2251		layout       : 'qwerty',
2252		customLayout : null,
2253
2254		position     : {
2255			// optional - null (attach to input/textarea) or a jQuery object (attach elsewhere)
2256			of : null,
2257			my : 'center top',
2258			at : 'center top',
2259			// used when 'usePreview' is false (centers the keyboard at the bottom of the input/textarea)
2260			at2: 'center bottom'
2261		},
2262
2263		// allow jQuery position utility to reposition the keyboard on window resize
2264		reposition   : true,
2265
2266		// preview added above keyboard if true, original input/textarea used if false
2267		usePreview   : true,
2268
2269		// if true, the keyboard will always be visible
2270		alwaysOpen   : false,
2271
2272		// give the preview initial focus when the keyboard becomes visible
2273		initialFocus : true,
2274
2275		// avoid changing the focus (hardware keyboard probably won't work)
2276		noFocus      : false,
2277
2278		// if true, keyboard will remain open even if the input loses focus, but closes on escape
2279		// or when another keyboard opens.
2280		stayOpen     : false,
2281
2282		// if true, keyboard will not close if you press escape.
2283		ignoreEsc    : false,
2284
2285		css : {
2286			// input & preview
2287			input          : 'ui-widget-content ui-corner-all',
2288			// keyboard container
2289			container      : 'ui-widget-content ui-widget ui-corner-all ui-helper-clearfix',
2290			// keyboard container extra class (same as container, but separate)
2291			popup          : '',
2292			// default state
2293			buttonDefault  : 'ui-state-default ui-corner-all',
2294			// hovered button
2295			buttonHover    : 'ui-state-hover',
2296			// Action keys (e.g. Accept, Cancel, Tab, etc); this replaces 'actionClass' option
2297			buttonAction   : 'ui-state-active',
2298			// Active keys (e.g. shift down, meta keyset active, combo keys active)
2299			buttonActive   : 'ui-state-active',
2300			// used when disabling the decimal button {dec} when a decimal exists in the input area
2301			buttonDisabled : 'ui-state-disabled',
2302			buttonEmpty    : 'ui-keyboard-empty'
2303		},
2304
2305		// *** Useability ***
2306		// Auto-accept content when clicking outside the keyboard (popup will close)
2307		autoAccept      : false,
2308		// Auto-accept content even if the user presses escape (only works if `autoAccept` is `true`)
2309		autoAcceptOnEsc : false,
2310
2311		// Prevents direct input in the preview window when true
2312		lockInput    : false,
2313
2314		// Prevent keys not in the displayed keyboard from being typed in
2315		restrictInput : false,
2316		// Additional allowed characters while restrictInput is true
2317		restrictInclude : '', // e.g. 'a b foo \ud83d\ude38'
2318
2319		// Check input against validate function, if valid the accept button gets a class name of
2320		// 'ui-keyboard-valid-input'. If invalid, the accept button gets a class name of
2321		// 'ui-keyboard-invalid-input'
2322		acceptValid  : false,
2323
2324		// if acceptValid is true & the validate function returns a fal
2324se, this option will cancel
2325		// a keyboard close only after the accept button is pressed
2326		cancelClose  : true,
2327
2328		// tab to go to next, shift-tab for previous (default behavior)
2329		tabNavigation: false,
2330
2331		// enter for next input; shift+enter accepts content & goes to next
2332		// shift + 'enterMod' + enter ('enterMod' is the alt as set below) will accept content and go
2333		// to previous in a textarea
2334		enterNavigation : false,
2335		// mod key options: 'ctrlKey', 'shiftKey', 'altKey', 'metaKey' (MAC only)
2336		enterMod : 'altKey', // alt-enter to go to previous; shift-alt-enter to accept & go to previous
2337
2338		// if true, the next button will stop on the last keyboard input/textarea; prev button stops at first
2339		// if false, the next button will wrap to target the first input/textarea; prev will go to the last
2340		stopAtEnd : true,
2341
2342		// Set this to append the keyboard after the input/textarea (appended to the input/textarea parent).
2343		// This option works best when the input container doesn't have a set width & when the 'tabNavigation'
2344		// option is true.
2345		appendLocally: false,
2346		// When appendLocally is false, the keyboard will be appended to this object
2347		appendTo     : 'body',
2348
2349		// If false, the shift key will remain active until the next key is (mouse) clicked on; if true it will
2350		// stay active until pressed again
2351		stickyShift  : true,
2352
2353		// Prevent pasting content into the area
2354		preventPaste : false,
2355
2356		// caret placed at the end of any text when keyboard becomes visible
2357		caretToEnd   : false,
2358
2359		// caret stays this many pixels from the edge of the input while scrolling left/right;
2360		// use "c" or "center" to center the caret while scrolling
2361		scrollAdjustment : 10,
2362
2363		// Set the max number of characters allowed in the input, setting it to false disables this option
2364		maxLength    : false,
2365		// allow inserting characters @ caret when maxLength is set
2366		maxInsert    : true,
2367
2368		// Mouse repeat delay - when clicking/touching a virtual keyboard key, after this delay the key will
2369		// start repeating
2370		repeatDelay  : 500,
2371
2372		// Mouse repeat rate - after the repeatDelay, this is the rate (characters per second) at which the
2373		// key is repeated Added to simulate holding down a real keyboard key and having it repeat. I haven't
2374		// calculated the upper limit of this rate, but it is limited to how fast the javascript can process
2375		// the keys. And for me, in Firefox, it's around 20.
2376		repeatRate   : 20,
2377
2378		// resets the keyboard to the default keyset when visible
2379		resetDefault : true,
2380
2381		// Event (namespaced) on the input to reveal the keyboard. To disable it, just set it to ''.
2382		openOn       : 'focus',
2383
2384		// Event (namepaced) for when the character is added to the input (clicking on the keyboard)
2385		keyBinding   : 'mousedown touchstart',
2386
2387		// enable/disable mousewheel functionality
2388		// enabling still depends on the mousewheel plugin
2389		useWheel : true,
2390
2391		// combos (emulate dead keys : http://en.wikipedia.org/wiki/Keyboard_layout#US-International)
2392		// if user inputs `a the script converts it to à, ^o becomes ô, etc.
2393		useCombos : true,
2394
2395/*
2396		// *** Methods ***
2397		// commenting these out to reduce the size of the minified version
2398		// Callbacks - attach a function to any of these callbacks as desired
2399		initialized   : function(e, keyboard, el) {},
2400		beforeVisible : function(e, keyboard, el) {},
2401		visible       : function(e, keyboard, el) {},
2402		change        : function(e, keyboard, el) {},
2403		beforeClose   : function(e, keyboard, el, accepted) {},
2404		accepted      : function(e, keyboard, el) {},
2405		canceled      : function(e, keyboard, el) {},
2406		restricted    : function(e, keyboard, el) {},
2407		hidden        : function(e, keyboard, el) {},
2408		// called instead of base.switchInput
2409		switchInput   : function(keyboard, goToNext, isAccepted) {},
2410		// used if you want to create a custom layout or modify the built-in keyboard
2411		create        : function(keyboard) { return keyboard.buildKeyboard(); },
2412
2413		// build key callback
2414		buildKey : function( keyboard, data ) {
2415			/ *
2416			data = {
2417				// READ ONLY
2418				isAction : [boolean] true if key is an action key
2419				name     : [string]  key class name suffix ( prefix = 'ui-keyboard-' ); may include decimal ascii value of character
2420				value    : [string]  text inserted (non-action keys)
2421				title    : [string]  title attribute of key
2422				action   : [string]  keyaction name
2423				html     : [string]  HTML of the key; it includes a <span> wrapping the text
2424				// use to modify key HTML
2425				$key     : [object]  jQuery selector of key which is already appended to keyboard
2426			}
2427			* /
2428			return data;
2429		},
2430*/
2431
2432		// this callback is called, if the acceptValid is true, and just before the 'beforeClose' to check
2433		// the value if the value is valid, return true and the keyboard will continue as it should
2434		// (close if not always open, etc). If the value is not valid, return false and clear the keyboard
2435		// value ( like this "keyboard.$preview.val('');" ), if desired. The validate function is called after
2436		// each input, the 'isClosing' value will be false; when the accept button is clicked,
2437		// 'isClosing' is true
2438		validate : function( keyboard, value, isClosing ) { return true; }
2439
2440	};
2441
2442	// for checking combos
2443	$keyboard.comboRegex = /([`\'~\^\"ao])([a-z])/mig;
2444
2445	// store current keyboard element; used by base.isCurrent()
2446	$keyboard.currentKeyboard = '';
2447
2448	$('<!--[if lte IE 8]><script>jQuery("body").addClass("oldie");</script><![endif]--><!--[if IE]>' +
2449		'<script>jQuery("body").addClass("ie");</script><![endif]-->').appendTo('body').remove();
2450	$keyboard.msie = $('body').hasClass('oldie'); // Old IE flag, used for caret positioning
2451	$keyboard.allie = $('body').hasClass('ie');
2452
2453	$keyboard.watermark = (typeof(document.createElement('input').placeholder) !== 'undefined');
2454
2455	$keyboard.checkCaretSupport = function() {
2456		if ( typeof $keyboard.checkCaret !== 'boolean' ) {
2457			// Check if caret position is saved when input is hidden or loses focus
2458			// (*cough* all versions of IE and I think Opera has/had an issue as well
2459			var $temp = $('<div style="height:0px;width:0px;overflow:hidden;"><input type="text" value="testing"/></div>')
2460				.prependTo( 'body' ); // stop page scrolling
2461			$keyboard.caret( $temp.find('input'), 3, 3 );
2462			// Also save caret position of the input if it is locked
2463			$keyboard.checkCaret = $keyboard.caret( $temp.find('input').hide().show() ).start !== 3;
2464			$temp.remove();
2465		}
2466		return $keyboard.checkCaret;
2467	};
2468
2469	$keyboard.caret = function($el, param1, param2) {
2470		if ( !$el.length || $el.is(':hidden') || $el.css('visibility') === 'hidden' ) {
2471			return {};
2472		}
2473		var start, end, txt, pos,
2474			kb = $el.data( 'keyboard' ),
2475			noFocus = kb && kb.options.noFocus;
2476		if (!noFocus) { $el.focus(); }
2477		// set caret position
2478		if (typeof param1 !== 'undefined') {
2479			// allow setting caret using ( $el, { start: x, end: y } )
2480			if (typeof param1 === 'object' && 'start' in param1 && 'end' in param1) {
2481				start = param1.start;
2482				end = param1.end;
2483			} else if (typeof param2 === 'undefined') {
2484				param2 = param1; // set caret using start position
2485			}
2486			// set caret using ( $el, start, end );
2487			if (typeof param1 === 'number' && typeof param2 === 'number') {
2488				start = param1;
2489				end = param2;
2490			} else if ( param1 === 'start' ) {
2491				start = end = 0;
2492			} else if ( typeof param1 === 'string' ) {
2493				// unknown string setting, move caret to end
2494				start = end = $el.val().length;
2495			}
2496
2497			// *** SET CARET POSITION ***
2498			// modify the line below to adapt to other caret plugins
2499			return $el.caret( start, end, noFocus );
2500		}
2501		// *** GET CARET POSITION ***
2502		// modify the line below to adapt to other caret plugins
2503		pos = $el.caret();
2504		start = pos.start;
2505		end = pos.end;
2506
2507		// *** utilities ***
2508		txt = ($el[0].value || $el.text() || '');
2509		return {
2510			start : start,
2511			end : end,
2512			// return selected text
2513			text : txt.substring( start, end ),
2514			// return a replace selected string method
2515			replaceStr : function( str ) {
2516				return txt.substring( 0, start ) + str + txt.substring( end, txt.length );
2517			}
2518		};
2519	};
2520
2521	$.fn.keyboard = function(options){
2522		return this.each(function(){
2523			if (!$(this).data('keyboard')) {
2524				/*jshint nonew:false */
2525				(new $.keyboard(this, options));
2526			}
2527		});
2528	};
2529
2530	$.fn.getkeyboard = function(){
2531		return this.data('keyboard');
2532	};
2533
2534/* Copyright (c) 2010 C. F., Wong (<a href="http://cloudgen.w0ng.hk">Cloudgen Examplet Store</a>)
2535 * Licensed under the MIT License:
2536 * http://www.opensource.org/licenses/mit-license.php
2537 * Highly modified from the original
2538 */
2539
2540$.fn.caret = function( start, end, noFocus ) {
2541	if ( typeof this[0] === 'undefined' || this.is(':hidden') || this.css('visibility') === 'hidden' ) {
2542		return this;
2543	}
2544	var selRange, range, stored_range, txt, val,
2545		selection = document.selection,
2546		$el = this,
2547		el = $el[0],
2548		sTop = el.scrollTop,
2549		ss = false,
2550		supportCaret = true;
2551	try {
2552		ss = 'selectionStart' in el;
2553	} catch(err){
2554		supportCaret = false;
2555	}
2556	if (supportCaret && typeof start !== 'undefined') {
2557		if (!/(email|number)/i.test(el.type)) {
2558			if (ss){
2559				el.selectionStart = start;
2560				el.selectionEnd = end;
2561			} else {
2562				selRange = el.createTextRange();
2563				selRange.collapse(true);
2564				selRange.moveStart('character', start);
2565				selRange.moveEnd('character', end - start);
2566				selRange.select();
2567			}
2568		}
2569		// must be visible or IE8 crashes; IE9 in compatibility mode works fine - issue #56
2570		if ( !noFocus && ($el.is(':visible') || $el.css('visibility') !== 'hidden') ) { el.focus(); }
2571		el.scrollTop = sTop;
2572		return this;
2573	} else {
2574		if (/(email|number)/i.test(el.type)) {
2575			// fix suggested by raduanastase (https://github.com/Mottie/Keyboard/issues/105#issuecomment-40456535)
2576			start = end = $el.val().length;
2577		} else if (ss) {
2578			start = el.selectionStart;
2579			end = el.selectionEnd;
2580		} else if (selection) {
2581			if (el.nodeName === 'TEXTAREA') {
2582				val = $el.val();
2583				range = selection.createRange();
2584				stored_range = range.duplicate();
2585				stored_range.moveToElementText(el);
2586				stored_range.setEndPoint('EndToEnd', range);
2587				// thanks to the awesome comments in the rangy plugin
2588				start = stored_range.text.replace(/\r/g, '\n').length;
2589				end = start + range.text.replace(/\r/g, '\n').length;
2590			} else {
2591				val = $el.val().replace(/\r/g, '\n');
2592				range = selection.createRange().duplicate();
2593				range.moveEnd('character', val.length);
2594				start = (range.text === '' ? val.length : val.lastIndexOf(range.text));
2595				range = selection.createRange().duplicate();
2596				range.moveStart('character', -val.length);
2597				end = range.text.length;
2598			}
2599		} else {
2600			// caret positioning not supported
2601			start = end = (el.value || '').length;
2602		}
2603		txt = (el.value || '');
2604		return {
2605			start : start,
2606			end : end,
2607			text : txt.substring( start, end ),
2608			replace : function(str) {
2609				return txt.substring( 0, start ) + str + txt.substring( end, txt.length );
2610			}
2611		};
2612	}
2613};
2614
2615return $keyboard;
2616
2617}));

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.