├── .gitignore ├── .npmignore ├── .nvmrc ├── CHANGELOG.md ├── README.md ├── composer.json ├── dist └── js │ ├── table-selection.js │ ├── table-selection.min.js │ ├── table-selection.min.js.LICENSE.txt │ └── table-selection.min.js.map ├── examples ├── example.html └── table-selection-demo.gif ├── jest.config.js ├── package.json ├── src ├── index.ts └── ts │ ├── TableSelection.ts │ └── interfaces │ ├── TableSelectionConfig.ts │ ├── TableSelectionRange.ts │ └── TableSelectionTableElements.ts ├── table-selection.d.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | esm 3 | cjs 4 | umd 5 | .idea 6 | .vscode 7 | coverage 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs 3 | src 4 | test 5 | coverage 6 | .* 7 | tsconfig.json 8 | tslint.json 9 | webpack.config.js 10 | jest.config.js 11 | yarn.lock 12 | typedoc.json 13 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | --------- 3 | 4 | # 1.0.0 5 | - Complete rewrite 6 | - Removed CSS/SCSS/LESS styles 7 | - Switched to CSS variables for styling 8 | 9 | # 0.9.3 10 | Initial release 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TableSelection 2 | Adds **rectangular selection** to **HTML tables** to allow copy of specific columns and rows without copying contents of other cells in range. 3 | 4 | ![](examples/table-selection-demo.gif) 5 | 6 |
7 | 8 | - Supports horizontal, vertical and rectangular selections. 9 | - Includes **clipboard** support with table formatting for **Excel** or other **spreadsheet** software! 10 | - It even works on **mobile**! 11 | - Tested and working in Chrome, Firefox, Edge, Mobile Chrome and Samsung Browser 12 | - Lightweight (5,4 kB) 13 | 14 |
15 | 16 | ## Table of contents 17 | - [Installation](#installation) 18 | - [Using precompiled js](#installation-precompiled) 19 | - [Using a module loader](#installation-module-loader) 20 | - [Upgrading from 0.9.x](#upgrading) 21 | - [Usage](#usage) 22 | - [API](#api) 23 | - [Demo](#demo) 24 | - [License (it's free!)](#license) 25 | 26 |
27 | 28 | 29 | ## Installation 30 | - Run `npm i @pxlwidgets/table-selection` or download the sources. 31 | 32 | 33 | 34 | ##### Using the precompiled js and css files 35 | 36 | - Include `dist/js/table-selection.js` or `dist/js/table-selection.min.js` in your project or import using a module loader (more info below). 37 | - Include `dist/css/table-selection.css` or `dist/css/table-selection.min.css` in your project (or use the provided LESS or SCSS file). 38 | 39 | 40 | 41 | ##### Using a module loader 42 | 43 | The package includes files for integration in **Typescript** and **ES6** projects: 44 | 45 | ```typescript 46 | import { TableSelection } from '@pxlwidgets/table-selection'; 47 | ``` 48 |
49 |
50 | 51 | 52 | ## Upgrading from v0.9.x 53 | v1.x no longer uses a stylesheet file, so you should remove any of these CSS/SCSS/LESS imports: 54 | - CSS: `dist/css/table-selection.css` 55 | - SCSS: `@import '~/@pxlwidgets/table-selection'` 56 | - LESS: `@include 'node_modules/@pxlwidgets/table-selection/dist/less/table-selection.less'` 57 | 58 | 59 | 60 | ## Usage 61 | - Add `.table-selection` class to the table(s) you want to apply the selection functionality on. 62 | - Initialize the script using `new TableSelection()`. 63 | 64 | > **Note**
65 | > You can use a different DOM selector by setting the `selector` configuration option when creating the Javascript instance. 66 | 67 |
68 | 69 | 70 | ## Javascript API 71 | The constructor takes one, optional argument. 72 | 73 | ```typescript 74 | constructor(config: TableSelectionConfig = {}) 75 | ``` 76 | 77 | The `config` parameter (interface `TableSelectionConfig`) has the following properties: 78 | 79 | | Property | Type | Required | Default | Description | 80 | |---------------------|----------|----------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 81 | | `selector` | `string` | No | `'.table-selection'` | Sets the DOM selector to apply the functionality to. This should point to one or more table elements on your page. E.g. `table` or `.table`. | 82 | | `selectionCssMode` | `string` | No | `'.style'` | One of `'style'` or `'cssClass'`. Sets which method to use to highlight selected cells. `'style'` uses style attributes with CSS variables. `'cssClass'` adds the CSS class specified in the `selectionCssClass` config property. | 83 | | `selectionCssClass` | `string` | No | `'selected'` | Sets the CSS class to apply to 'selected' table cells. E.g. `active`, `selected` or `highlighted`. This only applies if the `selectionCssMode` is set to `'cssClass'` and requires you to add styles for the selector yourself. | 84 | 85 | 86 | Example usage : 87 | ```typescript 88 | new TableSelection({ 89 | selector: '#my-table', 90 | selectionCssMode: 'cssClass', 91 | selectionCssClass: 'my-highlighted-cell-class', 92 | }); 93 | ``` 94 |
95 | 96 | For example:
97 | 98 | ```typescript 99 | new TableSelection(); 100 | ``` 101 |
102 | 103 | ```typescript 104 | new TableSelection({ selector: 'table' }); 105 | ``` 106 |
107 | 108 |
109 | 110 | 111 | ## Demo 112 | [View live example on Codepen](https://codepen.io/opznhaarlems/full/YzrxmOr) 113 | 114 |
115 | 116 | 117 | ## License 118 | MIT License 119 | 120 | Copyright (c) 2019 Wouter Smit 121 | Copyright (c) 2019 PXL.Widgets B.V. 122 | 123 | Permission is hereby granted, free of charge, to any person obtaining a copy 124 | of this software and associated documentation files (the "Software"), to deal 125 | in the Software without restriction, including without limitation the rights 126 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 127 | copies of the Software, and to permit persons to whom the Software is 128 | furnished to do so, subject to the following conditions: 129 | 130 | The above copyright notice and this permission notice shall be included in all 131 | copies or substantial portions of the Software. 132 | 133 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 134 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 135 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 136 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 137 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 138 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 139 | SOFTWARE. 140 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "widgets-nl/table-selection", 3 | "description": "Adds rectangular selection to HTML tables to allow copy of specific columns and rows without copying contents of other cells in range.", 4 | "keywords": [ 5 | "html", 6 | "css", 7 | "js", 8 | "less", 9 | "sass", 10 | "table", 11 | "select", 12 | "selection", 13 | "column", 14 | "row", 15 | "cell", 16 | "vertical", 17 | "rectangular" 18 | ], 19 | "homepage": "https://github.com/PXLWidgets/table-selection", 20 | "authors": [ 21 | { 22 | "name": "Wouter Smit", 23 | "email": "wouter@pxlwidgets.com" 24 | } 25 | ], 26 | "support": { 27 | "issues": "https://github.com/PXLWidgets/table-selection/issues" 28 | }, 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /dist/js/table-selection.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see table-selection.min.js.LICENSE.txt */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.TableSelection=t():e.TableSelection=t()}(self,(function(){return(()=>{"use strict";var e={d:(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>l});var n=function(){return n=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0)&&!(r=l.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=l.return)&&n.call(l)}finally{if(o)throw o.error}}return a},o=function(e,t,n){if(n||2===arguments.length)for(var r,o=0,l=t.length;othis.range.end.cell&&(n=r([this.range.end.cell,this.range.start.cell],2),this.range.start.cell=n[0],this.range.end.cell=n[1]),this.range.start.row>this.range.end.row&&(o=r([this.range.end.row,this.range.start.row],2),this.range.start.row=o[0],this.range.end.row=o[1]))},e.prototype.getRowElements=function(){if(this.range&&this.elements.table){var e=this.elements.table.querySelectorAll("thead tr").length,t=[],n=[],l=this.range.start.row<=e?Math.min(e,this.range.start.row):null,a=this.range.start.row<=e?Math.min(e,this.range.end.row):null,s=this.range.end.row>e?Math.max(1,this.range.start.row-e):null,i=this.range.end.row>e?Math.max(1,this.range.end.row-e):null;l&&(t=Array.from(this.elements.table.querySelectorAll("thead tr:nth-of-type(n+".concat(l,"):nth-of-type(-n+").concat(a,")")))),s&&(n=Array.from(this.elements.table.querySelectorAll("tbody tr:nth-of-type(n+".concat(s,"):nth-of-type(-n+").concat(i,")")))),this.range.rowElements=o(o([],r(t),!1),r(n),!1)}},e.prototype.getRowsAndCells=function(){var e=this;if(this.range&&this.range.rowElements){var t=[];this.range.rowElements.forEach((function(n,l){if(e.range){var a=Array.from(n.querySelectorAll(["td:nth-of-type(n+".concat(e.range.start.cell,"):nth-of-type(-n+").concat(e.range.end.cell,")"),"th:nth-of-type(n+".concat(e.range.start.cell,"):nth-of-type(-n+").concat(e.range.end.cell,")")].join(",")));t=o(o([],r(t),!1),r(a),!1),e.range.rows[l]=a}})),this.range.cells=t}},e.prototype.getCellElementFromNode=function(e){if(!e)return null;var t="#text"===e.nodeName?e.parentElement:e;return t&&["TD","TH"].includes(t.tagName)?t:null},e.prototype.getTableElementsFromCellElement=function(e){if(!e||!e.parentElement||!e.parentElement.parentElement)return null;var t=e.parentElement.parentElement,n=["TBODY","THEAD"].includes(t.tagName)?t.parentElement:t;return n&&this.config.selector&&n.matches(this.config.selector)?{table:n,thead:n.querySelector("thead"),tbody:n.querySelector("tbody")}:null},e.prototype.select=function(){var e=this;this.range&&this.range.cells&&this.range.cells.forEach((function(t){"cssClass"===e.config.selectionCssMode&&e.config.selectionCssClass?t.classList.add(e.config.selectionCssClass):(t.style.backgroundColor="var(--table-selection-background-color, Highlight)",t.style.color="var(--table-selection-color, HighlightText)")}))},e.prototype.deselect=function(){var e=this;this.range&&this.range.cells&&(this.range.cells.forEach((function(t){"cssClass"===e.config.selectionCssMode&&e.config.selectionCssClass?t.classList.remove(e.config.selectionCssClass):(t.style.removeProperty("background-color"),t.style.removeProperty("color"))})),this.range=null)},e.prototype.copyToClipboard=function(e){if(this.range&&this.range.rows&&e.clipboardData){var t=this.range.rows.map((function(e){return e.map((function(e){return e.innerText})).join("\t")})).join("\r\n");t&&(e.clipboardData.setData("text/plain",t),e.preventDefault())}},e}();return t.default})()})); 3 | //# sourceMappingURL=table-selection.min.js.map -------------------------------------------------------------------------------- /dist/js/table-selection.min.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see table-selection.min.js.LICENSE.txt */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.TableSelection=t():e.TableSelection=t()}(self,(function(){return(()=>{"use strict";var e={d:(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>l});var n=function(){return n=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0)&&!(r=l.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=l.return)&&n.call(l)}finally{if(o)throw o.error}}return a},o=function(e,t,n){if(n||2===arguments.length)for(var r,o=0,l=t.length;othis.range.end.cell&&(n=r([this.range.end.cell,this.range.start.cell],2),this.range.start.cell=n[0],this.range.end.cell=n[1]),this.range.start.row>this.range.end.row&&(o=r([this.range.end.row,this.range.start.row],2),this.range.start.row=o[0],this.range.end.row=o[1]))},e.prototype.getRowElements=function(){if(this.range&&this.elements.table){var e=this.elements.table.querySelectorAll("thead tr").length,t=[],n=[],l=this.range.start.row<=e?Math.min(e,this.range.start.row):null,a=this.range.start.row<=e?Math.min(e,this.range.end.row):null,s=this.range.end.row>e?Math.max(1,this.range.start.row-e):null,i=this.range.end.row>e?Math.max(1,this.range.end.row-e):null;l&&(t=Array.from(this.elements.table.querySelectorAll("thead tr:nth-of-type(n+".concat(l,"):nth-of-type(-n+").concat(a,")")))),s&&(n=Array.from(this.elements.table.querySelectorAll("tbody tr:nth-of-type(n+".concat(s,"):nth-of-type(-n+").concat(i,")")))),this.range.rowElements=o(o([],r(t),!1),r(n),!1)}},e.prototype.getRowsAndCells=function(){var e=this;if(this.range&&this.range.rowElements){var t=[];this.range.rowElements.forEach((function(n,l){if(e.range){var a=Array.from(n.querySelectorAll(["td:nth-of-type(n+".concat(e.range.start.cell,"):nth-of-type(-n+").concat(e.range.end.cell,")"),"th:nth-of-type(n+".concat(e.range.start.cell,"):nth-of-type(-n+").concat(e.range.end.cell,")")].join(",")));t=o(o([],r(t),!1),r(a),!1),e.range.rows[l]=a}})),this.range.cells=t}},e.prototype.getCellElementFromNode=function(e){if(!e)return null;var t="#text"===e.nodeName?e.parentElement:e;return t&&["TD","TH"].includes(t.tagName)?t:null},e.prototype.getTableElementsFromCellElement=function(e){if(!e||!e.parentElement||!e.parentElement.parentElement)return null;var t=e.parentElement.parentElement,n=["TBODY","THEAD"].includes(t.tagName)?t.parentElement:t;return n&&this.config.selector&&n.matches(this.config.selector)?{table:n,thead:n.querySelector("thead"),tbody:n.querySelector("tbody")}:null},e.prototype.select=function(){var e=this;this.range&&this.range.cells&&this.range.cells.forEach((function(t){"cssClass"===e.config.selectionCssMode&&e.config.selectionCssClass?t.classList.add(e.config.selectionCssClass):(t.style.backgroundColor="var(--table-selection-background-color, Highlight)",t.style.color="var(--table-selection-color, HighlightText)")}))},e.prototype.deselect=function(){var e=this;this.range&&this.range.cells&&(this.range.cells.forEach((function(t){"cssClass"===e.config.selectionCssMode&&e.config.selectionCssClass?t.classList.remove(e.config.selectionCssClass):(t.style.removeProperty("background-color"),t.style.removeProperty("color"))})),this.range=null)},e.prototype.copyToClipboard=function(e){if(this.range&&this.range.rows&&e.clipboardData){var t=this.range.rows.map((function(e){return e.map((function(e){return e.innerText})).join("\t")})).join("\r\n");t&&(e.clipboardData.setData("text/plain",t),e.preventDefault())}},e}();return t.default})()})); 3 | //# sourceMappingURL=table-selection.min.js.map -------------------------------------------------------------------------------- /dist/js/table-selection.min.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * TableSelection library v1.0.0 (https://github.com/PXLWidgets/table-selection) 3 | * Copyright (c) 2019 Wouter Smit 4 | * Licensed under MIT (https://github.com/PXLWidgets/table-selection/blob/master/LICENSE) 5 | */ 6 | -------------------------------------------------------------------------------- /dist/js/table-selection.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"table-selection.min.js","mappings":"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","sources":["webpack://TableSelection/webpack/universalModuleDefinition","webpack://TableSelection/webpack/bootstrap","webpack://TableSelection/webpack/runtime/define property getters","webpack://TableSelection/webpack/runtime/hasOwnProperty shorthand","webpack://TableSelection/./src/index.ts","webpack://TableSelection/./src/TableSelection.ts"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"TableSelection\"] = factory();\n\telse\n\t\troot[\"TableSelection\"] = factory();\n})(self, function() {\nreturn ","// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","/*!\n * TableSelection library v1.0.0 (https://github.com/PXLWidgets/table-selection)\n * Copyright (c) 2019 Wouter Smit\n * Licensed under MIT (https://github.com/PXLWidgets/table-selection/blob/master/LICENSE)\n*/\n\nimport { TableSelection } from './TableSelection';\n\nexport default TableSelection;\n","/*!\n * TableSelection library v1.0.0 (https://github.com/PXLWidgets/table-selection)\n * Copyright (c) 2019 Wouter Smit\n * Licensed under MIT (https://github.com/PXLWidgets/table-selection/blob/master/LICENSE)\n*/\n\nimport { TableSelectionConfig } from './interfaces/TableSelectionConfig';\nimport { TableSelectionRange } from './interfaces/TableSelectionRange';\nimport { TableSelectionTableElements } from './interfaces/TableSelectionTableElements';\n\nexport class TableSelection {\n\n config: TableSelectionConfig = {\n selector: '.table-selection',\n selectionCssMode: 'style',\n selectionCssClass: 'selected',\n };\n\n range: TableSelectionRange | null = null;\n elements: TableSelectionTableElements;\n\n constructor(config: TableSelectionConfig = {}) {\n this.config = { ...this.config, ...config };\n\n // Hide default selection\n const styles = document.createElement('style');\n styles.innerHTML = `${this.config.selector} *::selection {\n background: transparent;\n color: inherit;\n }`;\n document.head.appendChild(styles);\n\n document.addEventListener('selectionchange', () => this.onSelectionChange());\n document.addEventListener('copy', (e) => this.copyToClipboard(e));\n }\n\n protected onSelectionChange(): void {\n const selection = getSelection();\n\n this.deselect();\n if (! selection || selection.isCollapsed) {\n return;\n }\n\n const startCell: HTMLTableCellElement | null = this.getCellElementFromNode(selection.anchorNode);\n const endCell: HTMLTableCellElement | null = this.getCellElementFromNode(selection.focusNode);\n if (! startCell || ! endCell) {\n return;\n }\n\n const startTableElements = this.getTableElementsFromCellElement(startCell);\n const endTableElements = this.getTableElementsFromCellElement(endCell);\n if (! startTableElements || ! endTableElements) {\n return;\n }\n\n const isSameTable = startTableElements.table === endTableElements.table;\n if (! isSameTable) {\n return;\n }\n\n this.elements = startTableElements;\n\n this.getRangeByStartAndEndElement(startCell, endCell);\n this.getRowElements();\n this.getRowsAndCells();\n\n if (this.range) {\n this.select();\n } else {\n this.deselect();\n }\n }\n\n protected getRangeByStartAndEndElement(\n startCellElement: HTMLTableCellElement,\n endCellElement: HTMLTableCellElement,\n ): void {\n\n const startRowElement: HTMLTableRowElement | null = startCellElement.parentElement as HTMLTableRowElement;\n const endRowElement: HTMLTableRowElement | null = endCellElement.parentElement as HTMLTableRowElement;\n\n if (! startRowElement || ! endRowElement) {\n return;\n }\n\n this.range = {\n start: {\n row: startRowElement.rowIndex + 1,\n cell: startCellElement.cellIndex + 1,\n },\n end: {\n row: endRowElement.rowIndex + 1,\n cell: endCellElement.cellIndex + 1,\n },\n rowElements: [],\n rows: [],\n cells: [],\n };\n\n // Flip start/end if end > start\n if (this.range.start.cell > this.range.end.cell) {\n [this.range.start.cell, this.range.end.cell] = [this.range.end.cell, this.range.start.cell];\n }\n if (this.range.start.row > this.range.end.row) {\n [this.range.start.row, this.range.end.row] = [this.range.end.row, this.range.start.row];\n }\n }\n\n protected getRowElements(): void {\n if (! this.range || ! this.elements.table) {\n return;\n }\n\n const numTableHeaders = this.elements.table.querySelectorAll('thead tr').length;\n let theadRows: HTMLTableRowElement[] = [];\n let tbodyRows: HTMLTableRowElement[] = [];\n\n const theadStart = this.range.start.row <= numTableHeaders\n ? Math.min(numTableHeaders, this.range.start.row)\n : null;\n const theadEnd = this.range.start.row <= numTableHeaders\n ? Math.min(numTableHeaders, this.range.end.row)\n : null;\n\n const tbodyStart = this.range.end.row > numTableHeaders\n ? Math.max(1, this.range.start.row - numTableHeaders)\n : null;\n const tbodyEnd = this.range.end.row > numTableHeaders\n ? Math.max(1, this.range.end.row - numTableHeaders)\n : null;\n\n if (theadStart) {\n theadRows = Array.from(this.elements.table.querySelectorAll(\n `thead tr:nth-of-type(n+${theadStart}):nth-of-type(-n+${theadEnd})`,\n ));\n }\n\n if (tbodyStart) {\n tbodyRows = Array.from(this.elements.table.querySelectorAll(\n `tbody tr:nth-of-type(n+${tbodyStart}):nth-of-type(-n+${tbodyEnd})`,\n ));\n }\n\n this.range.rowElements = [...theadRows, ...tbodyRows];\n }\n\n protected getRowsAndCells(): void {\n if (! this.range || ! this.range.rowElements) {\n return;\n }\n\n let cells: HTMLTableCellElement[] = [];\n this.range.rowElements.forEach((row: HTMLTableRowElement, i: number) => {\n if (! this.range) {\n return;\n }\n\n const cellsInRow = Array.from(row.querySelectorAll(\n [\n `td:nth-of-type(n+${this.range.start.cell}):nth-of-type(-n+${this.range.end.cell})`,\n `th:nth-of-type(n+${this.range.start.cell}):nth-of-type(-n+${this.range.end.cell})`,\n ].join(',')\n )) as HTMLTableCellElement[];\n\n cells = [...cells, ...cellsInRow];\n this.range.rows[i] = cellsInRow;\n });\n\n this.range.cells = cells;\n\n }\n\n protected getCellElementFromNode(inputNode: Node | null): HTMLTableCellElement | null {\n if (! inputNode) {\n return null;\n }\n\n const element: HTMLTableCellElement | null = inputNode.nodeName === '#text'\n ? (inputNode.parentElement as HTMLTableCellElement)\n : (inputNode as HTMLTableCellElement);\n\n if (! element || ! ['TD', 'TH'].includes(element.tagName)) {\n return null;\n }\n\n return element;\n }\n\n protected getTableElementsFromCellElement(cellElement: HTMLTableCellElement): TableSelectionTableElements | null {\n if (! cellElement || ! cellElement.parentElement || ! cellElement.parentElement.parentElement) {\n return null;\n }\n\n const tHeadOrBody = cellElement.parentElement.parentElement;\n const table: HTMLTableElement = ['TBODY', 'THEAD'].includes(tHeadOrBody.tagName)\n ? (tHeadOrBody.parentElement as HTMLTableElement)\n : (tHeadOrBody as HTMLTableElement);\n\n if (! table || ! this.config.selector || ! table.matches(this.config.selector)) {\n return null;\n }\n\n return {\n table,\n thead: table.querySelector('thead') as HTMLTableSectionElement,\n tbody: table.querySelector('tbody') as HTMLTableSectionElement,\n };\n }\n\n protected select(): void {\n if (this.range && this.range.cells) {\n this.range.cells.forEach((cellElement) => {\n if (this.config.selectionCssMode === 'cssClass' && this.config.selectionCssClass) {\n cellElement.classList.add(this.config.selectionCssClass);\n } else {\n cellElement.style.backgroundColor = 'var(--table-selection-background-color, Highlight)';\n cellElement.style.color = 'var(--table-selection-color, HighlightText)';\n }\n });\n }\n }\n\n protected deselect(): void {\n if (this.range && this.range.cells) {\n this.range.cells.forEach((cellElement) => {\n if (this.config.selectionCssMode === 'cssClass' && this.config.selectionCssClass) {\n cellElement.classList.remove(this.config.selectionCssClass);\n } else {\n cellElement.style.removeProperty('background-color');\n cellElement.style.removeProperty('color');\n }\n });\n this.range = null;\n }\n }\n\n protected copyToClipboard(e: ClipboardEvent): void {\n if (! this.range || ! this.range.rows || ! e.clipboardData) {\n return;\n }\n\n const selectionText = (this.range.rows as HTMLTableCellElement[][])\n .map((row) => row.map((cell) => cell.innerText).join('\\t'))\n .join('\\r\\n');\n\n if (! selectionText) {\n return;\n }\n\n e.clipboardData.setData('text/plain', selectionText);\n e.preventDefault();\n }\n\n}\n"],"names":["root","factory","exports","module","define","amd","self","__webpack_require__","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","config","selector","selectionCssMode","selectionCssClass","range","this","styles","document","createElement","innerHTML","head","appendChild","addEventListener","onSelectionChange","e","copyToClipboard","selection","getSelection","deselect","isCollapsed","startCell","getCellElementFromNode","anchorNode","endCell","focusNode","startTableElements","getTableElementsFromCellElement","endTableElements","table","elements","getRangeByStartAndEndElement","getRowElements","getRowsAndCells","select","startCellElement","endCellElement","startRowElement","parentElement","endRowElement","start","row","rowIndex","cell","cellIndex","end","rowElements","rows","cells","numTableHeaders","querySelectorAll","length","theadRows","tbodyRows","theadStart","Math","min","theadEnd","tbodyStart","max","tbodyEnd","Array","from","forEach","i","cellsInRow","join","inputNode","element","nodeName","includes","tagName","cellElement","tHeadOrBody","matches","thead","querySelector","tbody","classList","add","style","backgroundColor","color","remove","removeProperty","clipboardData","selectionText","map","innerText","setData","preventDefault"],"sourceRoot":""} -------------------------------------------------------------------------------- /examples/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Table selection 5 | 6 | 7 | 8 |
9 | 10 |

Table selection

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
Header 1Header 2Header 3Header 4Header 5Header 6Header 7Header 8
Row:1 Cell:1Row:1 Cell:2Row:1 Cell:3Row:1 Cell:4Row:1 Cell:5Row:1 Cell:6Row:1 Cell:7Row:1 Cell:8
Row:2 Cell:1Row:2 Cell:2Row:2 Cell:3Row:2 Cell:4Row:2 Cell:5Row:2 Cell:6Row:2 Cell:7Row:2 Cell:8
Row:3 Cell:1Row:3 Cell:2Row:3 Cell:3Row:3 Cell:4Row:3 Cell:5Row:3 Cell:6Row:3 Cell:7Row:3 Cell:8
Row:4 Cell:1Row:4 Cell:2Row:4 Cell:3Row:4 Cell:4Row:4 Cell:5Row:4 Cell:6Row:4 Cell:7Row:4 Cell:8
Row:5 Cell:1Row:5 Cell:2Row:5 Cell:3Row:5 Cell:4Row:5 Cell:5Row:5 Cell:6Row:5 Cell:7Row:5 Cell:8
Row:6 Cell:1Row:6 Cell:2Row:6 Cell:3Row:6 Cell:4Row:6 Cell:5Row:6 Cell:6Row:6 Cell:7Row:6 Cell:8
Row:7 Cell:1Row:7 Cell:2Row:7 Cell:3Row:7 Cell:4Row:7 Cell:5Row:7 Cell:6Row:7 Cell:7Row:7 Cell:8
Row:8 Cell:1Row:8 Cell:2Row:8 Cell:3Row:8 Cell:4Row:8 Cell:5Row:8 Cell:6Row:8 Cell:7Row:8 Cell:8
Row:9 Cell:1Row:9 Cell:2Row:9 Cell:3Row:9 Cell:4Row:9 Cell:5Row:9 Cell:6Row:9 Cell:7Row:9 Cell:8
Row:10 Cell:1Row:10 Cell:2Row:10 Cell:3Row:10 Cell:4Row:10 Cell:5Row:10 Cell:6Row:10 Cell:7Row:10 Cell:8
Row:11 Cell:1Row:11 Cell:2Row:11 Cell:3Row:11 Cell:4Row:11 Cell:5Row:11 Cell:6Row:11 Cell:7Row:11 Cell:8
Row:12 Cell:1Row:12 Cell:2Row:12 Cell:3Row:12 Cell:4Row:12 Cell:5Row:12 Cell:6Row:12 Cell:7Row:12 Cell:8
Row:13 Cell:1Row:13 Cell:2Row:13 Cell:3Row:13 Cell:4Row:13 Cell:5Row:13 Cell:6Row:13 Cell:7Row:13 Cell:8
Row:14 Cell:1Row:14 Cell:2Row:14 Cell:3Row:14 Cell:4Row:14 Cell:5Row:14 Cell:6Row:14 Cell:7Row:14 Cell:8
Row:15 Cell:1Row:15 Cell:2Row:15 Cell:3Row:15 Cell:4Row:15 Cell:5Row:15 Cell:6Row:15 Cell:7Row:15 Cell:8
Row:16 Cell:1Row:16 Cell:2Row:16 Cell:3Row:16 Cell:4Row:16 Cell:5Row:16 Cell:6Row:16 Cell:7Row:16 Cell:8
Row:17 Cell:1Row:17 Cell:2Row:17 Cell:3Row:17 Cell:4Row:17 Cell:5Row:17 Cell:6Row:17 Cell:7Row:17 Cell:8
Row:18 Cell:1Row:18 Cell:2Row:18 Cell:3Row:18 Cell:4Row:18 Cell:5Row:18 Cell:6Row:18 Cell:7Row:18 Cell:8
Row:19 Cell:1Row:19 Cell:2Row:19 Cell:3Row:19 Cell:4Row:19 Cell:5Row:19 Cell:6Row:19 Cell:7Row:19 Cell:8
Row:20 Cell:1Row:20 Cell:2Row:20 Cell:3Row:20 Cell:4Row:20 Cell:5Row:20 Cell:6Row:20 Cell:7Row:20 Cell:8
Row:21 Cell:1Row:21 Cell:2Row:21 Cell:3Row:21 Cell:4Row:21 Cell:5Row:21 Cell:6Row:21 Cell:7Row:21 Cell:8
Row:22 Cell:1Row:22 Cell:2Row:22 Cell:3Row:22 Cell:4Row:22 Cell:5Row:22 Cell:6Row:22 Cell:7Row:22 Cell:8
Row:23 Cell:1Row:23 Cell:2Row:23 Cell:3Row:23 Cell:4Row:23 Cell:5Row:23 Cell:6Row:23 Cell:7Row:23 Cell:8
Row:24 Cell:1Row:24 Cell:2Row:24 Cell:3Row:24 Cell:4Row:24 Cell:5Row:24 Cell:6Row:24 Cell:7Row:24 Cell:8
Row:25 Cell:1Row:25 Cell:2Row:25 Cell:3Row:25 Cell:4Row:25 Cell:5Row:25 Cell:6Row:25 Cell:7Row:25 Cell:8
Row:26 Cell:1Row:26 Cell:2Row:26 Cell:3Row:26 Cell:4Row:26 Cell:5Row:26 Cell:6Row:26 Cell:7Row:26 Cell:8
Row:27 Cell:1Row:27 Cell:2Row:27 Cell:3Row:27 Cell:4Row:27 Cell:5Row:27 Cell:6Row:27 Cell:7Row:27 Cell:8
Row:28 Cell:1Row:28 Cell:2Row:28 Cell:3Row:28 Cell:4Row:28 Cell:5Row:28 Cell:6Row:28 Cell:7Row:28 Cell:8
Row:29 Cell:1Row:29 Cell:2Row:29 Cell:3Row:29 Cell:4Row:29 Cell:5Row:29 Cell:6Row:29 Cell:7Row:29 Cell:8
Row:30 Cell:1Row:30 Cell:2Row:30 Cell:3Row:30 Cell:4Row:30 Cell:5Row:30 Cell:6Row:30 Cell:7Row:30 Cell:8
Row:31 Cell:1Row:31 Cell:2Row:31 Cell:3Row:31 Cell:4Row:31 Cell:5Row:31 Cell:6Row:31 Cell:7Row:31 Cell:8
Row:32 Cell:1Row:32 Cell:2Row:32 Cell:3Row:32 Cell:4Row:32 Cell:5Row:32 Cell:6Row:32 Cell:7Row:32 Cell:8
Row:33 Cell:1Row:33 Cell:2Row:33 Cell:3Row:33 Cell:4Row:33 Cell:5Row:33 Cell:6Row:33 Cell:7Row:33 Cell:8
Row:34 Cell:1Row:34 Cell:2Row:34 Cell:3Row:34 Cell:4Row:34 Cell:5Row:34 Cell:6Row:34 Cell:7Row:34 Cell:8
Row:35 Cell:1Row:35 Cell:2Row:35 Cell:3Row:35 Cell:4Row:35 Cell:5Row:35 Cell:6Row:35 Cell:7Row:35 Cell:8
Row:36 Cell:1Row:36 Cell:2Row:36 Cell:3Row:36 Cell:4Row:36 Cell:5Row:36 Cell:6Row:36 Cell:7Row:36 Cell:8
Row:37 Cell:1Row:37 Cell:2Row:37 Cell:3Row:37 Cell:4Row:37 Cell:5Row:37 Cell:6Row:37 Cell:7Row:37 Cell:8
Row:38 Cell:1Row:38 Cell:2Row:38 Cell:3Row:38 Cell:4Row:38 Cell:5Row:38 Cell:6Row:38 Cell:7Row:38 Cell:8
Row:39 Cell:1Row:39 Cell:2Row:39 Cell:3Row:39 Cell:4Row:39 Cell:5Row:39 Cell:6Row:39 Cell:7Row:39 Cell:8
Row:40 Cell:1Row:40 Cell:2Row:40 Cell:3Row:40 Cell:4Row:40 Cell:5Row:40 Cell:6Row:40 Cell:7Row:40 Cell:8
Row:41 Cell:1Row:41 Cell:2Row:41 Cell:3Row:41 Cell:4Row:41 Cell:5Row:41 Cell:6Row:41 Cell:7Row:41 Cell:8
Row:42 Cell:1Row:42 Cell:2Row:42 Cell:3Row:42 Cell:4Row:42 Cell:5Row:42 Cell:6Row:42 Cell:7Row:42 Cell:8
Row:43 Cell:1Row:43 Cell:2Row:43 Cell:3Row:43 Cell:4Row:43 Cell:5Row:43 Cell:6Row:43 Cell:7Row:43 Cell:8
Row:44 Cell:1Row:44 Cell:2Row:44 Cell:3Row:44 Cell:4Row:44 Cell:5Row:44 Cell:6Row:44 Cell:7Row:44 Cell:8
Row:45 Cell:1Row:45 Cell:2Row:45 Cell:3Row:45 Cell:4Row:45 Cell:5Row:45 Cell:6Row:45 Cell:7Row:45 Cell:8
Row:46 Cell:1Row:46 Cell:2Row:46 Cell:3Row:46 Cell:4Row:46 Cell:5Row:46 Cell:6Row:46 Cell:7Row:46 Cell:8
Row:47 Cell:1Row:47 Cell:2Row:47 Cell:3Row:47 Cell:4Row:47 Cell:5Row:47 Cell:6Row:47 Cell:7Row:47 Cell:8
Row:48 Cell:1Row:48 Cell:2Row:48 Cell:3Row:48 Cell:4Row:48 Cell:5Row:48 Cell:6Row:48 Cell:7Row:48 Cell:8
Row:49 Cell:1Row:49 Cell:2Row:49 Cell:3Row:49 Cell:4Row:49 Cell:5Row:49 Cell:6Row:49 Cell:7Row:49 Cell:8
Row:50 Cell:1Row:50 Cell:2Row:50 Cell:3Row:50 Cell:4Row:50 Cell:5Row:50 Cell:6Row:50 Cell:7Row:50 Cell:8
Row:51 Cell:1Row:51 Cell:2Row:51 Cell:3Row:51 Cell:4Row:51 Cell:5Row:51 Cell:6Row:51 Cell:7Row:51 Cell:8
Row:52 Cell:1Row:52 Cell:2Row:52 Cell:3Row:52 Cell:4Row:52 Cell:5Row:52 Cell:6Row:52 Cell:7Row:52 Cell:8
Row:53 Cell:1Row:53 Cell:2Row:53 Cell:3Row:53 Cell:4Row:53 Cell:5Row:53 Cell:6Row:53 Cell:7Row:53 Cell:8
Row:54 Cell:1Row:54 Cell:2Row:54 Cell:3Row:54 Cell:4Row:54 Cell:5Row:54 Cell:6Row:54 Cell:7Row:54 Cell:8
Row:55 Cell:1Row:55 Cell:2Row:55 Cell:3Row:55 Cell:4Row:55 Cell:5Row:55 Cell:6Row:55 Cell:7Row:55 Cell:8
Row:56 Cell:1Row:56 Cell:2Row:56 Cell:3Row:56 Cell:4Row:56 Cell:5Row:56 Cell:6Row:56 Cell:7Row:56 Cell:8
Row:57 Cell:1Row:57 Cell:2Row:57 Cell:3Row:57 Cell:4Row:57 Cell:5Row:57 Cell:6Row:57 Cell:7Row:57 Cell:8
Row:58 Cell:1Row:58 Cell:2Row:58 Cell:3Row:58 Cell:4Row:58 Cell:5Row:58 Cell:6Row:58 Cell:7Row:58 Cell:8
Row:59 Cell:1Row:59 Cell:2Row:59 Cell:3Row:59 Cell:4Row:59 Cell:5Row:59 Cell:6Row:59 Cell:7Row:59 Cell:8
Row:60 Cell:1Row:60 Cell:2Row:60 Cell:3Row:60 Cell:4Row:60 Cell:5Row:60 Cell:6Row:60 Cell:7Row:60 Cell:8
Row:61 Cell:1Row:61 Cell:2Row:61 Cell:3Row:61 Cell:4Row:61 Cell:5Row:61 Cell:6Row:61 Cell:7Row:61 Cell:8
Row:62 Cell:1Row:62 Cell:2Row:62 Cell:3Row:62 Cell:4Row:62 Cell:5Row:62 Cell:6Row:62 Cell:7Row:62 Cell:8
Row:63 Cell:1Row:63 Cell:2Row:63 Cell:3Row:63 Cell:4Row:63 Cell:5Row:63 Cell:6Row:63 Cell:7Row:63 Cell:8
Row:64 Cell:1Row:64 Cell:2Row:64 Cell:3Row:64 Cell:4Row:64 Cell:5Row:64 Cell:6Row:64 Cell:7Row:64 Cell:8
Row:65 Cell:1Row:65 Cell:2Row:65 Cell:3Row:65 Cell:4Row:65 Cell:5Row:65 Cell:6Row:65 Cell:7Row:65 Cell:8
Row:66 Cell:1Row:66 Cell:2Row:66 Cell:3Row:66 Cell:4Row:66 Cell:5Row:66 Cell:6Row:66 Cell:7Row:66 Cell:8
Row:67 Cell:1Row:67 Cell:2Row:67 Cell:3Row:67 Cell:4Row:67 Cell:5Row:67 Cell:6Row:67 Cell:7Row:67 Cell:8
Row:68 Cell:1Row:68 Cell:2Row:68 Cell:3Row:68 Cell:4Row:68 Cell:5Row:68 Cell:6Row:68 Cell:7Row:68 Cell:8
Row:69 Cell:1Row:69 Cell:2Row:69 Cell:3Row:69 Cell:4Row:69 Cell:5Row:69 Cell:6Row:69 Cell:7Row:69 Cell:8
Row:70 Cell:1Row:70 Cell:2Row:70 Cell:3Row:70 Cell:4Row:70 Cell:5Row:70 Cell:6Row:70 Cell:7Row:70 Cell:8
Row:71 Cell:1Row:71 Cell:2Row:71 Cell:3Row:71 Cell:4Row:71 Cell:5Row:71 Cell:6Row:71 Cell:7Row:71 Cell:8
Row:72 Cell:1Row:72 Cell:2Row:72 Cell:3Row:72 Cell:4Row:72 Cell:5Row:72 Cell:6Row:72 Cell:7Row:72 Cell:8
Row:73 Cell:1Row:73 Cell:2Row:73 Cell:3Row:73 Cell:4Row:73 Cell:5Row:73 Cell:6Row:73 Cell:7Row:73 Cell:8
Row:74 Cell:1Row:74 Cell:2Row:74 Cell:3Row:74 Cell:4Row:74 Cell:5Row:74 Cell:6Row:74 Cell:7Row:74 Cell:8
Row:75 Cell:1Row:75 Cell:2Row:75 Cell:3Row:75 Cell:4Row:75 Cell:5Row:75 Cell:6Row:75 Cell:7Row:75 Cell:8
Row:76 Cell:1Row:76 Cell:2Row:76 Cell:3Row:76 Cell:4Row:76 Cell:5Row:76 Cell:6Row:76 Cell:7Row:76 Cell:8
Row:77 Cell:1Row:77 Cell:2Row:77 Cell:3Row:77 Cell:4Row:77 Cell:5Row:77 Cell:6Row:77 Cell:7Row:77 Cell:8
Row:78 Cell:1Row:78 Cell:2Row:78 Cell:3Row:78 Cell:4Row:78 Cell:5Row:78 Cell:6Row:78 Cell:7Row:78 Cell:8
Row:79 Cell:1Row:79 Cell:2Row:79 Cell:3Row:79 Cell:4Row:79 Cell:5Row:79 Cell:6Row:79 Cell:7Row:79 Cell:8
Row:80 Cell:1Row:80 Cell:2Row:80 Cell:3Row:80 Cell:4Row:80 Cell:5Row:80 Cell:6Row:80 Cell:7Row:80 Cell:8
Row:81 Cell:1Row:81 Cell:2Row:81 Cell:3Row:81 Cell:4Row:81 Cell:5Row:81 Cell:6Row:81 Cell:7Row:81 Cell:8
Row:82 Cell:1Row:82 Cell:2Row:82 Cell:3Row:82 Cell:4Row:82 Cell:5Row:82 Cell:6Row:82 Cell:7Row:82 Cell:8
Row:83 Cell:1Row:83 Cell:2Row:83 Cell:3Row:83 Cell:4Row:83 Cell:5Row:83 Cell:6Row:83 Cell:7Row:83 Cell:8
Row:84 Cell:1Row:84 Cell:2Row:84 Cell:3Row:84 Cell:4Row:84 Cell:5Row:84 Cell:6Row:84 Cell:7Row:84 Cell:8
Row:85 Cell:1Row:85 Cell:2Row:85 Cell:3Row:85 Cell:4Row:85 Cell:5Row:85 Cell:6Row:85 Cell:7Row:85 Cell:8
Row:86 Cell:1Row:86 Cell:2Row:86 Cell:3Row:86 Cell:4Row:86 Cell:5Row:86 Cell:6Row:86 Cell:7Row:86 Cell:8
Row:87 Cell:1Row:87 Cell:2Row:87 Cell:3Row:87 Cell:4Row:87 Cell:5Row:87 Cell:6Row:87 Cell:7Row:87 Cell:8
Row:88 Cell:1Row:88 Cell:2Row:88 Cell:3Row:88 Cell:4Row:88 Cell:5Row:88 Cell:6Row:88 Cell:7Row:88 Cell:8
Row:89 Cell:1Row:89 Cell:2Row:89 Cell:3Row:89 Cell:4Row:89 Cell:5Row:89 Cell:6Row:89 Cell:7Row:89 Cell:8
Row:90 Cell:1Row:90 Cell:2Row:90 Cell:3Row:90 Cell:4Row:90 Cell:5Row:90 Cell:6Row:90 Cell:7Row:90 Cell:8
Row:91 Cell:1Row:91 Cell:2Row:91 Cell:3Row:91 Cell:4Row:91 Cell:5Row:91 Cell:6Row:91 Cell:7Row:91 Cell:8
Row:92 Cell:1Row:92 Cell:2Row:92 Cell:3Row:92 Cell:4Row:92 Cell:5Row:92 Cell:6Row:92 Cell:7Row:92 Cell:8
Row:93 Cell:1Row:93 Cell:2Row:93 Cell:3Row:93 Cell:4Row:93 Cell:5Row:93 Cell:6Row:93 Cell:7Row:93 Cell:8
Row:94 Cell:1Row:94 Cell:2Row:94 Cell:3Row:94 Cell:4Row:94 Cell:5Row:94 Cell:6Row:94 Cell:7Row:94 Cell:8
Row:95 Cell:1Row:95 Cell:2Row:95 Cell:3Row:95 Cell:4Row:95 Cell:5Row:95 Cell:6Row:95 Cell:7Row:95 Cell:8
Row:96 Cell:1Row:96 Cell:2Row:96 Cell:3Row:96 Cell:4Row:96 Cell:5Row:96 Cell:6Row:96 Cell:7Row:96 Cell:8
Row:97 Cell:1Row:97 Cell:2Row:97 Cell:3Row:97 Cell:4Row:97 Cell:5Row:97 Cell:6Row:97 Cell:7Row:97 Cell:8
Row:98 Cell:1Row:98 Cell:2Row:98 Cell:3Row:98 Cell:4Row:98 Cell:5Row:98 Cell:6Row:98 Cell:7Row:98 Cell:8
Row:99 Cell:1Row:99 Cell:2Row:99 Cell:3Row:99 Cell:4Row:99 Cell:5Row:99 Cell:6Row:99 Cell:7Row:99 Cell:8
Row:100 Cell:1Row:100 Cell:2Row:100 Cell:3Row:100 Cell:4Row:100 Cell:5Row:100 Cell:6Row:100 Cell:7Row:100 Cell:8
119 | 120 |
121 | 122 |
123 |
124 |
125 |
    126 |
  • Just add .table-selection class to a table and add the table-selection NPM package to your project.
  • 127 |
  • Supports horizontal, vertical and rectangular selections.
  • 128 |
  • Includes clipboard support with table formatting for Excel or other spreadsheet software!
  • 129 |
  • It even works on mobile!
  • 130 |
  • Tested and working in Chrome, Firefox, Edge, Mobile Chrome and Samsung Browser
  • 131 |
132 |
133 |
134 | 135 |

136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 |

146 |
147 | 148 | 149 | 150 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /examples/table-selection-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PXLWidgets/table-selection/8e7b7fc8dde81cd3e1423e979a9da105cf26d453/examples/table-selection-demo.gif -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | verbose: true, 5 | collectCoverage: true, 6 | collectCoverageFrom: [ 7 | './src/**/*.ts', 8 | ], 9 | coverageDirectory: 'coverage', 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pxlwidgets/table-selection", 3 | "version": "1.0.0", 4 | "author": "Wouter Smit", 5 | "repository": "https://github.com/PXLWidgets/table-selection", 6 | "homepage": "https://github.com/PXLWidgets/table-selection", 7 | "support": { 8 | "issues": "https://github.com/PXLWidgets/table-selection/issues" 9 | }, 10 | "description": "Adds vertical and rectangular selection to HTML tables to allow copy of specific columns and rows without copying contents of other cells in range.", 11 | "keywords": [ 12 | "table", 13 | "select", 14 | "selection", 15 | "column", 16 | "row", 17 | "cell", 18 | "vertical", 19 | "rectangular" 20 | ], 21 | "license": "MIT", 22 | "main": "./cjs/index.js", 23 | "module": "./esm/index.js", 24 | "types": "./table-selection.d.ts", 25 | "exports": { 26 | ".": { 27 | "require": "./cjs/index.js", 28 | "import": "./esm/index.js", 29 | "default": "./esm/index.js" 30 | }, 31 | "./lib/*": "./lib/*.js" 32 | }, 33 | "unpkg": "umd/table-selection.min.js", 34 | "jsdelivr": "umd/table-selection.min.js", 35 | "scripts": { 36 | "clean:dist": "rm -rf cjs esm umd", 37 | "dev": "run-p \"build:** -- --watch\"", 38 | "build": "npm run clean:dist && run-p build:** && npm run create:dist", 39 | "build:cjs": "tsc --outDir cjs --sourceMap false -m CommonJS", 40 | "build:esm": "tsc --outDir esm --sourceMap false -m es2015", 41 | "build:umd": "webpack --mode production", 42 | "create:dist": "mkdir -p dist && mkdir -p dist/js && cp umd/* dist/js && cp dist/js/table-selection.min.js dist/js/table-selection.js", 43 | "preversion": "npm run docs && git reset && git add ./CHANGELOG.md ./README.md", 44 | "prepublishOnly": "git checkout master && npm run test && npm run build", 45 | "test": "jest", 46 | "coveralls": "cat ./coverage/lcov.info | coveralls" 47 | }, 48 | "devDependencies": { 49 | "@types/jest": "^26.0.15", 50 | "@types/jsdom": "^16.2.5", 51 | "@types/webpack-env": "^1.15.3", 52 | "coveralls": "^3.1.0", 53 | "jest": "^26.6.3", 54 | "jsdom": "^16.4.0", 55 | "npm-run-all": "^4.1.5", 56 | "ts-jest": "^26.4.4", 57 | "ts-loader": "^8.0.11", 58 | "tslib": "^2.0.0", 59 | "typescript": "^4.2", 60 | "webpack": "^5.4.0", 61 | "webpack-cli": "^4.2.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * TableSelection library v1.0.0 (https://github.com/PXLWidgets/table-selection) 3 | * Copyright (c) 2019 Wouter Smit 4 | * Licensed under MIT (https://github.com/PXLWidgets/table-selection/blob/master/LICENSE) 5 | */ 6 | 7 | import { TableSelection } from './ts/TableSelection'; 8 | 9 | export default TableSelection; 10 | -------------------------------------------------------------------------------- /src/ts/TableSelection.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * TableSelection library v1.0.0 (https://github.com/PXLWidgets/table-selection) 3 | * Copyright (c) 2019 Wouter Smit 4 | * Licensed under MIT (https://github.com/PXLWidgets/table-selection/blob/master/LICENSE) 5 | */ 6 | 7 | import { TableSelectionConfig } from './interfaces/TableSelectionConfig'; 8 | import { TableSelectionRange } from './interfaces/TableSelectionRange'; 9 | import { TableSelectionTableElements } from './interfaces/TableSelectionTableElements'; 10 | 11 | export class TableSelection { 12 | 13 | config: TableSelectionConfig = { 14 | selector: '.table-selection', 15 | selectionCssMode: 'style', 16 | selectionCssClass: 'selected', 17 | }; 18 | 19 | range: TableSelectionRange | null = null; 20 | elements: TableSelectionTableElements; 21 | 22 | constructor(config: TableSelectionConfig = {}) { 23 | this.config = { ...this.config, ...config }; 24 | 25 | // Hide default selection 26 | const styles = document.createElement('style'); 27 | styles.innerHTML = `${this.config.selector} *::selection { 28 | background: transparent; 29 | color: inherit; 30 | }`; 31 | document.head.appendChild(styles); 32 | 33 | document.addEventListener('selectionchange', () => this.onSelectionChange()); 34 | document.addEventListener('copy', (e) => this.copyToClipboard(e)); 35 | } 36 | 37 | protected onSelectionChange(): void { 38 | const selection = getSelection(); 39 | 40 | this.deselect(); 41 | if (! selection || selection.isCollapsed) { 42 | return; 43 | } 44 | 45 | const startCell: HTMLTableCellElement | null = this.getCellElementFromNode(selection.anchorNode); 46 | const endCell: HTMLTableCellElement | null = this.getCellElementFromNode(selection.focusNode); 47 | 48 | if (! startCell || ! endCell) { 49 | return; 50 | } 51 | 52 | const startTableElements = this.getTableElementsFromCellElement(startCell); 53 | const endTableElements = this.getTableElementsFromCellElement(endCell); 54 | if (! startTableElements || ! endTableElements) { 55 | return; 56 | } 57 | 58 | const isSameTable = startTableElements.table === endTableElements.table; 59 | if (! isSameTable) { 60 | return; 61 | } 62 | 63 | this.elements = startTableElements; 64 | 65 | this.getRangeByStartAndEndElement(startCell, endCell); 66 | this.getRowElements(); 67 | this.getRowsAndCells(); 68 | 69 | if (this.range) { 70 | this.select(); 71 | } else { 72 | this.deselect(); 73 | } 74 | } 75 | 76 | protected getRangeByStartAndEndElement( 77 | startCellElement: HTMLTableCellElement, 78 | endCellElement: HTMLTableCellElement, 79 | ): void { 80 | 81 | const startRowElement: HTMLTableRowElement | null = startCellElement.parentElement as HTMLTableRowElement; 82 | const endRowElement: HTMLTableRowElement | null = endCellElement.parentElement as HTMLTableRowElement; 83 | 84 | if (! startRowElement || ! endRowElement) { 85 | return; 86 | } 87 | 88 | this.range = { 89 | start: { 90 | row: startRowElement.rowIndex + 1, 91 | cell: startCellElement.cellIndex + 1, 92 | }, 93 | end: { 94 | row: endRowElement.rowIndex + 1, 95 | cell: endCellElement.cellIndex + 1, 96 | }, 97 | rowElements: [], 98 | rows: [], 99 | cells: [], 100 | }; 101 | 102 | // Flip start/end if end > start 103 | if (this.range.start.cell > this.range.end.cell) { 104 | [this.range.start.cell, this.range.end.cell] = [this.range.end.cell, this.range.start.cell]; 105 | } 106 | if (this.range.start.row > this.range.end.row) { 107 | [this.range.start.row, this.range.end.row] = [this.range.end.row, this.range.start.row]; 108 | } 109 | } 110 | 111 | protected getRowElements(): void { 112 | if (! this.range || ! this.elements.table) { 113 | return; 114 | } 115 | 116 | const numTableHeaders = this.elements.table.querySelectorAll('thead tr').length; 117 | let theadRows: HTMLTableRowElement[] = []; 118 | let tbodyRows: HTMLTableRowElement[] = []; 119 | 120 | const theadStart = this.range.start.row <= numTableHeaders 121 | ? Math.min(numTableHeaders, this.range.start.row) 122 | : null; 123 | const theadEnd = this.range.start.row <= numTableHeaders 124 | ? Math.min(numTableHeaders, this.range.end.row) 125 | : null; 126 | 127 | const tbodyStart = this.range.end.row > numTableHeaders 128 | ? Math.max(1, this.range.start.row - numTableHeaders) 129 | : null; 130 | const tbodyEnd = this.range.end.row > numTableHeaders 131 | ? Math.max(1, this.range.end.row - numTableHeaders) 132 | : null; 133 | 134 | if (theadStart) { 135 | theadRows = Array.from(this.elements.table.querySelectorAll( 136 | `thead tr:nth-of-type(n+${theadStart}):nth-of-type(-n+${theadEnd})`, 137 | )); 138 | } 139 | 140 | if (tbodyStart) { 141 | tbodyRows = Array.from(this.elements.table.querySelectorAll( 142 | `tbody tr:nth-of-type(n+${tbodyStart}):nth-of-type(-n+${tbodyEnd})`, 143 | )); 144 | } 145 | 146 | this.range.rowElements = [...theadRows, ...tbodyRows]; 147 | } 148 | 149 | protected getRowsAndCells(): void { 150 | if (! this.range || ! this.range.rowElements) { 151 | return; 152 | } 153 | 154 | let cells: HTMLTableCellElement[] = []; 155 | this.range.rowElements.forEach((row: HTMLTableRowElement, i: number) => { 156 | if (! this.range) { 157 | return; 158 | } 159 | 160 | const cellsInRow = Array.from(row.querySelectorAll( 161 | [ 162 | `td:nth-of-type(n+${this.range.start.cell}):nth-of-type(-n+${this.range.end.cell})`, 163 | `th:nth-of-type(n+${this.range.start.cell}):nth-of-type(-n+${this.range.end.cell})`, 164 | ].join(',') 165 | )) as HTMLTableCellElement[]; 166 | 167 | cells = [...cells, ...cellsInRow]; 168 | this.range.rows[i] = cellsInRow; 169 | }); 170 | 171 | this.range.cells = cells; 172 | 173 | } 174 | 175 | protected getCellElementFromNode(inputNode: Node | null): HTMLTableCellElement | null { 176 | if (! inputNode) { 177 | return null; 178 | } 179 | 180 | const element: HTMLTableCellElement | null = inputNode.nodeName === '#text' 181 | ? (inputNode.parentElement as HTMLTableCellElement).closest('td,th') 182 | : (inputNode as HTMLTableCellElement); 183 | 184 | if (! element || ! ['TD', 'TH'].includes(element.tagName)) { 185 | return null; 186 | } 187 | 188 | return element; 189 | } 190 | 191 | protected getTableElementsFromCellElement(cellElement: HTMLTableCellElement): TableSelectionTableElements | null { 192 | if (! cellElement || ! cellElement.parentElement || ! cellElement.parentElement.parentElement) { 193 | return null; 194 | } 195 | 196 | const tHeadOrBody = cellElement.parentElement.parentElement; 197 | const table: HTMLTableElement = ['TBODY', 'THEAD'].includes(tHeadOrBody.tagName) 198 | ? (tHeadOrBody.parentElement as HTMLTableElement) 199 | : (tHeadOrBody as HTMLTableElement); 200 | 201 | if (! table || ! this.config.selector || ! table.matches(this.config.selector)) { 202 | return null; 203 | } 204 | 205 | return { 206 | table, 207 | thead: table.querySelector('thead') as HTMLTableSectionElement, 208 | tbody: table.querySelector('tbody') as HTMLTableSectionElement, 209 | }; 210 | } 211 | 212 | protected select(): void { 213 | if (this.range && this.range.cells) { 214 | this.range.cells.forEach((cellElement) => { 215 | if (this.config.selectionCssMode === 'cssClass' && this.config.selectionCssClass) { 216 | cellElement.classList.add(this.config.selectionCssClass); 217 | } else { 218 | cellElement.style.backgroundColor = 'var(--table-selection-background-color, Highlight)'; 219 | cellElement.style.color = 'var(--table-selection-color, HighlightText)'; 220 | } 221 | }); 222 | } 223 | } 224 | 225 | protected deselect(): void { 226 | if (this.range && this.range.cells) { 227 | this.range.cells.forEach((cellElement) => { 228 | if (this.config.selectionCssMode === 'cssClass' && this.config.selectionCssClass) { 229 | cellElement.classList.remove(this.config.selectionCssClass); 230 | } else { 231 | cellElement.style.removeProperty('background-color'); 232 | cellElement.style.removeProperty('color'); 233 | } 234 | }); 235 | this.range = null; 236 | } 237 | } 238 | 239 | protected copyToClipboard(e: ClipboardEvent): void { 240 | if (! this.range || ! this.range.rows || ! e.clipboardData) { 241 | return; 242 | } 243 | 244 | const selectionText = (this.range.rows as HTMLTableCellElement[][]) 245 | .map((row) => row.map((cell) => cell.innerText).join('\t')) 246 | .join('\r\n'); 247 | 248 | if (! selectionText) { 249 | return; 250 | } 251 | 252 | e.clipboardData.setData('text/plain', selectionText); 253 | e.preventDefault(); 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /src/ts/interfaces/TableSelectionConfig.ts: -------------------------------------------------------------------------------- 1 | export interface TableSelectionConfig { 2 | selector?: string; 3 | selectionCssMode?: 'style' | 'cssClass'; 4 | selectionCssClass?: string | null; 5 | } 6 | -------------------------------------------------------------------------------- /src/ts/interfaces/TableSelectionRange.ts: -------------------------------------------------------------------------------- 1 | export interface TableSelectionRange { 2 | start: { 3 | row: number; 4 | cell: number; 5 | }; 6 | end: { 7 | row: number; 8 | cell: number; 9 | }; 10 | rowElements?: HTMLTableRowElement[], 11 | rows: HTMLTableCellElement[][], 12 | cells: HTMLTableCellElement[], 13 | } 14 | -------------------------------------------------------------------------------- /src/ts/interfaces/TableSelectionTableElements.ts: -------------------------------------------------------------------------------- 1 | export interface TableSelectionTableElements { 2 | table?: HTMLTableElement; 3 | thead?: HTMLTableSectionElement; 4 | tbody?: HTMLTableSectionElement; 5 | } 6 | -------------------------------------------------------------------------------- /table-selection.d.ts: -------------------------------------------------------------------------------- 1 | import { TableSelectionConfig } from './src/interfaces/TableSelectionConfig'; 2 | 3 | declare const TableSelection: TableSelection.IStatic; 4 | export default TableSelection; 5 | export as namespace TableSelection; 6 | 7 | /* tslint:disable:interface-name */ 8 | /* tslint:disable:no-empty-interface */ 9 | 10 | declare namespace TableSelection { 11 | 12 | interface IStatic { 13 | initialize(config: TableSelectionConfig): SelectableTable; 14 | } 15 | 16 | interface SelectableTable { 17 | // readonly table: HTMLTableElement; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "lib": ["es2017", "dom"], 5 | "target": "es5", 6 | "module": "es2015", 7 | "noImplicitAny": true, 8 | "noImplicitReturns": true, 9 | "noImplicitThis": true, 10 | "sourceMap": true, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "declaration": true, 14 | "outDir": "cjs", 15 | "downlevelIteration": true, 16 | "allowJs": true 17 | }, 18 | "include":[ 19 | "src/**/*.ts" 20 | ], 21 | "exclude": [ 22 | "node_modules", 23 | "cjs", 24 | "umd", 25 | "esm" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/index.ts', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.ts$/, 10 | use: 'ts-loader', 11 | exclude: /node_modules/, 12 | }, 13 | ], 14 | }, 15 | devtool: 'source-map', 16 | resolve: { 17 | extensions: ['.ts', '.js'], 18 | }, 19 | output: { 20 | path: resolve(__dirname, 'umd'), 21 | filename: 'table-selection.min.js', 22 | library: { 23 | name: 'TableSelection', 24 | type: 'umd', 25 | export: 'default', 26 | }, 27 | }, 28 | }; 29 | --------------------------------------------------------------------------------