├── .babelrc ├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── README.md ├── lib └── Highlighter.js ├── package-lock.json ├── package.json ├── src └── Highlighter.jsx └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/react", 4 | "@babel/env" 5 | ] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | demo 3 | .babelrc 4 | webpack.config.js -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "terminal.integrated.fontSize": 18 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Highlight Selection 2 | 3 | React component to highlight and retrieve text selected with mouse. 4 | 5 | ## Installation 6 | ``` 7 | npm i react-highlight-selection 8 | ``` 9 | 10 | ## Usage 11 | [![Edit ReactHighlightSelection](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/y0vw15o39j) 12 | 13 | - Provide required **text** prop. This will be the text contained inside the component. 14 | - Highlight the required portion of text by dragging the mouse over it with left-button pressed. The text would be highlighted on button's release 15 | - Optionally, provide a custom class to give your own background color or any other styling. 16 | - Optionally, provide a call back function **selectionHandler**. An object containing the following will be returned to the function 17 | - selected text 18 | - selection start index 19 | - selection end index 20 | 21 | ```css 22 | .custom-class { 23 | background-color: #e06f6f 24 | } 25 | ``` 26 | 27 | ```js 28 | import React, { Component } from 'react'; 29 | import ReactDOM from 'react-dom'; 30 | import SelectionHighlighter from 'react-highlight-selection'; 31 | import './App.css'; 32 | 33 | class App extends Component { 34 | constructor() { 35 | super(); 36 | this.selectionHandler = this.selectionHandler.bind(this); 37 | } 38 | 39 | selectionHandler(selection) { 40 | //do something with selection 41 | console.log(selection); 42 | 43 | } 44 | render() { 45 | const text = "Let there be light, let there be Sun."; 46 | return ( 47 | 52 | ); 53 | } 54 | } 55 | 56 | ReactDOM.render(, document.getElementById('root')); 57 | 58 | 59 | ``` 60 | 61 | ## What's unique 62 | - It's a very simple and lightweight component. 63 | - Most other existing components highlight only given text (somewhat like search/replace). This component, however, helps in retrieving dynamic selection done with the mouse. 64 | 65 | 66 | -------------------------------------------------------------------------------- /lib/Highlighter.js: -------------------------------------------------------------------------------- 1 | module.exports=function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=2)}([function(e,t,r){"use strict";e.exports=r(3)},function(e,t,r){e.exports=r(5)()},function(e,t,r){"use strict";r.r(t),r.d(t,"default",function(){return d});var n=r(0),o=r.n(n),i=r(1),u=r.n(i);function a(e){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function c(e,t){for(var r=0;r0);var l=c?i:u;c?(n.parentNode.getAttribute("data-order")&&"middle"===n.parentNode.getAttribute("data-order")&&(l+=this.state.selectionStart),n.parentNode.getAttribute("data-order")&&"last"===n.parentNode.getAttribute("data-order")&&(l+=this.state.selectionEnd)):(o.parentNode.getAttribute("data-order")&&"middle"===o.parentNode.getAttribute("data-order")&&(l+=this.state.selectionStart),o.parentNode.getAttribute("data-order")&&"last"===o.parentNode.getAttribute("data-order")&&(l+=this.state.selectionEnd));var f=l+r.length,s=this.state.text.slice(0,l),p=this.state.text.slice(l,f),d=this.state.text.slice(f);this.setState({selection:r,anchorNode:n,focusNode:o,selectionStart:l,selectionEnd:f,first:s,middle:p,last:d}),this.props.selectionHandler&&this.props.selectionHandler({selection:r,selectionStart:l,selectionEnd:f})}},{key:"render",value:function(){return this.state.selection?o.a.createElement("span",{onMouseUp:this.onMouseUpHandler},o.a.createElement("span",{"data-order":"first"},this.state.first),o.a.createElement("span",{"data-order":"middle",className:this.props.customClass||"default"},this.state.middle),o.a.createElement("span",{"data-order":"last"},this.state.last)):o.a.createElement("span",{onMouseUp:this.onMouseUpHandler},this.state.text)}}])&&c(r.prototype,i),u&&c(r,u),t}();d.propTypes=p},function(e,t,r){"use strict"; 2 | /** @license React v16.7.0 3 | * react.production.min.js 4 | * 5 | * Copyright (c) Facebook, Inc. and its affiliates. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE file in the root directory of this source tree. 9 | */var n=r(4),o="function"==typeof Symbol&&Symbol.for,i=o?Symbol.for("react.element"):60103,u=o?Symbol.for("react.portal"):60106,a=o?Symbol.for("react.fragment"):60107,c=o?Symbol.for("react.strict_mode"):60108,l=o?Symbol.for("react.profiler"):60114,f=o?Symbol.for("react.provider"):60109,s=o?Symbol.for("react.context"):60110,p=o?Symbol.for("react.concurrent_mode"):60111,d=o?Symbol.for("react.forward_ref"):60112,y=o?Symbol.for("react.suspense"):60113,b=o?Symbol.for("react.memo"):60115,h=o?Symbol.for("react.lazy"):60116,m="function"==typeof Symbol&&Symbol.iterator;function v(e){for(var t=arguments.length-1,r="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=0;n$.length&&$.push(e)}function A(e,t,r){return null==e?0:function e(t,r,n,o){var a=typeof t;"undefined"!==a&&"boolean"!==a||(t=null);var c=!1;if(null===t)c=!0;else switch(a){case"string":case"number":c=!0;break;case"object":switch(t.$$typeof){case i:case u:c=!0}}if(c)return n(o,t,""===r?"."+M(t,0):r),1;if(c=0,r=""===r?".":r+":",Array.isArray(t))for(var l=0;l 0; 52 | } 53 | 54 | let selectionStart = forward ? anchorOffset : focusOffset; 55 | 56 | if (forward) { 57 | if (anchorNode.parentNode.getAttribute('data-order') 58 | && anchorNode.parentNode.getAttribute('data-order') === 'middle') { 59 | selectionStart += this.state.selectionStart; 60 | } 61 | if (anchorNode.parentNode.getAttribute('data-order') 62 | && anchorNode.parentNode.getAttribute('data-order') === 'last') { 63 | selectionStart += this.state.selectionEnd; 64 | } 65 | } else { 66 | if (focusNode.parentNode.getAttribute('data-order') 67 | && focusNode.parentNode.getAttribute('data-order') === 'middle') { 68 | selectionStart += this.state.selectionStart; 69 | } 70 | if (focusNode.parentNode.getAttribute('data-order') 71 | && focusNode.parentNode.getAttribute('data-order') === 'last') { 72 | selectionStart += this.state.selectionEnd; 73 | } 74 | } 75 | 76 | const selectionEnd = selectionStart + selection.length; 77 | const first = this.state.text.slice(0, selectionStart); 78 | const middle = this.state.text.slice(selectionStart, selectionEnd); 79 | const last = this.state.text.slice(selectionEnd); 80 | 81 | this.setState({ 82 | selection, 83 | anchorNode, 84 | focusNode, 85 | selectionStart, 86 | selectionEnd, 87 | first, 88 | middle, 89 | last 90 | }); 91 | 92 | if (this.props.selectionHandler) { 93 | this.props.selectionHandler({ 94 | selection, 95 | selectionStart, 96 | selectionEnd 97 | }); 98 | } 99 | 100 | } 101 | 102 | render() { 103 | if (!this.state.selection) { 104 | return ( 105 | {this.state.text} 107 | 108 | ) 109 | } else { 110 | return ( 111 | 113 | 115 | {this.state.first} 116 | 117 | 120 | {this.state.middle} 121 | 122 | 124 | {this.state.last} 125 | 126 | 127 | ) 128 | } 129 | 130 | } 131 | } 132 | 133 | HighLighter.propTypes = propTypes; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/Highlighter.jsx', 6 | output: { 7 | path: path.resolve('lib'), 8 | filename: 'Highlighter.js', 9 | libraryTarget: 'commonjs2' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx?$/, 15 | exclude: /(node_modules)/, 16 | use: 'babel-loader' 17 | } 18 | ] 19 | } 20 | } --------------------------------------------------------------------------------