2012-10-03 21:15:56 +01:00
// Cross-broswer implementation of text ranges and selections
2021-08-20 20:13:24 -04:00
// documentation: http://bililite.com/blog/2011/01/17/cross-browser-text-ranges-and-selections/
2021-08-21 00:19:55 -04:00
// Version: 2.0
// Copyright (c) 2013 Daniel Wachsstock
2012-10-03 21:15:56 +01:00
// MIT license:
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
2021-08-20 19:33:56 -04:00
( function ( ) {
2012-10-03 21:15:56 +01:00
bililiteRange = function ( el , debug ) {
2021-08-20 19:33:56 -04:00
var ret ;
if ( debug ) {
ret = new NothingRange ( ) ; // Easier to force it to use the no-selection type than to try to find an old browser
} else if ( document . selection && ! document . addEventListener ) {
// Internet Explorer 8 and lower
ret = new IERange ( ) ;
} else if ( window . getSelection && el . setSelectionRange ) {
// Standards. Element is an input or textarea
ret = new InputRange ( ) ;
} else if ( window . getSelection ) {
// Standards, with any other kind of element
ret = new W3CRange ( )
} else {
// doesn't support selection
ret = new NothingRange ( ) ;
}
ret . _el = el ;
2021-08-20 20:13:24 -04:00
// determine parent document, as implemented by John McLear <john@mclear.co.uk>
2021-08-20 19:33:56 -04:00
ret . _doc = el . ownerDocument ;
ret . _win = 'defaultView' in ret . _doc ? ret . _doc . defaultView : ret . _doc . parentWindow ;
ret . _textProp = textProp ( el ) ;
ret . _bounds = [ 0 , ret . length ( ) ] ;
2021-08-21 00:19:55 -04:00
if ( ! ( 'oninput' in el ) ) {
// give IE8 a chance
var inputhack = function ( ) { ret . dispatch ( { type : 'input' } ) } ;
ret . listen ( 'keyup' , inputhack ) ;
ret . listen ( 'cut' , inputhack ) ;
ret . listen ( 'paste' , inputhack ) ;
ret . listen ( 'drop' , inputhack ) ;
el . oninput = 'patched' ;
}
2021-08-20 19:33:56 -04:00
return ret ;
2012-10-03 21:15:56 +01:00
}
function textProp ( el ) {
2021-08-20 19:33:56 -04:00
// returns the property that contains the text of the element
2021-08-21 00:19:55 -04:00
// note that for <body> elements the text attribute represents the obsolete text color, not the textContent.
// we document that these routines do not work for <body> elements so that should not be relevant
2021-08-20 19:33:56 -04:00
if ( typeof el . value != 'undefined' ) return 'value' ;
if ( typeof el . text != 'undefined' ) return 'text' ;
if ( typeof el . textContent != 'undefined' ) return 'textContent' ;
return 'innerText' ;
2012-10-03 21:15:56 +01:00
}
// base class
function Range ( ) { }
Range . prototype = {
2021-08-20 19:33:56 -04:00
length : function ( ) {
return this . _el [ this . _textProp ] . replace ( /\r/g , '' ) . length ; // need to correct for IE's CrLf weirdness
} ,
bounds : function ( s ) {
if ( s === 'all' ) {
this . _bounds = [ 0 , this . length ( ) ] ;
} else if ( s === 'start' ) {
this . _bounds = [ 0 , 0 ] ;
} else if ( s === 'end' ) {
this . _bounds = [ this . length ( ) , this . length ( ) ] ;
} else if ( s === 'selection' ) {
this . bounds ( 'all' ) ; // first select the whole thing for constraining
this . _bounds = this . _nativeSelection ( ) ;
} else if ( s ) {
2021-08-20 20:13:24 -04:00
this . _bounds = s ; // don't do error checking now; things may change at a moment's notice
2021-08-20 19:33:56 -04:00
} else {
var b = [
Math . max ( 0 , Math . min ( this . length ( ) , this . _bounds [ 0 ] ) ) ,
Math . max ( 0 , Math . min ( this . length ( ) , this . _bounds [ 1 ] ) )
] ;
2021-08-20 20:13:24 -04:00
b [ 1 ] = Math . max ( b [ 0 ] , b [ 1 ] ) ;
2021-08-20 19:33:56 -04:00
return b ; // need to constrain it to fit
}
return this ; // allow for chaining
} ,
select : function ( ) {
this . _nativeSelect ( this . _nativeRange ( this . bounds ( ) ) ) ;
2021-08-21 00:19:55 -04:00
this . dispatch ( { type : 'select' } ) ;
2021-08-20 19:33:56 -04:00
return this ; // allow for chaining
} ,
text : function ( text , select ) {
if ( arguments . length ) {
2021-08-21 00:19:55 -04:00
var bounds = this . bounds ( ) , el = this . _el ;
// signal the input per DOM 3 input events, http://www.w3.org/TR/DOM-Level-3-Events/#h4_events-inputevents
// we add another field, bounds, which are the bounds of the original text before being changed.
this . dispatch ( { type : 'beforeinput' , data : text , bounds : bounds } ) ;
this . _nativeSetText ( text , this . _nativeRange ( bounds ) ) ;
2021-08-20 19:33:56 -04:00
if ( select == 'start' ) {
2021-08-21 00:19:55 -04:00
this . bounds ( [ bounds [ 0 ] , bounds [ 0 ] ] ) ;
2021-08-20 19:33:56 -04:00
} else if ( select == 'end' ) {
2021-08-21 00:19:55 -04:00
this . bounds ( [ bounds [ 0 ] + text . length , bounds [ 0 ] + text . length ] ) ;
2021-08-20 19:33:56 -04:00
} else if ( select == 'all' ) {
2021-08-21 00:19:55 -04:00
this . bounds ( [ bounds [ 0 ] , bounds [ 0 ] + text . length ] ) ;
2021-08-20 19:33:56 -04:00
}
2021-08-21 00:19:55 -04:00
this . dispatch ( { type : 'input' , data : text , bounds : bounds } ) ;
2021-08-20 19:33:56 -04:00
return this ; // allow for chaining
} else {
return this . _nativeGetText ( this . _nativeRange ( this . bounds ( ) ) ) ;
}
} ,
insertEOL : function ( ) {
this . _nativeEOL ( ) ;
this . _bounds = [ this . _bounds [ 0 ] + 1 , this . _bounds [ 0 ] + 1 ] ; // move past the EOL marker
return this ;
2021-08-20 20:13:24 -04:00
} ,
scrollIntoView : function ( ) {
this . _nativeScrollIntoView ( this . _nativeRange ( this . bounds ( ) ) ) ;
return this ;
2021-08-21 00:19:55 -04:00
} ,
wrap : function ( n ) {
this . _nativeWrap ( n , this . _nativeRange ( this . bounds ( ) ) ) ;
return this ;
} ,
selection : function ( text ) {
if ( arguments . length ) {
return this . bounds ( 'selection' ) . text ( text , 'end' ) . select ( ) ;
} else {
return this . bounds ( 'selection' ) . text ( ) ;
}
} ,
clone : function ( ) {
return bililiteRange ( this . _el ) . bounds ( this . bounds ( ) ) ;
} ,
all : function ( text ) {
if ( arguments . length ) {
this . dispatch ( { type : 'beforeinput' , data : text } ) ;
this . _el [ this . _textProp ] = text ;
this . dispatch ( { type : 'input' , data : text } ) ;
return this ;
} else {
return this . _el [ this . _textProp ] . replace ( /\r/g , '' ) ; // need to correct for IE's CrLf weirdness;
}
} ,
element : function ( ) { return this . _el } ,
// includes a quickie polyfill for CustomEvent for IE that isn't perfect but works for me
// IE10 allows custom events but not "new CustomEvent"; have to do it the old-fashioned way
dispatch : function ( opts ) {
opts = opts || { } ;
var event = document . createEvent ? document . createEvent ( 'CustomEvent' ) : this . _doc . createEventObject ( ) ;
event . initCustomEvent && event . initCustomEvent ( opts . type , ! ! opts . bubbles , ! ! opts . cancelable , opts . detail ) ;
for ( var key in opts ) event [ key ] = opts [ key ] ;
// dispatch event asynchronously (in the sense of on the next turn of the event loop; still should be fired in order of dispatch
var el = this . _el ;
setTimeout ( function ( ) {
try {
el . dispatchEvent ? el . dispatchEvent ( event ) : el . fireEvent ( "on" + opts . type , document . createEventObject ( ) ) ;
} catch ( e ) {
// IE8 will not let me fire custom events at all. Call them directly
if ( jQuery ) {
jQuery ( el ) . trigger ( event ) ;
} else {
var listeners = el [ 'listen' + opts . type ] ;
if ( listeners ) for ( var i = 0 ; i < listeners . length ; ++ i ) {
listeners [ i ] . call ( el , event ) ;
}
}
}
} , 0 ) ;
return this ;
} ,
listen : function ( type , func ) {
var el = this . _el ;
if ( el . addEventListener ) {
el . addEventListener ( type , func ) ;
} else if ( jQuery ) {
jQuery ( el ) . on ( type , func ) ;
} else {
el . attachEvent ( "on" + type , func ) ;
// IE8 can't even handle custom events created with createEventObject (though it permits attachEvent), so we have to make our own
var listeners = el [ 'listen' + type ] = el [ 'listen' + type ] || [ ] ;
listeners . push ( func ) ;
}
return this ;
} ,
dontlisten : function ( type , func ) {
var el = this . _el ;
if ( el . removeEventListener ) {
el . removeEventListener ( type , func ) ;
} else if ( jQuery ) {
jQuery ( el ) . off ( type , func ) ;
} else try {
el . detachEvent ( "on" + type , func ) ;
} catch ( e ) {
var listeners = el [ 'listen' + type ] ;
if ( listeners ) for ( var i = 0 ; i < listeners . length ; ++ i ) {
if ( listeners [ i ] === func ) listeners [ i ] = function ( ) { } ; // replace with a noop
}
}
return this ;
2021-08-20 19:33:56 -04:00
}
2012-10-03 21:15:56 +01:00
} ;
2021-08-20 20:13:24 -04:00
// allow extensions ala jQuery
bililiteRange . fn = Range . prototype ; // to allow monkey patching
bililiteRange . extend = function ( fns ) {
for ( fn in fns ) Range . prototype [ fn ] = fns [ fn ] ;
} ;
2012-10-03 21:15:56 +01:00
function IERange ( ) { }
IERange . prototype = new Range ( ) ;
IERange . prototype . _nativeRange = function ( bounds ) {
2021-08-20 19:33:56 -04:00
var rng ;
if ( this . _el . tagName == 'INPUT' ) {
// IE 8 is very inconsistent; textareas have createTextRange but it doesn't work
rng = this . _el . createTextRange ( ) ;
} else {
rng = this . _doc . body . createTextRange ( ) ;
rng . moveToElementText ( this . _el ) ;
}
if ( bounds ) {
if ( bounds [ 1 ] < 0 ) bounds [ 1 ] = 0 ; // IE tends to run elements out of bounds
if ( bounds [ 0 ] > this . length ( ) ) bounds [ 0 ] = this . length ( ) ;
if ( bounds [ 1 ] < rng . text . replace ( /\r/g , '' ) . length ) { // correct for IE's CrLf wierdness
// block-display elements have an invisible, uncounted end of element marker, so we move an extra one and use the current length of the range
rng . moveEnd ( 'character' , - 1 ) ;
rng . moveEnd ( 'character' , bounds [ 1 ] - rng . text . replace ( /\r/g , '' ) . length ) ;
}
if ( bounds [ 0 ] > 0 ) rng . moveStart ( 'character' , bounds [ 0 ] ) ;
}
return rng ;
2012-10-03 21:15:56 +01:00
} ;
IERange . prototype . _nativeSelect = function ( rng ) {
2021-08-20 19:33:56 -04:00
rng . select ( ) ;
2012-10-03 21:15:56 +01:00
} ;
IERange . prototype . _nativeSelection = function ( ) {
2021-08-20 19:33:56 -04:00
// returns [start, end] for the selection constrained to be in element
2021-08-21 00:19:55 -04:00
// this fails for an empty selection! selection.createRange() if in a text area does not create a text selection, so I can't compare it.
2021-08-20 19:33:56 -04:00
var rng = this . _nativeRange ( ) ; // range of the element to constrain to
var len = this . length ( ) ;
2021-08-21 00:19:55 -04:00
// this._el.focus(); This solves the problem of text areas not having a real selection , but sucks the focus from everything else, so I can't use it
2021-08-20 19:33:56 -04:00
var sel = this . _doc . selection . createRange ( ) ;
try {
return [
iestart ( sel , rng ) ,
ieend ( sel , rng )
] ;
} catch ( e ) {
// IE gets upset sometimes about comparing text to input elements, but the selections cannot overlap, so make a best guess
return ( sel . parentElement ( ) . sourceIndex < this . _el . sourceIndex ) ? [ 0 , 0 ] : [ len , len ] ;
}
2012-10-03 21:15:56 +01:00
} ;
IERange . prototype . _nativeGetText = function ( rng ) {
2021-08-20 19:33:56 -04:00
return rng . text . replace ( /\r/g , '' ) ; // correct for IE's CrLf weirdness
2012-10-03 21:15:56 +01:00
} ;
IERange . prototype . _nativeSetText = function ( text , rng ) {
2021-08-20 19:33:56 -04:00
rng . text = text ;
2012-10-03 21:15:56 +01:00
} ;
IERange . prototype . _nativeEOL = function ( ) {
2021-08-21 00:19:55 -04:00
if ( 'value' in this . _el ) {
2021-08-20 19:33:56 -04:00
this . text ( '\n' ) ; // for input and textarea, insert it straight
} else {
2021-08-21 00:19:55 -04:00
this . _nativeRange ( this . bounds ( ) ) . pasteHTML ( '\n<br/>' ) ;
2021-08-20 19:33:56 -04:00
}
2012-10-03 21:15:56 +01:00
} ;
2021-08-20 20:13:24 -04:00
IERange . prototype . _nativeScrollIntoView = function ( rng ) {
rng . scrollIntoView ( ) ;
}
2021-08-21 00:19:55 -04:00
IERange . prototype . _nativeWrap = function ( n , rng ) {
// hacky to use string manipulation but I don't see another way to do it.
var div = document . createElement ( 'div' ) ;
div . appendChild ( n ) ;
// insert the existing range HTML after the first tag
var html = div . innerHTML . replace ( '><' , '>' + rng . htmlText + '<' ) ;
rng . pasteHTML ( html ) ;
} ;
2012-10-03 21:15:56 +01:00
// IE internals
function iestart ( rng , constraint ) {
2021-08-20 19:33:56 -04:00
// returns the position (in character) of the start of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after
var len = constraint . text . replace ( /\r/g , '' ) . length ; // correct for IE's CrLf wierdness
if ( rng . compareEndPoints ( 'StartToStart' , constraint ) <= 0 ) return 0 ; // at or before the beginning
if ( rng . compareEndPoints ( 'StartToEnd' , constraint ) >= 0 ) return len ;
for ( var i = 0 ; rng . compareEndPoints ( 'StartToStart' , constraint ) > 0 ; ++ i , rng . moveStart ( 'character' , - 1 ) ) ;
return i ;
2012-10-03 21:15:56 +01:00
}
function ieend ( rng , constraint ) {
2021-08-20 19:33:56 -04:00
// returns the position (in character) of the end of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after
var len = constraint . text . replace ( /\r/g , '' ) . length ; // correct for IE's CrLf wierdness
if ( rng . compareEndPoints ( 'EndToEnd' , constraint ) >= 0 ) return len ; // at or after the end
if ( rng . compareEndPoints ( 'EndToStart' , constraint ) <= 0 ) return 0 ;
for ( var i = 0 ; rng . compareEndPoints ( 'EndToStart' , constraint ) > 0 ; ++ i , rng . moveEnd ( 'character' , - 1 ) ) ;
return i ;
2012-10-03 21:15:56 +01:00
}
// an input element in a standards document. "Native Range" is just the bounds array
function InputRange ( ) { }
InputRange . prototype = new Range ( ) ;
InputRange . prototype . _nativeRange = function ( bounds ) {
2021-08-20 19:33:56 -04:00
return bounds || [ 0 , this . length ( ) ] ;
2012-10-03 21:15:56 +01:00
} ;
InputRange . prototype . _nativeSelect = function ( rng ) {
2021-08-20 19:33:56 -04:00
this . _el . setSelectionRange ( rng [ 0 ] , rng [ 1 ] ) ;
2012-10-03 21:15:56 +01:00
} ;
InputRange . prototype . _nativeSelection = function ( ) {
2021-08-20 19:33:56 -04:00
return [ this . _el . selectionStart , this . _el . selectionEnd ] ;
2012-10-03 21:15:56 +01:00
} ;
InputRange . prototype . _nativeGetText = function ( rng ) {
2021-08-20 19:33:56 -04:00
return this . _el . value . substring ( rng [ 0 ] , rng [ 1 ] ) ;
2012-10-03 21:15:56 +01:00
} ;
InputRange . prototype . _nativeSetText = function ( text , rng ) {
2021-08-20 19:33:56 -04:00
var val = this . _el . value ;
this . _el . value = val . substring ( 0 , rng [ 0 ] ) + text + val . substring ( rng [ 1 ] ) ;
2012-10-03 21:15:56 +01:00
} ;
InputRange . prototype . _nativeEOL = function ( ) {
2021-08-20 19:33:56 -04:00
this . text ( '\n' ) ;
2012-10-03 21:15:56 +01:00
} ;
2021-08-20 20:13:24 -04:00
InputRange . prototype . _nativeScrollIntoView = function ( rng ) {
// I can't remember where I found this clever hack to find the location of text in a text area
2021-08-21 00:19:55 -04:00
var clone = this . _el . cloneNode ( true ) ;
clone . style . visibility = 'hidden' ;
clone . style . position = 'absolute' ;
this . _el . parentNode . insertBefore ( clone , this . _el ) ;
clone . style . height = '1px' ;
clone . value = this . _el . value . slice ( 0 , rng [ 0 ] ) ;
var top = clone . scrollHeight ;
2021-08-20 20:13:24 -04:00
// this gives the bottom of the text, so we have to subtract the height of a single line
2021-08-21 00:19:55 -04:00
clone . value = 'X' ;
top -= 2 * clone . scrollHeight ; // show at least a line above
clone . parentNode . removeChild ( clone ) ;
2021-08-20 20:13:24 -04:00
// scroll into position if necessary
if ( this . _el . scrollTop > top || this . _el . scrollTop + this . _el . clientHeight < top ) {
this . _el . scrollTop = top ;
}
// now scroll the element into view; get its position as in jQuery.offset
var rect = this . _el . getBoundingClientRect ( ) ;
rect . top += this . _win . pageYOffset - this . _doc . documentElement . clientTop ;
rect . left += this . _win . pageXOffset - this . _doc . documentElement . clientLeft ;
2021-08-21 00:19:55 -04:00
// create an element to scroll to (can't just use the clone above, since scrollIntoView wants a visible element)
2021-08-20 20:13:24 -04:00
var div = this . _doc . createElement ( 'div' ) ;
div . style . position = 'absolute' ;
div . style . top = ( rect . top + top - this . _el . scrollTop ) + 'px' ; // adjust for how far in the range is; it may not have scrolled all the way to the top
div . style . left = rect . left + 'px' ;
div . innerHTML = ' ' ;
this . _doc . body . appendChild ( div ) ;
div . scrollIntoViewIfNeeded ? div . scrollIntoViewIfNeeded ( ) : div . scrollIntoView ( ) ;
div . parentNode . removeChild ( div ) ;
}
2021-08-21 00:19:55 -04:00
InputRange . prototype . _nativeWrap = function ( ) { throw new Error ( "Cannot wrap in a text element" ) } ;
2012-10-03 21:15:56 +01:00
function W3CRange ( ) { }
W3CRange . prototype = new Range ( ) ;
W3CRange . prototype . _nativeRange = function ( bounds ) {
2021-08-20 19:33:56 -04:00
var rng = this . _doc . createRange ( ) ;
rng . selectNodeContents ( this . _el ) ;
if ( bounds ) {
w3cmoveBoundary ( rng , bounds [ 0 ] , true , this . _el ) ;
rng . collapse ( true ) ;
w3cmoveBoundary ( rng , bounds [ 1 ] - bounds [ 0 ] , false , this . _el ) ;
}
return rng ;
2012-10-03 21:15:56 +01:00
} ;
W3CRange . prototype . _nativeSelect = function ( rng ) {
2021-08-20 19:33:56 -04:00
this . _win . getSelection ( ) . removeAllRanges ( ) ;
this . _win . getSelection ( ) . addRange ( rng ) ;
2012-10-03 21:15:56 +01:00
} ;
W3CRange . prototype . _nativeSelection = function ( ) {
2021-08-20 19:33:56 -04:00
// returns [start, end] for the selection constrained to be in element
var rng = this . _nativeRange ( ) ; // range of the element to constrain to
if ( this . _win . getSelection ( ) . rangeCount == 0 ) return [ this . length ( ) , this . length ( ) ] ; // append to the end
var sel = this . _win . getSelection ( ) . getRangeAt ( 0 ) ;
return [
w3cstart ( sel , rng ) ,
w3cend ( sel , rng )
] ;
}
2012-10-03 21:15:56 +01:00
W3CRange . prototype . _nativeGetText = function ( rng ) {
2021-08-20 19:33:56 -04:00
return rng . toString ( ) ;
2012-10-03 21:15:56 +01:00
} ;
W3CRange . prototype . _nativeSetText = function ( text , rng ) {
2021-08-20 19:33:56 -04:00
rng . deleteContents ( ) ;
rng . insertNode ( this . _doc . createTextNode ( text ) ) ;
this . _el . normalize ( ) ; // merge the text with the surrounding text
2012-10-03 21:15:56 +01:00
} ;
W3CRange . prototype . _nativeEOL = function ( ) {
2021-08-20 19:33:56 -04:00
var rng = this . _nativeRange ( this . bounds ( ) ) ;
rng . deleteContents ( ) ;
var br = this . _doc . createElement ( 'br' ) ;
br . setAttribute ( '_moz_dirty' , '' ) ; // for Firefox
rng . insertNode ( br ) ;
rng . insertNode ( this . _doc . createTextNode ( '\n' ) ) ;
rng . collapse ( false ) ;
2012-10-03 21:15:56 +01:00
} ;
2021-08-20 20:13:24 -04:00
W3CRange . prototype . _nativeScrollIntoView = function ( rng ) {
// can't scroll to a range; have to scroll to an element instead
var span = this . _doc . createElement ( 'span' ) ;
rng . insertNode ( span ) ;
span . scrollIntoViewIfNeeded ? span . scrollIntoViewIfNeeded ( ) : span . scrollIntoView ( ) ;
span . parentNode . removeChild ( span ) ;
}
2021-08-21 00:19:55 -04:00
W3CRange . prototype . _nativeWrap = function ( n , rng ) {
rng . surroundContents ( n ) ;
} ;
2012-10-03 21:15:56 +01:00
// W3C internals
function nextnode ( node , root ) {
2021-08-20 19:33:56 -04:00
// in-order traversal
// we've already visited node, so get kids then siblings
if ( node . firstChild ) return node . firstChild ;
if ( node . nextSibling ) return node . nextSibling ;
if ( node === root ) return null ;
while ( node . parentNode ) {
// get uncles
node = node . parentNode ;
if ( node == root ) return null ;
if ( node . nextSibling ) return node . nextSibling ;
}
return null ;
2012-10-03 21:15:56 +01:00
}
function w3cmoveBoundary ( rng , n , bStart , el ) {
2021-08-20 19:33:56 -04:00
// move the boundary (bStart == true ? start : end) n characters forward, up to the end of element el. Forward only!
// if the start is moved after the end, then an exception is raised
if ( n <= 0 ) return ;
var node = rng [ bStart ? 'startContainer' : 'endContainer' ] ;
2021-08-21 04:50:07 -04:00
if ( node . nodeType === Node . TEXT _NODE ) {
2021-08-20 19:33:56 -04:00
// we may be starting somewhere into the text
n += rng [ bStart ? 'startOffset' : 'endOffset' ] ;
}
2021-08-21 04:50:07 -04:00
for ( ; node ; node = nextnode ( node , el ) ) {
if ( node . nodeType === Node . TEXT _NODE ) {
if ( n < node . nodeValue . length ) {
2021-08-20 19:33:56 -04:00
rng [ bStart ? 'setStart' : 'setEnd' ] ( node , n ) ;
return ;
}
2021-08-21 04:50:07 -04:00
n -= node . nodeValue . length ;
2021-08-20 19:33:56 -04:00
}
2021-08-21 04:50:07 -04:00
if ( ! node . firstChild ) rng [ bStart ? 'setStartAfter' : 'setEndAfter' ] ( node ) ;
2021-08-20 19:33:56 -04:00
}
2012-10-03 21:15:56 +01:00
}
var START _TO _START = 0 ; // from the w3c definitions
var START _TO _END = 1 ;
var END _TO _END = 2 ;
var END _TO _START = 3 ;
// from the Mozilla documentation, for range.compareBoundaryPoints(how, sourceRange)
2021-08-20 19:33:56 -04:00
// -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before, equal to, or after the corresponding boundary-point of sourceRange.
2012-10-03 21:15:56 +01:00
// * Range.END_TO_END compares the end boundary-point of sourceRange to the end boundary-point of range.
// * Range.END_TO_START compares the end boundary-point of sourceRange to the start boundary-point of range.
// * Range.START_TO_END compares the start boundary-point of sourceRange to the end boundary-point of range.
2021-08-20 19:33:56 -04:00
// * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range.
2012-10-03 21:15:56 +01:00
function w3cstart ( rng , constraint ) {
2021-08-20 19:33:56 -04:00
if ( rng . compareBoundaryPoints ( START _TO _START , constraint ) <= 0 ) return 0 ; // at or before the beginning
if ( rng . compareBoundaryPoints ( END _TO _START , constraint ) >= 0 ) return constraint . toString ( ) . length ;
rng = rng . cloneRange ( ) ; // don't change the original
rng . setEnd ( constraint . endContainer , constraint . endOffset ) ; // they now end at the same place
return constraint . toString ( ) . length - rng . toString ( ) . length ;
2012-10-03 21:15:56 +01:00
}
function w3cend ( rng , constraint ) {
2021-08-20 19:33:56 -04:00
if ( rng . compareBoundaryPoints ( END _TO _END , constraint ) >= 0 ) return constraint . toString ( ) . length ; // at or after the end
if ( rng . compareBoundaryPoints ( START _TO _END , constraint ) <= 0 ) return 0 ;
rng = rng . cloneRange ( ) ; // don't change the original
rng . setStart ( constraint . startContainer , constraint . startOffset ) ; // they now start at the same place
return rng . toString ( ) . length ;
2012-10-03 21:15:56 +01:00
}
function NothingRange ( ) { }
NothingRange . prototype = new Range ( ) ;
NothingRange . prototype . _nativeRange = function ( bounds ) {
2021-08-20 19:33:56 -04:00
return bounds || [ 0 , this . length ( ) ] ;
2012-10-03 21:15:56 +01:00
} ;
NothingRange . prototype . _nativeSelect = function ( rng ) { // do nothing
} ;
NothingRange . prototype . _nativeSelection = function ( ) {
2021-08-20 19:33:56 -04:00
return [ 0 , 0 ] ;
2012-10-03 21:15:56 +01:00
} ;
NothingRange . prototype . _nativeGetText = function ( rng ) {
2021-08-20 19:33:56 -04:00
return this . _el [ this . _textProp ] . substring ( rng [ 0 ] , rng [ 1 ] ) ;
2012-10-03 21:15:56 +01:00
} ;
NothingRange . prototype . _nativeSetText = function ( text , rng ) {
2021-08-20 19:33:56 -04:00
var val = this . _el [ this . _textProp ] ;
this . _el [ this . _textProp ] = val . substring ( 0 , rng [ 0 ] ) + text + val . substring ( rng [ 1 ] ) ;
2012-10-03 21:15:56 +01:00
} ;
NothingRange . prototype . _nativeEOL = function ( ) {
2021-08-20 19:33:56 -04:00
this . text ( '\n' ) ;
2012-10-03 21:15:56 +01:00
} ;
2021-08-20 20:13:24 -04:00
NothingRange . prototype . _nativeScrollIntoView = function ( ) {
this . _el . scrollIntoView ( ) ;
} ;
2021-08-21 00:19:55 -04:00
NothingRange . prototype . _nativeWrap = function ( ) { throw new Error ( "Wrapping not implemented" ) } ;
2012-10-03 21:15:56 +01:00
2021-08-20 19:33:56 -04:00
} ) ( ) ;