├── .babelrc.json ├── .github └── workflows │ └── main.yml ├── .gitignore ├── CNAME ├── LICENSE.md ├── README.md ├── build.sh ├── dist ├── index-pretty.js ├── index.js └── index.js.gz ├── docs ├── index.html ├── logo.png └── logo.svg ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── _LooseArray.js ├── _cleanVal.js ├── _defaultCss.js ├── _isEmpty.js ├── _shift.js ├── _shift.test.js ├── demo │ ├── demo.css │ ├── demo.js │ ├── index.ejs │ ├── public │ │ ├── logo.png │ │ └── logo.svg │ ├── samples │ │ ├── simple.js │ │ └── with-checks.js │ └── screenshot.jpg ├── index.js └── sheetclip.js └── webpack.config.js /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "current" 8 | } 9 | } 10 | ] 11 | ], 12 | "plugins": [ 13 | "@babel/plugin-proposal-class-properties" 14 | ] 15 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | on: [push] 6 | 7 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 8 | jobs: 9 | # This workflow contains a single job called "build" 10 | test: 11 | # The type of runner that the job will run on 12 | runs-on: ubuntu-latest 13 | 14 | # Steps represent a sequence of tasks that will be executed as part of the job 15 | steps: 16 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 17 | - uses: actions/checkout@v2 18 | 19 | # Runs a single command using the runners shell 20 | - run: npm install 21 | - run: npm run test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | node_modules -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | importabular.lecaro.me 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Renan LE CARO 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # importabular 2 | 3 | Lightweight spreadsheet editor for the web, to easily let your users import their data from excel. 4 | 5 | - Lightweight (under 5kb gzipped) 6 | - Mobile friendly 7 | - Copy / paste 8 | - MIT License 9 | 10 | 11 | # Quickstart 12 | 13 | The quick and dirty way : 14 | 15 | ``` 16 |
17 | 18 | 34 | ``` 35 | # Demo and doc 36 | 37 | The website will give you more details : https://importabular.lecaro.me/ 38 | 39 | NPM : https://www.npmjs.com/package/importabular 40 | 41 |  42 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NODE_ENV=production webpack 4 | 5 | # Gzipp size check 6 | gzip -kf ./dist/index.js 7 | # Pretty copy 8 | cp ./dist/index.js ./dist/index-pretty.js 9 | prettier --write ./dist/index-pretty.js 10 | 11 | 12 | ls -l ./dist/ 13 | -------------------------------------------------------------------------------- /dist/index-pretty.js: -------------------------------------------------------------------------------- 1 | !(function (t, e) { 2 | "object" == typeof exports && "object" == typeof module 3 | ? (module.exports = e()) 4 | : "function" == typeof define && define.amd 5 | ? define([], e) 6 | : "object" == typeof exports 7 | ? (exports.Importabular = e()) 8 | : (t.Importabular = e()); 9 | })(self, function () { 10 | return (() => { 11 | "use strict"; 12 | var t = { 13 | 103: (t, e, i) => { 14 | i.d(e, { default: () => l }); 15 | class s { 16 | constructor() { 17 | var t, e; 18 | (e = {}), 19 | (t = "_data") in this 20 | ? Object.defineProperty(this, t, { 21 | value: e, 22 | enumerable: !0, 23 | configurable: !0, 24 | writable: !0, 25 | }) 26 | : (this[t] = e); 27 | } 28 | _setVal(t, e, i) { 29 | const s = this._data, 30 | n = (function (t) { 31 | return 0 === t ? "0" : t ? t.toString() : ""; 32 | })(i); 33 | var o; 34 | n 35 | ? (s[t] || (s[t] = {}), (s[t][e] = n)) 36 | : s[t] && 37 | s[t][e] && 38 | (delete s[t][e], 39 | (o = s[t]), 40 | 0 === Object.keys(o).length && delete s[t]); 41 | } 42 | _clear() { 43 | this._data = {}; 44 | } 45 | _getVal(t, e) { 46 | const i = this._data; 47 | return (i && i[t] && i[t][e]) || ""; 48 | } 49 | _toArr(t, e) { 50 | const i = []; 51 | for (let s = 0; s < e; s++) { 52 | i.push([]); 53 | for (let e = 0; e < t; e++) i[s].push(this._getVal(e, s)); 54 | } 55 | return i; 56 | } 57 | } 58 | function n(t, e, i, s, n, o, r) { 59 | if ((t += i) < s) { 60 | if (n === 1 / 0) return { x: s, y: e }; 61 | if (((t = n), --e < o)) { 62 | if (r === 1 / 0) return { x: s, y: o }; 63 | e = r; 64 | } 65 | } 66 | return ( 67 | t > n && ((t = s), ++e > r && ((e = o), (t = s))), { x: t, y: e } 68 | ); 69 | } 70 | function o(t) { 71 | return t.split('"').length - 1; 72 | } 73 | function r(t, e, i) { 74 | return ( 75 | e in t 76 | ? Object.defineProperty(t, e, { 77 | value: i, 78 | enumerable: !0, 79 | configurable: !0, 80 | writable: !0, 81 | }) 82 | : (t[e] = i), 83 | t 84 | ); 85 | } 86 | const h = [ 87 | "mousedown", 88 | "mouseenter", 89 | "mouseup", 90 | "mouseleave", 91 | "touchstart", 92 | "touchend", 93 | "touchmove", 94 | "keydown", 95 | "paste", 96 | "cut", 97 | "copy", 98 | ]; 99 | class l { 100 | constructor(t) { 101 | r(this, "_width", 1), 102 | r(this, "_height", 1), 103 | r(this, "_data", new s()), 104 | r(this, "paste", (t) => { 105 | if (this._editing) return; 106 | t.preventDefault(); 107 | const e = (function (t) { 108 | var e, 109 | i, 110 | s, 111 | n, 112 | r, 113 | h, 114 | l, 115 | a = [], 116 | c = 0; 117 | for ( 118 | (s = t.split("\n")).length > 1 && 119 | "" === s[s.length - 1] && 120 | s.pop(), 121 | e = 0, 122 | i = s.length; 123 | e < i; 124 | e += 1 125 | ) { 126 | for ( 127 | s[e] = s[e].split("\t"), n = 0, r = s[e].length; 128 | n < r; 129 | n += 1 130 | ) 131 | a[c] || (a[c] = []), 132 | h && 0 === n 133 | ? ((l = a[c].length - 1), 134 | (a[c][l] = a[c][l] + "\n" + s[e][0]), 135 | h && 136 | 1 & o(s[e][0]) && 137 | ((h = !1), 138 | (a[c][l] = a[c][l] 139 | .substring(0, a[c][l].length - 1) 140 | .replace(/""/g, '"')))) 141 | : n === r - 1 && 142 | 0 === s[e][n].indexOf('"') && 143 | 1 & o(s[e][n]) 144 | ? (a[c].push( 145 | s[e][n].substring(1).replace(/""/g, '"') 146 | ), 147 | (h = !0)) 148 | : (a[c].push(s[e][n].replace(/""/g, '"')), 149 | (h = !1)); 150 | h || (c += 1); 151 | } 152 | return a; 153 | })( 154 | (t.clipboardData || window.clipboardData).getData( 155 | "text/plain" 156 | ) 157 | ), 158 | { rx: i, ry: s } = this._selection, 159 | n = { x: i[0], y: s[0] }; 160 | for (let t = 0; t < e.length; t++) 161 | for (let i = 0; i < e[0].length; i++) 162 | this._setVal(n.x + i, n.y + t, e[t][i]); 163 | this._changeSelectedCellsStyle(() => { 164 | (this._selectionStart = n), 165 | (this._selectionEnd = { 166 | x: n.x + e[0].length - 1, 167 | y: n.y + e.length - 1, 168 | }), 169 | this._onDataChanged(); 170 | }); 171 | }), 172 | r(this, "copy", (t) => { 173 | if (this._editing) return; 174 | const e = this._getSelectionAsArray(); 175 | e && 176 | (t.preventDefault(), 177 | t.clipboardData.setData( 178 | "text/plain", 179 | (function (t) { 180 | var e, 181 | i, 182 | s, 183 | n, 184 | o, 185 | r = ""; 186 | for (e = 0, i = t.length; e < i; e += 1) { 187 | for (s = 0, n = t[e].length; s < n; s += 1) 188 | s > 0 && (r += "\t"), 189 | "string" == typeof (o = t[e][s]) 190 | ? o.indexOf("\n") > -1 191 | ? (r += '"' + o.replace(/"/g, '""') + '"') 192 | : (r += o) 193 | : (r += null == o ? "" : o); 194 | r += "\n"; 195 | } 196 | return r; 197 | })(e) 198 | )); 199 | }), 200 | r(this, "cut", (t) => { 201 | this._editing || 202 | (this.copy(t), this._setAllSelectedCellsTo("")); 203 | }), 204 | r(this, "keydown", (t) => { 205 | t.ctrlKey || 206 | t.metaKey || 207 | (this._selectionStart && 208 | ("Escape" === t.key && 209 | this._editing && 210 | (t.preventDefault(), 211 | this._revertEdit(), 212 | this._stopEditing()), 213 | "Enter" === t.key && 214 | (t.preventDefault(), 215 | this._tabCursorInSelection(!1, t.shiftKey ? -1 : 1)), 216 | "Tab" === t.key && 217 | (t.preventDefault(), 218 | this._tabCursorInSelection(!0, t.shiftKey ? -1 : 1)), 219 | this._editing || 220 | ("F2" === t.key && 221 | (t.preventDefault(), this._startEditing(this._focus)), 222 | ("Delete" !== t.key && "Backspace" !== t.key) || 223 | (t.preventDefault(), this._setAllSelectedCellsTo("")), 224 | "ArrowDown" === t.key && 225 | (t.preventDefault(), 226 | this._moveCursor({ y: 1 }, t.shiftKey)), 227 | "ArrowUp" === t.key && 228 | (t.preventDefault(), 229 | this._moveCursor({ y: -1 }, t.shiftKey)), 230 | "ArrowLeft" === t.key && 231 | (t.preventDefault(), 232 | this._moveCursor({ x: -1 }, t.shiftKey)), 233 | "ArrowRight" === t.key && 234 | (t.preventDefault(), 235 | this._moveCursor({ x: 1 }, t.shiftKey))), 236 | 1 !== t.key.length || 237 | this._editing || 238 | this._changeSelectedCellsStyle(() => { 239 | const { x: t, y: e } = this._focus; 240 | this._startEditing({ x: t, y: e }), 241 | (this._getCell(t, e).firstChild.value = ""); 242 | }))); 243 | }), 244 | r(this, "_selecting", !1), 245 | r(this, "_selectionStart", null), 246 | r(this, "_selectionEnd", null), 247 | r(this, "_selection", { rx: [0, 0], ry: [0, 0] }), 248 | r(this, "_editing", null), 249 | r(this, "_focus", null), 250 | r(this, "mousedown", (t) => { 251 | if (!this.mobile) { 252 | if ( 253 | 3 === t.which && 254 | !this._editing && 255 | this._selectionSize() 256 | ) { 257 | let t = new Range(); 258 | const { rx: e, ry: i } = this._selection; 259 | return ( 260 | t.setStart(this._getCell(e[0], i[0]), 0), 261 | t.setEnd(this._getCell(e[0], i[0]), 1), 262 | this.cwd.getSelection().removeAllRanges(), 263 | void this.cwd.getSelection().addRange(t) 264 | ); 265 | } 266 | this._changeSelectedCellsStyle(() => { 267 | (this.tbody.style.userSelect = "none"), 268 | (this._selectionEnd = this._selectionStart = this._focus = this._getCoords( 269 | t 270 | )), 271 | (this._selecting = !0); 272 | }); 273 | } 274 | }), 275 | r(this, "mouseenter", (t) => { 276 | this.mobile || 277 | (this._selecting && 278 | this._changeSelectedCellsStyle(() => { 279 | this._selectionEnd = this._getCoords(t); 280 | })); 281 | }), 282 | r(this, "_lastMouseUp", null), 283 | r(this, "_lastMouseUpTarget", null), 284 | r(this, "mouseup", (t) => { 285 | this.mobile || 286 | (3 !== t.which && 287 | this._selecting && 288 | this._changeSelectedCellsStyle(() => { 289 | (this._selectionEnd = this._getCoords(t)), 290 | this._endSelection(), 291 | this._lastMouseUp && 292 | this._lastMouseUp > Date.now() - 300 && 293 | this._lastMouseUpTarget.x === 294 | this._selectionEnd.x && 295 | this._lastMouseUpTarget.y === 296 | this._selectionEnd.y && 297 | this._startEditing(this._selectionEnd), 298 | (this._lastMouseUp = Date.now()), 299 | (this._lastMouseUpTarget = this._selectionEnd); 300 | })); 301 | }), 302 | r(this, "mouseleave", (t) => { 303 | t.target === this.tbody && 304 | this._selecting && 305 | this._endSelection(); 306 | }), 307 | r(this, "touchstart", (t) => { 308 | this._editing || ((this.mobile = !0), (this.moved = !1)); 309 | }), 310 | r(this, "touchend", (t) => { 311 | this.mobile && 312 | (this._editing || 313 | this.moved || 314 | (this._changeSelectedCellsStyle(() => { 315 | this._selectionEnd = this._selectionStart = this._focus = this._getCoords( 316 | t 317 | ); 318 | }), 319 | this._startEditing(this._focus))); 320 | }), 321 | r(this, "touchmove", (t) => { 322 | this.mobile && (this.moved = !0); 323 | }), 324 | r(this, "_stopEditing", () => { 325 | if (!this._editing) return; 326 | const { x: t, y: e } = this._editing, 327 | i = this._getCell(t, e); 328 | (i.style.width = ""), (i.style.height = ""); 329 | const s = i.firstChild; 330 | s.removeEventListener("blur", this._stopEditing), 331 | s.removeEventListener("keydown", this._blurIfEnter), 332 | this._setVal(t, e, s.value), 333 | this._onDataChanged(), 334 | i.removeChild(s), 335 | (this._editing = null), 336 | this._renderTDContent(i, t, e); 337 | }), 338 | r(this, "_blurIfEnter", (t) => { 339 | 13 === t.keyCode && (this._stopEditing(), t.preventDefault()); 340 | }), 341 | r(this, "_restyle", ({ x: t, y: e }) => { 342 | const i = this._getCell(t, e); 343 | i.className = this._classNames(t, e); 344 | const s = a(this.checkResults.titles, t, e); 345 | s ? i.setAttribute("title", s) : i.removeAttribute("title"); 346 | }), 347 | r(this, "_refreshDisplayedValue", ({ x: t, y: e }) => { 348 | const i = this._getCell(t, e).firstChild; 349 | "DIV" === i.tagName && 350 | (i.textContent = this._divContent(t, e)), 351 | this._restyle({ x: t, y: e }); 352 | }), 353 | this._saveConstructorOptions(t), 354 | this._setupDom(), 355 | this._replaceDataWithArray(t.data), 356 | this._incrementToFit({ 357 | x: this.columns.length - 1, 358 | y: this._options.minRows - 1, 359 | }), 360 | this._fillScrollSpace(); 361 | } 362 | _runChecks(t) { 363 | const { titles: e, classNames: i } = this.checks(t); 364 | this.checkResults = { titles: e, classNames: i }; 365 | } 366 | _saveConstructorOptions({ 367 | data: t = [], 368 | node: e = null, 369 | onChange: i = null, 370 | minRows: s = 1, 371 | maxRows: n = 1 / 0, 372 | css: o = "", 373 | width: r = "100%", 374 | height: h = "80vh", 375 | columns: l, 376 | checks: a, 377 | }) { 378 | if ( 379 | ((this.columns = l), 380 | (this.checks = a || (() => ({}))), 381 | this._runChecks(t), 382 | !e) 383 | ) 384 | throw new Error( 385 | "You need to pass a node argument to Importabular, like this : new Importabular({node: document.body})" 386 | ); 387 | (this._parent = e), 388 | (this._options = { 389 | onChange: i, 390 | minRows: s, 391 | maxRows: n, 392 | css: 393 | "\nhtml{\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n::-webkit-scrollbar {\n width: 0;\n height:0;\n}\n*{\n box-sizing: border-box;\n}\nbody{\n padding: 0; \n margin: 0;\n}\ntable{\n border-spacing: 0;\n background: white;\n border: 1px solid #ddd;\n border-width: 0 1px 1px 0;\n font-size: 16px;\n font-family: sans-serif;\n border-collapse: separate;\n min-width:100%;\n}\ntd, th{\n padding:0;\n border: 1px solid;\n border-color: #ddd transparent transparent #ddd; \n}\ntd.selected.multi:not(.editing){\n background:#d7f2f9;\n} \ntd.focus:not(.editing){\n border-color: black;\n} \ntd>*, th>*{\n border:none;\n padding:10px;\n min-width:100px;\n min-height: 40px;\n font:inherit;\n line-height: 20px;\n color:inherit;\n white-space: normal;\n}\ntd>div::selection {\n color: none;\n background: none;\n}\n\n.placeholder div{\n user-select:none;\n color:rgba(0,0,0,0.2);\n}\n*[title] div{cursor:help;}\nth{text-align:left;}\n" + 394 | o, 395 | }), 396 | (this._iframeStyle = { 397 | width: r, 398 | height: h, 399 | border: "none", 400 | background: "transparent", 401 | }); 402 | } 403 | _fitBounds({ x: t, y: e }) { 404 | return ( 405 | t >= 0 && 406 | t < this.columns.length && 407 | e >= 0 && 408 | e < this._options.maxRows 409 | ); 410 | } 411 | _fillScrollSpace() { 412 | const t = Math.ceil(this.iframe.contentWindow.innerHeight / 40), 413 | e = Math.ceil(this.iframe.contentWindow.innerWidth / 100); 414 | this._incrementToFit({ x: e - 1, y: t - 1 }); 415 | } 416 | getData() { 417 | return this._data._toArr(this._width, this._height); 418 | } 419 | _onDataChanged() { 420 | const t = this.getData(); 421 | this._options.onChange && this._options.onChange(t), 422 | this._runChecks(t), 423 | this._restyleAll(); 424 | } 425 | _renderTDContent(t, e, i) { 426 | const s = document.createElement("div"); 427 | t.setAttribute("x", e.toString()), 428 | t.setAttribute("y", i.toString()); 429 | const n = this._divContent(e, i); 430 | n ? (s.textContent = n) : (s.innerHTML = " "), 431 | t.appendChild(s), 432 | this._restyle({ x: e, y: i }); 433 | } 434 | _divContent(t, e) { 435 | return this._getVal(t, e) || this.columns[t].placeholder; 436 | } 437 | _setupDom() { 438 | const t = document.createElement("iframe"); 439 | (this.iframe = t), this._parent.appendChild(t); 440 | const e = t.contentWindow.document; 441 | (this.cwd = e), 442 | e.open(), 443 | e.write( 444 | `` 445 | ), 446 | e.close(), 447 | Object.assign(t.style, this._iframeStyle); 448 | const i = document.createElement("table"), 449 | s = document.createElement("tbody"), 450 | n = document.createElement("THEAD"), 451 | o = document.createElement("TR"); 452 | n.appendChild(o), 453 | this.columns.forEach((t) => { 454 | const e = document.createElement("TH"), 455 | i = document.createElement("div"); 456 | (i.innerHTML = t.label), 457 | t.title && e.setAttribute("title", t.title), 458 | e.appendChild(i), 459 | o.appendChild(e); 460 | }), 461 | i.appendChild(n), 462 | i.appendChild(s), 463 | e.body.appendChild(i), 464 | (this.tbody = s), 465 | (this.table = i); 466 | for (let t = 0; t < this._height; t++) { 467 | const e = document.createElement("tr"); 468 | s.appendChild(e); 469 | for (let i = 0; i < this._width; i++) this._addCell(e, i, t); 470 | } 471 | h.forEach((t) => e.addEventListener(t, this[t], !0)); 472 | } 473 | destroy() { 474 | this._destroyEditing(), 475 | h.forEach((t) => this.cwd.removeEventListener(t, this[t], !0)), 476 | this.iframe.parentElement.removeChild(this.iframe); 477 | } 478 | _addCell(t, e, i) { 479 | const s = document.createElement("td"); 480 | t.appendChild(s), this._renderTDContent(s, e, i); 481 | } 482 | _incrementHeight() { 483 | if (!this._fitBounds({ x: 0, y: this._height })) return !1; 484 | this._height++; 485 | const t = this._height - 1, 486 | e = document.createElement("tr"); 487 | this.tbody.appendChild(e); 488 | for (let i = 0; i < this._width; i++) this._addCell(e, i, t); 489 | return !0; 490 | } 491 | _incrementWidth() { 492 | if (!this._fitBounds({ x: this._width, y: 0 })) return !1; 493 | this._width++; 494 | const t = this._width - 1; 495 | return ( 496 | Array.prototype.forEach.call(this.tbody.children, (e, i) => { 497 | this._addCell(e, t, i); 498 | }), 499 | !0 500 | ); 501 | } 502 | _incrementToFit({ x: t, y: e }) { 503 | for (; t > this._width - 1 && this._incrementWidth(); ); 504 | for (; e > this._height - 1 && this._incrementHeight(); ); 505 | } 506 | _getSelectionAsArray() { 507 | const { rx: t, ry: e } = this._selection; 508 | if (t[0] === t[1]) return null; 509 | const i = t[1] - t[0], 510 | s = e[1] - e[0], 511 | n = []; 512 | for (let o = 0; o < s; o++) { 513 | n.push([]); 514 | for (let s = 0; s < i; s++) 515 | n[o].push(this._getVal(t[0] + s, e[0] + o)); 516 | } 517 | return n; 518 | } 519 | _setAllSelectedCellsTo(t) { 520 | this._forSelectionCoord(this._selection, ({ x: e, y: i }) => 521 | this._setVal(e, i, t) 522 | ), 523 | this._onDataChanged(), 524 | this._forSelectionCoord( 525 | this._selection, 526 | this._refreshDisplayedValue 527 | ); 528 | } 529 | _moveCursor({ x: t = 0, y: e = 0 }, i) { 530 | const s = i ? this._selectionEnd : this._selectionStart, 531 | n = { x: s.x + t, y: s.y + e }; 532 | this._fitBounds(n) && 533 | (this._stopEditing(), 534 | this._incrementToFit(n), 535 | this._changeSelectedCellsStyle(() => { 536 | i 537 | ? (this._selectionEnd = n) 538 | : (this._selectionStart = this._selectionEnd = this._focus = n); 539 | }), 540 | this._scrollIntoView(n)); 541 | } 542 | _tabCursorInSelection(t, e = 1) { 543 | let { x: i, y: s } = this._focus || { x: 0, y: 0 }; 544 | const o = this._selectionSize(), 545 | { rx: r, ry: h } = 546 | o > 1 547 | ? this._selection 548 | : { 549 | rx: [0, this.columns.length], 550 | ry: [0, this._options.maxRows], 551 | }; 552 | let l; 553 | if (t) l = n(i, s, e, r[0], r[1] - 1, h[0], h[1] - 1); 554 | else { 555 | const t = n(s, i, e, h[0], h[1] - 1, r[0], r[1] - 1); 556 | l = { x: t.y, y: t.x }; 557 | } 558 | this._fitBounds(l) && 559 | (this._stopEditing(), 560 | this._incrementToFit(l), 561 | this._changeSelectedCellsStyle(() => { 562 | (this._focus = l), 563 | o <= 1 && (this._selectionStart = this._selectionEnd = l); 564 | }), 565 | this._scrollIntoView(l)); 566 | } 567 | _scrollIntoView({ x: t, y: e }) { 568 | this._getCell(t, e).scrollIntoView({ 569 | behavior: "smooth", 570 | block: "nearest", 571 | }); 572 | } 573 | _endSelection() { 574 | (this._selecting = !1), (this.tbody.style.userSelect = ""); 575 | } 576 | _startEditing({ x: t, y: e }) { 577 | this._editing = { x: t, y: e }; 578 | const i = this._getCell(t, e), 579 | s = i.getBoundingClientRect(), 580 | n = i.firstChild.getBoundingClientRect(); 581 | i.removeChild(i.firstChild); 582 | const o = document.createElement("input"); 583 | (o.type = "text"), 584 | (o.value = this._getVal(t, e)), 585 | i.appendChild(o), 586 | Object.assign(i.style, { 587 | width: s.width - 2, 588 | height: s.height, 589 | }), 590 | Object.assign(o.style, { 591 | width: `${n.width}px`, 592 | height: `${n.height}px`, 593 | }), 594 | o.focus(), 595 | o.addEventListener("blur", this._stopEditing), 596 | o.addEventListener("keydown", this._blurIfEnter); 597 | } 598 | _destroyEditing() { 599 | if (this._editing) { 600 | const { x: t, y: e } = this._editing, 601 | i = this._getCell(t, e).firstChild; 602 | i.removeEventListener("blur", this._stopEditing), 603 | i.removeEventListener("keydown", this._blurIfEnter); 604 | } 605 | } 606 | _revertEdit() { 607 | if (!this._editing) return; 608 | const { x: t, y: e } = this._editing; 609 | this._getCell(t, e).firstChild.value = this._getVal(t, e); 610 | } 611 | _changeSelectedCellsStyle(t) { 612 | const e = this._selection; 613 | t(), 614 | (this._selection = this._getSelectionCoords()), 615 | this._forSelectionCoord(e, this._restyle), 616 | this._forSelectionCoord(this._selection, this._restyle); 617 | } 618 | _getSelectionCoords() { 619 | if (!this._selectionStart) return { rx: [0, 0], ry: [0, 0] }; 620 | let t = [this._selectionStart.x, this._selectionEnd.x]; 621 | t[0] > t[1] && t.reverse(); 622 | let e = [this._selectionStart.y, this._selectionEnd.y]; 623 | return ( 624 | e[0] > e[1] && e.reverse(), 625 | { rx: [t[0], t[1] + 1], ry: [e[0], e[1] + 1] } 626 | ); 627 | } 628 | _forSelectionCoord({ rx: t, ry: e }, i) { 629 | for (let s = t[0]; s < t[1]; s++) 630 | for (let t = e[0]; t < e[1]; t++) 631 | this._fitBounds({ x: s, y: t }) && i({ x: s, y: t }); 632 | } 633 | _restyleAll() { 634 | for (var t = 0; t < this._width; t++) 635 | for (var e = 0; e < this._height; e++) 636 | this._restyle({ x: t, y: e }); 637 | } 638 | _selectionSize() { 639 | const { rx: t, ry: e } = this._selection; 640 | return (t[1] - t[0]) * (e[1] - e[0]); 641 | } 642 | _classNames(t, e) { 643 | const { rx: i, ry: s } = this._selection; 644 | let n = ""; 645 | return ( 646 | t >= i[0] && 647 | t < i[1] && 648 | e >= s[0] && 649 | e < s[1] && 650 | ((n += " selected"), 651 | this._selectionSize() > 1 && (n += " multi")), 652 | this._focus && 653 | this._focus.x === t && 654 | this._focus.y === e && 655 | (n += " focus"), 656 | this._editing && 657 | t === this._editing.x && 658 | e === this._editing.y && 659 | (n += " editing"), 660 | this._getVal(t, e) || (n += " placeholder"), 661 | (n += " " + a(this.checkResults.classNames, t, e)), 662 | n 663 | ); 664 | } 665 | _getCoords(t) { 666 | let e = t.target; 667 | for (; !e.getAttribute("x") && e.parentElement; ) 668 | e = e.parentElement; 669 | return { 670 | x: parseInt(e.getAttribute("x")) || 0, 671 | y: parseInt(e.getAttribute("y")) || 0, 672 | }; 673 | } 674 | setData(t) { 675 | this._data._clear(), this._replaceDataWithArray(t); 676 | for (let t = 0; t < this._width; t++) 677 | for (let e = 0; e < this._height; e++) 678 | this._refreshDisplayedValue({ x: t, y: e }); 679 | } 680 | _replaceDataWithArray(t = [[]]) { 681 | t.forEach((t, e) => { 682 | t.forEach((t, i) => { 683 | this._setVal(i, e, t); 684 | }); 685 | }); 686 | } 687 | _setVal(t, e, i) { 688 | this._fitBounds({ x: t, y: e }) && 689 | (this._data._setVal(t, e, i), 690 | this._incrementToFit({ x: t + 1, y: e + 1 }), 691 | this._refreshDisplayedValue({ x: t, y: e })); 692 | } 693 | _getVal(t, e) { 694 | return this._data._getVal(t, e); 695 | } 696 | _getCell(t, e) { 697 | return this.tbody.children[e].children[t]; 698 | } 699 | } 700 | function a(t, e, i) { 701 | return (t && t[i] && t[i][e]) || ""; 702 | } 703 | }, 704 | }, 705 | e = {}; 706 | function i(s) { 707 | if (e[s]) return e[s].exports; 708 | var n = (e[s] = { exports: {} }); 709 | return t[s](n, n.exports, i), n.exports; 710 | } 711 | return ( 712 | (i.d = (t, e) => { 713 | for (var s in e) 714 | i.o(e, s) && 715 | !i.o(t, s) && 716 | Object.defineProperty(t, s, { enumerable: !0, get: e[s] }); 717 | }), 718 | (i.o = (t, e) => Object.prototype.hasOwnProperty.call(t, e)), 719 | i(103) 720 | ); 721 | })().default; 722 | }); 723 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Importabular=e():t.Importabular=e()}(self,(function(){return(()=>{"use strict";var t={103:(t,e,i)=>{i.d(e,{default:()=>l});class s{constructor(){var t,e;e={},(t="_data")in this?Object.defineProperty(this,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):this[t]=e}_setVal(t,e,i){const s=this._data,n=function(t){return 0===t?"0":t?t.toString():""}(i);var o;n?(s[t]||(s[t]={}),s[t][e]=n):s[t]&&s[t][e]&&(delete s[t][e],o=s[t],0===Object.keys(o).length&&delete s[t])}_clear(){this._data={}}_getVal(t,e){const i=this._data;return i&&i[t]&&i[t][e]||""}_toArr(t,e){const i=[];for(let s=0;s