UNPKG

ngx-smart-spreadsheet

Version:

Lightweight spreadsheet module for Angular

771 lines (753 loc) 41.1 kB
import * as i0 from '@angular/core'; import { EventEmitter, Component, Input, Output, ViewChild, ContentChildren, HostListener, Directive, NgModule } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; function isObject(item) { return (item && typeof item === 'object' && !Array.isArray(item)); } function mergeDeep(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); if (isObject(target) && isObject(source)) { for (const key in source) { if (isObject(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); mergeDeep(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return mergeDeep(target, ...sources); } const defaultOptions = { contextMenuRowLabel: { INSERT_ROW_ABOVE: 'Insert 1 row above', INSERT_ROW_BELOW: 'Insert 1 row below', DELETE_ROW: 'Delete row', }, contextMenuColLabel: { INSERT_COLUMN_LEFT: 'Insert 1 column left', INSERT_COLUMN_RIGHT: 'Insert 1 column right', DELETE_COLUMN: 'Delete column', } }; class SpreadsheetSettings { constructor(rows, cols, data, options) { this.rows = rows; this.cols = cols; this.data = data; this.options = options; this.options = mergeDeep(defaultOptions, options || {}); } static empty(rows, cols, options) { return new SpreadsheetSettings(rows, cols, null, options); } static load(data, options) { return new SpreadsheetSettings(null, null, data, options); } } ; const DELIMITER = '\t'; const PARSE_PATTERN = new RegExp(('(\\' + DELIMITER + '|\\r?\\n|\\r|^)' + '(?:"([^"]*(?:""[^"]*)*)"|' + '([^"\\' + DELIMITER + '\\r\\n]*))'), "gi"); const csvToArray = (strData) => { const arrData = [[]]; let arrMatches = null; while (arrMatches = PARSE_PATTERN.exec(strData)) { const strMatchedDelimiter = arrMatches[1]; if (strMatchedDelimiter.length && (strMatchedDelimiter != DELIMITER)) { arrData.push([]); } const strMatchedValue = (arrMatches[2]) ? arrMatches[2].replace(new RegExp('""', 'g'), '"') : arrMatches[3]; arrData[arrData.length - 1].push(strMatchedValue); } return arrData; }; class Anchor { constructor(r, c) { this.r = r; this.c = c; } } class Range { constructor(r1, c1, r2, c2) { this.r1 = r1; this.c1 = c1; this.r2 = r2; this.c2 = c2; } calc(row, col) { if (row < this.r1) { this.r1 = row; } if (row > this.r2) { this.r2 = row; } if (col < this.c1) { this.c1 = col; } if (col > this.c2) { this.c2 = col; } } includes(row, col) { return (row >= this.r1 && row <= this.r2) && (col >= this.c1 && col <= this.c2); } equals(range) { return this.r1 === range.r1 && this.c1 === range.c1 && this.r2 === range.r2 && this.c2 === range.c2; } static of(row, col, row2 = row, col2 = col) { return new Range(row, col, row2, col2); } static marge(a1, a2) { const r1 = a1.r < a2.r ? a1.r : a2.r; const r2 = a1.r > a2.r ? a1.r : a2.r; const c1 = a1.c < a2.c ? a1.c : a2.c; const c2 = a1.c > a2.c ? a1.c : a2.c; return new Range(r1, c1, r2, c2); } } const CHARS$1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; const LENGTH$1 = CHARS$1.length; const generateHeader = (index) => { index -= 1; const remain = Math.floor(index / LENGTH$1); return (remain > 0) ? generateHeader(remain) + CHARS$1[index % LENGTH$1] : CHARS$1[index % LENGTH$1]; }; const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const LENGTH = CHARS.length; const generageId = () => new Array(8).fill(null) .map(() => CHARS.charAt(Math.floor(Math.random() * LENGTH))) .join(''); class Cell { constructor(tableId, row, col, value, editable = false) { this.tableId = tableId; this.row = row; this.col = col; this.value = value; this.editable = editable; this.id = `${tableId}-${row}-${col}`; } withRow(index) { return new Cell(this.tableId, index, this.col, this.value, this.editable); } withCol(index) { return new Cell(this.tableId, this.row, index, this.value, this.editable); } } class Table { constructor(id, head, body) { this.id = id; this.head = head; this.body = body; } static empty(rows, cols) { const tableId = generageId(); const row = Array(cols).fill(''); const head = row.map((v, c) => generateHeader(c + 1)); const body = []; for (let r = 0; r < rows; r++) { body.push(row.map((v, c) => new Cell(tableId, r, c, ''))); } return new Table(tableId, head, body); } static load(data) { if (!data.length) { throw new Error('Error: invalid data structure'); } const tableId = generageId(); const cols = data.reduce((prev, current) => Math.max(prev, current.length), 0); const head = Array(cols).fill('').map((v, c) => generateHeader(c + 1)); const body = []; for (let r = 0; r < data.length; r++) { const row = data[r]; const bodyRow = []; for (let c = 0; c < cols; c++) { const value = c < row.length ? row[c] : ''; bodyRow.push(new Cell(tableId, r, c, value)); } body.push(bodyRow); } return new Table(tableId, head, body); } findCell(row, col) { for (const record of this.body) { for (const field of record) { if (field.row === row && field.col === col) { return field; } } } return null; } insertColumn(colIndex) { { const remains = this.head.slice(0, colIndex); const updates = Array(this.head.length - colIndex + 1) .fill('') .map((v, c) => generateHeader((c + 1) + colIndex)); this.head = [...remains, ...updates]; } { const body = []; for (let r = 0; r < this.body.length; r++) { const row = this.body[r]; const above = row.slice(0, colIndex); const present = new Cell(this.id, r, colIndex, ''); const below = row.slice(colIndex).map(cell => cell.withCol(cell.col + 1)); const newRow = [...above, present, ...below]; body.push(newRow); } this.body = body; } } deleteColumn(colIndex) { { const remains = this.head.slice(0, colIndex); const updates = this.head.slice(colIndex + 1) .map((v, c) => generateHeader((c + 1) + colIndex)); this.head = [...remains, ...updates]; } { const body = []; for (let r = 0; r < this.body.length; r++) { const row = this.body[r]; const above = row.slice(0, colIndex); const below = row.slice(colIndex + 1).map(cell => cell.withCol(cell.col + 1)); const newRow = [...above, ...below]; body.push(newRow); } this.body = body; } } insertRow(rowIndex) { const above = this.body.slice(0, rowIndex); const present = Array(this.colCount).fill('') .map((v, c) => new Cell(this.id, rowIndex, c, '')); const below = this.body.slice(rowIndex) .map((row) => row.map((cell) => cell.withRow(cell.row + 1))); this.body = [...above, present, ...below]; } deleteRow(rowIndex) { const above = this.body.slice(0, rowIndex); const below = this.body.slice(rowIndex + 1) .map((row) => row.map((cell) => cell.withRow(cell.row + 1))); this.body = [...above, ...below]; } get rowCount() { return this.body.length; } get colCount() { return this.head.length; } } class NgxContextMenuItemComponent { constructor() { this.click = new EventEmitter(); } clicked(index) { if (!this.disabled) { this.click.emit(index); } } } NgxContextMenuItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxContextMenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); NgxContextMenuItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.2", type: NgxContextMenuItemComponent, selector: "ngx-context-menu-item", inputs: { label: "label", disabled: "disabled", divider: "divider" }, outputs: { click: "click" }, ngImport: i0, template: '', isInline: true }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxContextMenuItemComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-context-menu-item', template: '' }] }], propDecorators: { label: [{ type: Input }], disabled: [{ type: Input }], divider: [{ type: Input }], click: [{ type: Output }] } }); class NgxContextMenuComponent { constructor() { this.closed = new EventEmitter(); this.target = -1; } show(ev, index) { this.target = index; this.menuElement.style.display = 'flex'; const menuTop = ((ev.clientY + this.menuHeight) > this.documentHeight) ? ev.pageY - this.menuHeight : ev.pageY + 15; const menuLeft = ((ev.clientX + this.menuWidth) > this.documentWidth) ? ev.pageX - this.menuWidth : ev.pageX; this.menuElement.style.top = `${menuTop}px`; this.menuElement.style.left = `${menuLeft}px`; } click() { this.menuElement.style.display = 'none'; this.closed.emit(); } get menuElement() { return this.menuElementRef.nativeElement; } get menuStyle() { return getComputedStyle(this.menuElement); } get menuWidth() { return this.menuElement.offsetWidth + parseInt(this.menuStyle.marginLeft) + parseInt(this.menuStyle.marginRight) + parseInt(this.menuStyle.paddingLeft) + parseInt(this.menuStyle.paddingRight); } get menuHeight() { return this.menuElement.offsetHeight + parseInt(this.menuStyle.marginTop) + parseInt(this.menuStyle.marginBottom) + parseInt(this.menuStyle.paddingTop) + parseInt(this.menuStyle.paddingBottom); } get documentWidth() { return document.documentElement.clientWidth; } get documentHeight() { return document.documentElement.clientHeight; } } NgxContextMenuComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxContextMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); NgxContextMenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.2", type: NgxContextMenuComponent, selector: "ngx-context-menu", outputs: { closed: "closed" }, host: { listeners: { "document:click": "click($event)" } }, queries: [{ propertyName: "itemTemplates", predicate: NgxContextMenuItemComponent }], viewQueries: [{ propertyName: "menuElementRef", first: true, predicate: ["menu"], descendants: true, static: true }], ngImport: i0, template: "<div #menu class=\"menu\">\n <ng-container *ngFor=\"let item of itemTemplates\">\n <div class=\"item\" *ngIf=\"!item.divider; else divider\" (click)=\"item.clicked(target)\"\n [class.disabled]=\"item.disabled\">\n {{item.label}}\n </div>\n <ng-template #divider>\n <div class=\"divider\"></div>\n </ng-template>\n </ng-container>\n</div>", styles: [".menu{position:absolute;background-color:#fff;-webkit-user-select:none;user-select:none;min-width:16rem;box-shadow:0 .5rem .8rem #0000001a;z-index:9;display:none;flex-direction:column;padding:.5em 0;border-radius:4px}.menu .item{cursor:pointer;padding:.5em 1em}.menu .item:hover:not(.disabled){background:#f0f0f0}.menu .item.disabled{opacity:.5;cursor:default}.menu .divider{width:100%;margin-top:.5em;padding-top:.5em;border-top:1px solid #ddd}\n"], directives: [{ type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxContextMenuComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-context-menu', template: "<div #menu class=\"menu\">\n <ng-container *ngFor=\"let item of itemTemplates\">\n <div class=\"item\" *ngIf=\"!item.divider; else divider\" (click)=\"item.clicked(target)\"\n [class.disabled]=\"item.disabled\">\n {{item.label}}\n </div>\n <ng-template #divider>\n <div class=\"divider\"></div>\n </ng-template>\n </ng-container>\n</div>", styles: [".menu{position:absolute;background-color:#fff;-webkit-user-select:none;user-select:none;min-width:16rem;box-shadow:0 .5rem .8rem #0000001a;z-index:9;display:none;flex-direction:column;padding:.5em 0;border-radius:4px}.menu .item{cursor:pointer;padding:.5em 1em}.menu .item:hover:not(.disabled){background:#f0f0f0}.menu .item.disabled{opacity:.5;cursor:default}.menu .divider{width:100%;margin-top:.5em;padding-top:.5em;border-top:1px solid #ddd}\n"] }] }], propDecorators: { menuElementRef: [{ type: ViewChild, args: ['menu', { static: true }] }], itemTemplates: [{ type: ContentChildren, args: [NgxContextMenuItemComponent] }], closed: [{ type: Output }], click: [{ type: HostListener, args: ['document:click', ['$event']] }] } }); class ContentEditableDirective { constructor(elementRef) { this.elementRef = elementRef; this.modelChange = new EventEmitter(); this.element.tabIndex = 0; } set model(value) { this.element.innerText = value || ''; } blur() { this.modelChange.emit(this.element.innerText); } get element() { return this.elementRef.nativeElement; } } ContentEditableDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: ContentEditableDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); ContentEditableDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.0.2", type: ContentEditableDirective, selector: "[nssContentEditable]", inputs: { model: "model" }, outputs: { modelChange: "modelChange" }, host: { listeners: { "blur": "blur($event.target.value)" } }, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: ContentEditableDirective, decorators: [{ type: Directive, args: [{ selector: '[nssContentEditable]' }] }], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { model: [{ type: Input }], modelChange: [{ type: Output }], blur: [{ type: HostListener, args: ['blur', ['$event.target.value']] }] } }); class NgxSmartSpreadsheetComponent { constructor() { this.settings = null; this.copied = new EventEmitter(); this.table = null; this.activatedCell = null; this.range = null; this.anchor = null; this.activeTheadIndex = -1; this.activeTbodyIndex = -1; } ngOnInit() { if (this.settings?.rows && this.settings?.cols) { this.table = Table.empty(this.settings.rows, this.settings.cols); } else if (this.settings?.data) { this.table = Table.load(this.settings.data); } } get data() { if (!this.table) { return [[]]; } return this.table.body.map(row => row.map(cell => cell.value)); } mousedown(ev) { const { row, col, valid } = this.getPositionFromId(ev.target); if (!valid) { return; } this.range = Range.of(row, col); if (!ev.shiftKey || !this.anchor) { this.anchor = new Anchor(row, col); } } mousemove(ev) { if (!this.range || !this.anchor) { return; } const self = this.getPositionFromId(ev.target); if (self.valid) { const range = Range.marge({ r: self.row, c: self.col }, this.anchor); if (!this.range?.equals(range)) { this.range = range; } } } mouseup(ev) { if (ev.shiftKey && this.anchor) { const self = this.getPositionFromId(ev.target); if (self.valid) { const range = Range.marge({ r: self.row, c: self.col }, this.anchor); if (!this.range?.equals(range)) { this.range = range; } } } this.anchor = null; } onKeyDown(ev) { const key = ev.key.toLowerCase(); const isCtrl = ((ev.ctrlKey && !ev.metaKey) || (!ev.ctrlKey && ev.metaKey)); if (!this.table) { return; } if (!this.anchor && ev.shiftKey && this.activatedCell) { const { row, col } = this.activatedCell; this.anchor = new Anchor(row, col); } if (key === 'enter' && this.activatedCell) { const { row, col, editable } = this.activatedCell; if (editable && ev.shiftKey) { ev.preventDefault(); this.moveTo(row + 1, col, false, editable); } } else if (key === 'tab' && this.activatedCell) { ev.preventDefault(); const { rowCount, colCount } = this.table; const { row, col, editable } = this.activatedCell; const next = ev.shiftKey ? col - 1 : col + 1; if (next < 0 && row > 0) { this.moveTo(row - 1, colCount - 1, false, editable); } else if (next >= colCount && row < rowCount) { this.moveTo(row + 1, 0, false, editable); } else { this.moveTo(row, next, false, editable); } } else if (key === 'f2') { this.setEditable(ev, true); } else if (key === 'escape') { this.setEditable(ev, false); } else if (key === 'c' && isCtrl) { this.copy(); } else if (key === 'v' && isCtrl) { this.paste(); } else if (key === 'delete') { this.delete(); } } onKeyUp(ev) { if (!this.activatedCell || this.activatedCell.editable) { return; } if (!ev.shiftKey) { this.anchor = null; } const { row, col } = this.activatedCell; switch (ev.key.toLowerCase()) { case 'arrowup': this.moveTo(row - 1, col, ev.shiftKey, false); break; case 'arrowdown': this.moveTo(row + 1, col, ev.shiftKey, false); break; case 'arrowleft': this.moveTo(row, col - 1, ev.shiftKey, false); break; case 'arrowright': this.moveTo(row, col + 1, ev.shiftKey, false); break; } } trackByCell(index, value) { return value ? value.id : null; } clickHeader(colIndex) { const rowLength = this.table?.body.length || 0; if (rowLength > 0) { this.range = Range.of(0, colIndex, rowLength, colIndex); } } clickRow(rowIndex) { if (!this.table) { return; } if (rowIndex >= 0 && rowIndex < this.table.body.length) { const cols = this.table.body[rowIndex]; this.range = Range.of(rowIndex, 0, rowIndex, cols.length); } } focus(ev) { const found = this.findCellByEventTarget(ev.target); this.activatedCell = found; } blur(ev) { const found = this.findCellByEventTarget(ev.target); if (found) { found.editable = false; } } dblclick(ev, target) { const td = ev.target; if (target === this.activatedCell) { target.editable = true; } } setValue(ev, target) { const value = ev.target.innerText || ''; target.value = value; } setEditable(ev, editable) { ev.stopPropagation(); const found = this.findCellByEventTarget(ev.target); if (found) { found.editable = editable; } } //#region menu event handle showTheadMenu(ev, index) { ev.stopPropagation(); this.theadContextMenu.show(ev, index); } showTbodyMenu(ev, index) { ev.stopPropagation(); this.tbodyContextMenu.show(ev, index); } //#endregion moveTo(row, col, shiftKey, editable) { if (!this.table) { return; } const { body } = this.table; if (row >= 0 && row < body.length) { const cols = body[row]; if (col >= 0 && col < cols.length) { const cell = cols[col]; const e = document.getElementById(cell.id); if (e) { e.focus(); const s = window.getSelection(); const r = document.createRange(); r.setStart(e, e.childElementCount); r.setEnd(e, e.childElementCount); s?.removeAllRanges(); s?.addRange(r); } if (shiftKey && this.range && this.anchor) { this.range = Range.marge(this.anchor, { r: row, c: col }); } else { this.range = Range.of(cell.row, cell.col); } if (editable) { cell.editable = true; } } } } findCellByEventTarget(target) { const { row, col, valid } = this.getPositionFromId(target); return valid ? (this.table?.findCell(row, col) || null) : null; } getPositionFromId(target) { const element = target; if (!this.table || !element?.id?.match(/(\w+)-(\d+)-(\d+)/)) { return { row: NaN, col: NaN, valid: false }; } const valid = RegExp.$1 === this.table.id; const row = parseInt(RegExp.$2 || '', 10); const col = parseInt(RegExp.$3 || '', 10); return { row, col, valid }; } copy() { if (!this.table || !this.range) { return; } const lines = []; for (let r = this.range.r1; r <= this.range.r2; r++) { const line = []; for (let c = this.range.c1; c <= this.range.c2; c++) { const cell = this.table.findCell(r, c); if (cell) { const value = (cell.value.match(/[\t\n\r  "]+/)) ? '"' + cell.value.split('"').join('""') + '"' : cell.value; line.push(value); } } lines.push(line.join('\t')); } const text = lines.join('\n'); if (text) { navigator.clipboard.writeText(text) .then(() => this.copied.emit(text)); } } paste() { if (!this.table || !this.range) { return; } const { r1, c1, r2, c2 } = this.range; navigator.clipboard.readText() .then((data) => { const ar = csvToArray(data); if (!ar.length) { return; } if (ar.length === 1 && ar[0].length === 1) { const clipboardText = ar[0][0]; for (let r = r1; r <= r2; r++) { for (let c = c1; c <= c2; c++) { const cell = this.table.findCell(r, c); if (cell) { cell.value = clipboardText; } } } } else if ((r2 - r1 + 1) === ar.length && (c2 - c1 + 1) === ar[0].length) { for (let r = r1; r <= r2; r++) { for (let c = c1; c <= c2; c++) { const cell = this.table.findCell(r, c); if (cell) { cell.value = ar[r][c]; } } } } else { let cell = null; for (let r = 0, tableRow = r1; r < ar.length; r++, tableRow++) { const row = ar[r]; for (let c = 0, tableCol = c1; c < row.length; c++, tableCol++) { const col = row[c]; cell = this.table.findCell(tableRow, tableCol); if (cell) { cell.value = col; } } } if (cell) { this.range = Range.of(r1, c1, cell.row, cell.col); } } }); } delete() { if (!this.table || !this.range) { return; } const { r1, c1, r2, c2 } = this.range; for (let r = r1; r <= r2; r++) { for (let c = c1; c <= c2; c++) { const cell = this.table.findCell(r, c); if (cell) { cell.value = ''; } } } } } NgxSmartSpreadsheetComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxSmartSpreadsheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); NgxSmartSpreadsheetComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.2", type: NgxSmartSpreadsheetComponent, selector: "ngx-smart-spreadsheet", inputs: { settings: "settings" }, outputs: { copied: "copied" }, host: { listeners: { "mousedown": "mousedown($event)", "document:mousemove": "mousemove($event)", "document:mouseup": "mouseup($event)", "document:keydown": "onKeyDown($event)", "document:keyup": "onKeyUp($event)" } }, viewQueries: [{ propertyName: "theadContextMenu", first: true, predicate: ["theadMenu"], descendants: true }, { propertyName: "tbodyContextMenu", first: true, predicate: ["tbodyMenu"], descendants: true }], ngImport: i0, template: "<ng-container *ngIf=\"settings && table\">\n <div class=\"container\">\n <table>\n <thead>\n <tr>\n <th></th>\n <th *ngFor=\"let col of table.head; let c = index\" (click)=\"clickHeader(c)\"\n (mouseenter)=\"activeTheadIndex = c\" (mouseleave)=\"activeTheadIndex = -1\">\n <div class=\"head\">\n <div class=\"label\">{{col}}</div>\n <div class=\"dropdown\" *ngIf=\"activeTheadIndex === c\">\n <button class=\"dropbtn\" (click)=\"showTheadMenu($event, c)\">&gt;</button>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of table.body; let r = index;\">\n <th (click)=\"clickRow(r)\" (mouseenter)=\"activeTbodyIndex = r\" (mouseleave)=\"activeTbodyIndex = -1\">\n <div class=\"head\">\n <div class=\"label\">{{r + 1}}</div>\n <div class=\"dropdown\" *ngIf=\"activeTbodyIndex === r\">\n <button class=\"dropbtn\" (click)=\"showTbodyMenu($event, r)\">&gt;</button>\n </div>\n </div>\n </th>\n <td [id]=\"cell.id\" *ngFor=\"let cell of row; let c = index; trackBy: trackByCell\"\n [class.focus]=\"cell === activatedCell\" [class.sel]=\"range?.includes(cell.row, cell.col)\"\n nssContentEditable [(model)]=\"cell.value\" (focus)=\"focus($event)\" (blur)=\"blur($event)\"\n (dblclick)=\"dblclick($event, cell)\" [attr.contenteditable]=\"cell.editable\">\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <ngx-context-menu #theadMenu (closed)=\"activeTheadIndex = -1\">\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuColLabel?.INSERT_COLUMN_LEFT\"\n (click)=\"table.insertColumn($event)\">\n </ngx-context-menu-item>\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuColLabel?.INSERT_COLUMN_RIGHT\"\n (click)=\"table.insertColumn($event + 1)\">\n </ngx-context-menu-item>\n <ngx-context-menu-item [divider]=\"true\"></ngx-context-menu-item>\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuColLabel?.DELETE_COLUMN\"\n [disabled]=\"table.colCount <= 1\" (click)=\"table.deleteColumn($event)\">\n </ngx-context-menu-item>\n </ngx-context-menu>\n\n <ngx-context-menu #tbodyMenu (closed)=\"activeTbodyIndex = -1\">\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuRowLabel?.INSERT_ROW_ABOVE\"\n (click)=\"table.insertRow($event)\">\n </ngx-context-menu-item>\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuRowLabel?.INSERT_ROW_BELOW\"\n (click)=\"table.insertRow($event + 1)\">\n </ngx-context-menu-item>\n <ngx-context-menu-item [divider]=\"true\"></ngx-context-menu-item>\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuRowLabel?.DELETE_ROW\"\n [disabled]=\"table.rowCount <= 1\" (click)=\"table.deleteRow($event)\">\n </ngx-context-menu-item>\n </ngx-context-menu>\n</ng-container>", styles: [".container{font-size:14px}.container table,.container td,.container th,.container tr{height:2.5rem;border-spacing:0;height:100%}.container table{margin-bottom:2px}.container table thead{background:#f1f1f1;position:sticky;top:0;z-index:2}.container table thead th:first-child{background:#f1f1f1;z-index:3}.container table tbody th{min-width:5rem;background:#fafafa;z-index:1}.container table th{font-weight:400;padding:.2rem .4rem;border:1px solid #ddd;-webkit-user-select:none;user-select:none}.container table th:first-child{position:sticky;left:0}.container table th .head{display:grid;grid-template-columns:1fr auto}.container table th .head .label{grid-column:1/3;grid-row:1}.container table th .head .dropdown{grid-column:2/3;grid-row:1}.container table th .head .dropdown button{background-color:#e2e2e2d0;color:#00000080;padding:0rem .3rem;border:none;cursor:pointer;transition:ease .4s;transform:rotate(90deg);font-family:ui-monospace;-webkit-user-select:none;user-select:none}.container table td{min-width:10rem;padding:.2rem .5rem;border:1px solid #ddd;-webkit-user-select:none;user-select:none}.container table td.focus{outline:2px solid dodgerblue;-webkit-user-select:auto;user-select:auto}.container table td.sel{background:#eaf1fd}.container table td[contenteditable=true]{-webkit-user-select:auto;user-select:auto;outline:2px solid #48c21a;background:#eaffe2}\n"], components: [{ type: NgxContextMenuComponent, selector: "ngx-context-menu", outputs: ["closed"] }, { type: NgxContextMenuItemComponent, selector: "ngx-context-menu-item", inputs: ["label", "disabled", "divider"], outputs: ["click"] }], directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: ContentEditableDirective, selector: "[nssContentEditable]", inputs: ["model"], outputs: ["modelChange"] }] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxSmartSpreadsheetComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-smart-spreadsheet', template: "<ng-container *ngIf=\"settings && table\">\n <div class=\"container\">\n <table>\n <thead>\n <tr>\n <th></th>\n <th *ngFor=\"let col of table.head; let c = index\" (click)=\"clickHeader(c)\"\n (mouseenter)=\"activeTheadIndex = c\" (mouseleave)=\"activeTheadIndex = -1\">\n <div class=\"head\">\n <div class=\"label\">{{col}}</div>\n <div class=\"dropdown\" *ngIf=\"activeTheadIndex === c\">\n <button class=\"dropbtn\" (click)=\"showTheadMenu($event, c)\">&gt;</button>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of table.body; let r = index;\">\n <th (click)=\"clickRow(r)\" (mouseenter)=\"activeTbodyIndex = r\" (mouseleave)=\"activeTbodyIndex = -1\">\n <div class=\"head\">\n <div class=\"label\">{{r + 1}}</div>\n <div class=\"dropdown\" *ngIf=\"activeTbodyIndex === r\">\n <button class=\"dropbtn\" (click)=\"showTbodyMenu($event, r)\">&gt;</button>\n </div>\n </div>\n </th>\n <td [id]=\"cell.id\" *ngFor=\"let cell of row; let c = index; trackBy: trackByCell\"\n [class.focus]=\"cell === activatedCell\" [class.sel]=\"range?.includes(cell.row, cell.col)\"\n nssContentEditable [(model)]=\"cell.value\" (focus)=\"focus($event)\" (blur)=\"blur($event)\"\n (dblclick)=\"dblclick($event, cell)\" [attr.contenteditable]=\"cell.editable\">\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <ngx-context-menu #theadMenu (closed)=\"activeTheadIndex = -1\">\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuColLabel?.INSERT_COLUMN_LEFT\"\n (click)=\"table.insertColumn($event)\">\n </ngx-context-menu-item>\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuColLabel?.INSERT_COLUMN_RIGHT\"\n (click)=\"table.insertColumn($event + 1)\">\n </ngx-context-menu-item>\n <ngx-context-menu-item [divider]=\"true\"></ngx-context-menu-item>\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuColLabel?.DELETE_COLUMN\"\n [disabled]=\"table.colCount <= 1\" (click)=\"table.deleteColumn($event)\">\n </ngx-context-menu-item>\n </ngx-context-menu>\n\n <ngx-context-menu #tbodyMenu (closed)=\"activeTbodyIndex = -1\">\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuRowLabel?.INSERT_ROW_ABOVE\"\n (click)=\"table.insertRow($event)\">\n </ngx-context-menu-item>\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuRowLabel?.INSERT_ROW_BELOW\"\n (click)=\"table.insertRow($event + 1)\">\n </ngx-context-menu-item>\n <ngx-context-menu-item [divider]=\"true\"></ngx-context-menu-item>\n <ngx-context-menu-item [label]=\"settings.options?.contextMenuRowLabel?.DELETE_ROW\"\n [disabled]=\"table.rowCount <= 1\" (click)=\"table.deleteRow($event)\">\n </ngx-context-menu-item>\n </ngx-context-menu>\n</ng-container>", styles: [".container{font-size:14px}.container table,.container td,.container th,.container tr{height:2.5rem;border-spacing:0;height:100%}.container table{margin-bottom:2px}.container table thead{background:#f1f1f1;position:sticky;top:0;z-index:2}.container table thead th:first-child{background:#f1f1f1;z-index:3}.container table tbody th{min-width:5rem;background:#fafafa;z-index:1}.container table th{font-weight:400;padding:.2rem .4rem;border:1px solid #ddd;-webkit-user-select:none;user-select:none}.container table th:first-child{position:sticky;left:0}.container table th .head{display:grid;grid-template-columns:1fr auto}.container table th .head .label{grid-column:1/3;grid-row:1}.container table th .head .dropdown{grid-column:2/3;grid-row:1}.container table th .head .dropdown button{background-color:#e2e2e2d0;color:#00000080;padding:0rem .3rem;border:none;cursor:pointer;transition:ease .4s;transform:rotate(90deg);font-family:ui-monospace;-webkit-user-select:none;user-select:none}.container table td{min-width:10rem;padding:.2rem .5rem;border:1px solid #ddd;-webkit-user-select:none;user-select:none}.container table td.focus{outline:2px solid dodgerblue;-webkit-user-select:auto;user-select:auto}.container table td.sel{background:#eaf1fd}.container table td[contenteditable=true]{-webkit-user-select:auto;user-select:auto;outline:2px solid #48c21a;background:#eaffe2}\n"] }] }], propDecorators: { theadContextMenu: [{ type: ViewChild, args: ['theadMenu'] }], tbodyContextMenu: [{ type: ViewChild, args: ['tbodyMenu'] }], settings: [{ type: Input }], copied: [{ type: Output }], mousedown: [{ type: HostListener, args: ['mousedown', ['$event']] }], mousemove: [{ type: HostListener, args: ['document:mousemove', ['$event']] }], mouseup: [{ type: HostListener, args: ['document:mouseup', ['$event']] }], onKeyDown: [{ type: HostListener, args: ['document:keydown', ['$event']] }], onKeyUp: [{ type: HostListener, args: ['document:keyup', ['$event']] }] } }); class NgxSmartSpreadsheetModule { } NgxSmartSpreadsheetModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxSmartSpreadsheetModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NgxSmartSpreadsheetModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxSmartSpreadsheetModule, declarations: [NgxSmartSpreadsheetComponent, NgxContextMenuComponent, NgxContextMenuItemComponent, ContentEditableDirective], imports: [CommonModule], exports: [NgxSmartSpreadsheetComponent] }); NgxSmartSpreadsheetModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxSmartSpreadsheetModule, imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: NgxSmartSpreadsheetModule, decorators: [{ type: NgModule, args: [{ declarations: [ NgxSmartSpreadsheetComponent, NgxContextMenuComponent, NgxContextMenuItemComponent, ContentEditableDirective ], imports: [ CommonModule ], exports: [ NgxSmartSpreadsheetComponent ] }] }] }); /* * Public API Surface of ngx-smart-spreadsheet */ /** * Generated bundle index. Do not edit. */ export { NgxSmartSpreadsheetComponent, NgxSmartSpreadsheetModule, SpreadsheetSettings }; //# sourceMappingURL=ngx-smart-spreadsheet.mjs.map