diff --git a/.gitignore b/.gitignore index a547bf3..251ce6d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ pnpm-debug.log* lerna-debug.log* node_modules -dist dist-ssr *.local diff --git a/dist/components/editor.d.ts b/dist/components/editor.d.ts new file mode 100644 index 0000000..674cfd4 --- /dev/null +++ b/dist/components/editor.d.ts @@ -0,0 +1,11 @@ +import { Spreadsheet } from "../main"; +import { Position } from "../modules/cell"; +export declare class Editor { + element: HTMLInputElement; + root: Spreadsheet; + constructor(root: Spreadsheet); + hide(): void; + show(position: Position): void; + handleKeydown: (event: KeyboardEvent) => void; + handleClickOutside: (event: MouseEvent) => void; +} diff --git a/dist/components/header.d.ts b/dist/components/header.d.ts new file mode 100644 index 0000000..e8affcb --- /dev/null +++ b/dist/components/header.d.ts @@ -0,0 +1,6 @@ +import { Spreadsheet } from "../main"; +export declare class Header { + element: HTMLHeadElement; + root: Spreadsheet; + constructor(root: Spreadsheet); +} diff --git a/dist/components/scroller.d.ts b/dist/components/scroller.d.ts new file mode 100644 index 0000000..58f3786 --- /dev/null +++ b/dist/components/scroller.d.ts @@ -0,0 +1,28 @@ +import { Spreadsheet } from "../main"; +export interface ViewportRect { + top: number; + left: number; + right: number; + bottom: number; +} +export declare class Scroller { + element: HTMLDivElement; + private verticalScroller; + private horizontalScroller; + private root; + private isSelecting; + constructor(root: Spreadsheet); + private handleMouseMove; + private handleMouseUp; + private handleDoubleClick; + private handleKeydown; + private handleClick; + private handleScroll; + getViewportBoundlingRect(): ViewportRect; + private buildComponent; + private getActualHeight; + private getActualWidth; + updateScrollerSize(): void; + private setScrollerHeight; + private setScrollerWidth; +} diff --git a/dist/components/sheet.d.ts b/dist/components/sheet.d.ts new file mode 100644 index 0000000..a75fa03 --- /dev/null +++ b/dist/components/sheet.d.ts @@ -0,0 +1,14 @@ +import { Spreadsheet } from "../main"; +import { Position } from "../modules/cell"; +/** + * Display (CANVAS) element where cells render + */ +export declare class Sheet { + element: HTMLCanvasElement; + ctx: CanvasRenderingContext2D; + root: Spreadsheet; + constructor(root: Spreadsheet); + getCellByCoords(x: number, y: number): Position; + renderCell(position: Position): void; + renderSheet(): void; +} diff --git a/dist/components/table.d.ts b/dist/components/table.d.ts new file mode 100644 index 0000000..a9883df --- /dev/null +++ b/dist/components/table.d.ts @@ -0,0 +1,9 @@ +import { Spreadsheet } from "../main"; +import { ViewProperties } from "../modules/config"; +/** Base (root) component */ +export declare class Table { + element: HTMLDivElement; + root: Spreadsheet; + constructor(root: Spreadsheet); + changeElementSizes(sizes: ViewProperties): void; +} diff --git a/dist/components/toolbar.d.ts b/dist/components/toolbar.d.ts new file mode 100644 index 0000000..14600fc --- /dev/null +++ b/dist/components/toolbar.d.ts @@ -0,0 +1,6 @@ +import { Spreadsheet } from "../main"; +export declare class Toolbar { + element: HTMLDivElement; + root: Spreadsheet; + constructor(root: Spreadsheet); +} diff --git a/dist/main.cjs b/dist/main.cjs new file mode 100644 index 0000000..10c4974 --- /dev/null +++ b/dist/main.cjs @@ -0,0 +1,4 @@ +"use strict";var y=Object.defineProperty;var v=(r,t,e)=>t in r?y(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var o=(r,t,e)=>(v(r,typeof t!="symbol"?t+"":t,e),e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class w{constructor(t,e){o(this,"x");o(this,"y");o(this,"width");o(this,"height");this.x=this.getXCoord(e.column,t),this.y=this.getYCoord(e.row,t),this.width=t.columns[e.column].width,this.height=t.rows[e.row].height}getXCoord(t,e){let s=0;for(let l=0;l{const{key:e}=t;switch(e){case"Escape":{this.hide();break}case"Enter":this.root.changeCellValues(this.root.selection.selectedCell,{value:this.element.value,displayValue:this.element.value}),this.hide()}});o(this,"handleClickOutside",t=>{const e=t.target;this.element.contains(e)||this.hide()});this.root=t;const e=document.createElement("input");e.classList.add("editor"),this.element=e,this.hide()}hide(){this.element.style.display="none",this.element.classList.add("hide"),this.element.blur(),window.removeEventListener("click",this.handleClickOutside),this.element.removeEventListener("keydown",this.handleKeydown),this.root.focusTable()}show(t){const{height:e,width:s,x:l,y:n}=new w(this.root.config,t),i=this.root.getCell(t);this.element.classList.remove("hide"),this.element.style.top=n-this.root.viewport.top+"px",this.element.style.left=l-this.root.viewport.left+"px",this.element.style.width=s+"px",this.element.style.height=e+"px",this.element.style.display="block",window.addEventListener("click",this.handleClickOutside),this.element.addEventListener("keydown",this.handleKeydown),this.element.value=i.value,this.element.focus(),this.element.select()}}class x{constructor(t){o(this,"element");o(this,"root");this.root=t;const e=document.createElement("header");e.classList.add(),this.element=e}}class R{constructor(t){o(this,"element");o(this,"verticalScroller");o(this,"horizontalScroller");o(this,"root");o(this,"isSelecting",!1);o(this,"handleMouseMove",t=>{if(!this.isSelecting)return;const{offsetX:e,offsetY:s}=t,l=this.root.getCellByCoords(e,s);this.root.selection.selectedRange&&(this.root.selection.selectedRange.to=l),this.root.renderSheet()});o(this,"handleMouseUp",()=>{this.isSelecting=!1,this.root.selection.selectedRange&&this.root.selection.selectedRange.from.row===this.root.selection.selectedRange.to.row&&this.root.selection.selectedRange.from.column===this.root.selection.selectedRange.to.column&&(this.root.selection.selectedRange=null),this.root.renderSheet()});o(this,"handleDoubleClick",t=>{t.preventDefault();const e=this.root.getCellByCoords(t.offsetX,t.offsetY);this.root.showEditor(e)});o(this,"handleKeydown",t=>{if(console.log(t),["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"].includes(t.key))switch(t.preventDefault(),this.root.selection.selectedRange=null,t.key){case"ArrowLeft":{this.root.selection.selectedCell&&this.root.selection.selectedCell.column>0&&(console.log("tick"),this.root.selection.selectedCell.column-=1,this.root.renderSheet());break}case"ArrowRight":{this.root.selection.selectedCell&&this.root.selection.selectedCell.column0&&(this.root.selection.selectedCell.row-=1,this.root.renderSheet());break}case"ArrowDown":{this.root.selection.selectedCell&&this.root.selection.selectedCell.row{if(t.button!==0)return;const{offsetX:e,offsetY:s}=t,l=this.root.getCellByCoords(e,s);this.isSelecting=!0,this.root.selection.selectedRange={from:l,to:l},this.root.selection.selectedCell=l,this.root.renderSheet()});o(this,"handleScroll",()=>{const t=this.getViewportBoundlingRect();this.root.viewport.updateValues(t),this.root.renderSheet()});this.root=t;const{horizontalScroller:e,scroller:s,verticalScroller:l}=this.buildComponent();this.element=s,this.verticalScroller=l,this.horizontalScroller=e,this.element.style.height=this.root.config.view.height+"px",this.element.style.width=this.root.config.view.width+"px",this.element.tabIndex=-1,this.updateScrollerSize(),this.element.addEventListener("scroll",this.handleScroll),this.element.addEventListener("mousedown",this.handleClick),this.element.addEventListener("mousemove",this.handleMouseMove),this.element.addEventListener("mouseup",this.handleMouseUp),this.element.addEventListener("dblclick",this.handleDoubleClick),this.element.addEventListener("keydown",this.handleKeydown)}getViewportBoundlingRect(){const{scrollTop:t,scrollLeft:e}=this.element,{height:s,width:l}=this.element.getBoundingClientRect(),n=t+s,i=e+l;return{top:t,left:e,bottom:n,right:i}}buildComponent(){const t=document.createElement("div"),e=document.createElement("div"),s=document.createElement("div"),l=document.createElement("div"),n=document.createElement("div");return e.style.width="0px",e.style.pointerEvents="none",s.style.pointerEvents="none",l.style.display="flex",n.appendChild(e),n.appendChild(s),l.appendChild(n),this.verticalScroller=e,this.horizontalScroller=s,t.appendChild(l),t.classList.add("scroller"),{scroller:t,verticalScroller:e,horizontalScroller:s}}getActualHeight(){return this.root.config.rows.reduce((t,e)=>(t+=e.height,t),0)}getActualWidth(){return this.root.config.columns.reduce((t,e)=>(t+=e.width,t),0)}updateScrollerSize(){const t=this.getActualHeight(),e=this.getActualWidth();this.setScrollerHeight(t),this.setScrollerWidth(e)}setScrollerHeight(t){this.verticalScroller.style.height=t+"px"}setScrollerWidth(t){this.horizontalScroller.style.width=t+"px"}}class b{constructor(t){o(this,"fontSize",16);o(this,"fontColor","black");o(this,"background","white");o(this,"borderColor","black");o(this,"selectedBackground","#4287f5");o(this,"selectedFontColor","#ffffff");t&&Object.assign(this,t)}}class k{constructor(t,e){o(this,"row");o(this,"column");this.row=t,this.column=e}}class E{constructor(t){o(this,"value");o(this,"displayValue");o(this,"resultValue");o(this,"position");o(this,"style",new b);this.value=t.value,this.displayValue=t.displayValue,this.resultValue=t.resultValue,this.position=t.position}changeValues(t){Object.assign(this,t)}isCellInRange(t){const{column:e,row:s}=this.position,{selectedRange:l}=t.selection;if(!l)return!1;const n=s>=Math.min(l.from.row,l.to.row)&&s<=Math.max(l.to.row,l.from.row);return e>=Math.min(l.from.column,l.to.column)&&e<=Math.max(l.to.column,l.from.column)&&n}render(t){var a;let{height:e,width:s,x:l,y:n}=new w(t.config,this.position);const{ctx:i}=t,c=((a=t.selection.selectedCell)==null?void 0:a.row)===this.position.row&&t.selection.selectedCell.column===this.position.column,h=this.isCellInRange(t);n-=t.viewport.top,l-=t.viewport.left,i.clearRect(l,n,s,e),i.fillStyle=c||h?this.style.selectedBackground:this.style.background,i.strokeStyle="black",i.fillRect(l,n,s-1,e-1),i.strokeRect(l,n,s,e),i.fillStyle=c||h?this.style.selectedFontColor:this.style.fontColor,i.textAlign="left",i.font=`${this.style.fontSize}px Arial`,i.textBaseline="middle",i.fillText(this.displayValue,l+2,n+e/2,s)}}class I{constructor(t){o(this,"element");o(this,"ctx");o(this,"root");this.root=t;const e=document.createElement("canvas");e.classList.add("sheet"),e.height=this.root.config.view.height,e.width=this.root.config.view.width,e.style.width=this.root.config.view.width+"px",e.style.height=this.root.config.view.height+"px",this.element=e;const s=this.element.getContext("2d");if(!s)throw new Error("Enable hardware acceleration");this.ctx=s}getCellByCoords(t,e){let s=0,l=0;for(;l<=e&&(l+=this.root.config.rows[s].height,!(l>=e));)s++;let n=0,i=0;for(;i<=t&&(i+=this.root.config.columns[n].width,!(i>=t));)n++;return new k(s,n)}renderCell(t){const{column:e,row:s}=t;this.root.data[s][e].render(this.root)}renderSheet(){const t=this.root.viewport.firstRow,e=this.root.viewport.lastCol+3,s=this.root.viewport.lastRow+3,l=this.root.viewport.firstCol;for(let n=t;n<=s;n++)for(let i=l;i<=e&&!(!this.root.config.columns[i]||!this.root.config.rows[n]);i++)this.renderCell({column:i,row:n})}}class V{constructor(t){o(this,"element");o(this,"root");this.root=t;const e=document.createElement("div");e.classList.add("spreadsheet_container"),this.element=e,this.changeElementSizes(this.root.viewProps)}changeElementSizes(t){const{height:e,width:s}=t;this.element.style.width=s+"px",this.element.style.height=e+"px"}}class L{constructor(t){o(this,"element");o(this,"root");this.root=t;const e=document.createElement("div");e.classList.add("toolbar"),this.element=e}}class d{constructor(t){o(this,"rows");o(this,"columns");o(this,"view",{width:800,height:600});this.columns=t.columns,this.rows=t.rows,this.view=t.view}}class B{constructor(){o(this,"selectedCell",null);o(this,"selectedRange",null)}}class A{}class u{constructor(t,e){o(this,"root");o(this,"top");o(this,"left");o(this,"right");o(this,"bottom");o(this,"firstRow");o(this,"lastRow");o(this,"firstCol");o(this,"lastCol");this.root=t,this.top=e.top,this.left=e.left,this.right=e.right,this.bottom=e.bottom,this.firstRow=this.getFirstRow(),this.lastCol=this.getFirstRow();//!Temp +this.firstCol=this.getFirstRow();//!Temp +this.lastRow=this.getLastRow(),this.updateValues({top:0,left:0,right:this.root.viewProps.width,bottom:this.root.viewProps.height})}updateValues(t){this.top=t.top,this.left=t.left,this.right=t.right,this.bottom=t.bottom,this.firstRow=this.getFirstRow(),this.lastRow=this.getLastRow(),this.firstCol=this.getFirstCol(),this.lastCol=this.getLastCol()}getFirstRow(){return this.root.cache.getRowByYCoord(this.top)}getLastRow(){return this.root.cache.getRowByYCoord(this.bottom)}getFirstCol(){return this.root.cache.getColumnByXCoord(this.left)}getLastCol(){return this.root.cache.getColumnByXCoord(this.right)}}class m{constructor(t){o(this,"width");o(this,"title");this.width=t.width,this.title=t.title}}class g{constructor(t){o(this,"height");o(this,"title");this.height=t.height,this.title=t.title}}function f(r,t,e=!1){const s=[];for(let l=0;l<=r;l++){const n=[];for(let i=0;i<=t;i++){const c=e?`${l}:${i}`:"",h=new E({displayValue:c,resultValue:c,value:c,position:{column:i,row:l}});n.push(h)}s.push(n)}return s}function M(r,t){const e=[];for(let n=0;n<=r;n++){const i=new g({height:40,title:String(n)});e.push(i)}const s=[];for(let n=0;n<=t;n++){const i=new m({title:String(n),width:150});s.push(i)}return new d({columns:s,rows:e,view:{height:600,width:800}})}class T{constructor(t){o(this,"xPos");o(this,"colIdx");this.xPos=t.xPos,this.colIdx=t.colIdx}}class D{constructor(t){o(this,"yPos");o(this,"rowIdx");this.yPos=t.yPos,this.rowIdx=t.rowIdx}}class z{constructor(t){o(this,"columns");o(this,"rows");this.columns=t.columns,this.rows=t.rows}getRowByYCoord(t){let e=0;for(let s=0;s{this.changeCellValues(t.position,{displayValue:"",resultValue:"",value:""})});else{if(!this.selection.selectedCell)return;this.changeCellValues(this.selection.selectedCell,{displayValue:"",resultValue:"",value:""})}}showEditor(t){this.editor.show(t)}renderSheet(){this.sheet.renderSheet()}renderCell(t,e){this.data[t][e].render(this)}loadData(t){this.data=t,this.config=this.makeConfigFromData(t,this.config.view),this.cache=this.getInitialCache(),this.scroller.updateScrollerSize(),this.viewport=new u(this,this.scroller.getViewportBoundlingRect()),this.renderSheet()}makeConfigFromData(t,e){const s=t.length-1,l=t[0]?t[0].length:0,n=[];for(let h=0;h{p.loadData(F)},2e3);exports.Spreadsheet=C; diff --git a/dist/main.d.ts b/dist/main.d.ts new file mode 100644 index 0000000..31467e6 --- /dev/null +++ b/dist/main.d.ts @@ -0,0 +1,43 @@ +import { Cell, CellConstructorProps, Position } from "./modules/cell"; +import { Config, ViewProperties } from "./modules/config"; +import { RangeSelectionType, Selection } from "./modules/selection"; +import { Styles } from "./modules/styles"; +import { Viewport } from "./modules/viewport"; +import './scss/main.scss'; +import { Cache } from "./modules/cache"; +interface SpreadsheetConstructorProperties { + config?: Omit; + view?: ViewProperties; +} +export declare class Spreadsheet { + private table; + private scroller; + private toolbar; + private header; + private sheet; + private editor; + styles: Styles; + config: Config; + data: Cell[][]; + viewport: Viewport; + selection: Selection; + cache: Cache; + constructor(target: string | HTMLElement, props?: SpreadsheetConstructorProperties); + private getInitialCache; + private buildComponent; + private appendTableToTarget; + get ctx(): CanvasRenderingContext2D; + get viewProps(): ViewProperties; + focusTable(): void; + getCellByCoords(x: number, y: number): Position; + getCell(position: Position): Cell; + changeCellValues(position: Position, values: Partial>): void; + applyActionToRange(range: RangeSelectionType, callback: (cell: Cell) => any): void; + deleteSelectedCellsValues(): void; + showEditor(position: Position): void; + renderSheet(): void; + renderCell(row: number, col: number): void; + loadData(data: Cell[][]): void; + private makeConfigFromData; +} +export {}; diff --git a/dist/main.js b/dist/main.js new file mode 100644 index 0000000..08e435d --- /dev/null +++ b/dist/main.js @@ -0,0 +1,577 @@ +var p = Object.defineProperty; +var y = (r, t, e) => t in r ? p(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e; +var o = (r, t, e) => (y(r, typeof t != "symbol" ? t + "" : t, e), e); +class w { + constructor(t, e) { + o(this, "x"); + o(this, "y"); + o(this, "width"); + o(this, "height"); + this.x = this.getXCoord(e.column, t), this.y = this.getYCoord(e.row, t), this.width = t.columns[e.column].width, this.height = t.rows[e.row].height; + } + getXCoord(t, e) { + let s = 0; + for (let l = 0; l < t; l++) + s += e.columns[l].width; + return s; + } + getYCoord(t, e) { + let s = 0; + for (let l = 0; l < t; l++) + s += e.rows[l].height; + return s; + } +} +class v { + constructor(t) { + o(this, "element"); + o(this, "root"); + o(this, "handleKeydown", (t) => { + const { key: e } = t; + switch (e) { + case "Escape": { + this.hide(); + break; + } + case "Enter": + this.root.changeCellValues(this.root.selection.selectedCell, { + value: this.element.value, + displayValue: this.element.value + }), this.hide(); + } + }); + o(this, "handleClickOutside", (t) => { + const e = t.target; + this.element.contains(e) || this.hide(); + }); + this.root = t; + const e = document.createElement("input"); + e.classList.add("editor"), this.element = e, this.hide(); + } + hide() { + this.element.style.display = "none", this.element.classList.add("hide"), this.element.blur(), window.removeEventListener("click", this.handleClickOutside), this.element.removeEventListener("keydown", this.handleKeydown), this.root.focusTable(); + } + show(t) { + const { height: e, width: s, x: l, y: n } = new w(this.root.config, t), i = this.root.getCell(t); + this.element.classList.remove("hide"), this.element.style.top = n - this.root.viewport.top + "px", this.element.style.left = l - this.root.viewport.left + "px", this.element.style.width = s + "px", this.element.style.height = e + "px", this.element.style.display = "block", window.addEventListener("click", this.handleClickOutside), this.element.addEventListener("keydown", this.handleKeydown), this.element.value = i.value, this.element.focus(), this.element.select(); + } +} +class S { + constructor(t) { + o(this, "element"); + o(this, "root"); + this.root = t; + const e = document.createElement("header"); + e.classList.add(), this.element = e; + } +} +class x { + constructor(t) { + o(this, "element"); + o(this, "verticalScroller"); + o(this, "horizontalScroller"); + o(this, "root"); + o(this, "isSelecting", !1); + o(this, "handleMouseMove", (t) => { + if (!this.isSelecting) + return; + const { offsetX: e, offsetY: s } = t, l = this.root.getCellByCoords(e, s); + this.root.selection.selectedRange && (this.root.selection.selectedRange.to = l), this.root.renderSheet(); + }); + o(this, "handleMouseUp", () => { + this.isSelecting = !1, this.root.selection.selectedRange && this.root.selection.selectedRange.from.row === this.root.selection.selectedRange.to.row && this.root.selection.selectedRange.from.column === this.root.selection.selectedRange.to.column && (this.root.selection.selectedRange = null), this.root.renderSheet(); + }); + o(this, "handleDoubleClick", (t) => { + t.preventDefault(); + const e = this.root.getCellByCoords(t.offsetX, t.offsetY); + this.root.showEditor(e); + }); + o(this, "handleKeydown", (t) => { + if (console.log(t), ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(t.key)) + switch (t.preventDefault(), this.root.selection.selectedRange = null, t.key) { + case "ArrowLeft": { + this.root.selection.selectedCell && this.root.selection.selectedCell.column > 0 && (console.log("tick"), this.root.selection.selectedCell.column -= 1, this.root.renderSheet()); + break; + } + case "ArrowRight": { + this.root.selection.selectedCell && this.root.selection.selectedCell.column < this.root.config.columns.length - 1 && (this.root.selection.selectedCell.column += 1, this.root.renderSheet()); + break; + } + case "ArrowUp": { + this.root.selection.selectedCell && this.root.selection.selectedCell.row > 0 && (this.root.selection.selectedCell.row -= 1, this.root.renderSheet()); + break; + } + case "ArrowDown": { + this.root.selection.selectedCell && this.root.selection.selectedCell.row < this.root.config.rows.length - 1 && (this.root.selection.selectedCell.row += 1, this.root.renderSheet()); + break; + } + } + if (!t.metaKey && !t.ctrlKey && (t.key === "F2" || /^([a-z]|[а-я])$/.test(t.key.toLowerCase()))) { + if (t.preventDefault(), !this.root.selection.selectedCell) + return; + this.root.showEditor(this.root.selection.selectedCell); + } + t.key === "Delete" && (t.preventDefault(), this.root.deleteSelectedCellsValues(), this.root.renderSheet()); + }); + o(this, "handleClick", (t) => { + if (t.button !== 0) + return; + const { offsetX: e, offsetY: s } = t, l = this.root.getCellByCoords(e, s); + this.isSelecting = !0, this.root.selection.selectedRange = { + from: l, + to: l + }, this.root.selection.selectedCell = l, this.root.renderSheet(); + }); + o(this, "handleScroll", () => { + const t = this.getViewportBoundlingRect(); + this.root.viewport.updateValues(t), this.root.renderSheet(); + }); + this.root = t; + const { horizontalScroller: e, scroller: s, verticalScroller: l } = this.buildComponent(); + this.element = s, this.verticalScroller = l, this.horizontalScroller = e, this.element.style.height = this.root.config.view.height + "px", this.element.style.width = this.root.config.view.width + "px", this.element.tabIndex = -1, this.updateScrollerSize(), this.element.addEventListener("scroll", this.handleScroll), this.element.addEventListener("mousedown", this.handleClick), this.element.addEventListener("mousemove", this.handleMouseMove), this.element.addEventListener("mouseup", this.handleMouseUp), this.element.addEventListener("dblclick", this.handleDoubleClick), this.element.addEventListener("keydown", this.handleKeydown); + } + getViewportBoundlingRect() { + const { scrollTop: t, scrollLeft: e } = this.element, { height: s, width: l } = this.element.getBoundingClientRect(), n = t + s, i = e + l; + return { + top: t, + left: e, + bottom: n, + right: i + }; + } + buildComponent() { + const t = document.createElement("div"), e = document.createElement("div"), s = document.createElement("div"), l = document.createElement("div"), n = document.createElement("div"); + return e.style.width = "0px", e.style.pointerEvents = "none", s.style.pointerEvents = "none", l.style.display = "flex", n.appendChild(e), n.appendChild(s), l.appendChild(n), this.verticalScroller = e, this.horizontalScroller = s, t.appendChild(l), t.classList.add("scroller"), { scroller: t, verticalScroller: e, horizontalScroller: s }; + } + getActualHeight() { + return this.root.config.rows.reduce((t, e) => (t += e.height, t), 0); + } + getActualWidth() { + return this.root.config.columns.reduce((t, e) => (t += e.width, t), 0); + } + updateScrollerSize() { + const t = this.getActualHeight(), e = this.getActualWidth(); + this.setScrollerHeight(t), this.setScrollerWidth(e); + } + setScrollerHeight(t) { + this.verticalScroller.style.height = t + "px"; + } + setScrollerWidth(t) { + this.horizontalScroller.style.width = t + "px"; + } +} +class R { + constructor(t) { + o(this, "fontSize", 16); + o(this, "fontColor", "black"); + o(this, "background", "white"); + o(this, "borderColor", "black"); + o(this, "selectedBackground", "#4287f5"); + o(this, "selectedFontColor", "#ffffff"); + t && Object.assign(this, t); + } +} +class b { + constructor(t, e) { + o(this, "row"); + o(this, "column"); + this.row = t, this.column = e; + } +} +class k { + constructor(t) { + o(this, "value"); + o(this, "displayValue"); + /** This refers to the values ​​​​that were obtained by calculations, for example, after calculating the formula */ + o(this, "resultValue"); + o(this, "position"); + o(this, "style", new R()); + this.value = t.value, this.displayValue = t.displayValue, this.resultValue = t.resultValue, this.position = t.position; + } + changeValues(t) { + Object.assign(this, t); + } + isCellInRange(t) { + const { column: e, row: s } = this.position, { selectedRange: l } = t.selection; + if (!l) + return !1; + const n = s >= Math.min(l.from.row, l.to.row) && s <= Math.max(l.to.row, l.from.row); + return e >= Math.min(l.from.column, l.to.column) && e <= Math.max(l.to.column, l.from.column) && n; + } + render(t) { + var a; + let { height: e, width: s, x: l, y: n } = new w(t.config, this.position); + const { ctx: i } = t, c = ((a = t.selection.selectedCell) == null ? void 0 : a.row) === this.position.row && t.selection.selectedCell.column === this.position.column, h = this.isCellInRange(t); + n -= t.viewport.top, l -= t.viewport.left, i.clearRect(l, n, s, e), i.fillStyle = c || h ? this.style.selectedBackground : this.style.background, i.strokeStyle = "black", i.fillRect(l, n, s - 1, e - 1), i.strokeRect(l, n, s, e), i.fillStyle = c || h ? this.style.selectedFontColor : this.style.fontColor, i.textAlign = "left", i.font = `${this.style.fontSize}px Arial`, i.textBaseline = "middle", i.fillText(this.displayValue, l + 2, n + e / 2, s); + } +} +class E { + constructor(t) { + o(this, "element"); + o(this, "ctx"); + o(this, "root"); + this.root = t; + const e = document.createElement("canvas"); + e.classList.add("sheet"), e.height = this.root.config.view.height, e.width = this.root.config.view.width, e.style.width = this.root.config.view.width + "px", e.style.height = this.root.config.view.height + "px", this.element = e; + const s = this.element.getContext("2d"); + if (!s) + throw new Error("Enable hardware acceleration"); + this.ctx = s; + } + getCellByCoords(t, e) { + let s = 0, l = 0; + for (; l <= e && (l += this.root.config.rows[s].height, !(l >= e)); ) + s++; + let n = 0, i = 0; + for (; i <= t && (i += this.root.config.columns[n].width, !(i >= t)); ) + n++; + return new b(s, n); + } + renderCell(t) { + const { column: e, row: s } = t; + this.root.data[s][e].render(this.root); + } + renderSheet() { + const t = this.root.viewport.firstRow, e = this.root.viewport.lastCol + 3, s = this.root.viewport.lastRow + 3, l = this.root.viewport.firstCol; + for (let n = t; n <= s; n++) + for (let i = l; i <= e && !(!this.root.config.columns[i] || !this.root.config.rows[n]); i++) + this.renderCell({ column: i, row: n }); + } +} +class I { + constructor(t) { + o(this, "element"); + o(this, "root"); + this.root = t; + const e = document.createElement("div"); + e.classList.add("spreadsheet_container"), this.element = e, this.changeElementSizes(this.root.viewProps); + } + changeElementSizes(t) { + const { height: e, width: s } = t; + this.element.style.width = s + "px", this.element.style.height = e + "px"; + } +} +class V { + constructor(t) { + o(this, "element"); + o(this, "root"); + this.root = t; + const e = document.createElement("div"); + e.classList.add("toolbar"), this.element = e; + } +} +class d { + constructor(t) { + o(this, "rows"); + o(this, "columns"); + o(this, "view", { + width: 800, + height: 600 + }); + this.columns = t.columns, this.rows = t.rows, this.view = t.view; + } +} +class L { + constructor() { + o(this, "selectedCell", null); + o(this, "selectedRange", null); + } +} +class B { +} +class u { + constructor(t, e) { + o(this, "root"); + o(this, "top"); + o(this, "left"); + o(this, "right"); + o(this, "bottom"); + o(this, "firstRow"); + o(this, "lastRow"); + o(this, "firstCol"); + o(this, "lastCol"); + this.root = t, this.top = e.top, this.left = e.left, this.right = e.right, this.bottom = e.bottom, this.firstRow = this.getFirstRow(), this.lastCol = this.getFirstRow(); + //!Temp + this.firstCol = this.getFirstRow(); + //!Temp + this.lastRow = this.getLastRow(), this.updateValues({ + top: 0, + left: 0, + right: this.root.viewProps.width, + bottom: this.root.viewProps.height + }); + } + updateValues(t) { + this.top = t.top, this.left = t.left, this.right = t.right, this.bottom = t.bottom, this.firstRow = this.getFirstRow(), this.lastRow = this.getLastRow(), this.firstCol = this.getFirstCol(), this.lastCol = this.getLastCol(); + } + /** Get index of first row in viewport */ + getFirstRow() { + return this.root.cache.getRowByYCoord(this.top); + } + getLastRow() { + return this.root.cache.getRowByYCoord(this.bottom); + } + getFirstCol() { + return this.root.cache.getColumnByXCoord(this.left); + } + getLastCol() { + return this.root.cache.getColumnByXCoord(this.right); + } +} +class m { + constructor(t) { + o(this, "width"); + o(this, "title"); + this.width = t.width, this.title = t.title; + } +} +class g { + constructor(t) { + o(this, "height"); + o(this, "title"); + this.height = t.height, this.title = t.title; + } +} +function f(r, t, e = !1) { + const s = []; + for (let l = 0; l <= r; l++) { + const n = []; + for (let i = 0; i <= t; i++) { + const c = e ? `${l}:${i}` : "", h = new k({ + displayValue: c, + resultValue: c, + value: c, + position: { + column: i, + row: l + } + }); + n.push(h); + } + s.push(n); + } + return s; +} +function A(r, t) { + const e = []; + for (let n = 0; n <= r; n++) { + const i = new g({ + height: 40, + title: String(n) + }); + e.push(i); + } + const s = []; + for (let n = 0; n <= t; n++) { + const i = new m({ + title: String(n), + width: 150 + }); + s.push(i); + } + return new d({ + columns: s, + rows: e, + view: { + height: 600, + width: 800 + } + }); +} +class M { + constructor(t) { + o(this, "xPos"); + o(this, "colIdx"); + this.xPos = t.xPos, this.colIdx = t.colIdx; + } +} +class T { + constructor(t) { + o(this, "yPos"); + o(this, "rowIdx"); + this.yPos = t.yPos, this.rowIdx = t.rowIdx; + } +} +class D { + constructor(t) { + o(this, "columns"); + o(this, "rows"); + this.columns = t.columns, this.rows = t.rows; + } + getRowByYCoord(t) { + let e = 0; + for (let s = 0; s < this.rows.length; s++) + if (t <= this.rows[s].yPos) { + e = s; + break; + } + return e; + } + getColumnByXCoord(t) { + let e = 0; + for (let s = 0; s < this.columns.length; s++) + if (t <= this.columns[s].xPos) { + e = s; + break; + } + return e; + } +} +class z { + constructor(t, e) { + o(this, "table"); + o(this, "scroller"); + o(this, "toolbar"); + o(this, "header"); + o(this, "sheet"); + o(this, "editor"); + o(this, "styles"); + o(this, "config"); + o(this, "data"); + o(this, "viewport"); + o(this, "selection"); + o(this, "cache"); + const s = A(500, 500); + e != null && e.view && (s.view = e.view), this.config = new d(s), this.sheet = new E(this); + const l = f(500, 500); + this.table = new I(this), this.scroller = new x(this), this.toolbar = new V(this), this.header = new S(this), this.editor = new v(this), this.cache = this.getInitialCache(), this.viewport = new u(this, this.scroller.getViewportBoundlingRect()), this.selection = new L(), this.data = l, this.styles = new B(), this.buildComponent(), this.appendTableToTarget(t), this.renderSheet(); + } + getInitialCache() { + const t = []; + let e = 0; + for (let i = 0; i <= this.config.columns.length - 1; i++) { + const c = this.config.columns[i]; + e += c.width; + const h = new M({ + xPos: e, + colIdx: i + }); + t.push(h); + } + const s = []; + let l = 0; + for (let i = 0; i <= this.config.rows.length - 1; i++) { + const c = this.config.rows[i]; + l += c.height; + const h = new T({ + yPos: l, + rowIdx: i + }); + s.push(h); + } + const n = new D({ + columns: t, + rows: s + }); + return console.log("CACHE: ", n), console.log("CONFIG: ", this.config), n; + } + buildComponent() { + const t = document.createElement("div"); + t.appendChild(this.header.element), t.appendChild(this.sheet.element), t.classList.add("content"), this.table.element.appendChild(this.toolbar.element), this.table.element.appendChild(t), this.table.element.appendChild(this.scroller.element), this.table.element.append(this.editor.element); + } + appendTableToTarget(t) { + if (typeof t == "string") { + const e = document.querySelector(t); + if (!e) + throw new Error(`Element with selector ${t} is not finded in DOM. + Make sure it exists.`); + e == null || e.appendChild(this.table.element); + } + t instanceof HTMLElement && t.append(this.table.element); + } + get ctx() { + return this.sheet.ctx; + } + get viewProps() { + return this.config.view; + } + focusTable() { + this.scroller.element.focus(); + } + getCellByCoords(t, e) { + return this.sheet.getCellByCoords(t, e); + } + getCell(t) { + const { column: e, row: s } = t; + return this.data[s][e]; + } + changeCellValues(t, e) { + const { column: s, row: l } = t; + this.data[l][s].changeValues(e), this.renderCell(l, s); + } + applyActionToRange(t, e) { + const s = Math.min(t.from.row, t.to.row), l = Math.max(t.from.row, t.to.row), n = Math.min(t.from.column, t.to.column), i = Math.max(t.from.column, t.to.column); + for (let c = s; c <= l; c++) + for (let h = n; h <= i; h++) { + const a = this.data[c][h]; + e(a); + } + } + deleteSelectedCellsValues() { + if (this.selection.selectedRange !== null) + this.applyActionToRange(this.selection.selectedRange, (t) => { + this.changeCellValues(t.position, { + displayValue: "", + resultValue: "", + value: "" + }); + }); + else { + if (!this.selection.selectedCell) + return; + this.changeCellValues(this.selection.selectedCell, { + displayValue: "", + resultValue: "", + value: "" + }); + } + } + showEditor(t) { + this.editor.show(t); + } + renderSheet() { + this.sheet.renderSheet(); + } + renderCell(t, e) { + this.data[t][e].render(this); + } + loadData(t) { + this.data = t, this.config = this.makeConfigFromData(t, this.config.view), this.cache = this.getInitialCache(), this.scroller.updateScrollerSize(), this.viewport = new u(this, this.scroller.getViewportBoundlingRect()), this.renderSheet(); + } + makeConfigFromData(t, e) { + const s = t.length - 1, l = t[0] ? t[0].length : 0, n = []; + for (let h = 0; h < s; h++) + n.push(new g({ + height: 40, + title: String(h) + })); + const i = []; + for (let h = 0; h < l; h++) + i.push(new m({ + width: 150, + title: String(h) + })); + return new d({ + view: e, + rows: n, + columns: i + }); + } +} +const C = new z("#spreadsheet", { + view: { + height: 768, + width: 1366 + } +}), F = f(45, 45, !0); +C.changeCellValues({ column: 2, row: 2 }, { + displayValue: "Loading...", + resultValue: "Loading...", + value: "Loading..." +}); +setTimeout(() => { + C.loadData(F); +}, 2e3); +export { + z as Spreadsheet +}; diff --git a/dist/manifest.json b/dist/manifest.json new file mode 100644 index 0000000..8413795 --- /dev/null +++ b/dist/manifest.json @@ -0,0 +1,11 @@ +{ + "src/main.ts": { + "file": "main.js", + "isEntry": true, + "src": "src/main.ts" + }, + "style.css": { + "file": "style.css", + "src": "style.css" + } +} \ No newline at end of file diff --git a/dist/modules/cache.d.ts b/dist/modules/cache.d.ts new file mode 100644 index 0000000..8c82e48 --- /dev/null +++ b/dist/modules/cache.d.ts @@ -0,0 +1,29 @@ +export interface CachedColumnProperties { + xPos: number; + colIdx: number; +} +export declare class CachedColumn { + xPos: number; + colIdx: number; + constructor(props: CachedColumnProperties); +} +export interface CachedRowProperties { + yPos: number; + rowIdx: number; +} +export declare class CachedRow { + yPos: number; + rowIdx: number; + constructor(props: CachedRowProperties); +} +export interface CacheConstructorProps { + columns: CachedColumn[]; + rows: CachedRow[]; +} +export declare class Cache { + columns: CachedColumn[]; + rows: CachedRow[]; + constructor(initial: CacheConstructorProps); + getRowByYCoord(y: number): number; + getColumnByXCoord(x: number): number; +} diff --git a/dist/modules/cell.d.ts b/dist/modules/cell.d.ts new file mode 100644 index 0000000..255dddd --- /dev/null +++ b/dist/modules/cell.d.ts @@ -0,0 +1,42 @@ +import { Spreadsheet } from "../main"; +export type CellConstructorProps = { + value: string; + displayValue: string; + resultValue: string; + position: Position; +}; +interface CellStylesConstructorProps { + fontSize: number; + fontColor: string; + background: string; + borderColor: string; + selectedBackground: string; + selectedFontColor: string; +} +export declare class CellStyles { + fontSize: number; + fontColor: string; + background: string; + borderColor: string; + selectedBackground: string; + selectedFontColor: string; + constructor(props?: CellStylesConstructorProps); +} +export declare class Position { + row: number; + column: number; + constructor(row: number, column: number); +} +export declare class Cell { + value: string; + displayValue: string; + /** This refers to the values ​​​​that were obtained by calculations, for example, after calculating the formula */ + resultValue: string; + position: Position; + style: CellStyles; + constructor(props: CellConstructorProps); + changeValues(values: Partial>): void; + private isCellInRange; + render(root: Spreadsheet): void; +} +export {}; diff --git a/dist/modules/column.d.ts b/dist/modules/column.d.ts new file mode 100644 index 0000000..6cc7ac9 --- /dev/null +++ b/dist/modules/column.d.ts @@ -0,0 +1,9 @@ +export type ColumnConstructorProperties = { + width: number; + title: string; +}; +export declare class Column { + width: number; + title: string; + constructor(props: ColumnConstructorProperties); +} diff --git a/dist/modules/config.d.ts b/dist/modules/config.d.ts new file mode 100644 index 0000000..0384c7b --- /dev/null +++ b/dist/modules/config.d.ts @@ -0,0 +1,27 @@ +import { Column } from "./column"; +import { Row } from "./row"; +export interface ViewProperties { + width: number; + height: number; +} +export type ConfigProperties = { + /** Please, end it with '_' symbol. + * + * *Example:* + * + * 'test_' + * 'google_' */ + rows: Row[]; + columns: Column[]; + view: ViewProperties; +}; +export type SheetConfigConstructorProps = { + rows: Row[]; + columns: Column[]; +}; +export declare class Config { + rows: Row[]; + columns: Column[]; + view: ViewProperties; + constructor(props: ConfigProperties); +} diff --git a/dist/modules/renderBox.d.ts b/dist/modules/renderBox.d.ts new file mode 100644 index 0000000..ba0eef1 --- /dev/null +++ b/dist/modules/renderBox.d.ts @@ -0,0 +1,11 @@ +import { Position } from "./cell"; +import { Config } from "./config"; +export declare class RenderBox { + x: number; + y: number; + width: number; + height: number; + constructor(config: Config, cellPosition: Position); + private getXCoord; + private getYCoord; +} diff --git a/dist/modules/row.d.ts b/dist/modules/row.d.ts new file mode 100644 index 0000000..7c860e7 --- /dev/null +++ b/dist/modules/row.d.ts @@ -0,0 +1,9 @@ +export type RowConstructorProps = { + height: number; + title: string; +}; +export declare class Row { + height: number; + title: string; + constructor(props: RowConstructorProps); +} diff --git a/dist/modules/selection.d.ts b/dist/modules/selection.d.ts new file mode 100644 index 0000000..f76c2e5 --- /dev/null +++ b/dist/modules/selection.d.ts @@ -0,0 +1,12 @@ +export type BaseSelectionType = { + row: number; + column: number; +}; +export type RangeSelectionType = { + from: BaseSelectionType; + to: BaseSelectionType; +}; +export declare class Selection { + selectedCell: BaseSelectionType | null; + selectedRange: RangeSelectionType | null; +} diff --git a/dist/modules/styles.d.ts b/dist/modules/styles.d.ts new file mode 100644 index 0000000..5fdd30e --- /dev/null +++ b/dist/modules/styles.d.ts @@ -0,0 +1,2 @@ +export declare class Styles { +} diff --git a/dist/modules/viewport.d.ts b/dist/modules/viewport.d.ts new file mode 100644 index 0000000..176bb21 --- /dev/null +++ b/dist/modules/viewport.d.ts @@ -0,0 +1,25 @@ +import { Spreadsheet } from "../main"; +export type ViewportConstructorProps = { + top: number; + left: number; + right: number; + bottom: number; +}; +export declare class Viewport { + root: Spreadsheet; + top: number; + left: number; + right: number; + bottom: number; + firstRow: number; + lastRow: number; + firstCol: number; + lastCol: number; + constructor(root: Spreadsheet, props: ViewportConstructorProps); + updateValues(props: ViewportConstructorProps): void; + /** Get index of first row in viewport */ + private getFirstRow; + private getLastRow; + private getFirstCol; + private getLastCol; +} diff --git a/dist/style.css b/dist/style.css new file mode 100644 index 0000000..0ad91bb --- /dev/null +++ b/dist/style.css @@ -0,0 +1 @@ +body{padding:0;margin:0}.content{position:absolute;top:0;left:0}.spreadsheet_container{position:relative;isolation:isolate;border:2px solid black}.sheet{display:block;contain:strict}.scroller{overflow:scroll;box-sizing:border-box;transform:translateZ(0)}.scroller:focus{outline:none}.editor{position:absolute;box-sizing:border-box;font-size:16px;font-family:Arial,Helvetica,sans-serif}.hide{visibility:hidden} diff --git a/dist/utils/createData.d.ts b/dist/utils/createData.d.ts new file mode 100644 index 0000000..cf92052 --- /dev/null +++ b/dist/utils/createData.d.ts @@ -0,0 +1,10 @@ +import { Cell } from "../modules/cell"; +import { Config } from "../modules/config"; +export declare function createSampleData(rows: number, columns: number, fillCellsByCoords?: boolean): Cell[][]; +export declare function createSampleConfig(rows: number, columns: number): Config; +type SpreadsheetConfigAndDataReturnType = { + config: Config; + data: Cell[][]; +}; +export declare function makeSpreadsheetConfigAndData(rows: number, columns: number): SpreadsheetConfigAndDataReturnType; +export {}; diff --git a/dist/vite.svg b/dist/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/dist/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package.json b/package.json index a6db371..80ad48a 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,39 @@ { "name": "spreadsheet", "private": true, - "version": "0.0.0", + "version": "0.0.1", + "exports": { + ".": { + "import": "./dist/main.js", + "require": "./dist/main.cjs.js" + } + }, + "main": "./dist/main.cjs.js", + "module": "./dist/main.es.js", + "typings": "./dist/main.d.ts", + "files": [ + "dist" + ], "type": "module", "homepage": "https://yazmeyaa.github.io/spreadsheet_2", "scripts": { "dev": "vite", "build": "tsc && vite build", + "build:watch": "tsc && vite build --watch", "preview": "vite preview", "predeploy": "npm run dist", "deploy": "gh-pages -d dist" }, "devDependencies": { + "@rollup/plugin-typescript": "^11.1.2", "gh-pages": "^5.0.0", + "rollup-plugin-typescript-paths": "^1.4.0", + "tslib": "^2.6.0", "typescript": "^5.0.2", "vite": "^4.4.0" }, "dependencies": { + "@types/node": "^20.4.4", "sass": "^1.63.6" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d716b3..d98e076 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,20 +5,32 @@ settings: excludeLinksFromLockfile: false dependencies: + '@types/node': + specifier: ^20.4.4 + version: 20.4.4 sass: specifier: ^1.63.6 version: 1.63.6 devDependencies: + '@rollup/plugin-typescript': + specifier: ^11.1.2 + version: 11.1.2(tslib@2.6.0)(typescript@5.0.2) gh-pages: specifier: ^5.0.0 version: 5.0.0 + rollup-plugin-typescript-paths: + specifier: ^1.4.0 + version: 1.4.0(typescript@5.0.2) + tslib: + specifier: ^2.6.0 + version: 2.6.0 typescript: specifier: ^5.0.2 version: 5.0.2 vite: specifier: ^4.4.0 - version: 4.4.0(sass@1.63.6) + version: 4.4.0(@types/node@20.4.4)(sass@1.63.6) packages: @@ -220,6 +232,46 @@ packages: dev: true optional: true + /@rollup/plugin-typescript@11.1.2(tslib@2.6.0)(typescript@5.0.2): + resolution: {integrity: sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.14.0||^3.0.0 + tslib: '*' + typescript: '>=3.7.0' + peerDependenciesMeta: + rollup: + optional: true + tslib: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2 + resolve: 1.22.2 + tslib: 2.6.0 + typescript: 5.0.2 + dev: true + + /@rollup/pluginutils@5.0.2: + resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.1 + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: true + + /@types/estree@1.0.1: + resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + dev: true + + /@types/node@20.4.4: + resolution: {integrity: sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew==} + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -329,6 +381,10 @@ packages: engines: {node: '>=0.8.0'} dev: true + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + /filename-reserved-regex@2.0.0: resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} engines: {node: '>=4'} @@ -386,6 +442,10 @@ packages: requiresBuild: true optional: true + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + /gh-pages@5.0.0: resolution: {integrity: sha512-Nqp1SjkPIB94Xw/3yYNTUL+G2dxlhjvv1zeN/4kMC1jfViTEqhtVz/Ba1zSXHuvXCN9ADNS1dN4r5/J/nZWEQQ==} engines: {node: '>=10'} @@ -432,6 +492,13 @@ packages: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: true + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + /immutable@4.3.1: resolution: {integrity: sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A==} @@ -452,6 +519,12 @@ packages: dependencies: binary-extensions: 2.2.0 + /is-core-module@2.12.1: + resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} + dependencies: + has: 1.0.3 + dev: true + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -542,6 +615,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -589,6 +666,23 @@ packages: dependencies: picomatch: 2.3.1 + /resolve@1.22.2: + resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + hasBin: true + dependencies: + is-core-module: 2.12.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /rollup-plugin-typescript-paths@1.4.0(typescript@5.0.2): + resolution: {integrity: sha512-6EgeLRjTVmymftEyCuYu91XzY5XMB5lR0YrJkeT0D7OG2RGSdbNL+C/hfPIdc/sjMa9Sl5NLsxIr6C/+/5EUpA==} + peerDependencies: + typescript: '>=3.4' + dependencies: + typescript: 5.0.2 + dev: true + /rollup@3.26.3: resolution: {integrity: sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -622,6 +716,11 @@ packages: escape-string-regexp: 1.0.5 dev: true + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -635,6 +734,10 @@ packages: escape-string-regexp: 1.0.5 dev: true + /tslib@2.6.0: + resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} + dev: true + /typescript@5.0.2: resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==} engines: {node: '>=12.20'} @@ -646,7 +749,7 @@ packages: engines: {node: '>= 4.0.0'} dev: true - /vite@4.4.0(sass@1.63.6): + /vite@4.4.0(@types/node@20.4.4)(sass@1.63.6): resolution: {integrity: sha512-Wf+DCEjuM8aGavEYiF77hnbxEZ+0+/jC9nABR46sh5Xi+GYeSvkeEFRiVuI3x+tPjxgZeS91h1jTAQTPFgePpA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -674,6 +777,7 @@ packages: terser: optional: true dependencies: + '@types/node': 20.4.4 esbuild: 0.18.14 postcss: 8.4.26 rollup: 3.26.3 diff --git a/src/main.ts b/src/main.ts index 83d0ff9..517e8e7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,8 @@ import { Viewport } from "./modules/viewport"; import './scss/main.scss' import { createSampleConfig, createSampleData } from "./utils/createData"; import { Cache, CachedColumn, CachedRow } from "./modules/cache"; +import { Row } from "./modules/row"; +import { Column } from "./modules/column"; /* ! Component structure @@ -57,11 +59,11 @@ export class Spreadsheet { this.scroller = new Scroller(this) this.toolbar = new Toolbar(this) this.header = new Header(this) - this.editor = new Editor(this) + this.editor = new Editor(this) this.cache = this.getInitialCache() this.viewport = new Viewport(this, this.scroller.getViewportBoundlingRect()) this.selection = new Selection() - + this.data = data this.styles = new Styles() @@ -73,7 +75,7 @@ export class Spreadsheet { private getInitialCache(): Cache { const cachedCols: CachedColumn[] = [] let currentWidth = 0 - for(let i = 0; i <= this.config.columns.length - 1; i++) { + for (let i = 0; i <= this.config.columns.length - 1; i++) { const col = this.config.columns[i] currentWidth += col.width const cacheCol = new CachedColumn({ @@ -85,7 +87,7 @@ export class Spreadsheet { const cachedRows: CachedRow[] = [] let currentHeight = 0 - for(let i = 0; i <= this.config.rows.length - 1; i++) { + for (let i = 0; i <= this.config.rows.length - 1; i++) { const row = this.config.rows[i] currentHeight += row.height const cacheRow = new CachedRow({ @@ -95,12 +97,12 @@ export class Spreadsheet { cachedRows.push(cacheRow) } - + const cache = new Cache({ columns: cachedCols, rows: cachedRows }) - + console.log("CACHE: ", cache) console.log("CONFIG: ", this.config) return cache @@ -206,14 +208,62 @@ export class Spreadsheet { renderCell(row: number, col: number) { this.data[row][col].render(this) } + + public loadData(data: Cell[][]): void { + this.data = data + this.config = this.makeConfigFromData(data, this.config.view) + this.cache = this.getInitialCache() + this.scroller.updateScrollerSize() + this.viewport = new Viewport(this, this.scroller.getViewportBoundlingRect()) + this.renderSheet() + } + + private makeConfigFromData(data: Cell[][], view: ViewProperties): Config { + const lastRowIdx = data.length - 1 + const lastColIdx = data[0] ? data[0].length : 0 + + const rows: Row[] = [] + for (let row = 0; row < lastRowIdx; row++) { + rows.push(new Row({ + height: 40, + title: String(row) + })) + } + + const columns: Column[] = [] + + for (let col = 0; col < lastColIdx; col++) { + columns.push(new Column({ + width: 150, + title: String(col) + })) + } + + const config = new Config({ + view, + rows, + columns + }) + + return config + } } - - const spreadsheet = new Spreadsheet('#spreadsheet', { view: { height: 768, width: 1366 }, }) -console.log(spreadsheet) + +const data = createSampleData(45, 45, true) + +spreadsheet.changeCellValues({column: 2, row: 2}, { + displayValue: 'Loading...', + resultValue: 'Loading...', + value: 'Loading...' +}) + +setTimeout(() => { + spreadsheet.loadData(data) +}, 2000) \ No newline at end of file diff --git a/src/utils/createData.ts b/src/utils/createData.ts index 3268dc5..c7eccff 100644 --- a/src/utils/createData.ts +++ b/src/utils/createData.ts @@ -3,13 +3,13 @@ import { Column } from "../modules/column"; import { Config } from "../modules/config"; import { Row } from "../modules/row"; -export function createSampleData(rows: number, columns: number): Cell[][] { +export function createSampleData(rows: number, columns: number, fillCellsByCoords: boolean = false): Cell[][] { const data: Cell[][] = [] for (let row = 0; row <= rows; row++) { const innerRow: Cell[] = [] for (let col = 0; col <= columns; col++) { - const value = `${row}:${col}` + const value = fillCellsByCoords ? `${row}:${col}` : '' const cell = new Cell({ displayValue: value, @@ -31,7 +31,7 @@ export function createSampleData(rows: number, columns: number): Cell[][] { export function createSampleConfig(rows: number, columns: number): Config { const rowsArr: Row[] = [] - for(let i = 0; i <= rows; i++) { + for (let i = 0; i <= rows; i++) { const rowItem = new Row({ height: 40, title: String(i) @@ -40,7 +40,7 @@ export function createSampleConfig(rows: number, columns: number): Config { } const colsArr: Column[] = [] - for(let i = 0; i <= columns; i++) { + for (let i = 0; i <= columns; i++) { const colItem = new Column({ title: String(i), width: 150 @@ -69,5 +69,5 @@ export function makeSpreadsheetConfigAndData(rows: number, columns: number): Spr const data = createSampleData(rows, columns) const config = createSampleConfig(rows, columns) - return {data, config} + return { data, config } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 75abdef..376766e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,30 @@ { "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "target": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "types": ["vite/client", "node"], + "allowJs": false, "skipLibCheck": true, - - /* Bundler mode */ + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", "moduleResolution": "bundler", - "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, + "useDefineForClassFields": true, + + /* Bundler mode */ + "allowImportingTsExtensions": true, + /* Linting */ - "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["node_modules"] } diff --git a/vite.config.ts b/vite.config.ts index f213d1f..6149bda 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,37 @@ -import {defineConfig} from 'vite' +import { defineConfig } from 'vite' +import path from 'path' +import typescript from "@rollup/plugin-typescript"; +import { typescriptPaths } from "rollup-plugin-typescript-paths"; + export default defineConfig({ - base: '/spreadsheet_2/' + base: '/spreadsheet_2/', + plugins: [], + resolve: {}, + server: { + port: 3000 + }, + build: { + manifest: true, + minify: true, + reportCompressedSize: true, + lib: { + entry: path.resolve(__dirname, "src/main.ts"), + fileName: "main", + formats: ["es", "cjs"], + }, + rollupOptions: { + external: [], + plugins: [ + typescriptPaths({ + preserveExtensions: true + }), + typescript({ + sourceMap: false, + declaration: true, + outDir: 'dist' + }) + ] + } + } }) \ No newline at end of file