[483] | 1 | define([ |
---|
| 2 | "dojo/_base/array", |
---|
| 3 | "dojo/dom", // dom.byId |
---|
| 4 | "dojo/_base/lang", |
---|
| 5 | "dojo/sniff", // has("ie") has("opera") |
---|
| 6 | "dojo/_base/window", |
---|
| 7 | "dijit/focus" |
---|
| 8 | ], function(array, dom, lang, has, baseWindow, focus){ |
---|
| 9 | |
---|
| 10 | // module: |
---|
| 11 | // dijit/selection |
---|
| 12 | |
---|
| 13 | // Note that this class is using feature detection, but doesn't use has() because sometimes on IE the outer window |
---|
| 14 | // may be running in standards mode (ie, IE9 mode) but an iframe may be in compatibility mode. So the code path |
---|
| 15 | // used will vary based on the window. |
---|
| 16 | |
---|
| 17 | var SelectionManager = function(win){ |
---|
| 18 | // summary: |
---|
| 19 | // Class for monitoring / changing the selection (typically highlighted text) in a given window |
---|
| 20 | // win: Window |
---|
| 21 | // The window to monitor/adjust the selection on. |
---|
| 22 | |
---|
| 23 | var doc = win.document; |
---|
| 24 | |
---|
| 25 | this.getType = function(){ |
---|
| 26 | // summary: |
---|
| 27 | // Get the selection type (like doc.select.type in IE). |
---|
| 28 | if(doc.getSelection){ |
---|
| 29 | // W3C path |
---|
| 30 | var stype = "text"; |
---|
| 31 | |
---|
| 32 | // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...). |
---|
| 33 | var oSel; |
---|
| 34 | try{ |
---|
| 35 | oSel = win.getSelection(); |
---|
| 36 | }catch(e){ /*squelch*/ } |
---|
| 37 | |
---|
| 38 | if(oSel && oSel.rangeCount == 1){ |
---|
| 39 | var oRange = oSel.getRangeAt(0); |
---|
| 40 | if( (oRange.startContainer == oRange.endContainer) && |
---|
| 41 | ((oRange.endOffset - oRange.startOffset) == 1) && |
---|
| 42 | (oRange.startContainer.nodeType != 3 /* text node*/) |
---|
| 43 | ){ |
---|
| 44 | stype = "control"; |
---|
| 45 | } |
---|
| 46 | } |
---|
| 47 | return stype; //String |
---|
| 48 | }else{ |
---|
| 49 | // IE6-8 |
---|
| 50 | return doc.selection.type.toLowerCase(); |
---|
| 51 | } |
---|
| 52 | }; |
---|
| 53 | |
---|
| 54 | this.getSelectedText = function(){ |
---|
| 55 | // summary: |
---|
| 56 | // Return the text (no html tags) included in the current selection or null if no text is selected |
---|
| 57 | if(doc.getSelection){ |
---|
| 58 | // W3C path |
---|
| 59 | var selection = win.getSelection(); |
---|
| 60 | return selection ? selection.toString() : ""; //String |
---|
| 61 | }else{ |
---|
| 62 | // IE6-8 |
---|
| 63 | if(this.getType() == 'control'){ |
---|
| 64 | return null; |
---|
| 65 | } |
---|
| 66 | return doc.selection.createRange().text; |
---|
| 67 | } |
---|
| 68 | }; |
---|
| 69 | |
---|
| 70 | this.getSelectedHtml = function(){ |
---|
| 71 | // summary: |
---|
| 72 | // Return the html text of the current selection or null if unavailable |
---|
| 73 | if(doc.getSelection){ |
---|
| 74 | // W3C path |
---|
| 75 | var selection = win.getSelection(); |
---|
| 76 | if(selection && selection.rangeCount){ |
---|
| 77 | var i; |
---|
| 78 | var html = ""; |
---|
| 79 | for(i = 0; i < selection.rangeCount; i++){ |
---|
| 80 | //Handle selections spanning ranges, such as Opera |
---|
| 81 | var frag = selection.getRangeAt(i).cloneContents(); |
---|
| 82 | var div = doc.createElement("div"); |
---|
| 83 | div.appendChild(frag); |
---|
| 84 | html += div.innerHTML; |
---|
| 85 | } |
---|
| 86 | return html; //String |
---|
| 87 | } |
---|
| 88 | return null; |
---|
| 89 | }else{ |
---|
| 90 | // IE6-8 |
---|
| 91 | if(this.getType() == 'control'){ |
---|
| 92 | return null; |
---|
| 93 | } |
---|
| 94 | return doc.selection.createRange().htmlText; |
---|
| 95 | } |
---|
| 96 | }; |
---|
| 97 | |
---|
| 98 | this.getSelectedElement = function(){ |
---|
| 99 | // summary: |
---|
| 100 | // Retrieves the selected element (if any), just in the case that |
---|
| 101 | // a single element (object like and image or a table) is |
---|
| 102 | // selected. |
---|
| 103 | if(this.getType() == "control"){ |
---|
| 104 | if(doc.getSelection){ |
---|
| 105 | // W3C path |
---|
| 106 | var selection = win.getSelection(); |
---|
| 107 | return selection.anchorNode.childNodes[ selection.anchorOffset ]; |
---|
| 108 | }else{ |
---|
| 109 | // IE6-8 |
---|
| 110 | var range = doc.selection.createRange(); |
---|
| 111 | if(range && range.item){ |
---|
| 112 | return doc.selection.createRange().item(0); |
---|
| 113 | } |
---|
| 114 | } |
---|
| 115 | } |
---|
| 116 | return null; |
---|
| 117 | }; |
---|
| 118 | |
---|
| 119 | this.getParentElement = function(){ |
---|
| 120 | // summary: |
---|
| 121 | // Get the parent element of the current selection |
---|
| 122 | if(this.getType() == "control"){ |
---|
| 123 | var p = this.getSelectedElement(); |
---|
| 124 | if(p){ return p.parentNode; } |
---|
| 125 | }else{ |
---|
| 126 | if(doc.getSelection){ |
---|
| 127 | var selection = doc.getSelection(); |
---|
| 128 | if(selection){ |
---|
| 129 | var node = selection.anchorNode; |
---|
| 130 | while(node && (node.nodeType != 1)){ // not an element |
---|
| 131 | node = node.parentNode; |
---|
| 132 | } |
---|
| 133 | return node; |
---|
| 134 | } |
---|
| 135 | }else{ |
---|
| 136 | var r = doc.selection.createRange(); |
---|
| 137 | r.collapse(true); |
---|
| 138 | return r.parentElement(); |
---|
| 139 | } |
---|
| 140 | } |
---|
| 141 | return null; |
---|
| 142 | }; |
---|
| 143 | |
---|
| 144 | this.hasAncestorElement = function(/*String*/ tagName /* ... */){ |
---|
| 145 | // summary: |
---|
| 146 | // Check whether current selection has a parent element which is |
---|
| 147 | // of type tagName (or one of the other specified tagName) |
---|
| 148 | // tagName: String |
---|
| 149 | // The tag name to determine if it has an ancestor of. |
---|
| 150 | return this.getAncestorElement.apply(this, arguments) != null; //Boolean |
---|
| 151 | }; |
---|
| 152 | |
---|
| 153 | this.getAncestorElement = function(/*String*/ tagName /* ... */){ |
---|
| 154 | // summary: |
---|
| 155 | // Return the parent element of the current selection which is of |
---|
| 156 | // type tagName (or one of the other specified tagName) |
---|
| 157 | // tagName: String |
---|
| 158 | // The tag name to determine if it has an ancestor of. |
---|
| 159 | var node = this.getSelectedElement() || this.getParentElement(); |
---|
| 160 | return this.getParentOfType(node, arguments); //DOMNode |
---|
| 161 | }; |
---|
| 162 | |
---|
| 163 | this.isTag = function(/*DomNode*/ node, /*String[]*/ tags){ |
---|
| 164 | // summary: |
---|
| 165 | // Function to determine if a node is one of an array of tags. |
---|
| 166 | // node: |
---|
| 167 | // The node to inspect. |
---|
| 168 | // tags: |
---|
| 169 | // An array of tag name strings to check to see if the node matches. |
---|
| 170 | if(node && node.tagName){ |
---|
| 171 | var _nlc = node.tagName.toLowerCase(); |
---|
| 172 | for(var i=0; i<tags.length; i++){ |
---|
| 173 | var _tlc = String(tags[i]).toLowerCase(); |
---|
| 174 | if(_nlc == _tlc){ |
---|
| 175 | return _tlc; // String |
---|
| 176 | } |
---|
| 177 | } |
---|
| 178 | } |
---|
| 179 | return ""; |
---|
| 180 | }; |
---|
| 181 | |
---|
| 182 | this.getParentOfType = function(/*DomNode*/ node, /*String[]*/ tags){ |
---|
| 183 | // summary: |
---|
| 184 | // Function to locate a parent node that matches one of a set of tags |
---|
| 185 | // node: |
---|
| 186 | // The node to inspect. |
---|
| 187 | // tags: |
---|
| 188 | // An array of tag name strings to check to see if the node matches. |
---|
| 189 | while(node){ |
---|
| 190 | if(this.isTag(node, tags).length){ |
---|
| 191 | return node; // DOMNode |
---|
| 192 | } |
---|
| 193 | node = node.parentNode; |
---|
| 194 | } |
---|
| 195 | return null; |
---|
| 196 | }; |
---|
| 197 | |
---|
| 198 | this.collapse = function(/*Boolean*/ beginning){ |
---|
| 199 | // summary: |
---|
| 200 | // Function to collapse (clear), the current selection |
---|
| 201 | // beginning: Boolean |
---|
| 202 | // Indicates whether to collapse the cursor to the beginning of the selection or end. |
---|
| 203 | if(doc.getSelection){ |
---|
| 204 | // W3C path |
---|
| 205 | var selection = win.getSelection(); |
---|
| 206 | if(selection.removeAllRanges){ // Mozilla |
---|
| 207 | if(beginning){ |
---|
| 208 | selection.collapseToStart(); |
---|
| 209 | }else{ |
---|
| 210 | selection.collapseToEnd(); |
---|
| 211 | } |
---|
| 212 | }else{ // Safari |
---|
| 213 | // pulled from WebCore/ecma/kjs_window.cpp, line 2536 |
---|
| 214 | selection.collapse(beginning); |
---|
| 215 | } |
---|
| 216 | }else{ |
---|
| 217 | // IE6-8 |
---|
| 218 | var range = doc.selection.createRange(); |
---|
| 219 | range.collapse(beginning); |
---|
| 220 | range.select(); |
---|
| 221 | } |
---|
| 222 | }; |
---|
| 223 | |
---|
| 224 | this.remove = function(){ |
---|
| 225 | // summary: |
---|
| 226 | // Function to delete the currently selected content from the document. |
---|
| 227 | var sel = doc.selection; |
---|
| 228 | if(doc.getSelection){ |
---|
| 229 | // W3C path |
---|
| 230 | sel = win.getSelection(); |
---|
| 231 | sel.deleteFromDocument(); |
---|
| 232 | return sel; //Selection |
---|
| 233 | }else{ |
---|
| 234 | // IE6-8 |
---|
| 235 | if(sel.type.toLowerCase() != "none"){ |
---|
| 236 | sel.clear(); |
---|
| 237 | } |
---|
| 238 | return sel; //Selection |
---|
| 239 | } |
---|
| 240 | }; |
---|
| 241 | |
---|
| 242 | this.selectElementChildren = function(/*DomNode*/ element, /*Boolean?*/ nochangefocus){ |
---|
| 243 | // summary: |
---|
| 244 | // clear previous selection and select the content of the node |
---|
| 245 | // (excluding the node itself) |
---|
| 246 | // element: DOMNode |
---|
| 247 | // The element you wish to select the children content of. |
---|
| 248 | // nochangefocus: Boolean |
---|
| 249 | // Indicates if the focus should change or not. |
---|
| 250 | |
---|
| 251 | var range; |
---|
| 252 | element = dom.byId(element); |
---|
| 253 | if(doc.getSelection){ |
---|
| 254 | // W3C |
---|
| 255 | var selection = win.getSelection(); |
---|
| 256 | if(has("opera")){ |
---|
| 257 | //Opera's selectAllChildren doesn't seem to work right |
---|
| 258 | //against <body> nodes and possibly others ... so |
---|
| 259 | //we use the W3C range API |
---|
| 260 | if(selection.rangeCount){ |
---|
| 261 | range = selection.getRangeAt(0); |
---|
| 262 | }else{ |
---|
| 263 | range = doc.createRange(); |
---|
| 264 | } |
---|
| 265 | range.setStart(element, 0); |
---|
| 266 | range.setEnd(element,(element.nodeType == 3) ? element.length : element.childNodes.length); |
---|
| 267 | selection.addRange(range); |
---|
| 268 | }else{ |
---|
| 269 | selection.selectAllChildren(element); |
---|
| 270 | } |
---|
| 271 | }else{ |
---|
| 272 | // IE6-8 |
---|
| 273 | range = element.ownerDocument.body.createTextRange(); |
---|
| 274 | range.moveToElementText(element); |
---|
| 275 | if(!nochangefocus){ |
---|
| 276 | try{ |
---|
| 277 | range.select(); // IE throws an exception here if the widget is hidden. See #5439 |
---|
| 278 | }catch(e){ /* squelch */} |
---|
| 279 | } |
---|
| 280 | } |
---|
| 281 | }; |
---|
| 282 | |
---|
| 283 | this.selectElement = function(/*DomNode*/ element, /*Boolean?*/ nochangefocus){ |
---|
| 284 | // summary: |
---|
| 285 | // clear previous selection and select element (including all its children) |
---|
| 286 | // element: DOMNode |
---|
| 287 | // The element to select. |
---|
| 288 | // nochangefocus: Boolean |
---|
| 289 | // Boolean indicating if the focus should be changed. IE only. |
---|
| 290 | var range; |
---|
| 291 | element = dom.byId(element); // TODO: remove for 2.0 or sooner, spec listed above doesn't allow for string |
---|
| 292 | if(doc.getSelection){ |
---|
| 293 | // W3C path |
---|
| 294 | var selection = doc.getSelection(); |
---|
| 295 | range = doc.createRange(); |
---|
| 296 | if(selection.removeAllRanges){ // Mozilla |
---|
| 297 | // FIXME: does this work on Safari? |
---|
| 298 | if(has("opera")){ |
---|
| 299 | //Opera works if you use the current range on |
---|
| 300 | //the selection if present. |
---|
| 301 | if(selection.getRangeAt(0)){ |
---|
| 302 | range = selection.getRangeAt(0); |
---|
| 303 | } |
---|
| 304 | } |
---|
| 305 | range.selectNode(element); |
---|
| 306 | selection.removeAllRanges(); |
---|
| 307 | selection.addRange(range); |
---|
| 308 | } |
---|
| 309 | }else{ |
---|
| 310 | // IE6-8 |
---|
| 311 | try{ |
---|
| 312 | var tg = element.tagName ? element.tagName.toLowerCase() : ""; |
---|
| 313 | if(tg === "img" || tg === "table"){ |
---|
| 314 | range = baseWindow.body(doc).createControlRange(); |
---|
| 315 | }else{ |
---|
| 316 | range = baseWindow.body(doc).createRange(); |
---|
| 317 | } |
---|
| 318 | range.addElement(element); |
---|
| 319 | if(!nochangefocus){ |
---|
| 320 | range.select(); |
---|
| 321 | } |
---|
| 322 | }catch(e){ |
---|
| 323 | this.selectElementChildren(element, nochangefocus); |
---|
| 324 | } |
---|
| 325 | } |
---|
| 326 | }; |
---|
| 327 | |
---|
| 328 | this.inSelection = function(node){ |
---|
| 329 | // summary: |
---|
| 330 | // This function determines if 'node' is |
---|
| 331 | // in the current selection. |
---|
| 332 | // tags: |
---|
| 333 | // public |
---|
| 334 | if(node){ |
---|
| 335 | var newRange; |
---|
| 336 | var range; |
---|
| 337 | |
---|
| 338 | if(doc.getSelection){ |
---|
| 339 | // WC3 |
---|
| 340 | var sel = win.getSelection(); |
---|
| 341 | if(sel && sel.rangeCount > 0){ |
---|
| 342 | range = sel.getRangeAt(0); |
---|
| 343 | } |
---|
| 344 | if(range && range.compareBoundaryPoints && doc.createRange){ |
---|
| 345 | try{ |
---|
| 346 | newRange = doc.createRange(); |
---|
| 347 | newRange.setStart(node, 0); |
---|
| 348 | if(range.compareBoundaryPoints(range.START_TO_END, newRange) === 1){ |
---|
| 349 | return true; |
---|
| 350 | } |
---|
| 351 | }catch(e){ /* squelch */} |
---|
| 352 | } |
---|
| 353 | }else{ |
---|
| 354 | // IE6-8, so we can't use the range object as the pseudo |
---|
| 355 | // range doesn't implement the boundary checking, we have to |
---|
| 356 | // use IE specific crud. |
---|
| 357 | range = doc.selection.createRange(); |
---|
| 358 | try{ |
---|
| 359 | newRange = node.ownerDocument.body.createTextRange(); |
---|
| 360 | newRange.moveToElementText(node); |
---|
| 361 | }catch(e2){/* squelch */} |
---|
| 362 | if(range && newRange){ |
---|
| 363 | // We can finally compare similar to W3C |
---|
| 364 | if(range.compareEndPoints("EndToStart", newRange) === 1){ |
---|
| 365 | return true; |
---|
| 366 | } |
---|
| 367 | } |
---|
| 368 | } |
---|
| 369 | } |
---|
| 370 | return false; // Boolean |
---|
| 371 | }, |
---|
| 372 | |
---|
| 373 | this.getBookmark = function(){ |
---|
| 374 | // summary: |
---|
| 375 | // Retrieves a bookmark that can be used with moveToBookmark to reselect the currently selected range. |
---|
| 376 | |
---|
| 377 | // TODO: merge additional code from Editor._getBookmark into this method |
---|
| 378 | |
---|
| 379 | var bm, rg, tg, sel = doc.selection, cf = focus.curNode; |
---|
| 380 | |
---|
| 381 | if(doc.getSelection){ |
---|
| 382 | // W3C Range API for selections. |
---|
| 383 | sel = win.getSelection(); |
---|
| 384 | if(sel){ |
---|
| 385 | if(sel.isCollapsed){ |
---|
| 386 | tg = cf? cf.tagName : ""; |
---|
| 387 | if(tg){ |
---|
| 388 | // Create a fake rangelike item to restore selections. |
---|
| 389 | tg = tg.toLowerCase(); |
---|
| 390 | if(tg == "textarea" || |
---|
| 391 | (tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){ |
---|
| 392 | sel = { |
---|
| 393 | start: cf.selectionStart, |
---|
| 394 | end: cf.selectionEnd, |
---|
| 395 | node: cf, |
---|
| 396 | pRange: true |
---|
| 397 | }; |
---|
| 398 | return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object. |
---|
| 399 | } |
---|
| 400 | } |
---|
| 401 | bm = {isCollapsed:true}; |
---|
| 402 | if(sel.rangeCount){ |
---|
| 403 | bm.mark = sel.getRangeAt(0).cloneRange(); |
---|
| 404 | } |
---|
| 405 | }else{ |
---|
| 406 | rg = sel.getRangeAt(0); |
---|
| 407 | bm = {isCollapsed: false, mark: rg.cloneRange()}; |
---|
| 408 | } |
---|
| 409 | } |
---|
| 410 | }else if(sel){ |
---|
| 411 | // If the current focus was a input of some sort and no selection, don't bother saving |
---|
| 412 | // a native bookmark. This is because it causes issues with dialog/page selection restore. |
---|
| 413 | // So, we need to create pseudo bookmarks to work with. |
---|
| 414 | tg = cf ? cf.tagName : ""; |
---|
| 415 | tg = tg.toLowerCase(); |
---|
| 416 | if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){ |
---|
| 417 | if(sel.type && sel.type.toLowerCase() == "none"){ |
---|
| 418 | return { |
---|
| 419 | isCollapsed: true, |
---|
| 420 | mark: null |
---|
| 421 | } |
---|
| 422 | }else{ |
---|
| 423 | rg = sel.createRange(); |
---|
| 424 | return { |
---|
| 425 | isCollapsed: rg.text && rg.text.length?false:true, |
---|
| 426 | mark: { |
---|
| 427 | range: rg, |
---|
| 428 | pRange: true |
---|
| 429 | } |
---|
| 430 | }; |
---|
| 431 | } |
---|
| 432 | } |
---|
| 433 | bm = {}; |
---|
| 434 | |
---|
| 435 | //'IE' way for selections. |
---|
| 436 | try{ |
---|
| 437 | // createRange() throws exception when dojo in iframe |
---|
| 438 | // and nothing selected, see #9632 |
---|
| 439 | rg = sel.createRange(); |
---|
| 440 | bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length); |
---|
| 441 | }catch(e){ |
---|
| 442 | bm.isCollapsed = true; |
---|
| 443 | return bm; |
---|
| 444 | } |
---|
| 445 | if(sel.type.toUpperCase() == 'CONTROL'){ |
---|
| 446 | if(rg.length){ |
---|
| 447 | bm.mark=[]; |
---|
| 448 | var i=0,len=rg.length; |
---|
| 449 | while(i<len){ |
---|
| 450 | bm.mark.push(rg.item(i++)); |
---|
| 451 | } |
---|
| 452 | }else{ |
---|
| 453 | bm.isCollapsed = true; |
---|
| 454 | bm.mark = null; |
---|
| 455 | } |
---|
| 456 | }else{ |
---|
| 457 | bm.mark = rg.getBookmark(); |
---|
| 458 | } |
---|
| 459 | }else{ |
---|
| 460 | console.warn("No idea how to store the current selection for this browser!"); |
---|
| 461 | } |
---|
| 462 | return bm; // Object |
---|
| 463 | }; |
---|
| 464 | |
---|
| 465 | this.moveToBookmark = function(/*Object*/ bookmark){ |
---|
| 466 | // summary: |
---|
| 467 | // Moves current selection to a bookmark. |
---|
| 468 | // bookmark: |
---|
| 469 | // This should be a returned object from getBookmark(). |
---|
| 470 | |
---|
| 471 | // TODO: merge additional code from Editor._moveToBookmark into this method |
---|
| 472 | |
---|
| 473 | var mark = bookmark.mark; |
---|
| 474 | if(mark){ |
---|
| 475 | if(doc.getSelection){ |
---|
| 476 | // W3C Range API (FF, WebKit, Opera, etc) |
---|
| 477 | var sel = win.getSelection(); |
---|
| 478 | if(sel && sel.removeAllRanges){ |
---|
| 479 | if(mark.pRange){ |
---|
| 480 | var n = mark.node; |
---|
| 481 | n.selectionStart = mark.start; |
---|
| 482 | n.selectionEnd = mark.end; |
---|
| 483 | }else{ |
---|
| 484 | sel.removeAllRanges(); |
---|
| 485 | sel.addRange(mark); |
---|
| 486 | } |
---|
| 487 | }else{ |
---|
| 488 | console.warn("No idea how to restore selection for this browser!"); |
---|
| 489 | } |
---|
| 490 | }else if(doc.selection && mark){ |
---|
| 491 | //'IE' way. |
---|
| 492 | var rg; |
---|
| 493 | if(mark.pRange){ |
---|
| 494 | rg = mark.range; |
---|
| 495 | }else if(lang.isArray(mark)){ |
---|
| 496 | rg = doc.body.createControlRange(); |
---|
| 497 | //rg.addElement does not have call/apply method, so can not call it directly |
---|
| 498 | //rg is not available in "range.addElement(item)", so can't use that either |
---|
| 499 | array.forEach(mark, function(n){ |
---|
| 500 | rg.addElement(n); |
---|
| 501 | }); |
---|
| 502 | }else{ |
---|
| 503 | rg = doc.body.createTextRange(); |
---|
| 504 | rg.moveToBookmark(mark); |
---|
| 505 | } |
---|
| 506 | rg.select(); |
---|
| 507 | } |
---|
| 508 | } |
---|
| 509 | }; |
---|
| 510 | |
---|
| 511 | this.isCollapsed = function(){ |
---|
| 512 | // summary: |
---|
| 513 | // Returns true if there is no text selected |
---|
| 514 | return this.getBookmark().isCollapsed; |
---|
| 515 | }; |
---|
| 516 | }; |
---|
| 517 | |
---|
| 518 | // singleton on the main window |
---|
| 519 | var selection = new SelectionManager(window); |
---|
| 520 | |
---|
| 521 | // hook for editor to use class |
---|
| 522 | selection.SelectionManager = SelectionManager; |
---|
| 523 | |
---|
| 524 | return selection; |
---|
| 525 | }); |
---|