ngx-smart-spreadsheet
Version:
Lightweight spreadsheet module for Angular
771 lines (753 loc) • 41.1 kB
JavaScript
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)\">></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)\">></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)\">></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)\">></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