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