├── .gitattributes
├── LICENSE
├── README.md
├── build
├── demo.html
└── selectable.min.js
├── gulpfile.js
├── index.js
└── package.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 drylikov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 | drylikov
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Selectable
2 | ==========
3 |
4 | Easily get and set the text selection with an HTML element.
5 |
--------------------------------------------------------------------------------
/build/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Selection Test
6 |
15 |
16 |
17 |
18 |
this is some styled content inside adiv element. It happens to also be multiple lines of text!
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/build/selectable.min.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Selectable=e()}}(function(){return function e(t,n,o){function r(f,d){if(!n[f]){if(!t[f]){var u="function"==typeof require&&require;if(!d&&u)return u(f,!0);if(i)return i(f,!0);var l=new Error("Cannot find module '"+f+"'");throw l.code="MODULE_NOT_FOUND",l}var a=n[f]={exports:{}};t[f][0].call(a.exports,function(e){var n=t[f][1][e];return r(n?n:e)},a,a.exports,e,t,n,o)}return n[f].exports}for(var i="function"==typeof require&&require,f=0;fe&&(e=o+(e+1)),0>t&&(t=o+(t+1)),e>o&&(e=o),t>o&&(t=o);u;){if(e>=n&&e<=n+u.length&&i.setStart(u,e-n),t>=n&&t<=n+u.length){i.setEnd(u,t-n);break}n+=d.currentNode.length,u=d.nextNode()}f.removeAllRanges(),f.addRange(i)},t.exports=o},{}]},{},[1])(1)});
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var browserify = require('browserify');
2 | var buffer = require('vinyl-buffer');
3 | var gulp = require('gulp');
4 | var gutil = require('gulp-util');
5 | var rename = require('gulp-rename');
6 | var source = require('vinyl-source-stream');
7 | var uglify = require('gulp-uglify');
8 |
9 |
10 | function streamError(err) {
11 | gutil.beep();
12 | gutil.log(err);
13 | }
14 |
15 |
16 | gulp.task('javascript', function() {
17 | browserify('.', {standalone: 'Selectable'})
18 | .bundle()
19 | .on('error', streamError)
20 | .pipe(source('index.js'))
21 | .pipe(buffer())
22 | .pipe(uglify())
23 | .pipe(rename('selectable.min.js'))
24 | .pipe(gulp.dest('./build'));
25 | });
26 |
27 |
28 | gulp.task('watch', ['javascript'], function() {
29 | gulp.watch('index.js', ['javascript']);
30 | });
31 |
32 |
33 | gulp.task('default', ['javascript']);
34 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @constructor
3 | */
4 | function Selectable(root) {
5 | this.root = root;
6 | }
7 |
8 |
9 | /**
10 | * Gets the instances root element's text selection start and end index. The
11 | * indexes are based on the text content, regardless of the HTML structure.
12 | * @return {{startIndex: number, endIndex: number}}
13 | */
14 | Selectable.prototype.get = function() {
15 |
16 | var index = 0;
17 | var startIndex;
18 | var endIndex;
19 | var selection = window.getSelection();
20 | var treeWalker = createTextNodeTreeWalker(this.root);
21 | var node = treeWalker.firstChild();
22 |
23 | // Make sure the selection is inside of root.
24 | if (!this.root.contains(selection.anchorNode)) {
25 | return {startIndex: null, endIndex: null};
26 | }
27 |
28 | // If there are no text nodes it means the root element is empty so both
29 | // indexes must be 0.
30 | if (!node) {
31 | return {startIndex: 0, endIndex: 0};
32 | }
33 |
34 | while (node) {
35 | if (node == selection.anchorNode) {
36 | startIndex = index + selection.anchorOffset;
37 | }
38 | if (node == selection.focusNode) {
39 | endIndex = index + selection.focusOffset;
40 | break;
41 | }
42 | index += node.length;
43 | node = treeWalker.nextNode();
44 | }
45 | return {
46 | startIndex: Math.min(startIndex, endIndex),
47 | endIndex: Math.max(startIndex, endIndex)
48 | };
49 | }
50 |
51 |
52 | /**
53 | * Sets the text selection of the instance's root element to the position
54 | * specified by the start and end index values. The indexes correspond to
55 | * the text content of the element, regardless of what its actual HTML
56 | * content is.
57 | * @param {Number} startIndex The starting point of the selection.
58 | * @param {Number} endIndex The ending point of the selection. If omitted,
59 | * the startIndex is used.
60 | */
61 | Selectable.prototype.set = function(startIndex, endIndex) {
62 |
63 | // `endIndex` is optional.
64 | if (!endIndex) endIndex = startIndex;
65 |
66 | var index = 0;
67 | var length = this.root.textContent.length;
68 | var range = document.createRange();
69 | var selection = window.getSelection();
70 | var treeWalker = createTextNodeTreeWalker(this.root);
71 | var node = treeWalker.firstChild();
72 |
73 | // Handle negative or out-of-range start/end indexes.
74 | if (startIndex < 0) startIndex = length + (startIndex + 1);
75 | if (endIndex < 0) endIndex = length + (endIndex + 1);
76 | if (startIndex > length) startIndex = length;
77 | if (endIndex > length) endIndex = length;
78 |
79 | while (node) {
80 | if (startIndex >= index && startIndex <= index + node.length) {
81 | range.setStart(node, startIndex - index);
82 | }
83 | if (endIndex >= index && endIndex <= index + node.length) {
84 | range.setEnd(node, endIndex - index);
85 | break;
86 | }
87 | index += treeWalker.currentNode.length;
88 | node = treeWalker.nextNode();
89 | }
90 |
91 | selection.removeAllRanges();
92 | selection.addRange(range);
93 | };
94 |
95 |
96 | /**
97 | * Create an instance of a TreeWalker the only traverses text nodes.
98 | * @return {TreeWalker} The new instance.
99 | */
100 | function createTextNodeTreeWalker(element) {
101 | return document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
102 | }
103 |
104 |
105 | module.exports = Selectable;
106 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "selectable",
3 | "version": "0.1.0",
4 | "description": "Easily get and set the text selection with an HTML element.",
5 | "scripts": {
6 | "build": "gulp",
7 | "watch": "gulp watch"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/philipwalton/selectable.git"
12 | },
13 | "keywords": [
14 | "getSelection",
15 | "selection",
16 | "range",
17 | "contenteditable",
18 | "content",
19 | "editable"
20 | ],
21 | "author": "philipwalton",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/philipwalton/selectable/issues"
25 | },
26 | "homepage": "https://github.com/philipwalton/selectable",
27 | "devDependencies": {
28 | "browserify": "^10.1.3",
29 | "gulp": "^3.8.11",
30 | "gulp-rename": "^1.2.2",
31 | "gulp-uglify": "^1.2.0",
32 | "gulp-util": "^3.0.4",
33 | "vinyl-buffer": "^1.0.0",
34 | "vinyl-source-stream": "^1.1.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------