/*!
* VisualEditor DOM selection-like class
*
* @copyright See AUTHORS.txt
*/
/**
* Like the DOM Selection object, but not updated live from the actual selection
*
* WARNING: the Nodes are still live and mutable, which can change the meaning
* of the offsets or invalidate the value of isBackwards.
*
* @class
*
* @constructor
* @param {ve.SelectionState|Selection|Object} selection DOM Selection-like object
* @param {Node|null} selection.anchorNode The anchor node (null if no selection)
* @param {number} selection.anchorOffset The anchor offset (0 if no selection)
* @param {Node|null} selection.focusNode The focus node (null if no selection)
* @param {number} selection.focusOffset The focus offset (0 if no selection)
* @param {boolean} [selection.isCollapsed] Whether the anchor and focus are the same
* @param {boolean} [selection.isBackwards] Whether the focus is before the anchor in document order
*/
ve.SelectionState = function VeSelectionState( selection ) {
this.anchorNode = selection.anchorNode;
this.anchorOffset = selection.anchorOffset;
this.focusNode = selection.focusNode;
this.focusOffset = selection.focusOffset;
this.isCollapsed = selection.isCollapsed;
if ( this.isCollapsed === undefined ) {
// Set to true if nodes are null (matches DOM Selection object's behaviour)
this.isCollapsed = this.anchorNode === this.focusNode &&
this.anchorOffset === this.focusOffset;
}
this.isBackwards = selection.isBackwards;
if ( this.isBackwards === undefined ) {
// Set to false if nodes are null or focus is no earlier than anchor
this.isBackwards = ve.compareDocumentOrder(
this.focusNode,
this.focusOffset,
this.anchorNode,
this.anchorOffset
) < 0;
}
};
/* Inheritance */
OO.initClass( ve.SelectionState );
/* Static methods */
/**
* Create a selection state object representing no selection
*
* @return {ve.SelectionState} Object representing no selection
*/
ve.SelectionState.static.newNullSelection = function () {
return new ve.SelectionState( {
focusNode: null,
focusOffset: 0,
anchorNode: null,
anchorOffset: 0
} );
};
/* Methods */
/**
* Returns the selection with the anchor and focus swapped
*
* @return {ve.SelectionState} selection with anchor/focus swapped. Object-identical to this if isCollapsed
*/
ve.SelectionState.prototype.flip = function () {
if ( this.isCollapsed ) {
return this;
}
return new ve.SelectionState( {
anchorNode: this.focusNode,
anchorOffset: this.focusOffset,
focusNode: this.anchorNode,
focusOffset: this.anchorOffset,
isCollapsed: false,
isBackwards: !this.isBackwards
} );
};
/**
* Whether the selection represents is the same range as another DOM Selection-like object
*
* @param {Object} other DOM Selection-like object
* @return {boolean} True if the anchors/focuses are equal (including null)
*/
ve.SelectionState.prototype.equalsSelection = function ( other ) {
return this.anchorNode === other.anchorNode &&
this.anchorOffset === other.anchorOffset &&
this.focusNode === other.focusNode &&
this.focusOffset === other.focusOffset;
};
/**
* Get a range representation of the selection
*
* N.B. Range objects do not show whether the selection is backwards
*
* @param {HTMLDocument} doc The owner document of the selection nodes
* @return {Range|null}
*/
ve.SelectionState.prototype.getNativeRange = function ( doc ) {
if ( this.anchorNode === null ) {
return null;
}
const range = doc.createRange();
try {
if ( this.isBackwards ) {
range.setStart( this.focusNode, this.focusOffset );
range.setEnd( this.anchorNode, this.anchorOffset );
} else {
range.setStart( this.anchorNode, this.anchorOffset );
if ( !this.isCollapsed ) {
range.setEnd( this.focusNode, this.focusOffset );
}
}
} catch ( e ) {
// Range#setStart/setEnd can throw exceptions with invalid offsets (T258191)
return null;
}
return range;
};