rangy-rails 1.3alpha.780.0 → 1.3alpha.804.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,524 @@
1
- /**
2
- * Position module for Rangy.
3
- * Extensions to Range and Selection objects to provide access to pixel positions relative to the viewport or document.
4
- *
5
- * Part of Rangy, a cross-browser JavaScript range and selection library
6
- * http://code.google.com/p/rangy/
7
- *
8
- * Depends on Rangy core.
9
- *
10
- * Copyright 2013, Tim Down
11
- * Licensed under the MIT license.
12
- * Version: 1.3alpha.780M
13
- * Build date: 17 May 2013
14
- */
15
- 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})
1
+ rangy.createModule("Position", ["WrappedSelection"], function(api, module) {
2
+ //var log = log4javascript.getLogger("rangy.position");
3
+
4
+ var NUMBER = "number", UNDEF = "undefined";
5
+ var WrappedRange = api.WrappedRange;
6
+ var WrappedTextRange = api.WrappedTextRange;
7
+ var dom = api.dom, util = api.util, DomPosition = dom.DomPosition;
8
+
9
+ // Feature detection
10
+
11
+ //var caretPositionFromPointSupported = (typeof document.caretPositionFromPoint != UNDEF);
12
+
13
+ // Since Rangy can deal with multiple documents which could be in different modes, we have to do the checks every
14
+ // time, unless we cache a getScrollPosition function in each document. This would necessarily pollute the
15
+ // document's global namespace, which I'm choosing to view as a greater evil than a slight performance hit.
16
+ function getScrollPosition(win) {
17
+ var x = 0, y = 0;
18
+ if (typeof win.pageXOffset == NUMBER && typeof win.pageYOffset == NUMBER) {
19
+ x = win.pageXOffset;
20
+ y = win.pageYOffset;
21
+ } else {
22
+ var doc = win.document;
23
+ var docEl = doc.documentElement;
24
+ var compatMode = doc.compatMode;
25
+ var scrollEl = (typeof compatMode == "string" && compatMode.indexOf("CSS") >= 0 && docEl)
26
+ ? docEl : dom.getBody(doc);
27
+
28
+ if (scrollEl && typeof scrollEl.scrollLeft == NUMBER && typeof scrollEl.scrollTop == NUMBER) {
29
+ try {
30
+ x = scrollEl.scrollLeft;
31
+ y = scrollEl.scrollTop;
32
+ } catch (ex) {}
33
+ }
34
+ }
35
+ return { x: x, y: y };
36
+ }
37
+
38
+ function getAncestorElement(node, tagName) {
39
+ tagName = tagName.toLowerCase();
40
+ while (node) {
41
+ if (node.nodeType == 1 && node.tagName.toLowerCase() == tagName) {
42
+ return node;
43
+ }
44
+ node = node.parentNode;
45
+ }
46
+ return null;
47
+ }
48
+
49
+ function Rect(top, right, bottom, left) {
50
+ this.top = top;
51
+ this.right = right;
52
+ this.bottom = bottom;
53
+ this.left = left;
54
+ this.width = right - left;
55
+ this.height = bottom - top;
56
+ }
57
+
58
+ function createRelativeRect(rect, dx, dy) {
59
+ return new Rect(rect.top + dy, rect.right + dx, rect.bottom + dy, rect.left + dx);
60
+ }
61
+
62
+ function adjustClientRect(rect, doc) {
63
+ // Older IEs have an issue with a two pixel margin on the body element
64
+ var dx = 0, dy = 0;
65
+ var docEl = doc.documentElement, body = dom.getBody(doc);
66
+ var container = (docEl.clientWidth === 0 && typeof body.clientTop == NUMBER) ? body : docEl;
67
+ var clientLeft = container.clientLeft, clientTop = container.clientTop;
68
+ if (clientLeft) {
69
+ dx = -clientLeft;
70
+ }
71
+ if (clientTop) {
72
+ dy = -clientTop;
73
+ }
74
+ return createRelativeRect(rect, dx, dy);
75
+ }
76
+
77
+ function mergeRects(rects) {
78
+ var tops = [], bottoms = [], lefts = [], rights = [];
79
+ for (var i = 0, len = rects.length, rect; i < len; ++i) {
80
+ rect = rects[i];
81
+ if (rect) {
82
+ tops.push(rect.top);
83
+ bottoms.push(rect.bottom);
84
+ lefts.push(rect.left);
85
+ rights.push(rect.right);
86
+ }
87
+ }
88
+ return new Rect(
89
+ Math.min.apply(Math, tops),
90
+ Math.max.apply(Math, rights),
91
+ Math.max.apply(Math, bottoms),
92
+ Math.min.apply(Math, lefts)
93
+ );
94
+ }
95
+
96
+ function getTextRangePosition(doc, x, y) {
97
+ var textRange = dom.getBody(doc).createTextRange();
98
+ textRange.moveToPoint(x, y);
99
+ var range = new api.WrappedTextRange(textRange);
100
+ return new DomPosition(range.startContainer, range.startOffset);
101
+ }
102
+
103
+ function caretPositionFromPoint(doc, x, y) {
104
+ var pos = doc.caretPositionFromPoint(x, y);
105
+ return new DomPosition(pos.offsetNode, pos.offset);
106
+ }
107
+
108
+ function caretRangeFromPoint(doc, x, y) {
109
+ var range = doc.caretRangeFromPoint(x, y);
110
+ return new DomPosition(range.startContainer, range.startOffset);
111
+ }
112
+
113
+ function getLastRangeRect(range) {
114
+ var rects = (range.nativeRange || range).getClientRects();
115
+ return (rects.length > 0) ? rects[rects.length - 1] : null;
116
+ }
117
+
118
+ function pointIsInOrAboveRect(x, y, rect) {
119
+ console.log("pointIsInOrAboveRect", x, y, Math.floor(rect.top), Math.floor(rect.right), Math.floor(rect.bottom), Math.floor(rect.left))
120
+ return y < rect.bottom && x >= rect.left && x <= rect.right;
121
+ }
122
+
123
+ function positionFromPoint(doc, x, y, favourPrecedingPosition) {
124
+ var el = doc.elementFromPoint(x, y);
125
+
126
+ console.log("elementFromPoint is ", el);
127
+
128
+ var range = api.createRange(doc);
129
+ range.selectNodeContents(el);
130
+ range.collapse(true);
131
+
132
+ var node = el.firstChild, offset, rect, textLen;
133
+
134
+ if (!node) {
135
+ node = el.parentNode;
136
+ offset = dom.getNodeIndex(el);
137
+ if (!favourPrecedingPosition) {
138
+ ++offset;
139
+ }
140
+ } else {
141
+ // Search through the text node children of el
142
+ main: while (node) {
143
+ console.log(node);
144
+ if (node.nodeType == 3) {
145
+ // Go through the text node character by character
146
+ for (offset = 0, textLen = node.length; offset <= textLen; ++offset) {
147
+ range.setEnd(node, offset);
148
+ rect = getLastRangeRect(range);
149
+ if (rect && pointIsInOrAboveRect(x, y, rect)) {
150
+ // We've gone past the point. Now we check which side (left or right) of the character the point is nearer to
151
+ if (rect.right - x > x - rect.left) {
152
+ --offset;
153
+ }
154
+ break main;
155
+ }
156
+ }
157
+ } else {
158
+ // Handle elements
159
+ range.setEndAfter(node);
160
+ rect = getLastRangeRect(range);
161
+ if (rect && pointIsInOrAboveRect(x, y, rect)) {
162
+ offset = dom.getNodeIndex(node);
163
+ node = el.parentNode;
164
+ if (!favourPrecedingPosition) {
165
+ ++offset;
166
+ }
167
+ break;
168
+ }
169
+ }
170
+
171
+ node = node.nextSibling;
172
+ }
173
+ if (!node) {
174
+ node = el;
175
+ offset = el.childNodes.length;
176
+ }
177
+ }
178
+
179
+ return new DomPosition(node, offset);
180
+ }
181
+
182
+ function createCaretPositionFromPointGetter(doc) {
183
+ if (api.features.implementsTextRange) {
184
+ return getTextRangePosition;
185
+ } else if (typeof doc.caretPositionFromPoint != UNDEF) {
186
+ return caretPositionFromPoint;
187
+ } else if (typeof doc.caretRangeFromPoint != UNDEF) {
188
+ return caretRangeFromPoint;
189
+ } else if (typeof doc.elementFromPoint != UNDEF && rangeSupportsGetClientRects) {
190
+ return positionFromPoint;
191
+ } else {
192
+ throw module.createError("createCaretPositionFromPointGetter(): Browser does not provide a recognised method to create a selection from pixel coordinates");
193
+ }
194
+ }
195
+
196
+ function createRangeFromPoints(startX, startY, endX, endY, doc) {
197
+ doc = dom.getContentDocument(doc, module, "createRangeFromPoints");
198
+ var positionFinder = createCaretPositionFromPointGetter(doc);
199
+ var startPos = positionFinder(doc, startX, startY, false);
200
+ var endPos = positionFinder(doc, endX, endY, true);
201
+ console.log(startPos.node, startPos.offset, endPos.node, endPos.offset);
202
+ var range = api.createRange(doc);
203
+ range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset);
204
+ return range;
205
+ }
206
+
207
+ function moveSelectionToPoints(anchorX, anchorY, focusX, focusY, doc) {
208
+ var startX, startY, endX, endY;
209
+
210
+ // Detect backward selection for coordinates and flip start and end coordinates if necessary
211
+ var backward = focusY < anchorY || (anchorY == focusY && focusX < anchorX);
212
+
213
+ if (backward) {
214
+ startX = focusX;
215
+ startY = focusY;
216
+ endX = anchorX;
217
+ endY = anchorY;
218
+ } else {
219
+ startX = anchorX;
220
+ startY = anchorY;
221
+ endX = focusX;
222
+ endY = focusY;
223
+ }
224
+
225
+ var sel = rangy.getSelection(doc);
226
+ var range = createRangeFromPoints(startX, startY, endX, endY, doc);
227
+ sel.setSingleRange(range);
228
+ return sel;
229
+ }
230
+
231
+ // Test that <span> elements support getBoundingClientRect
232
+ var span = document.createElement("span");
233
+ var elementSupportsGetBoundingClientRect = util.isHostMethod(span, "getBoundingClientRect");
234
+ span = null;
235
+
236
+ // Test for getBoundingClientRect support in Range
237
+ var rangeSupportsGetClientRects = false, rangeSupportsGetBoundingClientRect = false;
238
+ if (api.features.implementsDomRange) {
239
+ var testRange = api.createNativeRange();
240
+ rangeSupportsGetClientRects = util.isHostMethod(testRange, "getClientRects");
241
+ rangeSupportsGetBoundingClientRect = util.isHostMethod(testRange, "getBoundingClientRect");
242
+ testRange.detach();
243
+ }
244
+
245
+ util.extend(api.features, {
246
+ rangeSupportsGetBoundingClientRect: rangeSupportsGetBoundingClientRect,
247
+ rangeSupportsGetClientRects: rangeSupportsGetClientRects,
248
+ elementSupportsGetBoundingClientRect: elementSupportsGetBoundingClientRect
249
+ });
250
+
251
+ var createClientBoundaryPosGetter = function(isStart) {
252
+ return function() {
253
+ var boundaryRange = this.cloneRange();
254
+ boundaryRange.collapse(isStart);
255
+ var rect = boundaryRange.getBoundingClientRect();
256
+ return {
257
+ x: rect[isStart ? "left" : "right"],
258
+ y: rect[isStart ? "top" : "bottom"]
259
+ };
260
+ };
261
+ };
262
+
263
+ var rangeProto = api.rangePrototype;
264
+
265
+ if (api.features.implementsTextRange && elementSupportsGetBoundingClientRect) {
266
+ rangeProto.getBoundingClientRect = function() {
267
+ // We need a TextRange
268
+ var textRange = WrappedTextRange.rangeToTextRange(this);
269
+
270
+ // Work around table problems (table cell bounding rects seem not to count if TextRange spans cells)
271
+ var cells = this.getNodes([1], function(el) {
272
+ return /^t[dh]$/i.test(el.tagName);
273
+ });
274
+
275
+ // Merge rects for each cell selected by the range into overall rect
276
+ var rect, rects = [];
277
+ if (cells.length > 0) {
278
+ var lastTable = getAncestorElement(this.startContainer, "table");
279
+
280
+ for (var i = 0, cell, tempTextRange, table, subRange, subRect; cell = cells[i]; ++i) {
281
+ // Handle non-table sections of the range
282
+ table = getAncestorElement(cell, "table");
283
+ if (!lastTable || table != lastTable) {
284
+ // There is a section of the range prior to the current table, or lying between tables.
285
+ // Merge in its rect
286
+ subRange = this.cloneRange();
287
+ if (lastTable) {
288
+ subRange.setStartAfter(lastTable);
289
+ }
290
+ subRange.setEndBefore(table);
291
+ rects.push(WrappedTextRange.rangeToTextRange(subRange).getBoundingClientRect());
292
+ }
293
+
294
+ if (this.containsNode(cell)) {
295
+ rects.push(cell.getBoundingClientRect());
296
+ } else {
297
+ tempTextRange = textRange.duplicate();
298
+ tempTextRange.moveToElementText(cell);
299
+ if (tempTextRange.compareEndPoints("StartToStart", textRange) == -1) {
300
+ tempTextRange.setEndPoint("StartToStart", textRange);
301
+ } else if (tempTextRange.compareEndPoints("EndToEnd", textRange) == 1) {
302
+ tempTextRange.setEndPoint("EndToEnd", textRange);
303
+ }
304
+ rects.push(tempTextRange.getBoundingClientRect());
305
+ }
306
+ lastTable = table;
307
+ }
308
+
309
+ // Merge in the rect for any content lying after the final table
310
+ var endTable = getAncestorElement(this.endContainer, "table");
311
+ if (!endTable && lastTable) {
312
+ subRange = this.cloneRange();
313
+ subRange.setStartAfter(lastTable);
314
+ rects.push(WrappedTextRange.rangeToTextRange(subRange).getBoundingClientRect());
315
+ }
316
+ rect = mergeRects(rects);
317
+ } else {
318
+ rect = textRange.getBoundingClientRect();
319
+ }
320
+
321
+ return adjustClientRect(rect, dom.getDocument(this.startContainer));
322
+ };
323
+ } else if (api.features.implementsDomRange) {
324
+ var createWrappedRange = function(range) {
325
+ return (range instanceof WrappedRange) ? range : new WrappedRange(range);
326
+ };
327
+
328
+ if (rangeSupportsGetBoundingClientRect) {
329
+ rangeProto.getBoundingClientRect = function() {
330
+ var nativeRange = createWrappedRange(this).nativeRange;
331
+ // Test for WebKit getBoundingClientRect bug (https://bugs.webkit.org/show_bug.cgi?id=65324)
332
+ var rect = nativeRange.getBoundingClientRect() || nativeRange.getClientRects()[0];
333
+ return adjustClientRect(rect, dom.getDocument(this.startContainer));
334
+ };
335
+
336
+ if (rangeSupportsGetClientRects) {
337
+ var getElementRectsForPosition = function(node, offset) {
338
+ var children = node.childNodes;
339
+ //if (offset < children.length)
340
+ };
341
+
342
+ createClientBoundaryPosGetter = function(isStart) {
343
+ return function() {
344
+ var rect, nativeRange = createWrappedRange(this).nativeRange;
345
+ var rects = nativeRange.getClientRects();
346
+
347
+ if (rects.length == 0 && elementSupportsGetBoundingClientRect) {
348
+ if (isStart) {
349
+
350
+
351
+ }
352
+
353
+ console.log(nativeRange, nativeRange.getClientRects(), nativeRange.getBoundingClientRect());
354
+ if (this.collapsed
355
+ && this.startContainer.nodeType == 1
356
+ && this.startOffset < this.startContainer.childNodes.length) {
357
+ var n = this.startContainer.childNodes[this.startOffset];
358
+ if (n.getClientRects) {
359
+ console.log(n, n.getClientRects(), this.startContainer.getClientRects())
360
+ }
361
+
362
+ }
363
+ }
364
+
365
+ if (rects.length > 0) {
366
+ if (isStart) {
367
+ rect = rects[0];
368
+ return { x: rect.left, y: rect.top };
369
+ } else {
370
+ rect = rects[rects.length - 1];
371
+ return { x: rect.right, y: rect.bottom };
372
+ }
373
+ } else {
374
+ throw module.createError("Cannot get position for range " + this.inspect());
375
+ }
376
+ };
377
+ }
378
+ }
379
+ } else {
380
+ var getElementBoundingClientRect = elementSupportsGetBoundingClientRect ?
381
+ function(el) {
382
+ return adjustClientRect(el.getBoundingClientRect(), dom.getDocument(el));
383
+ } :
384
+
385
+ // This implementation is very naive. There are many browser quirks that make it extremely
386
+ // difficult to get accurate element coordinates in all situations
387
+ function(el) {
388
+ var x = 0, y = 0, offsetEl = el, width = el.offsetWidth, height = el.offsetHeight;
389
+ while (offsetEl) {
390
+ x += offsetEl.offsetLeft;
391
+ y += offsetEl.offsetTop;
392
+ offsetEl = offsetEl.offsetParent;
393
+ }
394
+
395
+ return adjustClientRect(new Rect(y, x + width, y + height, x), dom.getDocument(el));
396
+ };
397
+
398
+ var getRectFromBoundaries = function(range) {
399
+ var rect;
400
+ range.splitBoundaries();
401
+ var span = document.createElement("span");
402
+
403
+ if (range.collapsed) {
404
+ range.insertNode(span);
405
+ rect = getElementBoundingClientRect(span);
406
+ span.parentNode.removeChild(span);
407
+ } else {
408
+ // TODO: This isn't right. I'm not sure it can be made right sensibly. Consider what to do.
409
+ // This doesn't consider all the line boxes it needs to consider.
410
+ var workingRange = range.cloneRange();
411
+
412
+ // Get the start rectangle
413
+ workingRange.collapse(true);
414
+ workingRange.insertNode(span);
415
+ var startRect = getElementBoundingClientRect(span);
416
+ span.parentNode.removeChild(span);
417
+
418
+ // Get the end rectangle
419
+ workingRange.collapseToPoint(range.endContainer, range.endOffset);
420
+ workingRange.insertNode(span);
421
+ var endRect = getElementBoundingClientRect(span);
422
+ span.parentNode.removeChild(span);
423
+
424
+ // Merge the start and end rects
425
+ var rects = [startRect, endRect];
426
+
427
+ // Merge in rectangles for all elements in the range
428
+ var elements = range.getNodes([1], function(el) {
429
+ return range.containsNode(el);
430
+ });
431
+
432
+ for (var i = 0, len = elements.length; i < len; ++i) {
433
+ rects.push(getElementBoundingClientRect(elements[i]));
434
+ }
435
+ rect = mergeRects(rects)
436
+ }
437
+
438
+ // Clean up
439
+ range.normalizeBoundaries();
440
+ return rect;
441
+ };
442
+
443
+ rangeProto.getBoundingClientRect = function(range) {
444
+ return getRectFromBoundaries(createWrappedRange(range));
445
+ };
446
+ }
447
+
448
+ function createDocumentBoundaryPosGetter(isStart) {
449
+ return function() {
450
+ var pos = this["get" + (isStart ? "Start" : "End") + "ClientPos"]();
451
+ var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) );
452
+ return { x: pos.x + scrollPos.x, y: pos.y + scrollPos.y };
453
+ };
454
+ }
455
+ }
456
+
457
+ util.extend(rangeProto, {
458
+ getBoundingDocumentRect: function() {
459
+ var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) );
460
+ return createRelativeRect(this.getBoundingClientRect(), scrollPos.x, scrollPos.y);
461
+ },
462
+
463
+ getStartClientPos: createClientBoundaryPosGetter(true),
464
+ getEndClientPos: createClientBoundaryPosGetter(false),
465
+
466
+ getStartDocumentPos: createDocumentBoundaryPosGetter(true),
467
+ getEndDocumentPos: createDocumentBoundaryPosGetter(false)
468
+ });
469
+
470
+ // Add Selection methods
471
+ function compareRanges(r1, r2) {
472
+ return r1.compareBoundaryPoints(r2.START_TO_START, r2);
473
+ }
474
+
475
+ function createSelectionRectGetter(isDocument) {
476
+ return function() {
477
+ var rangeMethodName = "getBounding" + (isDocument ? "Document" : "Client") + "Rect";
478
+ var rects = [];
479
+ for (var i = 0, rect = null, rangeRect; i < this.rangeCount; ++i) {
480
+ rects.push(this.getRangeAt(i)[rangeMethodName]());
481
+ }
482
+ return mergeRects(rects);
483
+ };
484
+ }
485
+
486
+ function createSelectionBoundaryPosGetter(isStart, isDocument) {
487
+ return function() {
488
+ if (this.rangeCount == 0) {
489
+ return null;
490
+ }
491
+
492
+ var posType = isDocument ? "Document" : "Client";
493
+
494
+ var ranges = this.getAllRanges();
495
+ if (ranges.length > 1) {
496
+ // Order the ranges by position within the DOM
497
+ ranges.sort(compareRanges);
498
+ }
499
+
500
+ return isStart ?
501
+ ranges[0]["getStart" + posType + "Pos"]() :
502
+ ranges[ranges.length - 1]["getEnd" + posType + "Pos"]();
503
+ };
504
+ }
505
+
506
+ util.extend(api.selectionPrototype, {
507
+ getBoundingClientRect: createSelectionRectGetter(false),
508
+ getBoundingDocumentRect: createSelectionRectGetter(true),
509
+
510
+ getStartClientPos: createSelectionBoundaryPosGetter(true, false),
511
+ getEndClientPos: createSelectionBoundaryPosGetter(false, false),
512
+
513
+ getStartDocumentPos: createSelectionBoundaryPosGetter(true, true),
514
+ getEndDocumentPos: createSelectionBoundaryPosGetter(false, true)
515
+ });
516
+
517
+ api.positionFromPoint = function(x, y, doc) {
518
+ doc = dom.getContentDocument(doc, module, "positionFromPoint");
519
+ return createCaretPositionFromPointGetter(doc)(doc, x, y);
520
+ };
521
+
522
+ api.createRangeFromPoints = createRangeFromPoints;
523
+ api.moveSelectionToPoints = moveSelectionToPoints;
524
+ });