vendor/assets/javascripts/rangy-position.js in rangy-rails-1.3alpha.780.0 vs vendor/assets/javascripts/rangy-position.js in rangy-rails-1.3alpha.804.0
- old
+ new
@@ -1,15 +1,524 @@
-/**
- * Position module for Rangy.
- * Extensions to Range and Selection objects to provide access to pixel positions relative to the viewport or document.
- *
- * Part of Rangy, a cross-browser JavaScript range and selection library
- * http://code.google.com/p/rangy/
- *
- * Depends on Rangy core.
- *
- * Copyright 2013, Tim Down
- * Licensed under the MIT license.
- * Version: 1.3alpha.780M
- * Build date: 17 May 2013
- */
-rangy.createModule("Position",["WrappedSelection"],function(a,b){function j(a){var b=0,d=0;if(typeof a.pageXOffset==c&&typeof a.pageYOffset==c)b=a.pageXOffset,d=a.pageYOffset;else{var e=a.document,f=e.documentElement,h=e.compatMode,i=typeof h=="string"&&h.indexOf("CSS")>=0&&f?f:g.getBody(e);if(i&&typeof i.scrollLeft==c&&typeof i.scrollTop==c)try{b=i.scrollLeft,d=i.scrollTop}catch(j){}}return{x:b,y:d}}function k(a,b){b=b.toLowerCase();while(a){if(a.nodeType==1&&a.tagName.toLowerCase()==b)return a;a=a.parentNode}return null}function l(a,b,c,d){this.top=a,this.right=b,this.bottom=c,this.left=d,this.width=b-d,this.height=c-a}function m(a,b,c){return new l(a.top+c,a.right+b,a.bottom+c,a.left+b)}function n(a,b){var d=0,e=0,f=b.documentElement,h=g.getBody(b),i=f.clientWidth===0&&typeof h.clientTop==c?h:f,j=i.clientLeft,k=i.clientTop;return j&&(d=-j),k&&(e=-k),m(a,d,e)}function o(a){var b=[],c=[],d=[],e=[];for(var f=0,g=a.length,h;f<g;++f)h=a[f],h&&(b.push(h.top),c.push(h.bottom),d.push(h.left),e.push(h.right));return new l(Math.min.apply(Math,b),Math.max.apply(Math,e),Math.max.apply(Math,c),Math.min.apply(Math,d))}function p(b,c,d){var e=g.getBody(b).createTextRange();e.moveToPoint(c,d);var f=new a.WrappedTextRange(e);return new i(f.startContainer,f.startOffset)}function q(a,b,c){var d=a.caretPositionFromPoint(b,c);return new i(d.offsetNode,d.offset)}function r(a,b,c){var d=a.caretRangeFromPoint(b,c);return new i(d.startContainer,d.startOffset)}function s(a){var b=(a.nativeRange||a).getClientRects();return b.length>0?b[b.length-1]:null}function t(a,b,c){return console.log("pointIsInOrAboveRect",a,b,Math.floor(c.top),Math.floor(c.right),Math.floor(c.bottom),Math.floor(c.left)),b<c.bottom&&a>=c.left&&a<=c.right}function u(b,c,d,e){var f=b.elementFromPoint(c,d);console.log("elementFromPoint is ",f);var h=a.createRange(b);h.selectNodeContents(f),h.collapse(!0);var j=f.firstChild,k,l,m;if(!j)j=f.parentNode,k=g.getNodeIndex(f),e||++k;else{a:while(j){console.log(j);if(j.nodeType==3)for(k=0,m=j.length;k<=m;++k){h.setEnd(j,k),l=s(h);if(l&&t(c,d,l)){l.right-c>c-l.left&&--k;break a}}else{h.setEndAfter(j),l=s(h);if(l&&t(c,d,l)){k=g.getNodeIndex(j),j=f.parentNode,e||++k;break}}j=j.nextSibling}j||(j=f,k=f.childNodes.length)}return new i(j,k)}function v(c){if(a.features.implementsTextRange)return p;if(typeof c.caretPositionFromPoint!=d)return q;if(typeof c.caretRangeFromPoint!=d)return r;if(typeof c.elementFromPoint!=d&&A)return u;throw b.createError("createCaretPositionFromPointGetter(): Browser does not provide a recognised method to create a selection from pixel coordinates")}function w(c,d,e,f,h){h=g.getContentDocument(h,b,"createRangeFromPoints");var i=v(h),j=i(h,c,d,!1),k=i(h,e,f,!0);console.log(j.node,j.offset,k.node,k.offset);var l=a.createRange(h);return l.setStartAndEnd(j.node,j.offset,k.node,k.offset),l}function x(a,b,c,d,e){var f,g,h,i,j=d<b||b==d&&c<a;j?(f=c,g=d,h=a,i=b):(f=a,g=b,h=c,i=d);var k=rangy.getSelection(e),l=w(f,g,h,i,e);return k.setSingleRange(l),k}function K(a,b){return a.compareBoundaryPoints(b.START_TO_START,b)}function L(a){return function(){var b="getBounding"+(a?"Document":"Client")+"Rect",c=[];for(var d=0,e=null,f;d<this.rangeCount;++d)c.push(this.getRangeAt(d)[b]());return o(c)}}function M(a,b){return function(){if(this.rangeCount==0)return null;var c=b?"Document":"Client",d=this.getAllRanges();return d.length>1&&d.sort(K),a?d[0]["getStart"+c+"Pos"]():d[d.length-1]["getEnd"+c+"Pos"]()}}var c="number",d="undefined",e=a.WrappedRange,f=a.WrappedTextRange,g=a.dom,h=a.util,i=g.DomPosition,y=document.createElement("span"),z=h.isHostMethod(y,"getBoundingClientRect");y=null;var A=!1,B=!1;if(a.features.implementsDomRange){var C=a.createNativeRange();A=h.isHostMethod(C,"getClientRects"),B=h.isHostMethod(C,"getBoundingClientRect"),C.detach()}h.extend(a.features,{rangeSupportsGetBoundingClientRect:B,rangeSupportsGetClientRects:A,elementSupportsGetBoundingClientRect:z});var D=function(a){return function(){var b=this.cloneRange();b.collapse(a);var c=b.getBoundingClientRect();return{x:c[a?"left":"right"],y:c[a?"top":"bottom"]}}},E=a.rangePrototype;if(a.features.implementsTextRange&&z)E.getBoundingClientRect=function(){var a=f.rangeToTextRange(this),b=this.getNodes([1],function(a){return/^t[dh]$/i.test(a.tagName)}),c,d=[];if(b.length>0){var e=k(this.startContainer,"table");for(var h=0,i,j,l,m,p;i=b[h];++h){l=k(i,"table");if(!e||l!=e)m=this.cloneRange(),e&&m.setStartAfter(e),m.setEndBefore(l),d.push(f.rangeToTextRange(m).getBoundingClientRect());this.containsNode(i)?d.push(i.getBoundingClientRect()):(j=a.duplicate(),j.moveToElementText(i),j.compareEndPoints("StartToStart",a)==-1?j.setEndPoint("StartToStart",a):j.compareEndPoints("EndToEnd",a)==1&&j.setEndPoint("EndToEnd",a),d.push(j.getBoundingClientRect())),e=l}var q=k(this.endContainer,"table");!q&&e&&(m=this.cloneRange(),m.setStartAfter(e),d.push(f.rangeToTextRange(m).getBoundingClientRect())),c=o(d)}else c=a.getBoundingClientRect();return n(c,g.getDocument(this.startContainer))};else if(a.features.implementsDomRange){var F=function(a){return a instanceof e?a:new e(a)};if(B){E.getBoundingClientRect=function(){var a=F(this).nativeRange,b=a.getBoundingClientRect()||a.getClientRects()[0];return n(b,g.getDocument(this.startContainer))};if(A){var G=function(a,b){var c=a.childNodes};D=function(a){return function(){var c,d=F(this).nativeRange,e=d.getClientRects();if(e.length==0&&z){!a,console.log(d,d.getClientRects(),d.getBoundingClientRect());if(this.collapsed&&this.startContainer.nodeType==1&&this.startOffset<this.startContainer.childNodes.length){var f=this.startContainer.childNodes[this.startOffset];f.getClientRects&&console.log(f,f.getClientRects(),this.startContainer.getClientRects())}}if(e.length>0)return a?(c=e[0],{x:c.left,y:c.top}):(c=e[e.length-1],{x:c.right,y:c.bottom});throw b.createError("Cannot get position for range "+this.inspect())}}}}else{var H=z?function(a){return n(a.getBoundingClientRect(),g.getDocument(a))}:function(a){var b=0,c=0,d=a,e=a.offsetWidth,f=a.offsetHeight;while(d)b+=d.offsetLeft,c+=d.offsetTop,d=d.offsetParent;return n(new l(c,b+e,c+f,b),g.getDocument(a))},I=function(a){var b;a.splitBoundaries();var c=document.createElement("span");if(a.collapsed)a.insertNode(c),b=H(c),c.parentNode.removeChild(c);else{var d=a.cloneRange();d.collapse(!0),d.insertNode(c);var e=H(c);c.parentNode.removeChild(c),d.collapseToPoint(a.endContainer,a.endOffset),d.insertNode(c);var f=H(c);c.parentNode.removeChild(c);var g=[e,f],h=a.getNodes([1],function(b){return a.containsNode(b)});for(var i=0,j=h.length;i<j;++i)g.push(H(h[i]));b=o(g)}return a.normalizeBoundaries(),b};E.getBoundingClientRect=function(a){return I(F(a))}}function J(a){return function(){var b=this["get"+(a?"Start":"End")+"ClientPos"](),c=j(g.getWindow(this.startContainer));return{x:b.x+c.x,y:b.y+c.y}}}}h.extend(E,{getBoundingDocumentRect:function(){var a=j(g.getWindow(this.startContainer));return m(this.getBoundingClientRect(),a.x,a.y)},getStartClientPos:D(!0),getEndClientPos:D(!1),getStartDocumentPos:J(!0),getEndDocumentPos:J(!1)}),h.extend(a.selectionPrototype,{getBoundingClientRect:L(!1),getBoundingDocumentRect:L(!0),getStartClientPos:M(!0,!1),getEndClientPos:M(!1,!1),getStartDocumentPos:M(!0,!0),getEndDocumentPos:M(!1,!0)}),a.positionFromPoint=function(a,c,d){return d=g.getContentDocument(d,b,"positionFromPoint"),v(d)(d,a,c)},a.createRangeFromPoints=w,a.moveSelectionToPoints=x})
\ No newline at end of file
+rangy.createModule("Position", ["WrappedSelection"], function(api, module) {
+ //var log = log4javascript.getLogger("rangy.position");
+
+ var NUMBER = "number", UNDEF = "undefined";
+ var WrappedRange = api.WrappedRange;
+ var WrappedTextRange = api.WrappedTextRange;
+ var dom = api.dom, util = api.util, DomPosition = dom.DomPosition;
+
+ // Feature detection
+
+ //var caretPositionFromPointSupported = (typeof document.caretPositionFromPoint != UNDEF);
+
+ // Since Rangy can deal with multiple documents which could be in different modes, we have to do the checks every
+ // time, unless we cache a getScrollPosition function in each document. This would necessarily pollute the
+ // document's global namespace, which I'm choosing to view as a greater evil than a slight performance hit.
+ function getScrollPosition(win) {
+ var x = 0, y = 0;
+ if (typeof win.pageXOffset == NUMBER && typeof win.pageYOffset == NUMBER) {
+ x = win.pageXOffset;
+ y = win.pageYOffset;
+ } else {
+ var doc = win.document;
+ var docEl = doc.documentElement;
+ var compatMode = doc.compatMode;
+ var scrollEl = (typeof compatMode == "string" && compatMode.indexOf("CSS") >= 0 && docEl)
+ ? docEl : dom.getBody(doc);
+
+ if (scrollEl && typeof scrollEl.scrollLeft == NUMBER && typeof scrollEl.scrollTop == NUMBER) {
+ try {
+ x = scrollEl.scrollLeft;
+ y = scrollEl.scrollTop;
+ } catch (ex) {}
+ }
+ }
+ return { x: x, y: y };
+ }
+
+ function getAncestorElement(node, tagName) {
+ tagName = tagName.toLowerCase();
+ while (node) {
+ if (node.nodeType == 1 && node.tagName.toLowerCase() == tagName) {
+ return node;
+ }
+ node = node.parentNode;
+ }
+ return null;
+ }
+
+ function Rect(top, right, bottom, left) {
+ this.top = top;
+ this.right = right;
+ this.bottom = bottom;
+ this.left = left;
+ this.width = right - left;
+ this.height = bottom - top;
+ }
+
+ function createRelativeRect(rect, dx, dy) {
+ return new Rect(rect.top + dy, rect.right + dx, rect.bottom + dy, rect.left + dx);
+ }
+
+ function adjustClientRect(rect, doc) {
+ // Older IEs have an issue with a two pixel margin on the body element
+ var dx = 0, dy = 0;
+ var docEl = doc.documentElement, body = dom.getBody(doc);
+ var container = (docEl.clientWidth === 0 && typeof body.clientTop == NUMBER) ? body : docEl;
+ var clientLeft = container.clientLeft, clientTop = container.clientTop;
+ if (clientLeft) {
+ dx = -clientLeft;
+ }
+ if (clientTop) {
+ dy = -clientTop;
+ }
+ return createRelativeRect(rect, dx, dy);
+ }
+
+ function mergeRects(rects) {
+ var tops = [], bottoms = [], lefts = [], rights = [];
+ for (var i = 0, len = rects.length, rect; i < len; ++i) {
+ rect = rects[i];
+ if (rect) {
+ tops.push(rect.top);
+ bottoms.push(rect.bottom);
+ lefts.push(rect.left);
+ rights.push(rect.right);
+ }
+ }
+ return new Rect(
+ Math.min.apply(Math, tops),
+ Math.max.apply(Math, rights),
+ Math.max.apply(Math, bottoms),
+ Math.min.apply(Math, lefts)
+ );
+ }
+
+ function getTextRangePosition(doc, x, y) {
+ var textRange = dom.getBody(doc).createTextRange();
+ textRange.moveToPoint(x, y);
+ var range = new api.WrappedTextRange(textRange);
+ return new DomPosition(range.startContainer, range.startOffset);
+ }
+
+ function caretPositionFromPoint(doc, x, y) {
+ var pos = doc.caretPositionFromPoint(x, y);
+ return new DomPosition(pos.offsetNode, pos.offset);
+ }
+
+ function caretRangeFromPoint(doc, x, y) {
+ var range = doc.caretRangeFromPoint(x, y);
+ return new DomPosition(range.startContainer, range.startOffset);
+ }
+
+ function getLastRangeRect(range) {
+ var rects = (range.nativeRange || range).getClientRects();
+ return (rects.length > 0) ? rects[rects.length - 1] : null;
+ }
+
+ function pointIsInOrAboveRect(x, y, rect) {
+ console.log("pointIsInOrAboveRect", x, y, Math.floor(rect.top), Math.floor(rect.right), Math.floor(rect.bottom), Math.floor(rect.left))
+ return y < rect.bottom && x >= rect.left && x <= rect.right;
+ }
+
+ function positionFromPoint(doc, x, y, favourPrecedingPosition) {
+ var el = doc.elementFromPoint(x, y);
+
+ console.log("elementFromPoint is ", el);
+
+ var range = api.createRange(doc);
+ range.selectNodeContents(el);
+ range.collapse(true);
+
+ var node = el.firstChild, offset, rect, textLen;
+
+ if (!node) {
+ node = el.parentNode;
+ offset = dom.getNodeIndex(el);
+ if (!favourPrecedingPosition) {
+ ++offset;
+ }
+ } else {
+ // Search through the text node children of el
+ main: while (node) {
+ console.log(node);
+ if (node.nodeType == 3) {
+ // Go through the text node character by character
+ for (offset = 0, textLen = node.length; offset <= textLen; ++offset) {
+ range.setEnd(node, offset);
+ rect = getLastRangeRect(range);
+ if (rect && pointIsInOrAboveRect(x, y, rect)) {
+ // We've gone past the point. Now we check which side (left or right) of the character the point is nearer to
+ if (rect.right - x > x - rect.left) {
+ --offset;
+ }
+ break main;
+ }
+ }
+ } else {
+ // Handle elements
+ range.setEndAfter(node);
+ rect = getLastRangeRect(range);
+ if (rect && pointIsInOrAboveRect(x, y, rect)) {
+ offset = dom.getNodeIndex(node);
+ node = el.parentNode;
+ if (!favourPrecedingPosition) {
+ ++offset;
+ }
+ break;
+ }
+ }
+
+ node = node.nextSibling;
+ }
+ if (!node) {
+ node = el;
+ offset = el.childNodes.length;
+ }
+ }
+
+ return new DomPosition(node, offset);
+ }
+
+ function createCaretPositionFromPointGetter(doc) {
+ if (api.features.implementsTextRange) {
+ return getTextRangePosition;
+ } else if (typeof doc.caretPositionFromPoint != UNDEF) {
+ return caretPositionFromPoint;
+ } else if (typeof doc.caretRangeFromPoint != UNDEF) {
+ return caretRangeFromPoint;
+ } else if (typeof doc.elementFromPoint != UNDEF && rangeSupportsGetClientRects) {
+ return positionFromPoint;
+ } else {
+ throw module.createError("createCaretPositionFromPointGetter(): Browser does not provide a recognised method to create a selection from pixel coordinates");
+ }
+ }
+
+ function createRangeFromPoints(startX, startY, endX, endY, doc) {
+ doc = dom.getContentDocument(doc, module, "createRangeFromPoints");
+ var positionFinder = createCaretPositionFromPointGetter(doc);
+ var startPos = positionFinder(doc, startX, startY, false);
+ var endPos = positionFinder(doc, endX, endY, true);
+ console.log(startPos.node, startPos.offset, endPos.node, endPos.offset);
+ var range = api.createRange(doc);
+ range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset);
+ return range;
+ }
+
+ function moveSelectionToPoints(anchorX, anchorY, focusX, focusY, doc) {
+ var startX, startY, endX, endY;
+
+ // Detect backward selection for coordinates and flip start and end coordinates if necessary
+ var backward = focusY < anchorY || (anchorY == focusY && focusX < anchorX);
+
+ if (backward) {
+ startX = focusX;
+ startY = focusY;
+ endX = anchorX;
+ endY = anchorY;
+ } else {
+ startX = anchorX;
+ startY = anchorY;
+ endX = focusX;
+ endY = focusY;
+ }
+
+ var sel = rangy.getSelection(doc);
+ var range = createRangeFromPoints(startX, startY, endX, endY, doc);
+ sel.setSingleRange(range);
+ return sel;
+ }
+
+ // Test that <span> elements support getBoundingClientRect
+ var span = document.createElement("span");
+ var elementSupportsGetBoundingClientRect = util.isHostMethod(span, "getBoundingClientRect");
+ span = null;
+
+ // Test for getBoundingClientRect support in Range
+ var rangeSupportsGetClientRects = false, rangeSupportsGetBoundingClientRect = false;
+ if (api.features.implementsDomRange) {
+ var testRange = api.createNativeRange();
+ rangeSupportsGetClientRects = util.isHostMethod(testRange, "getClientRects");
+ rangeSupportsGetBoundingClientRect = util.isHostMethod(testRange, "getBoundingClientRect");
+ testRange.detach();
+ }
+
+ util.extend(api.features, {
+ rangeSupportsGetBoundingClientRect: rangeSupportsGetBoundingClientRect,
+ rangeSupportsGetClientRects: rangeSupportsGetClientRects,
+ elementSupportsGetBoundingClientRect: elementSupportsGetBoundingClientRect
+ });
+
+ var createClientBoundaryPosGetter = function(isStart) {
+ return function() {
+ var boundaryRange = this.cloneRange();
+ boundaryRange.collapse(isStart);
+ var rect = boundaryRange.getBoundingClientRect();
+ return {
+ x: rect[isStart ? "left" : "right"],
+ y: rect[isStart ? "top" : "bottom"]
+ };
+ };
+ };
+
+ var rangeProto = api.rangePrototype;
+
+ if (api.features.implementsTextRange && elementSupportsGetBoundingClientRect) {
+ rangeProto.getBoundingClientRect = function() {
+ // We need a TextRange
+ var textRange = WrappedTextRange.rangeToTextRange(this);
+
+ // Work around table problems (table cell bounding rects seem not to count if TextRange spans cells)
+ var cells = this.getNodes([1], function(el) {
+ return /^t[dh]$/i.test(el.tagName);
+ });
+
+ // Merge rects for each cell selected by the range into overall rect
+ var rect, rects = [];
+ if (cells.length > 0) {
+ var lastTable = getAncestorElement(this.startContainer, "table");
+
+ for (var i = 0, cell, tempTextRange, table, subRange, subRect; cell = cells[i]; ++i) {
+ // Handle non-table sections of the range
+ table = getAncestorElement(cell, "table");
+ if (!lastTable || table != lastTable) {
+ // There is a section of the range prior to the current table, or lying between tables.
+ // Merge in its rect
+ subRange = this.cloneRange();
+ if (lastTable) {
+ subRange.setStartAfter(lastTable);
+ }
+ subRange.setEndBefore(table);
+ rects.push(WrappedTextRange.rangeToTextRange(subRange).getBoundingClientRect());
+ }
+
+ if (this.containsNode(cell)) {
+ rects.push(cell.getBoundingClientRect());
+ } else {
+ tempTextRange = textRange.duplicate();
+ tempTextRange.moveToElementText(cell);
+ if (tempTextRange.compareEndPoints("StartToStart", textRange) == -1) {
+ tempTextRange.setEndPoint("StartToStart", textRange);
+ } else if (tempTextRange.compareEndPoints("EndToEnd", textRange) == 1) {
+ tempTextRange.setEndPoint("EndToEnd", textRange);
+ }
+ rects.push(tempTextRange.getBoundingClientRect());
+ }
+ lastTable = table;
+ }
+
+ // Merge in the rect for any content lying after the final table
+ var endTable = getAncestorElement(this.endContainer, "table");
+ if (!endTable && lastTable) {
+ subRange = this.cloneRange();
+ subRange.setStartAfter(lastTable);
+ rects.push(WrappedTextRange.rangeToTextRange(subRange).getBoundingClientRect());
+ }
+ rect = mergeRects(rects);
+ } else {
+ rect = textRange.getBoundingClientRect();
+ }
+
+ return adjustClientRect(rect, dom.getDocument(this.startContainer));
+ };
+ } else if (api.features.implementsDomRange) {
+ var createWrappedRange = function(range) {
+ return (range instanceof WrappedRange) ? range : new WrappedRange(range);
+ };
+
+ if (rangeSupportsGetBoundingClientRect) {
+ rangeProto.getBoundingClientRect = function() {
+ var nativeRange = createWrappedRange(this).nativeRange;
+ // Test for WebKit getBoundingClientRect bug (https://bugs.webkit.org/show_bug.cgi?id=65324)
+ var rect = nativeRange.getBoundingClientRect() || nativeRange.getClientRects()[0];
+ return adjustClientRect(rect, dom.getDocument(this.startContainer));
+ };
+
+ if (rangeSupportsGetClientRects) {
+ var getElementRectsForPosition = function(node, offset) {
+ var children = node.childNodes;
+ //if (offset < children.length)
+ };
+
+ createClientBoundaryPosGetter = function(isStart) {
+ return function() {
+ var rect, nativeRange = createWrappedRange(this).nativeRange;
+ var rects = nativeRange.getClientRects();
+
+ if (rects.length == 0 && elementSupportsGetBoundingClientRect) {
+ if (isStart) {
+
+
+ }
+
+ console.log(nativeRange, nativeRange.getClientRects(), nativeRange.getBoundingClientRect());
+ if (this.collapsed
+ && this.startContainer.nodeType == 1
+ && this.startOffset < this.startContainer.childNodes.length) {
+ var n = this.startContainer.childNodes[this.startOffset];
+ if (n.getClientRects) {
+ console.log(n, n.getClientRects(), this.startContainer.getClientRects())
+ }
+
+ }
+ }
+
+ if (rects.length > 0) {
+ if (isStart) {
+ rect = rects[0];
+ return { x: rect.left, y: rect.top };
+ } else {
+ rect = rects[rects.length - 1];
+ return { x: rect.right, y: rect.bottom };
+ }
+ } else {
+ throw module.createError("Cannot get position for range " + this.inspect());
+ }
+ };
+ }
+ }
+ } else {
+ var getElementBoundingClientRect = elementSupportsGetBoundingClientRect ?
+ function(el) {
+ return adjustClientRect(el.getBoundingClientRect(), dom.getDocument(el));
+ } :
+
+ // This implementation is very naive. There are many browser quirks that make it extremely
+ // difficult to get accurate element coordinates in all situations
+ function(el) {
+ var x = 0, y = 0, offsetEl = el, width = el.offsetWidth, height = el.offsetHeight;
+ while (offsetEl) {
+ x += offsetEl.offsetLeft;
+ y += offsetEl.offsetTop;
+ offsetEl = offsetEl.offsetParent;
+ }
+
+ return adjustClientRect(new Rect(y, x + width, y + height, x), dom.getDocument(el));
+ };
+
+ var getRectFromBoundaries = function(range) {
+ var rect;
+ range.splitBoundaries();
+ var span = document.createElement("span");
+
+ if (range.collapsed) {
+ range.insertNode(span);
+ rect = getElementBoundingClientRect(span);
+ span.parentNode.removeChild(span);
+ } else {
+ // TODO: This isn't right. I'm not sure it can be made right sensibly. Consider what to do.
+ // This doesn't consider all the line boxes it needs to consider.
+ var workingRange = range.cloneRange();
+
+ // Get the start rectangle
+ workingRange.collapse(true);
+ workingRange.insertNode(span);
+ var startRect = getElementBoundingClientRect(span);
+ span.parentNode.removeChild(span);
+
+ // Get the end rectangle
+ workingRange.collapseToPoint(range.endContainer, range.endOffset);
+ workingRange.insertNode(span);
+ var endRect = getElementBoundingClientRect(span);
+ span.parentNode.removeChild(span);
+
+ // Merge the start and end rects
+ var rects = [startRect, endRect];
+
+ // Merge in rectangles for all elements in the range
+ var elements = range.getNodes([1], function(el) {
+ return range.containsNode(el);
+ });
+
+ for (var i = 0, len = elements.length; i < len; ++i) {
+ rects.push(getElementBoundingClientRect(elements[i]));
+ }
+ rect = mergeRects(rects)
+ }
+
+ // Clean up
+ range.normalizeBoundaries();
+ return rect;
+ };
+
+ rangeProto.getBoundingClientRect = function(range) {
+ return getRectFromBoundaries(createWrappedRange(range));
+ };
+ }
+
+ function createDocumentBoundaryPosGetter(isStart) {
+ return function() {
+ var pos = this["get" + (isStart ? "Start" : "End") + "ClientPos"]();
+ var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) );
+ return { x: pos.x + scrollPos.x, y: pos.y + scrollPos.y };
+ };
+ }
+ }
+
+ util.extend(rangeProto, {
+ getBoundingDocumentRect: function() {
+ var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) );
+ return createRelativeRect(this.getBoundingClientRect(), scrollPos.x, scrollPos.y);
+ },
+
+ getStartClientPos: createClientBoundaryPosGetter(true),
+ getEndClientPos: createClientBoundaryPosGetter(false),
+
+ getStartDocumentPos: createDocumentBoundaryPosGetter(true),
+ getEndDocumentPos: createDocumentBoundaryPosGetter(false)
+ });
+
+ // Add Selection methods
+ function compareRanges(r1, r2) {
+ return r1.compareBoundaryPoints(r2.START_TO_START, r2);
+ }
+
+ function createSelectionRectGetter(isDocument) {
+ return function() {
+ var rangeMethodName = "getBounding" + (isDocument ? "Document" : "Client") + "Rect";
+ var rects = [];
+ for (var i = 0, rect = null, rangeRect; i < this.rangeCount; ++i) {
+ rects.push(this.getRangeAt(i)[rangeMethodName]());
+ }
+ return mergeRects(rects);
+ };
+ }
+
+ function createSelectionBoundaryPosGetter(isStart, isDocument) {
+ return function() {
+ if (this.rangeCount == 0) {
+ return null;
+ }
+
+ var posType = isDocument ? "Document" : "Client";
+
+ var ranges = this.getAllRanges();
+ if (ranges.length > 1) {
+ // Order the ranges by position within the DOM
+ ranges.sort(compareRanges);
+ }
+
+ return isStart ?
+ ranges[0]["getStart" + posType + "Pos"]() :
+ ranges[ranges.length - 1]["getEnd" + posType + "Pos"]();
+ };
+ }
+
+ util.extend(api.selectionPrototype, {
+ getBoundingClientRect: createSelectionRectGetter(false),
+ getBoundingDocumentRect: createSelectionRectGetter(true),
+
+ getStartClientPos: createSelectionBoundaryPosGetter(true, false),
+ getEndClientPos: createSelectionBoundaryPosGetter(false, false),
+
+ getStartDocumentPos: createSelectionBoundaryPosGetter(true, true),
+ getEndDocumentPos: createSelectionBoundaryPosGetter(false, true)
+ });
+
+ api.positionFromPoint = function(x, y, doc) {
+ doc = dom.getContentDocument(doc, module, "positionFromPoint");
+ return createCaretPositionFromPointGetter(doc)(doc, x, y);
+ };
+
+ api.createRangeFromPoints = createRangeFromPoints;
+ api.moveSelectionToPoints = moveSelectionToPoints;
+});