├── .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 |
--------------------------------------------------------------------------------