├── .gitignore ├── History.md ├── Makefile ├── Readme.md ├── component.json ├── example.html ├── index.js ├── package.json └── test └── selection-range.js /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | build 3 | node_modules 4 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 2 | - small fix for internet explorer 3 | 4 | ## 0.0.3 5 | - update to use latest version of iterator 6 | 7 | ## 0.0.2 8 | 9 | - refactor to use matthewmueller/iterator 10 | - add tests -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: components index.js 2 | @component build --dev 3 | @http-server 4 | 5 | components: component.json 6 | @component install --dev 7 | 8 | clean: 9 | rm -fr build components template.js 10 | 11 | test: build 12 | @./node_modules/.bin/component-test browser 13 | 14 | .PHONY: clean 15 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # selection-range 3 | 4 | Get or set the selection range, or cursor position. Useful for saving and restoring selections when your are programatically changing the dom. 5 | 6 | ## Installation 7 | 8 | $ component install bmcmahen/selection-range 9 | $ npm install selection-range 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | var select = require('selection-range'); 15 | select(el, { start: 5, end: 25 }); // select range of el from 5 - 25 16 | select(el, { start: 5 }); // set the cursor at 5 17 | var pos = select(el); // get range of selection 18 | // pos.start = start index 19 | // pos.end = end index 20 | // pos.atStart = boolean. true if cursor should appear at start of el 21 | // pos = undefined if no cursor 22 | select(el, pos); 23 | ``` 24 | 25 | ## Tests 26 | 27 | ``` 28 | npm install component-test -g 29 | component test browser 30 | ``` 31 | 32 | ## License 33 | 34 | MIT 35 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "selection-range", 3 | "repo": "bmcmahen/selection-range", 4 | "description": "get or set the selection range, or cursor position, for contenteditable", 5 | "version": "1.0.1", 6 | "keywords": [], 7 | "dependencies": { 8 | "matthewmueller/dom-iterator": "0.3.0" 9 | }, 10 | "development": { 11 | "component/assert": "*", 12 | "component/domify": "*" 13 | }, 14 | "license": "MIT", 15 | "main": "index.js", 16 | "scripts": [ 17 | "index.js" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |

hello world

11 |


12 |


13 |

Another paragraph

14 |
15 |
16 | 17 | 18 | 19 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | var iterator = require('dom-iterator'); 6 | var selection = window.getSelection(); 7 | 8 | /** 9 | * Expose position fn 10 | */ 11 | 12 | module.exports = position; 13 | 14 | /** 15 | * Get or set cursor, selection, relative to 16 | * an element. 17 | * 18 | * @param {Element} el 19 | * @param {Object} pos selection range 20 | * @return {Object|Undefined} 21 | */ 22 | 23 | function position(el, pos){ 24 | 25 | /** 26 | * Get cursor or selection position 27 | */ 28 | 29 | if (1 == arguments.length) { 30 | if (!selection.rangeCount) return; 31 | var indexes = {}; 32 | var range = selection.getRangeAt(0); 33 | var clone = range.cloneRange(); 34 | clone.selectNodeContents(el); 35 | clone.setEnd(range.endContainer, range.endOffset); 36 | indexes.end = clone.toString().length; 37 | clone.setStart(range.startContainer, range.startOffset); 38 | indexes.start = indexes.end - clone.toString().length; 39 | indexes.atStart = clone.startOffset === 0; 40 | indexes.commonAncestorContainer = clone.commonAncestorContainer; 41 | indexes.endContainer = clone.endContainer; 42 | indexes.startContainer = clone.startContainer; 43 | return indexes; 44 | } 45 | 46 | /** 47 | * Set cursor or selection position 48 | */ 49 | 50 | var setSelection = pos.end && (pos.end !== pos.start); 51 | var length = 0; 52 | var range = document.createRange(); 53 | var it = iterator(el).select(Node.TEXT_NODE).revisit(false); 54 | var next; 55 | var startindex; 56 | var start = pos.start > el.textContent.length ? el.textContent.length : pos.start; 57 | var end = pos.end > el.textContent.length ? el.textContent.length : pos.end; 58 | var atStart = pos.atStart; 59 | 60 | while (next = it.next()){ 61 | var olen = length; 62 | length += next.textContent.length; 63 | 64 | // Set start point of selection 65 | var atLength = atStart ? length > start : length >= start; 66 | if (!startindex && atLength) { 67 | startindex = true; 68 | range.setStart(next, start - olen); 69 | if (!setSelection) { 70 | range.collapse(true); 71 | makeSelection(el, range); 72 | break; 73 | } 74 | } 75 | 76 | // Set end point of selection 77 | if (setSelection && (length >= end)) { 78 | range.setEnd(next, end - olen); 79 | makeSelection(el, range); 80 | break; 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * add selection / insert cursor. 87 | * 88 | * @param {Element} el 89 | * @param {Range} range 90 | */ 91 | 92 | function makeSelection(el, range){ 93 | el.focus(); 94 | selection.removeAllRanges(); 95 | selection.addRange(range); 96 | } 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "selection-range", 3 | "description": "get or set the selction range, or cursor position, for contenteditable", 4 | "version": "1.1.0", 5 | "keywords": [ 6 | ], 7 | "dependencies": { 8 | "dom-iterator": "0.3.0" 9 | }, 10 | "component": { 11 | "scripts": { 12 | "selection-range/index.js": "index.js" 13 | } 14 | }, 15 | "license": "MIT", 16 | "main": "index.js", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/bmcmahen/selection-range.git" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/selection-range.js: -------------------------------------------------------------------------------- 1 | var sRange = require('selection-range'); 2 | var assert = require('assert'); 3 | var domify = require('domify'); 4 | var selection = window.getSelection; 5 | 6 | describe('selection-range', function(){ 7 | var el; 8 | 9 | beforeEach(function(){ 10 | el = domify('

hi! my name is sally.

' + 11 | '



hello world

'); 12 | document.body.appendChild(el); 13 | sel = selection(); 14 | sel.removeAllRanges(); 15 | }); 16 | 17 | afterEach(function(){ 18 | document.body.removeChild(el); 19 | }); 20 | 21 | it('should handle empty spaces', function () { 22 | sRange(el, { start: el.textContent.length - 1 }); 23 | var r = sRange(el); 24 | assert(r.start === el.textContent.length - 1); 25 | assert(r.end === el.textContent.length - 1); 26 | }); 27 | 28 | it('should set at the beginning of an element if specified', function () { 29 | var ps = el.querySelectorAll('p'); 30 | sRange(el, { start: ps[0].textContent.length, atStart: true }); 31 | var r = sRange(el); 32 | assert(r.atStart == true); 33 | var r = selection().getRangeAt(0); 34 | assert(r.startContainer.parentNode == ps[1]); 35 | }); 36 | 37 | it('should set at the end of an element if beginning not specified', function(){ 38 | var ps = el.querySelectorAll('p'); 39 | sRange(el, { start: ps[0].textContent.length }); 40 | var r = sRange(el); 41 | var r = selection().getRangeAt(0); 42 | assert(r.startContainer.parentNode == ps[0]); 43 | }) 44 | 45 | it('should get the proper ranges for selection', function(){ 46 | sRange(el, { start: 5, end: 10}); 47 | var r = sRange(el); 48 | assert(r.start === 5); 49 | assert(r.end === 10); 50 | }); 51 | 52 | it('should get the proper ranges for cursor', function(){ 53 | sRange(el, { start: 0 }); 54 | var r = sRange(el); 55 | assert(r.start === 0); 56 | assert(r.end === 0); 57 | }); 58 | 59 | it('should set cursor to specific spot', function(){ 60 | sRange(el, { start: 0 }); 61 | var r = sRange(el); 62 | assert(r.start === 0); 63 | assert(r.end === 0); 64 | }); 65 | 66 | it('should set selection', function(){ 67 | var pos = { start: 10, end: 15 }; 68 | sRange(el, pos); 69 | var r = sRange(el); 70 | assert(r.start === 10); 71 | assert(r.end === 15); 72 | var str = selection().toString(); 73 | assert(str == 'e is '); 74 | }); 75 | 76 | it('should return selected nodes', function(){ 77 | sRange(el, { start: 0, end: el.textContent.length - 1 }); 78 | var r = sRange(el); 79 | assert(r.startContainer.nodeName === 'P'); 80 | assert(r.endContainer.nodeName === 'P'); 81 | assert(r.commonAncestorContainer.nodeName === 'DIV'); 82 | }); 83 | 84 | it('should not exceede the text length', function(){ 85 | var p = el.textContent.length + 1; 86 | var pos = { start: p, end: p }; 87 | sRange(el, pos); 88 | var r = sRange(el); 89 | assert(r.start === el.textContent.length); 90 | assert(r.end === el.textContent.length); 91 | }); 92 | 93 | }); 94 | 95 | function selectAt(elem, str) { 96 | var range = document.createRange(); 97 | var i = elem.textContent.indexOf(str); 98 | 99 | // set to end 100 | if (~i) i++; 101 | else throw new Error('selectAt: unable to select'); 102 | 103 | range.setStart(elem.firstChild, i); 104 | range.setEnd(elem.firstChild, i); 105 | sel.addRange(range); 106 | } 107 | --------------------------------------------------------------------------------