Linted code

This commit is contained in:
Eugene 2023-07-25 23:57:19 +03:00
parent 0f277badb9
commit 06dd0a0cdf
14 changed files with 1351 additions and 705 deletions

View File

@ -4,7 +4,11 @@ module.exports = {
es2021: true, es2021: true,
node: true, node: true,
}, },
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
],
overrides: [ overrides: [
{ {
env: { env: {

View File

@ -1,8 +1,10 @@
# Modern Spreadsheet # Modern Spreadsheet
- High performance spreadsheet based on CanvasAPI. - High performance spreadsheet based on CanvasAPI.
- TypeScript supported - TypeScript supported
## Basic usage ## Basic usage
```ts ```ts
import Spreadsheet from "modern_spreadsheet"; import Spreadsheet from "modern_spreadsheet";
import "modern_spreadsheet/style.css"; // <= this is required import "modern_spreadsheet/style.css"; // <= this is required
@ -13,6 +15,7 @@ const sheet = new Spreadsheet(target);
``` ```
## Save and load data ## Save and load data
```ts ```ts
function saveData() { function saveData() {
const serialized = sheet.serializeData(); const serialized = sheet.serializeData();
@ -28,6 +31,7 @@ function loadData() {
``` ```
## Roadmap ## Roadmap
- ~~Rows number and columns heading render~~ - ~~Rows number and columns heading render~~
- Custom event functions (ex.: onSelectionChange, onCellEdit...). Full list of supported events will available on this page - Custom event functions (ex.: onSelectionChange, onCellEdit...). Full list of supported events will available on this page
- Rows and columns resizing - Rows and columns resizing

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spreadsheet example</title> <title>Spreadsheet example</title>
</head> </head>
<body style="padding: 2rem;"> <body style="padding: 2rem">
<div id="spreadsheet"></div> <div id="spreadsheet"></div>
<button id="save_button">Save sheet</button> <button id="save_button">Save sheet</button>
<button id="load_button">Load sheet</button> <button id="load_button">Load sheet</button>

File diff suppressed because it is too large Load Diff

View File

@ -1,54 +1,54 @@
import Spreadsheet, { RenderBox } from "../main" import Spreadsheet, { RenderBox } from "../main";
export class ColumnsBar { export class ColumnsBar {
public element: HTMLCanvasElement public element: HTMLCanvasElement;
private root: Spreadsheet private root: Spreadsheet;
public height: number = 32 public height: number = 32;
public width: number public width: number;
private resizerWidth = 1 private resizerWidth = 1;
ctx: CanvasRenderingContext2D ctx: CanvasRenderingContext2D;
constructor(root: Spreadsheet) { constructor(root: Spreadsheet) {
this.root = root this.root = root;
this.element = this.createElement() this.element = this.createElement();
const ctx = this.element.getContext('2d') const ctx = this.element.getContext("2d");
if (!ctx) throw new Error("Enable hardware acceleration"); if (!ctx) throw new Error("Enable hardware acceleration");
this.ctx = ctx this.ctx = ctx;
this.width = this.root.viewProps.width this.width = this.root.viewProps.width;
} }
private createElement(): HTMLCanvasElement { private createElement(): HTMLCanvasElement {
const element = document.createElement('canvas') const element = document.createElement("canvas");
element.style.position = 'absolute' element.style.position = "absolute";
element.style.height = this.height + 'px' element.style.height = this.height + "px";
element.style.width = this.root.viewProps.width + 'px' element.style.width = this.root.viewProps.width + "px";
element.style.display = 'block' element.style.display = "block";
element.style.borderLeft = '1px solid black' element.style.borderLeft = "1px solid black";
// element.style.boxSizing = 'border-box' // element.style.boxSizing = 'border-box'
element.width = this.root.viewProps.width;
element.width = this.root.viewProps.width element.height = this.height;
element.height = this.height return element;
return element
} }
public setElementPosition(top: number, left: number) { public setElementPosition(top: number, left: number) {
this.element.style.top = top + 'px' this.element.style.top = top + "px";
this.element.style.left = left + 'px' this.element.style.left = left + "px";
} }
private isColumnSelected(column: number): boolean { private isColumnSelected(column: number): boolean {
const { selectedCell, selectedRange } = this.root.selection const { selectedCell, selectedRange } = this.root.selection;
if (selectedCell && selectedCell.column === column) return true if (selectedCell && selectedCell.column === column) return true;
if (selectedRange) { if (selectedRange) {
const inRange = const inRange =
column >= Math.min(selectedRange.from.column, selectedRange.to.column) && column >=
column <= Math.max(selectedRange.from.column, selectedRange.to.column) Math.min(selectedRange.from.column, selectedRange.to.column) &&
column <= Math.max(selectedRange.from.column, selectedRange.to.column);
return inRange return inRange;
} }
return false return false;
} }
// private getYCoordWithOffset(renderBox: RenderBox): number { // private getYCoordWithOffset(renderBox: RenderBox): number {
@ -64,38 +64,44 @@ export class ColumnsBar {
// } // }
private renderText(column: number, renderBox: RenderBox) { private renderText(column: number, renderBox: RenderBox) {
const { width, x } = renderBox const { width, x } = renderBox;
this.ctx.fillStyle = 'black' this.ctx.fillStyle = "black";
this.ctx.textAlign = 'center' this.ctx.textAlign = "center";
this.ctx.textBaseline = 'middle' this.ctx.textBaseline = "middle";
this.ctx.font = '16px Arial' this.ctx.font = "16px Arial";
this.ctx.fillText(this.root.config.columns[column].title, x + (width / 2) - this.root.viewport.left, 0 + this.height / 2) this.ctx.fillText(
this.root.config.columns[column].title,
x + width / 2 - this.root.viewport.left,
0 + this.height / 2,
);
} }
private renderRect(column: number, renderBox: RenderBox) { private renderRect(column: number, renderBox: RenderBox) {
const { width, x } = renderBox const { width, x } = renderBox;
const isColSelected = this.isColumnSelected(column) const isColSelected = this.isColumnSelected(column);
this.ctx.fillStyle = isColSelected ? this.root.styles.cells.selectedBackground : 'white' this.ctx.fillStyle = isColSelected
this.ctx.strokeStyle = 'black' ? this.root.styles.cells.selectedBackground
this.ctx.lineWidth = 1 : "white";
this.ctx.strokeStyle = "black";
this.ctx.lineWidth = 1;
const specialX = x - this.root.viewport.left const specialX = x - this.root.viewport.left;
this.ctx.fillRect(specialX - 1 , 0, width, this.height) this.ctx.fillRect(specialX - 1, 0, width, this.height);
this.ctx.strokeRect(specialX - 1, 0, width, this.height) this.ctx.strokeRect(specialX - 1, 0, width, this.height);
} }
private renderSingleColumn(column: number) { private renderSingleColumn(column: number) {
const renderBox = new RenderBox(this.root.config, { const renderBox = new RenderBox(this.root.config, {
row: 0, row: 0,
column: column column: column,
}) });
this.renderRect(column, renderBox) this.renderRect(column, renderBox);
this.renderText(column, renderBox) this.renderText(column, renderBox);
} }
public renderBar() { public renderBar() {
@ -103,7 +109,7 @@ export class ColumnsBar {
const firstColIdx = this.root.viewport.firstCol; const firstColIdx = this.root.viewport.firstCol;
this.ctx.beginPath(); this.ctx.beginPath();
this.ctx.strokeStyle = 'black'; this.ctx.strokeStyle = "black";
this.ctx.lineWidth = 1; this.ctx.lineWidth = 1;
this.ctx.moveTo(0, 0); this.ctx.moveTo(0, 0);
this.ctx.lineTo(0, this.height); this.ctx.lineTo(0, this.height);
@ -112,7 +118,7 @@ export class ColumnsBar {
for (let col = firstColIdx; col <= lastColIdx; col++) { for (let col = firstColIdx; col <= lastColIdx; col++) {
if (!this.root.config.columns[col]) break; if (!this.root.config.columns[col]) break;
this.renderSingleColumn(col) this.renderSingleColumn(col);
} }
} }
} }

View File

@ -28,8 +28,10 @@ export class Editor {
const cell = this.root.getCell(position); const cell = this.root.getCell(position);
this.element.classList.remove("hide"); this.element.classList.remove("hide");
this.element.style.top = y - this.root.viewport.top + this.root.columnsBarHeight + "px"; this.element.style.top =
this.element.style.left = x - this.root.viewport.left + this.root.rowsBarWidth + "px"; y - this.root.viewport.top + this.root.columnsBarHeight + "px";
this.element.style.left =
x - this.root.viewport.left + this.root.rowsBarWidth + "px";
this.element.style.width = width + "px"; this.element.style.width = width + "px";
this.element.style.height = height + "px"; this.element.style.height = height + "px";
this.element.style.display = "block"; this.element.style.display = "block";

View File

@ -1,91 +1,94 @@
import Spreadsheet, { RenderBox } from "../main"; import Spreadsheet, { RenderBox } from "../main";
export class RowsBar { export class RowsBar {
element: HTMLCanvasElement element: HTMLCanvasElement;
ctx: CanvasRenderingContext2D ctx: CanvasRenderingContext2D;
root: Spreadsheet root: Spreadsheet;
width: number = 30 width: number = 30;
height: number height: number;
resizerHeight = 1 resizerHeight = 1;
constructor(root: Spreadsheet) { constructor(root: Spreadsheet) {
this.root = root this.root = root;
this.element = this.createElement() this.element = this.createElement();
const ctx = this.element.getContext('2d') const ctx = this.element.getContext("2d");
if (!ctx) throw new Error("Enable hardware acceleration"); if (!ctx) throw new Error("Enable hardware acceleration");
this.ctx = ctx this.ctx = ctx;
this.height = this.root.viewProps.height this.height = this.root.viewProps.height;
} }
private createElement() { private createElement() {
const element = document.createElement('canvas') const element = document.createElement("canvas");
element.style.position = 'absolute' element.style.position = "absolute";
element.style.height = this.root.viewProps.height + 'px' element.style.height = this.root.viewProps.height + "px";
element.style.width = this.width + 'px' element.style.width = this.width + "px";
element.style.display = 'block' element.style.display = "block";
element.style.borderTop = '1px solid black' element.style.borderTop = "1px solid black";
// element.style.boxSizing = 'border-box' // element.style.boxSizing = 'border-box'
element.width = this.width;
element.width = this.width element.height = this.root.viewProps.height;
element.height = this.root.viewProps.height return element;
return element
} }
public setElementPosition(top: number, left: number) { public setElementPosition(top: number, left: number) {
this.element.style.top = top + 'px' this.element.style.top = top + "px";
this.element.style.left = left + 'px' this.element.style.left = left + "px";
} }
private isRowSelected(row: number): boolean { private isRowSelected(row: number): boolean {
const { selectedCell, selectedRange } = this.root.selection const { selectedCell, selectedRange } = this.root.selection;
if (selectedCell && selectedCell.row === row) return true if (selectedCell && selectedCell.row === row) return true;
if (selectedRange) { if (selectedRange) {
const inRange = const inRange =
row >= Math.min(selectedRange.from.row, selectedRange.to.row) && row >= Math.min(selectedRange.from.row, selectedRange.to.row) &&
row <= Math.max(selectedRange.from.row, selectedRange.to.row) row <= Math.max(selectedRange.from.row, selectedRange.to.row);
return inRange return inRange;
} }
return false return false;
} }
private renderText(row: number, renderBox: RenderBox) { private renderText(row: number, renderBox: RenderBox) {
const { y, height } = renderBox const { y, height } = renderBox;
this.ctx.fillStyle = 'black' this.ctx.fillStyle = "black";
this.ctx.textAlign = 'center' this.ctx.textAlign = "center";
this.ctx.textBaseline = 'middle' this.ctx.textBaseline = "middle";
this.ctx.font = '16px Arial' this.ctx.font = "16px Arial";
this.ctx.fillText(this.root.config.rows[row].title, this.width / 2, y - this.root.viewport.top + height / 2) this.ctx.fillText(
this.root.config.rows[row].title,
this.width / 2,
y - this.root.viewport.top + height / 2,
);
} }
private renderRect(column: number, renderBox: RenderBox) { private renderRect(column: number, renderBox: RenderBox) {
const { y, height } = renderBox const { y, height } = renderBox;
const isRowSeleted = this.isRowSelected(column);
const isRowSeleted = this.isRowSelected(column) this.ctx.fillStyle = isRowSeleted
? this.root.styles.cells.selectedBackground
: "white";
this.ctx.strokeStyle = "black";
this.ctx.lineWidth = this.resizerHeight;
this.ctx.fillStyle = isRowSeleted ? this.root.styles.cells.selectedBackground : 'white' const specialY = y - this.root.viewport.top;
this.ctx.strokeStyle = 'black'
this.ctx.lineWidth = this.resizerHeight
const specialY = y - this.root.viewport.top this.ctx.fillRect(0, specialY - 1, this.width, height);
this.ctx.strokeRect(0, specialY - 1, this.width, height);
this.ctx.fillRect(0, specialY - 1, this.width, height )
this.ctx.strokeRect(0, specialY - 1, this.width, height )
} }
private renderSingleRow(row: number) { private renderSingleRow(row: number) {
const renderBox = new RenderBox(this.root.config, { const renderBox = new RenderBox(this.root.config, {
column: 0, column: 0,
row: row row: row,
}) });
this.renderRect(row, renderBox) this.renderRect(row, renderBox);
this.renderText(row, renderBox) this.renderText(row, renderBox);
} }
public renderBar() { public renderBar() {
@ -94,7 +97,7 @@ export class RowsBar {
this.ctx.beginPath(); this.ctx.beginPath();
this.ctx.moveTo(0, 0); this.ctx.moveTo(0, 0);
this.ctx.strokeStyle = 'black'; this.ctx.strokeStyle = "black";
this.ctx.lineWidth = 16; this.ctx.lineWidth = 16;
this.ctx.lineTo(35, 0); this.ctx.lineTo(35, 0);
this.ctx.closePath(); this.ctx.closePath();
@ -102,8 +105,7 @@ export class RowsBar {
for (let row = firstRowIdx; row <= lastRowIdx; row++) { for (let row = firstRowIdx; row <= lastRowIdx; row++) {
if (!this.root.config.rows[row]) break; if (!this.root.config.rows[row]) break;
this.renderSingleRow(row) this.renderSingleRow(row);
} }
} }
} }

View File

@ -25,8 +25,8 @@ export class Scroller {
this.element.style.height = this.root.config.view.height + "px"; this.element.style.height = this.root.config.view.height + "px";
this.element.style.width = this.root.config.view.width + "px"; this.element.style.width = this.root.config.view.width + "px";
this.element.style.top = this.root.columnsBarHeight + 'px' this.element.style.top = this.root.columnsBarHeight + "px";
this.element.style.left = this.root.rowsBarWidth + 'px' this.element.style.left = this.root.rowsBarWidth + "px";
this.element.tabIndex = -1; this.element.tabIndex = -1;
this.updateScrollerSize(); //* Init size set this.updateScrollerSize(); //* Init size set
@ -168,18 +168,20 @@ export class Scroller {
this.root.selection.selectedCell = clickedCell; this.root.selection.selectedCell = clickedCell;
this.root.renderSheet(); this.root.renderSheet();
this.root.renderColumnsBar() this.root.renderColumnsBar();
this.root.renderRowsBar(); }; this.root.renderRowsBar();
};
private handleScroll = () => { private handleScroll = () => {
const rect = this.getViewportBoundlingRect(); const rect = this.getViewportBoundlingRect();
this.root.viewport.updateValues(rect); this.root.viewport.updateValues(rect);
this.root.renderSheet(); this.root.renderSheet();
this.root.renderColumnsBar() this.root.renderColumnsBar();
this.root.renderRowsBar(); }; this.root.renderRowsBar();
};
public getViewportBoundlingRect(): ViewportRect { public getViewportBoundlingRect(): ViewportRect {
const { scrollTop, scrollLeft } = this.element; const { scrollTop, scrollLeft } = this.element;

View File

@ -18,7 +18,7 @@ export class Sheet {
canvas.width = this.root.config.view.width; canvas.width = this.root.config.view.width;
canvas.style.width = this.root.config.view.width + "px"; canvas.style.width = this.root.config.view.width + "px";
canvas.style.height = this.root.config.view.height + "px"; canvas.style.height = this.root.config.view.height + "px";
canvas.style.left = '0px' canvas.style.left = "0px";
this.element = canvas; this.element = canvas;

View File

@ -3,7 +3,7 @@ import Spreadsheet, { CSS_PREFIX } from "../main";
export class Toolbar { export class Toolbar {
element: HTMLDivElement; element: HTMLDivElement;
root: Spreadsheet; root: Spreadsheet;
height: number = 0 height: number = 0;
constructor(root: Spreadsheet) { constructor(root: Spreadsheet) {
this.root = root; this.root = root;
const toolbarElement = document.createElement("div"); const toolbarElement = document.createElement("div");

View File

@ -45,8 +45,8 @@ export default class Spreadsheet {
private table: Table; private table: Table;
private scroller: Scroller; private scroller: Scroller;
private toolbar: Toolbar; private toolbar: Toolbar;
private rowsBar: RowsBar private rowsBar: RowsBar;
private columnsBar: ColumnsBar private columnsBar: ColumnsBar;
private sheet: Sheet; private sheet: Sheet;
private editor: Editor; private editor: Editor;
public styles: Styles; public styles: Styles;
@ -71,8 +71,8 @@ export default class Spreadsheet {
this.config = new Config(config); this.config = new Config(config);
this.rowsBar = new RowsBar(this) this.rowsBar = new RowsBar(this);
this.columnsBar = new ColumnsBar(this) this.columnsBar = new ColumnsBar(this);
this.sheet = new Sheet(this); this.sheet = new Sheet(this);
this.table = new Table(this); this.table = new Table(this);
this.scroller = new Scroller(this); this.scroller = new Scroller(this);
@ -88,28 +88,28 @@ export default class Spreadsheet {
this.data = data; this.data = data;
this.styles = new Styles(); this.styles = new Styles();
this.buildComponent(); this.buildComponent();
this.setElementsPositions() this.setElementsPositions();
this.appendTableToTarget(target); this.appendTableToTarget(target);
this.renderSheet(); this.renderSheet();
this.renderColumnsBar() this.renderColumnsBar();
this.renderRowsBar() this.renderRowsBar();
} }
private setRowsBarPosition() { private setRowsBarPosition() {
const top = this.columnsBar.height + this.toolbar.height const top = this.columnsBar.height + this.toolbar.height;
const left = 0 const left = 0;
this.rowsBar.setElementPosition(top, left) this.rowsBar.setElementPosition(top, left);
} }
private setColumnsBarPosition() { private setColumnsBarPosition() {
const top = this.toolbar.height const top = this.toolbar.height;
const left = this.rowsBar.width const left = this.rowsBar.width;
console.log(top,left) console.log(top, left);
this.columnsBar.setElementPosition(top, left) this.columnsBar.setElementPosition(top, left);
} }
private setElementsPositions() { private setElementsPositions() {
this.setRowsBarPosition() this.setRowsBarPosition();
this.setColumnsBarPosition() this.setColumnsBarPosition();
} }
private getInitialCache(): Cache { private getInitialCache(): Cache {
@ -149,16 +149,16 @@ export default class Spreadsheet {
private buildComponent(): void { private buildComponent(): void {
const content = document.createElement("div"); //* Abstract const content = document.createElement("div"); //* Abstract
content.style.top = this.columnsBarHeight + 'px' content.style.top = this.columnsBarHeight + "px";
content.style.left = this.rowsBarWidth + 'px' content.style.left = this.rowsBarWidth + "px";
content.appendChild(this.sheet.element); content.appendChild(this.sheet.element);
content.classList.add(CSS_PREFIX + "content"); content.classList.add(CSS_PREFIX + "content");
this.table.element.appendChild(this.toolbar.element); this.table.element.appendChild(this.toolbar.element);
this.table.element.appendChild(this.rowsBar.element) this.table.element.appendChild(this.rowsBar.element);
this.table.element.appendChild(this.columnsBar.element) this.table.element.appendChild(this.columnsBar.element);
this.table.element.appendChild(content); this.table.element.appendChild(content);
this.table.element.appendChild(this.scroller.element); this.table.element.appendChild(this.scroller.element);
this.table.element.append(this.editor.element); this.table.element.append(this.editor.element);
@ -199,15 +199,15 @@ export default class Spreadsheet {
} }
get columnsBarHeight() { get columnsBarHeight() {
return this.columnsBar.height return this.columnsBar.height;
} }
get rowsBarWidth() { get rowsBarWidth() {
return this.rowsBar.width return this.rowsBar.width;
} }
get toolbarHeight() { get toolbarHeight() {
return this.toolbar.height return this.toolbar.height;
} }
/** Focusing on interactive part of spreadsheet */ /** Focusing on interactive part of spreadsheet */
@ -286,11 +286,11 @@ export default class Spreadsheet {
} }
renderColumnsBar() { renderColumnsBar() {
this.columnsBar.renderBar() this.columnsBar.renderBar();
} }
renderRowsBar() { renderRowsBar() {
this.rowsBar.renderBar() this.rowsBar.renderBar();
} }
renderCell(row: number, col: number) { renderCell(row: number, col: number) {

View File

@ -114,8 +114,8 @@ export class Cell {
render(root: Spreadsheet) { render(root: Spreadsheet) {
const renderBox = new RenderBox(root.config, this.position); const renderBox = new RenderBox(root.config, this.position);
let {x, y} = renderBox let { x, y } = renderBox;
const {height, width} = renderBox const { height, width } = renderBox;
const { ctx } = root; const { ctx } = root;
const isCellSelected = const isCellSelected =