197 lines
7.6 KiB
JavaScript
197 lines
7.6 KiB
JavaScript
/*! jQuery UI Virtual Keyboard Virtual Caret v1.1.3 (beta) *//*
|
|
* for Keyboard v1.18+ only (9/24/2015)
|
|
* modified from https://github.com/component/textarea-caret-position
|
|
*
|
|
* By Rob Garrison (aka Mottie)
|
|
* Licensed under the MIT License
|
|
*
|
|
* CSS changes
|
|
* NOTE: caret margin-top => is added to the caret height (top & bottom)
|
|
* .ui-keyboard-preview-wrapper { position: relative; overflow: hidden; }
|
|
* .ui-keyboard-caret { background: red; width: 1px; margin-top: 3px; }
|
|
*/
|
|
/*jshint browser:true, jquery:true, unused:false */
|
|
/*global require:false, define:false, module:false */
|
|
;( function( factory ) {
|
|
if ( typeof define === 'function' && define.amd ) {
|
|
define( [ 'jquery' ], factory );
|
|
} else if ( typeof module === 'object' && typeof module.exports === 'object' ) {
|
|
module.exports = factory( require( 'jquery' ) );
|
|
} else {
|
|
factory( jQuery );
|
|
}
|
|
}( function( $ ) {
|
|
'use strict';
|
|
var $keyboard = $.keyboard;
|
|
|
|
$keyboard.firefox = typeof window.mozInnerScreenX !== 'undefined';
|
|
|
|
$.extend( $keyboard.css, {
|
|
caret : 'ui-keyboard-caret'
|
|
});
|
|
|
|
$.fn.addCaret = function( options ) {
|
|
var defaults = {
|
|
caretClass : '',
|
|
// *** for future use ***
|
|
// data-attribute containing the character(s) next to the caret
|
|
charAttr : 'data-character',
|
|
// # character(s) next to the caret (can be negative for RTL)
|
|
charIndex : 1,
|
|
offsetX : 0,
|
|
offsetY : 0,
|
|
adjustHt : 0
|
|
};
|
|
return this.each( function() {
|
|
// make sure a keyboard is attached
|
|
var o, namespace,
|
|
kbevents = $keyboard.events,
|
|
base = $( this ).data( 'keyboard' );
|
|
if ( !base ) { return; }
|
|
|
|
// variables
|
|
o = base.caret_options = $.extend( {}, defaults, options );
|
|
namespace = base.caret_namespace = base.namespace + 'caret';
|
|
base.extensionNamespace.push( namespace );
|
|
|
|
// modified from https://github.com/component/textarea-caret-position
|
|
// The properties that we copy into a mirrored div.
|
|
// Note that some browsers, such as Firefox,
|
|
// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
|
|
// so we have to do every single property specifically.
|
|
base.textareaCaretProperties = [
|
|
'direction', 'boxSizing', 'width', 'height', 'overflowX', 'overflowY',
|
|
'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth', 'borderStyle',
|
|
'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
|
|
'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontSizeAdjust',
|
|
'lineHeight', 'fontFamily', 'textAlign', 'textTransform', 'textIndent', 'textDecoration',
|
|
'letterSpacing', 'wordSpacing', 'tabSize', 'MozTabSize'
|
|
];
|
|
|
|
base.caret_setup = function() {
|
|
var events = 'keyup keypress mouseup mouseleave '.split( ' ' ).join( namespace + ' ' ),
|
|
style = 'position:absolute;visibility:hidden;top:-9999em;left:-9999em;' +
|
|
'white-space:pre-wrap;' +
|
|
( base.preview.nodeName === 'INPUT' ? '' : 'word-wrap:break-word;' );
|
|
// add mirrored div
|
|
base.caret_$div = $( '<div class="ui-keyboard-mirror-div" style="' + style + '">' )
|
|
.appendTo( base.$keyboard );
|
|
|
|
// remove caret, just-in-case
|
|
if (base.$caret) { base.$caret.remove(); }
|
|
base.$caret = $( '<div class="ui-keyboard-caret ' + o.caretClass + '" style="position:absolute;">' )
|
|
.insertAfter( base.$preview );
|
|
|
|
base.$el
|
|
.unbind( kbevents.kbChange + namespace )
|
|
.bind( kbevents.kbChange + namespace, function() {
|
|
base.findCaretPos();
|
|
});
|
|
base.$preview
|
|
.unbind( events )
|
|
.bind( events, function() {
|
|
base.findCaretPos();
|
|
});
|
|
};
|
|
|
|
// getCaretCoordinatesFn = function (element, position, recalculate) {
|
|
base.findCaretPos = function() {
|
|
if ( !base.caret_$div ) { return; }
|
|
var style, computed, margin, pos, position, txt, span, offset,
|
|
element = base.preview,
|
|
isInput = element.nodeName === 'INPUT',
|
|
div = base.caret_$div[0];
|
|
|
|
style = div.style;
|
|
// getComputedStyle with null - fixes #384
|
|
computed = window.getComputedStyle ? getComputedStyle( element, null ) : element.currentStyle;
|
|
// get caret position based on text-direction
|
|
pos = $keyboard.caret( base.$preview );
|
|
position = Math[ computed.direction === 'ltr' ? 'max' : 'min' ]( pos.start, pos.end );
|
|
|
|
// transfer the element's properties to the div
|
|
base.textareaCaretProperties.forEach(function ( prop ) {
|
|
style[ prop ] = computed[ prop ];
|
|
});
|
|
|
|
if ( $keyboard.firefox ) {
|
|
// Firefox adds 2 pixels to the padding - https://bugzilla.mozilla.org/show_bug.cgi?id=753662
|
|
style.width = parseInt( computed.width, 10 ) - 2 + 'px';
|
|
// Firefox lies about the overflow property for textareas:
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=984275
|
|
if ( element.scrollHeight > parseInt( computed.height, 10 ) ) {
|
|
style.overflowY = 'scroll';
|
|
}
|
|
} else {
|
|
// for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
|
|
// style.overflow = 'hidden';
|
|
style.width = parseInt( isInput ? element.scrollWidth : computed.width, 10 ) +
|
|
// add 50 extra px if it's an input to prevent wrap
|
|
( isInput ? 50 : 0 ) + 'px';
|
|
}
|
|
|
|
div.textContent = element.value.substring( 0, position );
|
|
// the second special handling for input type="text" vs textarea:
|
|
// spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
|
|
if ( element.nodeName === 'INPUT' ) {
|
|
div.textContent = div.textContent.replace( /\x20/g, '\xa0' );
|
|
}
|
|
|
|
span = document.createElement( 'span' );
|
|
// Wrapping must be replicated *exactly*, including when a long word gets
|
|
// onto the next line, with whitespace at the end of the line before (#7).
|
|
// The *only* reliable way to do that is to copy the *entire* rest of the
|
|
// textarea's content into the <span> created at the caret position.
|
|
// for inputs, just '.' would be enough, but why bother?
|
|
// || because a completely empty faux span doesn't render at all
|
|
span.textContent = element.value.substring( position ) || '.';
|
|
div.appendChild( span );
|
|
|
|
offset = $(span).position();
|
|
base.caretPos = {
|
|
top: offset.top + parseInt( computed.borderTopWidth, 10 ) + o.offsetY,
|
|
left: offset.left + parseInt( computed.borderLeftWidth, 10 ) + o.offsetX
|
|
};
|
|
|
|
// make caret height = font-size + any margin-top x2 added by the css
|
|
margin = parseInt( base.$caret.css( 'margin-top' ), 10 );
|
|
style = Math.round( parseFloat( base.$preview.css( 'font-size' ) ) + margin * 2 ) + o.adjustHt;
|
|
offset = base.$preview.position();
|
|
|
|
base.$caret.css({
|
|
top: offset.top - element.scrollTop + base.caretPos.top - margin,
|
|
left: offset.left - element.scrollLeft + base.caretPos.left,
|
|
height: style
|
|
});
|
|
txt = element.value.substring( position, position + o.charIndex ).replace(/\s/, '\xa0' ) || '\xa0';
|
|
base.$caret.attr( o.charAttr, txt );
|
|
};
|
|
|
|
// setup caret when keyboard is visible
|
|
base.$el
|
|
.unbind( namespace )
|
|
.bind( kbevents.kbBeforeVisible + namespace, function() {
|
|
base.caret_setup();
|
|
})
|
|
.bind( kbevents.kbVisible + namespace, function() {
|
|
base.findCaretPos();
|
|
})
|
|
.bind( kbevents.kbHidden + namespace, function() {
|
|
// unbind events in case usePreview: false; see #376
|
|
var events = 'keyup keypress mouseup mouseleave '.split( ' ' ).join( namespace + ' ' );
|
|
base.$preview.unbind( events );
|
|
base.$caret.remove();
|
|
base.$caret = null;
|
|
base.caret_$div = null;
|
|
});
|
|
|
|
// visible event is fired before this extension is initialized, so check!
|
|
if ( base.options.alwaysOpen && base.isVisible() ) {
|
|
base.caret_setup();
|
|
base.findCaretPos();
|
|
}
|
|
|
|
});
|
|
};
|
|
|
|
}));
|