*/
( function ( $ ) {
+ var eventKeys = [
+ 'keyup.byteLimit',
+ 'keydown.byteLimit',
+ 'change.byteLimit',
+ 'mouseup.byteLimit',
+ 'cut.byteLimit',
+ 'paste.byteLimit',
+ 'focus.byteLimit',
+ 'blur.byteLimit'
+ ].join( ' ' );
+
+ // Like String#charAt, but return the pair of UTF-16 surrogates for characters outside of BMP.
+ function codePointAt( string, offset, backwards ) {
+ // We don't need to check for offsets at the beginning or end of string,
+ // String#slice will simply return a shorter (or empty) substring.
+ var maybePair = backwards ?
+ string.slice( offset - 1, offset + 1 ) :
+ string.slice( offset, offset + 2 );
+ if ( /^[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test( maybePair ) ) {
+ return maybePair;
+ } else {
+ return string.charAt( offset );
+ }
+ }
+
/**
* Utility function to trim down a string, based on byteLimit
* and given a safe start position. It supports insertion anywhere
* @return {boolean} return.trimmed
*/
$.trimByteLength = function ( safeVal, newVal, byteLimit, fn ) {
- var startMatches, endMatches, matchesLen, inpParts,
+ var startMatches, endMatches, matchesLen, inpParts, chopOff, oldChar, newChar,
oldVal = safeVal;
// Run the hook if one was provided, but only on the length
// Count same characters from the left, first.
// (if "foo" -> "foofoo", assume addition was at the end).
- while (
- startMatches < matchesLen &&
- oldVal.charAt( startMatches ) === newVal.charAt( startMatches )
- ) {
- startMatches += 1;
+ while ( startMatches < matchesLen ) {
+ oldChar = codePointAt( oldVal, startMatches, false );
+ newChar = codePointAt( newVal, startMatches, false );
+ if ( oldChar !== newChar ) {
+ break;
+ }
+ startMatches += oldChar.length;
}
- while (
- endMatches < ( matchesLen - startMatches ) &&
- oldVal.charAt( oldVal.length - 1 - endMatches ) === newVal.charAt( newVal.length - 1 - endMatches )
- ) {
- endMatches += 1;
+ while ( endMatches < ( matchesLen - startMatches ) ) {
+ oldChar = codePointAt( oldVal, oldVal.length - 1 - endMatches, true );
+ newChar = codePointAt( newVal, newVal.length - 1 - endMatches, true );
+ if ( oldChar !== newChar ) {
+ break;
+ }
+ endMatches += oldChar.length;
}
inpParts = [
// Chop off characters from the end of the "inserted content" string
// until the limit is statisfied.
if ( fn ) {
- // stop, when there is nothing to slice - bug 41450
+ // stop, when there is nothing to slice - T43450
while ( $.byteLength( fn( inpParts.join( '' ) ) ) > byteLimit && inpParts[ 1 ].length > 0 ) {
- inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -1 );
+ // Do not chop off halves of surrogate pairs
+ chopOff = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test( inpParts[ 1 ] ) ? 2 : 1;
+ inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -chopOff );
}
} else {
while ( $.byteLength( inpParts.join( '' ) ) > byteLimit ) {
- inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -1 );
+ // Do not chop off halves of surrogate pairs
+ chopOff = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test( inpParts[ 1 ] ) ? 2 : 1;
+ inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -chopOff );
}
}
};
};
- var eventKeys = [
- 'keyup.byteLimit',
- 'keydown.byteLimit',
- 'change.byteLimit',
- 'mouseup.byteLimit',
- 'cut.byteLimit',
- 'paste.byteLimit',
- 'focus.byteLimit',
- 'blur.byteLimit'
- ].join( ' ' );
-
/**
* Enforces a byte limit on an input field, so that UTF-8 entries are counted as well,
* when, for example, a database field has a byte limit rather than a character limit.
// maxLength is a strange property. Removing or setting the property to
// undefined directly doesn't work. Instead, it can only be unset internally
// by the browser when removing the associated attribute (Firefox/Chrome).
- // http://code.google.com/p/chromium/issues/detail?id=136004
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=136004
$el.removeAttr( 'maxlength' );
} else {
// changed while text is being entered and keyup/change will not be fired yet
// (such as holding down a single key, fires keydown, and after each keydown,
// we can trim the previous one).
- // See http://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for
+ // See https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for
// the order and characteristics of the key events.
$el.on( eventKeys, function () {
var res = $.trimByteLength(
}
// Always adjust prevSafeVal to reflect the input value. Not doing this could cause
// trimByteLength to compare the new value to an empty string instead of the
- // old value, resulting in trimming always from the end (bug 40850).
+ // old value, resulting in trimming always from the end (T42850).
prevSafeVal = res.newVal;
} );
} );