Update jquery.ime

Change-Id: Ibcf7a4f076ceb86b91e81310bcb2fa64abc551dd
This commit is contained in:
Ed Sanders
2020-07-15 18:25:57 +01:00
committed by jenkins-bot
parent 275603c277
commit a628b1c705
2 changed files with 61 additions and 138 deletions

View File

@@ -1,4 +1,4 @@
/*! jquery.ime - v0.2.0+20200614 /*! jquery.ime - v0.2.0+20200724
* https://github.com/wikimedia/jquery.ime * https://github.com/wikimedia/jquery.ime
* Copyright (c) 2020 Santhosh Thottingal; License: (GPL-2.0-or-later OR MIT) */ * Copyright (c) 2020 Santhosh Thottingal; License: (GPL-2.0-or-later OR MIT) */
( function ( $ ) { ( function ( $ ) {
@@ -7,11 +7,9 @@
var TextEntryFactory, TextEntry, FormWidgetEntry, ContentEditableEntry, var TextEntryFactory, TextEntry, FormWidgetEntry, ContentEditableEntry,
defaultInputMethod; defaultInputMethod;
// rangy is defined in the rangy library
/* global rangy */
/** /**
* private function for debugging * private function for debugging
*
* @param {jQuery} [$obj] * @param {jQuery} [$obj]
*/ */
function debug( $obj ) { function debug( $obj ) {
@@ -48,7 +46,7 @@
/** /**
* IME Class * IME Class
* *
* @class * @class IME
* @constructor * @constructor
* @param {HTMLElement} element Element on which to listen for events * @param {HTMLElement} element Element on which to listen for events
* @param {TextEntry} textEntry Text entry object to use to get/set text * @param {TextEntry} textEntry Text entry object to use to get/set text
@@ -75,6 +73,10 @@
this.language = null; this.language = null;
this.context = ''; this.context = '';
if ( this.options.showSelector ) { if ( this.options.showSelector ) {
this.options.selectorInside = options.selectorInside !== undefined ?
options.selectorInside :
// eslint-disable-next-line no-jquery/no-class-state
this.$element.hasClass( 'ime-position-inside' );
this.selector = this.$element.imeselector( this.options ); this.selector = this.$element.imeselector( this.options );
} }
this.listen(); this.listen();
@@ -405,7 +407,7 @@
/** /**
* TextEntry factory * TextEntry factory
* *
* @class * @class TextEntryFactory
* @constructor * @constructor
*/ */
TextEntryFactory = function IMETextEntryFactory() { TextEntryFactory = function IMETextEntryFactory() {
@@ -431,17 +433,21 @@
* Wrap an editable element with the appropriate TextEntry class * Wrap an editable element with the appropriate TextEntry class
* *
* @param {jQuery} $element The element to wrap * @param {jQuery} $element The element to wrap
* @return {TextEntry|undefined} A TextEntry, or undefined if no match * @return {TextEntry|null} A TextEntry, or null if no match
*/ */
TextEntryFactory.prototype.wrap = function ( $element ) { TextEntryFactory.prototype.wrap = function ( $element ) {
var i, len, TextEntryClass; var i, len, TextEntryClass;
// eslint-disable-next-line no-jquery/no-class-state
if ( $element.hasClass( 'noime' ) ) {
return null;
}
for ( i = 0, len = this.TextEntryClasses.length; i < len; i++ ) { for ( i = 0, len = this.TextEntryClasses.length; i < len; i++ ) {
TextEntryClass = this.TextEntryClasses[ i ]; TextEntryClass = this.TextEntryClasses[ i ];
if ( TextEntryClass.static.canWrap( $element ) ) { if ( TextEntryClass.static.canWrap( $element ) ) {
return new TextEntryClass( $element ); return new TextEntryClass( $element );
} }
} }
return undefined; return null;
}; };
/* Initialization */ /* Initialization */
@@ -451,7 +457,7 @@
/** /**
* Generic text entry * Generic text entry
* *
* @class * @class TextEntry
* @abstract * @abstract
*/ */
TextEntry = function IMETextEntry() { TextEntry = function IMETextEntry() {
@@ -496,7 +502,7 @@
/** /**
* TextEntry class for input/textarea widgets * TextEntry class for input/textarea widgets
* *
* @class * @class FormWidgetEntry
* @constructor * @constructor
* @param {jQuery} $element The element to wrap * @param {jQuery} $element The element to wrap
*/ */
@@ -516,9 +522,7 @@
FormWidgetEntry.static.canWrap = function ( $element ) { FormWidgetEntry.static.canWrap = function ( $element ) {
return $element.is( 'input:not([type]), input[type=text], input[type=search], textarea' ) && return $element.is( 'input:not([type]), input[type=text], input[type=search], textarea' ) &&
!$element.prop( 'readonly' ) && !$element.prop( 'readonly' ) &&
!$element.prop( 'disabled' ) && !$element.prop( 'disabled' );
// eslint-disable-next-line no-jquery/no-class-state
!$element.hasClass( 'noime' );
}; };
/* Instance methods */ /* Instance methods */
@@ -527,10 +531,10 @@
* @inheritdoc TextEntry * @inheritdoc TextEntry
*/ */
FormWidgetEntry.prototype.getTextBeforeSelection = function ( maxLength ) { FormWidgetEntry.prototype.getTextBeforeSelection = function ( maxLength ) {
var pos = this.getCaretPosition(); var element = this.$element.get( 0 );
return this.$element.val().substring( return this.$element.val().substring(
Math.max( 0, pos.start - maxLength ), Math.max( 0, element.selectionStart - maxLength ),
pos.start element.selectionStart
); );
}; };
@@ -538,112 +542,24 @@
* @inheritdoc TextEntry * @inheritdoc TextEntry
*/ */
FormWidgetEntry.prototype.replaceTextAtSelection = function ( precedingCharCount, newText ) { FormWidgetEntry.prototype.replaceTextAtSelection = function ( precedingCharCount, newText ) {
var selection, var element = this.$element.get( 0 ),
length, start = element.selectionStart,
newLines,
start,
scrollTop,
pos,
element = this.$element.get( 0 );
if ( typeof element.selectionStart === 'number' && typeof element.selectionEnd === 'number' ) {
// IE9+ and all other browsers
start = element.selectionStart;
scrollTop = element.scrollTop; scrollTop = element.scrollTop;
// Replace the whole text of the text area: // Replace the whole text of the text area:
// text before + newText + text after. // text before + newText + text after.
// This could be made better if range selection worked on browsers. // This could be made better if range selection worked on browsers.
// But for complex scripts, browsers place cursor in unexpected places // But for complex scripts, browsers place cursor in unexpected places
// and it's not possible to fix cursor programmatically. // and it's not possible to fix cursor programmatically.
// Ref Bug https://bugs.webkit.org/show_bug.cgi?id=66630 // Ref Bug https://bugs.webkit.org/show_bug.cgi?id=66630
element.value = element.value.substring( 0, start - precedingCharCount ) + element.value = element.value.substring( 0, start - precedingCharCount ) +
newText + newText +
element.value.substring( element.selectionEnd, element.value.length ); element.value.substring( element.selectionEnd, element.value.length );
// restore scroll // restore scroll
element.scrollTop = scrollTop; element.scrollTop = scrollTop;
// set selection // set selection
element.selectionStart = element.selectionEnd = start - precedingCharCount + newText.length; element.selectionStart = element.selectionEnd = start - precedingCharCount + newText.length;
} else {
// IE8 and lower
pos = this.getCaretPosition();
selection = element.createTextRange();
length = element.value.length;
// IE doesn't count \n when computing the offset, so we won't either
newLines = element.value.match( /\n/g );
if ( newLines ) {
length = length - newLines.length;
}
selection.moveStart( 'character', pos.start - precedingCharCount );
selection.moveEnd( 'character', pos.end - length );
selection.text = newText;
selection.collapse( false );
selection.select();
}
};
/**
* Get the current selection offsets inside the widget
*
* @return {Object} return Offsets in chars (0 means first offset *or* no selection in widget)
* @return {number} return.start Selection start
* @return {number} return.end Selection end
*/
FormWidgetEntry.prototype.getCaretPosition = function () {
var el = this.$element.get( 0 ),
start = 0,
end = 0,
normalizedValue,
range,
textInputRange,
len,
newLines,
endRange;
if ( typeof el.selectionStart === 'number' && typeof el.selectionEnd === 'number' ) {
start = el.selectionStart;
end = el.selectionEnd;
} else {
// IE
range = document.selection.createRange();
if ( range && range.parentElement() === el ) {
len = el.value.length;
normalizedValue = el.value.replace( /\r\n/g, '\n' );
newLines = normalizedValue.match( /\n/g );
// Create a working TextRange that lives only in the input
textInputRange = el.createTextRange();
textInputRange.moveToBookmark( range.getBookmark() );
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
endRange = el.createTextRange();
endRange.collapse( false );
if ( textInputRange.compareEndPoints( 'StartToEnd', endRange ) > -1 ) {
if ( newLines ) {
start = end = len - newLines.length;
} else {
start = end = len;
}
} else {
start = -textInputRange.moveStart( 'character', -len );
if ( textInputRange.compareEndPoints( 'EndToEnd', endRange ) > -1 ) {
end = len;
} else {
end = -textInputRange.moveEnd( 'character', -len );
}
}
}
}
return { start: start, end: end };
}; };
TextEntryFactory.static.singleton.register( FormWidgetEntry ); TextEntryFactory.static.singleton.register( FormWidgetEntry );
@@ -651,7 +567,7 @@
/** /**
* TextEntry class for ContentEditable * TextEntry class for ContentEditable
* *
* @class * @class ContentEditableEntry
* @constructor * @constructor
* @param {jQuery} $element The element to wrap * @param {jQuery} $element The element to wrap
*/ */
@@ -669,8 +585,7 @@
* @inheritdoc TextEntry * @inheritdoc TextEntry
*/ */
ContentEditableEntry.static.canWrap = function ( $element ) { ContentEditableEntry.static.canWrap = function ( $element ) {
// eslint-disable-next-line no-jquery/no-class-state return $element.is( '[contenteditable]' );
return $element.is( '[contenteditable]' ) && !$element.hasClass( 'noime' );
}; };
/* Instance methods */ /* Instance methods */
@@ -693,9 +608,11 @@
* @inheritdoc SelectionWrapper * @inheritdoc SelectionWrapper
*/ */
ContentEditableEntry.prototype.replaceTextAtSelection = function ( precedingCharCount, newText ) { ContentEditableEntry.prototype.replaceTextAtSelection = function ( precedingCharCount, newText ) {
var range, textNode, textOffset, newOffset, newRange; var textNode, textOffset, newOffset, newRange,
sel = window.getSelection(),
range = this.getSelectedRange();
if ( !this.getSelectedRange() ) { if ( !range ) {
return; return;
} }
@@ -705,12 +622,11 @@
// browsers that do not support it. // browsers that do not support it.
this.$element.trigger( 'compositionstart' ); this.$element.trigger( 'compositionstart' );
range = this.getSelectedRange();
if ( !range.collapsed ) { if ( !range.collapsed ) {
range.deleteContents(); range.deleteContents();
} }
newRange = document.createRange();
if ( range.startContainer.nodeType === Node.TEXT_NODE ) { if ( range.startContainer.nodeType === Node.TEXT_NODE ) {
// Alter this text node's content and move the cursor // Alter this text node's content and move the cursor
textNode = range.startContainer; textNode = range.startContainer;
@@ -720,10 +636,8 @@
newText + newText +
textNode.nodeValue.substr( textOffset ); textNode.nodeValue.substr( textOffset );
newOffset = textOffset - precedingCharCount + newText.length; newOffset = textOffset - precedingCharCount + newText.length;
newRange = rangy.createRange();
newRange.setStart( range.startContainer, newOffset ); newRange.setStart( range.startContainer, newOffset );
newRange.setEnd( range.startContainer, newOffset ); newRange.setEnd( range.startContainer, newOffset );
rangy.getSelection().setSingleRange( newRange );
} else { } else {
// XXX assert precedingCharCount === 0 // XXX assert precedingCharCount === 0
// Insert a new text node with the new text // Insert a new text node with the new text
@@ -732,11 +646,11 @@
textNode, textNode,
range.startContainer.childNodes[ range.startOffset ] range.startContainer.childNodes[ range.startOffset ]
); );
newRange = rangy.createRange();
newRange.setStart( textNode, textNode.length ); newRange.setStart( textNode, textNode.length );
newRange.setEnd( textNode, textNode.length ); newRange.setEnd( textNode, textNode.length );
rangy.getSelection().setSingleRange( newRange );
} }
sel.removeAllRanges();
sel.addRange( newRange );
// Trigger any externally registered jQuery compositionend / input event listeners. // Trigger any externally registered jQuery compositionend / input event listeners.
// TODO: Try node.dispatchEvent( new CompositionEvent(...) ) so listeners not // TODO: Try node.dispatchEvent( new CompositionEvent(...) ) so listeners not
@@ -752,9 +666,9 @@
* @return {Range|null} The selection range * @return {Range|null} The selection range
*/ */
ContentEditableEntry.prototype.getSelectedRange = function () { ContentEditableEntry.prototype.getSelectedRange = function () {
var sel, range; var range,
rangy.init(); sel = window.getSelection();
sel = rangy.getSelection();
if ( sel.rangeCount === 0 ) { if ( sel.rangeCount === 0 ) {
return null; return null;
} }
@@ -784,7 +698,7 @@
data = $this.data( 'ime' ); data = $this.data( 'ime' );
if ( !data ) { if ( !data ) {
textEntry = TextEntryFactory.static.singleton.wrap( $this ); textEntry = TextEntryFactory.static.singleton.wrap( $this );
if ( textEntry === undefined ) { if ( !textEntry ) {
return; return;
} }
data = new IME( this, textEntry, options ); data = new IME( this, textEntry, options );
@@ -877,7 +791,8 @@
$.ime.defaults = { $.ime.defaults = {
languages: [], // Languages to be used- by default all languages languages: [], // Languages to be used- by default all languages
helpHandler: null, // Called for each ime option in the menu helpHandler: null, // Called for each ime option in the menu
showSelector: true showSelector: true,
selectorInside: undefined // If not set will check if '.ime-position-inside' class is preset
}; };
}( jQuery ) ); }( jQuery ) );
@@ -1144,7 +1059,7 @@
/** /**
* Keydown event handler. Handles shortcut key presses * Keydown event handler. Handles shortcut key presses
* *
* @context {HTMLElement} * @this HTMLElement
* @param {jQuery.Event} e * @param {jQuery.Event} e
* @return {boolean} * @return {boolean}
*/ */
@@ -1215,6 +1130,10 @@
this.$imeSetting.outerWidth(); this.$imeSetting.outerWidth();
} }
if ( this.options.selectorInside ) {
top -= this.$imeSetting.outerHeight();
}
// While determining whether to place the selector above or below the input box, // While determining whether to place the selector above or below the input box,
// take into account the value of scrollTop, to avoid the selector from always // take into account the value of scrollTop, to avoid the selector from always
// getting placed above the input box since window.height would be less than top // getting placed above the input box since window.height would be less than top
@@ -1223,6 +1142,9 @@
if ( verticalRoom < this.$imeSetting.outerHeight() ) { if ( verticalRoom < this.$imeSetting.outerHeight() ) {
top = elementPosition.top - this.$imeSetting.outerHeight(); top = elementPosition.top - this.$imeSetting.outerHeight();
if ( this.options.selectorInside ) {
top += this.$imeSetting.outerHeight();
}
menuTop = this.$menu.outerHeight() + menuTop = this.$menu.outerHeight() +
this.$imeSetting.outerHeight(); this.$imeSetting.outerHeight();
@@ -1278,7 +1200,7 @@
* Select a language * Select a language
* *
* @param {string} languageCode * @param {string} languageCode
* @return {string|bool} Selected input method id or false * @return {string|boolean} Selected input method id or false
*/ */
selectLanguage: function ( languageCode ) { selectLanguage: function ( languageCode ) {
var ime, imePref, language; var ime, imePref, language;
@@ -1341,6 +1263,7 @@
/** /**
* Decide on initial language to select * Decide on initial language to select
*
* @return {string} * @return {string}
*/ */
decideLanguage: function () { decideLanguage: function () {

View File

@@ -70,7 +70,7 @@
[ 'q', '\u0B4C' ], [ 'q', '\u0B4C' ],
[ 'd', '\u0B4D' ], [ 'd', '\u0B4D' ],
[ '/', '\u0B5F' ], [ '/', '\u0B5F' ],
[ '\\>', '\u0B64' ], [ '\\>', '\u0964' ],
[ '0', '\u0B66' ], [ '0', '\u0B66' ],
[ '1', '\u0B67' ], [ '1', '\u0B67' ],
[ '2', '\u0B68' ], [ '2', '\u0B68' ],