Compare commits
No commits in common. "main" and "clipboard" have entirely different histories.
14
README.md
14
README.md
|
|
@ -1,15 +1,9 @@
|
|||
# Modern Spreadsheet
|
||||
|
||||
<img src="https://raw.githubusercontent.com/yazmeyaa/modern_spreadsheet/6dc20f92e769210c076600c7fcfacd4ed528f085/repo_assets/spreadsheet_preview.png?raw=true" alt="spreadsheet_preview">
|
||||
|
||||
## Features:
|
||||
- High performance spreadsheet based on CanvasAPI.
|
||||
- TypeScript supported
|
||||
- Native scrolling
|
||||
- Customizable
|
||||
- Copy & Paste support
|
||||
|
||||
### Basic usage
|
||||
## Basic usage
|
||||
|
||||
```ts
|
||||
import Spreadsheet from "modern_spreadsheet";
|
||||
|
|
@ -20,7 +14,7 @@ const sheet = new Spreadsheet(target);
|
|||
//...
|
||||
```
|
||||
|
||||
### Save and load data
|
||||
## Save and load data
|
||||
|
||||
```ts
|
||||
function saveData() {
|
||||
|
|
@ -36,7 +30,7 @@ function loadData() {
|
|||
}
|
||||
```
|
||||
|
||||
#### Supported events
|
||||
## Supported events
|
||||
- onCellClick
|
||||
- onSelectionChange
|
||||
- onCellChange
|
||||
|
|
@ -64,7 +58,7 @@ const options: SpreadsheetConstructorProperties = {
|
|||
const sheet = new Spreadsheet("#spreadsheet", options);
|
||||
```
|
||||
|
||||
### Roadmap
|
||||
## Roadmap
|
||||
|
||||
- ~~Rows number and columns heading render~~
|
||||
- ~~Custom event functions (ex.: onSelectionChange, onCellEdit...). Full list of supported events will available on this page~~
|
||||
|
|
|
|||
|
|
@ -10,8 +10,5 @@ export declare class Sheet {
|
|||
constructor(root: Spreadsheet);
|
||||
getCellByCoords(x: number, y: number): Position;
|
||||
renderCell(position: Position): void;
|
||||
private getSelectionRange;
|
||||
private renderSelectionRange;
|
||||
renderSelection(): void;
|
||||
renderSheet(): void;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -62,7 +62,6 @@ export default class Spreadsheet {
|
|||
deleteSelectedCellsValues(): void;
|
||||
showEditor(position: Position, initialString?: string): void;
|
||||
renderSheet(): void;
|
||||
renderSelection(): void;
|
||||
renderColumnsBar(): void;
|
||||
renderRowsBar(): void;
|
||||
renderCell(row: number, col: number): void;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
var I = Object.defineProperty;
|
||||
var A = (r, t, e) => t in r ? I(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
|
||||
var s = (r, t, e) => (A(r, typeof t != "symbol" ? t + "" : t, e), e);
|
||||
var u;
|
||||
var d;
|
||||
(function(r) {
|
||||
r.CELL_CLICK = "CELL_CLICK", r.SELECTION_CHANGE = "CHANGE_SELECTION", r.CELL_CHANGE = "CELL_CHANGE", r.COPY_CELLS = "COPY_CELLS";
|
||||
})(u || (u = {}));
|
||||
})(d || (d = {}));
|
||||
class P {
|
||||
constructor(t) {
|
||||
s(this, "root");
|
||||
|
|
@ -12,7 +12,7 @@ class P {
|
|||
var c, a;
|
||||
if (t.button !== 0)
|
||||
return;
|
||||
const { offsetX: o, offsetY: l } = t, i = this.root.getCellByCoords(o, l), n = this.root.getCell(i), h = new S();
|
||||
const { offsetX: o, offsetY: l } = t, i = this.root.getCellByCoords(o, l), n = this.root.getCell(i), h = new v();
|
||||
h.selectedCell = i, h.selectedRange = {
|
||||
from: i,
|
||||
to: i
|
||||
|
|
@ -30,22 +30,22 @@ class P {
|
|||
}
|
||||
dispatch(t) {
|
||||
switch (t.type) {
|
||||
case u.CELL_CLICK: {
|
||||
case d.CELL_CLICK: {
|
||||
const { event: e, scroller: o } = t;
|
||||
this.cellClick(e, o);
|
||||
break;
|
||||
}
|
||||
case u.SELECTION_CHANGE: {
|
||||
case d.SELECTION_CHANGE: {
|
||||
const { selection: e, enableCallback: o } = t;
|
||||
this.changeSelection(e, o);
|
||||
break;
|
||||
}
|
||||
case u.CELL_CHANGE: {
|
||||
case d.CELL_CHANGE: {
|
||||
const { cell: e, enableCallback: o } = t;
|
||||
this.changeCellValues(e, o);
|
||||
break;
|
||||
}
|
||||
case u.COPY_CELLS: {
|
||||
case d.COPY_CELLS: {
|
||||
const { data: e, dataAsString: o, range: l } = t;
|
||||
this.copy(l, e, o);
|
||||
break;
|
||||
|
|
@ -57,7 +57,7 @@ class P {
|
|||
e && ((l = (o = this.root.config).onCellChange) == null || l.call(o, t));
|
||||
}
|
||||
}
|
||||
class g {
|
||||
class y {
|
||||
constructor(t, e) {
|
||||
s(this, "x");
|
||||
s(this, "y");
|
||||
|
|
@ -96,9 +96,9 @@ class T {
|
|||
value: this.element.value,
|
||||
displayValue: this.element.value
|
||||
}), this.root.events.dispatch({
|
||||
type: u.CELL_CHANGE,
|
||||
type: d.CELL_CHANGE,
|
||||
cell: this.root.getCell(this.root.selection.selectedCell)
|
||||
}), this.hide(), this.root.renderSelection();
|
||||
}), this.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -108,17 +108,17 @@ class T {
|
|||
});
|
||||
this.root = t;
|
||||
const e = document.createElement("input");
|
||||
e.classList.add(C + "editor"), this.element = e, this.hide();
|
||||
e.classList.add(m + "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, e) {
|
||||
const { height: o, width: l, x: i, y: n } = new g(this.root.config, t), h = this.root.getCell(t);
|
||||
const { height: o, width: l, x: i, y: n } = new y(this.root.config, t), h = this.root.getCell(t);
|
||||
this.element.classList.remove("hide"), this.element.style.top = n - this.root.viewport.top + this.root.columnsBarHeight + "px", this.element.style.left = i - this.root.viewport.left + this.root.rowsBarWidth + "px", this.element.style.width = l + "px", this.element.style.height = o + "px", this.element.style.display = "block", window.addEventListener("click", this.handleClickOutside), this.element.addEventListener("keydown", this.handleKeydown), this.element.value = e || h.value, this.element.focus(), e || this.element.select();
|
||||
}
|
||||
}
|
||||
function v(r, t) {
|
||||
function b(r, t) {
|
||||
return r.column === t.column && r.row === t.row;
|
||||
}
|
||||
class H {
|
||||
|
|
@ -133,8 +133,8 @@ class H {
|
|||
return;
|
||||
const { offsetX: e, offsetY: o } = t, l = this.root.getCellByCoords(e, o);
|
||||
let i = !1;
|
||||
this.root.selection.selectedRange && (i = !v(this.root.selection.selectedRange.to, l), i && (this.root.selection.selectedRange.to = l, this.root.events.dispatch({
|
||||
type: u.SELECTION_CHANGE,
|
||||
this.root.selection.selectedRange && (i = !b(this.root.selection.selectedRange.to, l), i && (this.root.selection.selectedRange.to = l, this.root.events.dispatch({
|
||||
type: d.SELECTION_CHANGE,
|
||||
selection: this.root.selection,
|
||||
enableCallback: !0
|
||||
})));
|
||||
|
|
@ -142,8 +142,8 @@ class H {
|
|||
s(this, "handleMouseUp", () => {
|
||||
this.isSelecting = !1;
|
||||
const t = { ...this.root.selection };
|
||||
this.root.selection.selectedRange && v(this.root.selection.selectedRange.from, this.root.selection.selectedRange.to) && (t.selectedRange = null, this.root.events.dispatch({
|
||||
type: u.SELECTION_CHANGE,
|
||||
this.root.selection.selectedRange && b(this.root.selection.selectedRange.from, this.root.selection.selectedRange.to) && (t.selectedRange = null, this.root.events.dispatch({
|
||||
type: d.SELECTION_CHANGE,
|
||||
selection: t,
|
||||
enableCallback: !1
|
||||
})), this.root.renderSheet(), this.root.renderColumnsBar(), this.root.renderRowsBar();
|
||||
|
|
@ -174,7 +174,7 @@ class H {
|
|||
}
|
||||
}
|
||||
this.root.events.dispatch({
|
||||
type: u.SELECTION_CHANGE,
|
||||
type: d.SELECTION_CHANGE,
|
||||
selection: this.root.selection,
|
||||
enableCallback: !0
|
||||
});
|
||||
|
|
@ -189,9 +189,9 @@ class H {
|
|||
}
|
||||
}
|
||||
if (t.key === "Delete" && (t.preventDefault(), this.root.deleteSelectedCellsValues(), this.root.renderSheet()), t.metaKey || t.ctrlKey) {
|
||||
if (t.code === "KeyC") {
|
||||
if (console.log(t.code), t.code === "KeyC") {
|
||||
let o;
|
||||
const l = new S();
|
||||
const l = new v();
|
||||
if (this.root.selection.selectedRange) {
|
||||
const { from: i, to: n } = this.root.selection.selectedRange;
|
||||
l.selectedRange = this.root.selection.selectedRange, o = [...this.root.data.slice(i.row, n.row + 1).map((a) => a.slice(i.column, n.column + 1))];
|
||||
|
|
@ -211,7 +211,7 @@ class H {
|
|||
});
|
||||
s(this, "handleClick", (t) => {
|
||||
this.root.events.dispatch({
|
||||
type: u.CELL_CLICK,
|
||||
type: d.CELL_CLICK,
|
||||
event: t,
|
||||
scroller: this
|
||||
});
|
||||
|
|
@ -240,7 +240,7 @@ class H {
|
|||
}
|
||||
buildComponent() {
|
||||
const t = document.createElement("div"), e = document.createElement("div"), o = document.createElement("div"), l = document.createElement("div"), i = document.createElement("div");
|
||||
return e.style.width = "0px", e.style.pointerEvents = "none", o.style.pointerEvents = "none", l.style.display = "flex", i.appendChild(e), i.appendChild(o), l.appendChild(i), this.verticalScroller = e, this.horizontalScroller = o, t.appendChild(l), t.contentEditable = "false", t.classList.add(C + "scroller"), { scroller: t, verticalScroller: e, horizontalScroller: o };
|
||||
return e.style.width = "0px", e.style.pointerEvents = "none", o.style.pointerEvents = "none", l.style.display = "flex", i.appendChild(e), i.appendChild(o), l.appendChild(i), this.verticalScroller = e, this.horizontalScroller = o, t.appendChild(l), t.contentEditable = "false", t.classList.add(m + "scroller"), { scroller: t, verticalScroller: e, horizontalScroller: o };
|
||||
}
|
||||
getActualHeight() {
|
||||
return this.root.config.rows.reduce((t, e) => (t += e.height, t), 0);
|
||||
|
|
@ -287,7 +287,7 @@ class _ {
|
|||
this.value = t.value, this.displayValue = t.displayValue, this.resultValue = t.resultValue, this.position = t.position, this.style = t.style;
|
||||
}
|
||||
}
|
||||
class p {
|
||||
class x {
|
||||
constructor(t) {
|
||||
/** True value (data) */
|
||||
s(this, "value");
|
||||
|
|
@ -314,25 +314,21 @@ class p {
|
|||
changeValues(t) {
|
||||
Object.assign(this, t);
|
||||
}
|
||||
// private isCellInRange(root: Spreadsheet): boolean {
|
||||
// const { column, row } = this.position;
|
||||
// const { selectedRange } = root.selection;
|
||||
// if (!selectedRange) return false;
|
||||
// const isCellInRow =
|
||||
// row >= Math.min(selectedRange.from.row, selectedRange.to.row) &&
|
||||
// row <= Math.max(selectedRange.to.row, selectedRange.from.row);
|
||||
// const isCellInCol =
|
||||
// column >= Math.min(selectedRange.from.column, selectedRange.to.column) &&
|
||||
// column <= Math.max(selectedRange.to.column, selectedRange.from.column);
|
||||
// return isCellInCol && isCellInRow;
|
||||
// }
|
||||
isCellInRange(t) {
|
||||
const { column: e, row: o } = this.position, { selectedRange: l } = t.selection;
|
||||
if (!l)
|
||||
return !1;
|
||||
const i = o >= Math.min(l.from.row, l.to.row) && o <= 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) && i;
|
||||
}
|
||||
render(t) {
|
||||
const e = new g(t.config, this.position);
|
||||
var C;
|
||||
const e = new y(t.config, this.position);
|
||||
let { x: o, y: l } = e;
|
||||
const { height: i, width: n } = e, { ctx: h } = t;
|
||||
const { height: i, width: n } = e, { ctx: h } = t, c = ((C = t.selection.selectedCell) == null ? void 0 : C.row) === this.position.row && t.selection.selectedCell.column === this.position.column, a = this.isCellInRange(t);
|
||||
l -= t.viewport.top, o -= t.viewport.left;
|
||||
const c = this.style ?? t.styles.cells;
|
||||
h.clearRect(o, l, n, i), h.fillStyle = c.background, h.strokeStyle = "black", h.fillRect(o, l, n - 1, i - 1), h.strokeRect(o, l, n, i), h.fillStyle = c.fontColor, h.textAlign = "left", h.font = `${c.fontSize}px Arial`, h.textBaseline = "middle", h.fillText(this.displayValue, o + 2, l + i / 2);
|
||||
const u = this.style ?? t.styles.cells;
|
||||
h.clearRect(o, l, n, i), h.fillStyle = c || a ? u.selectedBackground : u.background, h.strokeStyle = "black", h.fillRect(o, l, n - 1, i - 1), h.strokeRect(o, l, n, i), h.fillStyle = c || a ? u.selectedFontColor : u.fontColor, h.textAlign = "left", h.font = `${u.fontSize}px Arial`, h.textBaseline = "middle", h.fillText(this.displayValue, o + 2, l + i / 2);
|
||||
}
|
||||
}
|
||||
class D {
|
||||
|
|
@ -342,7 +338,7 @@ class D {
|
|||
s(this, "root");
|
||||
this.root = t;
|
||||
const e = document.createElement("canvas");
|
||||
e.classList.add(C + "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", e.style.left = "0px", this.element = e;
|
||||
e.classList.add(m + "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", e.style.left = "0px", this.element = e;
|
||||
const o = this.element.getContext("2d");
|
||||
if (!o)
|
||||
throw new Error("Enable hardware acceleration");
|
||||
|
|
@ -361,45 +357,11 @@ class D {
|
|||
const { column: e, row: o } = t;
|
||||
this.root.data[o][e].render(this.root);
|
||||
}
|
||||
getSelectionRange() {
|
||||
const { selectedCell: t, selectedRange: e } = this.root.selection;
|
||||
if (!(!t && !e)) {
|
||||
if (e) {
|
||||
const o = Math.min(e.from.row, e.to.row), l = Math.min(e.from.column, e.to.column), i = Math.max(e.from.row, e.to.row), n = Math.max(e.from.column, e.to.column), h = new g(this.root.config, {
|
||||
row: o,
|
||||
column: l
|
||||
});
|
||||
let c = 0;
|
||||
for (let d = l; d <= n; d++)
|
||||
c += this.root.config.columns[d].width;
|
||||
let a = 0;
|
||||
for (let d = o; d <= i; d++)
|
||||
a += this.root.config.rows[d].height;
|
||||
const f = h.x - this.root.viewport.left, m = h.y - this.root.viewport.top;
|
||||
return { x: f, y: m, height: a, width: c };
|
||||
}
|
||||
if (!e && t) {
|
||||
const o = new g(this.root.config, t);
|
||||
return o.x -= this.root.viewport.left, o.y -= this.root.viewport.top, o;
|
||||
}
|
||||
}
|
||||
}
|
||||
renderSelectionRange(t, e, o, l) {
|
||||
this.ctx.save(), this.ctx.strokeStyle = "#7da8ff", this.ctx.lineWidth = 3, this.ctx.strokeRect(t, e, o, l), this.ctx.fillStyle = "#7da8ff35", this.ctx.fillRect(t, e, o, l), this.ctx.restore();
|
||||
}
|
||||
renderSelection() {
|
||||
const t = this.getSelectionRange();
|
||||
if (!t)
|
||||
return;
|
||||
const { height: e, width: o, x: l, y: i } = t;
|
||||
this.renderSelectionRange(l, i, o, e);
|
||||
}
|
||||
renderSheet() {
|
||||
const t = this.root.viewport.firstRow, e = this.root.viewport.lastCol + 3, o = this.root.viewport.lastRow + 3, l = this.root.viewport.firstCol;
|
||||
for (let i = t; i <= o; i++)
|
||||
for (let n = l; n <= e && !(!this.root.config.columns[n] || !this.root.config.rows[i]); n++)
|
||||
this.renderCell({ column: n, row: i });
|
||||
this.renderSelection();
|
||||
}
|
||||
}
|
||||
class z {
|
||||
|
|
@ -408,7 +370,7 @@ class z {
|
|||
s(this, "root");
|
||||
this.root = t;
|
||||
const e = document.createElement("div");
|
||||
e.classList.add(C + "spreadsheet_container"), this.element = e, this.changeElementSizes(this.root.viewProps);
|
||||
e.classList.add(m + "spreadsheet_container"), this.element = e, this.changeElementSizes(this.root.viewProps);
|
||||
}
|
||||
changeElementSizes(t) {
|
||||
const { height: e, width: o } = t;
|
||||
|
|
@ -422,10 +384,10 @@ class N {
|
|||
s(this, "height", 0);
|
||||
this.root = t;
|
||||
const e = document.createElement("div");
|
||||
e.classList.add(C + "toolbar"), this.element = e;
|
||||
e.classList.add(m + "toolbar"), this.element = e;
|
||||
}
|
||||
}
|
||||
class x {
|
||||
class p {
|
||||
constructor(t) {
|
||||
s(this, "rows");
|
||||
s(this, "columns");
|
||||
|
|
@ -440,7 +402,7 @@ class x {
|
|||
this.columns = t.columns, this.rows = t.rows, this.view = t.view, this.onCellClick = t.onCellClick ?? null, this.onSelectonChange = t.onSelectionChange ?? null, this.onCellChange = t.onCellChange ?? null, this.onCopy = t.onCopy ?? null;
|
||||
}
|
||||
}
|
||||
class S {
|
||||
class v {
|
||||
constructor() {
|
||||
s(this, "selectedCell", null);
|
||||
s(this, "selectedRange", null);
|
||||
|
|
@ -452,7 +414,7 @@ class O {
|
|||
this.cells = new R();
|
||||
}
|
||||
}
|
||||
class b {
|
||||
class S {
|
||||
constructor(t, e) {
|
||||
s(this, "root");
|
||||
s(this, "top");
|
||||
|
|
@ -505,12 +467,12 @@ class k {
|
|||
this.height = t.height, this.title = t.title;
|
||||
}
|
||||
}
|
||||
function L(r, t, e = !1) {
|
||||
function B(r, t, e = !1) {
|
||||
const o = [];
|
||||
for (let l = 0; l <= r; l++) {
|
||||
const i = [];
|
||||
for (let n = 0; n <= t; n++) {
|
||||
const h = e ? `${l}:${n}` : "", c = new p({
|
||||
const h = e ? `${l}:${n}` : "", c = new x({
|
||||
displayValue: h,
|
||||
resultValue: h,
|
||||
value: h,
|
||||
|
|
@ -543,7 +505,7 @@ function K(r, t) {
|
|||
});
|
||||
o.push(n);
|
||||
}
|
||||
return new x({
|
||||
return new p({
|
||||
columns: o,
|
||||
rows: e,
|
||||
view: {
|
||||
|
|
@ -553,17 +515,17 @@ function K(r, t) {
|
|||
});
|
||||
}
|
||||
function U(r, t) {
|
||||
const e = L(r, t), o = K(r, t);
|
||||
const e = B(r, t), o = K(r, t);
|
||||
return { data: e, config: o };
|
||||
}
|
||||
class W {
|
||||
class F {
|
||||
constructor(t) {
|
||||
s(this, "xPos");
|
||||
s(this, "colIdx");
|
||||
this.xPos = t.xPos, this.colIdx = t.colIdx;
|
||||
}
|
||||
}
|
||||
class F {
|
||||
class W {
|
||||
constructor(t) {
|
||||
s(this, "yPos");
|
||||
s(this, "rowIdx");
|
||||
|
|
@ -578,14 +540,20 @@ class Y {
|
|||
}
|
||||
getRowByYCoord(t) {
|
||||
let e = 0;
|
||||
for (let o = 0; o < this.rows.length && (e = o, !(t <= this.rows[o].yPos)); o++)
|
||||
;
|
||||
for (let o = 0; o < this.rows.length; o++)
|
||||
if (t <= this.rows[o].yPos) {
|
||||
e = o;
|
||||
break;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
getColumnByXCoord(t) {
|
||||
let e = 0;
|
||||
for (let o = 0; o < this.columns.length && (e = o, !(t <= this.columns[o].xPos)); o++)
|
||||
;
|
||||
for (let o = 0; o < this.columns.length; o++)
|
||||
if (t <= this.columns[o].xPos) {
|
||||
e = o;
|
||||
break;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
|
@ -628,12 +596,12 @@ class G {
|
|||
}
|
||||
renderRect(t, e) {
|
||||
const { width: o, x: l } = e, i = this.isColumnSelected(t);
|
||||
this.ctx.fillStyle = i ? "#c7ebff" : "white", this.ctx.strokeStyle = "black", this.ctx.lineWidth = 1;
|
||||
this.ctx.fillStyle = i ? this.root.styles.cells.selectedBackground : "white", this.ctx.strokeStyle = "black", this.ctx.lineWidth = 1;
|
||||
const n = l - this.root.viewport.left;
|
||||
this.ctx.fillRect(n - 1, 0, o, this.height), this.ctx.strokeRect(n - 1, 0, o, this.height);
|
||||
}
|
||||
renderSingleColumn(t) {
|
||||
const e = new g(this.root.config, {
|
||||
const e = new y(this.root.config, {
|
||||
row: 0,
|
||||
column: t
|
||||
});
|
||||
|
|
@ -677,12 +645,12 @@ class X {
|
|||
}
|
||||
renderRect(t, e) {
|
||||
const { y: o, height: l } = e, i = this.isRowSelected(t);
|
||||
this.ctx.fillStyle = i ? "#c7ebff" : "white", this.ctx.strokeStyle = "black", this.ctx.lineWidth = this.resizerHeight;
|
||||
this.ctx.fillStyle = i ? this.root.styles.cells.selectedBackground : "white", this.ctx.strokeStyle = "black", this.ctx.lineWidth = this.resizerHeight;
|
||||
const n = o - this.root.viewport.top;
|
||||
this.ctx.fillRect(0, n - 1, this.width, l), this.ctx.strokeRect(0, n - 1, this.width, l);
|
||||
}
|
||||
renderSingleRow(t) {
|
||||
const e = new g(this.root.config, {
|
||||
const e = new y(this.root.config, {
|
||||
column: 0,
|
||||
row: t
|
||||
});
|
||||
|
|
@ -705,7 +673,7 @@ class j {
|
|||
const o = t.map((l) => l.map((i) => i.displayValue).join(" ")).join(`
|
||||
`);
|
||||
this.saved = t, navigator.clipboard.writeText(o), this.root.events.dispatch({
|
||||
type: u.COPY_CELLS,
|
||||
type: d.COPY_CELLS,
|
||||
data: t,
|
||||
dataAsString: o,
|
||||
range: e
|
||||
|
|
@ -718,53 +686,52 @@ class j {
|
|||
const h = l.clipboardData.getData("text");
|
||||
try {
|
||||
const a = h.split(`
|
||||
`).map((d) => d.split(" ")).map((d) => d.map((w) => {
|
||||
const y = {
|
||||
displayValue: w,
|
||||
`).map((w) => w.split(" ")).map((w) => w.map((g) => {
|
||||
const f = {
|
||||
displayValue: g,
|
||||
position: {
|
||||
column: e,
|
||||
row: o
|
||||
},
|
||||
resultValue: w,
|
||||
resultValue: g,
|
||||
style: new R(),
|
||||
value: w
|
||||
value: g
|
||||
};
|
||||
return new p(y);
|
||||
})), f = a.length, m = a[0] ? a[0].length : 0;
|
||||
for (let d = 0; d < f; d++)
|
||||
for (let w = 0; w < m; w++) {
|
||||
const y = a[d][w], B = {
|
||||
column: e + w,
|
||||
row: o + d
|
||||
return new x(f);
|
||||
})), u = a.length, C = a[0] ? a[0].length : 0;
|
||||
for (let w = 0; w < u; w++)
|
||||
for (let g = 0; g < C; g++) {
|
||||
const f = a[w][g], L = {
|
||||
column: e + g,
|
||||
row: o + w
|
||||
}, V = {
|
||||
displayValue: y.displayValue,
|
||||
value: y.value,
|
||||
style: y.style
|
||||
displayValue: f.displayValue,
|
||||
value: f.value,
|
||||
style: f.style
|
||||
};
|
||||
t.changeCellValues(B, V, !1);
|
||||
t.changeCellValues(L, V, !1);
|
||||
}
|
||||
} catch (c) {
|
||||
console.error("Cannot read clipboard. ", c);
|
||||
}
|
||||
t.renderSheet();
|
||||
return;
|
||||
}
|
||||
const i = this.saved.length, n = this.saved[0] ? this.saved[0].length : 0;
|
||||
for (let h = 0; h < i; h++)
|
||||
for (let c = 0; c < n; c++) {
|
||||
const a = this.saved[h][c], f = {
|
||||
const a = this.saved[h][c], u = {
|
||||
column: e + c,
|
||||
row: o + h
|
||||
}, m = {
|
||||
}, C = {
|
||||
displayValue: a.displayValue,
|
||||
value: a.value,
|
||||
style: a.style
|
||||
};
|
||||
t.changeCellValues(f, m, !1);
|
||||
t.changeCellValues(u, C, !1);
|
||||
}
|
||||
}
|
||||
}
|
||||
const C = "modern_sc_";
|
||||
const m = "modern_sc_";
|
||||
class q {
|
||||
constructor(t, e) {
|
||||
s(this, "table");
|
||||
|
|
@ -782,8 +749,8 @@ class q {
|
|||
s(this, "cache");
|
||||
s(this, "events");
|
||||
s(this, "clipboard");
|
||||
const o = L(40, 40), l = this.makeConfigFromData(o, (e == null ? void 0 : e.view) ?? { height: 600, width: 800 });
|
||||
e != null && e.view && (l.view = e.view), this.config = new x(l), this.config.onCellClick = (e == null ? void 0 : e.onCellClick) ?? null, this.config.onSelectonChange = (e == null ? void 0 : e.onSelectionChange) ?? null, this.config.onCellChange = (e == null ? void 0 : e.onCellChange) ?? null, this.config.onCopy = (e == null ? void 0 : e.onCopy) ?? null, this.rowsBar = new X(this), this.columnsBar = new G(this), this.sheet = new D(this), this.table = new z(this), this.scroller = new H(this), this.toolbar = new N(this), this.editor = new T(this), this.cache = this.getInitialCache(), this.viewport = new b(this, this.scroller.getViewportBoundlingRect()), this.selection = new S(), this.events = new P(this), this.clipboard = new j(this), this.data = o, this.styles = new O(), this.buildComponent(), this.setElementsPositions(), this.appendTableToTarget(t), this.renderSheet(), this.renderColumnsBar(), this.renderRowsBar();
|
||||
const o = B(40, 40), l = this.makeConfigFromData(o, (e == null ? void 0 : e.view) ?? { height: 600, width: 800 });
|
||||
e != null && e.view && (l.view = e.view), this.config = new p(l), this.config.onCellClick = (e == null ? void 0 : e.onCellClick) ?? null, this.config.onSelectonChange = (e == null ? void 0 : e.onSelectionChange) ?? null, this.config.onCellChange = (e == null ? void 0 : e.onCellChange) ?? null, this.config.onCopy = (e == null ? void 0 : e.onCopy) ?? null, this.rowsBar = new X(this), this.columnsBar = new G(this), this.sheet = new D(this), this.table = new z(this), this.scroller = new H(this), this.toolbar = new N(this), this.editor = new T(this), this.cache = this.getInitialCache(), this.viewport = new S(this, this.scroller.getViewportBoundlingRect()), this.selection = new v(), this.events = new P(this), this.clipboard = new j(this), this.data = o, this.styles = new O(), this.buildComponent(), this.setElementsPositions(), this.appendTableToTarget(t), this.renderSheet(), this.renderColumnsBar(), this.renderRowsBar();
|
||||
}
|
||||
setRowsBarPosition() {
|
||||
const t = this.columnsBar.height + this.toolbar.height, e = 0;
|
||||
|
|
@ -791,7 +758,7 @@ class q {
|
|||
}
|
||||
setColumnsBarPosition() {
|
||||
const t = this.toolbar.height, e = this.rowsBar.width;
|
||||
this.columnsBar.setElementPosition(t, e);
|
||||
console.log(t, e), this.columnsBar.setElementPosition(t, e);
|
||||
}
|
||||
setElementsPositions() {
|
||||
this.setRowsBarPosition(), this.setColumnsBarPosition();
|
||||
|
|
@ -802,7 +769,7 @@ class q {
|
|||
for (let n = 0; n <= this.config.columns.length - 1; n++) {
|
||||
const h = this.config.columns[n];
|
||||
e += h.width;
|
||||
const c = new W({
|
||||
const c = new F({
|
||||
xPos: e,
|
||||
colIdx: n
|
||||
});
|
||||
|
|
@ -813,20 +780,21 @@ class q {
|
|||
for (let n = 0; n <= this.config.rows.length - 1; n++) {
|
||||
const h = this.config.rows[n];
|
||||
l += h.height;
|
||||
const c = new F({
|
||||
const c = new W({
|
||||
yPos: l,
|
||||
rowIdx: n
|
||||
});
|
||||
o.push(c);
|
||||
}
|
||||
return new Y({
|
||||
const i = new Y({
|
||||
columns: t,
|
||||
rows: o
|
||||
});
|
||||
return console.log("CACHE: ", i), console.log("CONFIG: ", this.config), i;
|
||||
}
|
||||
buildComponent() {
|
||||
const t = document.createElement("div");
|
||||
t.style.top = this.columnsBarHeight + "px", t.style.left = this.rowsBarWidth + "px", t.appendChild(this.sheet.element), t.classList.add(C + "content"), this.table.element.appendChild(this.toolbar.element), this.table.element.appendChild(this.rowsBar.element), this.table.element.appendChild(this.columnsBar.element), this.table.element.appendChild(t), this.table.element.appendChild(this.scroller.element), this.table.element.append(this.editor.element);
|
||||
t.style.top = this.columnsBarHeight + "px", t.style.left = this.rowsBarWidth + "px", t.appendChild(this.sheet.element), t.classList.add(m + "content"), this.table.element.appendChild(this.toolbar.element), this.table.element.appendChild(this.rowsBar.element), this.table.element.appendChild(this.columnsBar.element), this.table.element.appendChild(t), this.table.element.appendChild(this.scroller.element), this.table.element.append(this.editor.element);
|
||||
}
|
||||
/**Destroy spreadsheet DOM element.
|
||||
*
|
||||
|
|
@ -878,7 +846,7 @@ class q {
|
|||
changeCellValues(t, e, o = !0) {
|
||||
const { column: l, row: i } = t;
|
||||
this.data[i][l].changeValues(e), this.events.dispatch({
|
||||
type: u.CELL_CHANGE,
|
||||
type: d.CELL_CHANGE,
|
||||
cell: this.data[i][l],
|
||||
enableCallback: o
|
||||
}), this.renderCell(i, l);
|
||||
|
|
@ -920,9 +888,6 @@ class q {
|
|||
renderSheet() {
|
||||
this.sheet.renderSheet();
|
||||
}
|
||||
renderSelection() {
|
||||
this.sheet.renderSelection();
|
||||
}
|
||||
renderColumnsBar() {
|
||||
this.columnsBar.renderBar();
|
||||
}
|
||||
|
|
@ -933,25 +898,24 @@ class q {
|
|||
this.data[t][e].render(this);
|
||||
}
|
||||
loadData(t) {
|
||||
const e = t.length, o = t[0] ? t[0].length : 0;
|
||||
const e = t.length, o = t[0] ? this.data[0].length : 0;
|
||||
this.data = [];
|
||||
const l = [];
|
||||
for (let n = 0; n < e; n++) {
|
||||
const h = [];
|
||||
for (let c = 0; c < o; c++) {
|
||||
const a = t[n][c];
|
||||
h.push(new p({
|
||||
displayValue: a.displayValue,
|
||||
position: a.position,
|
||||
resultValue: a.resultValue,
|
||||
value: a.value,
|
||||
style: a.style
|
||||
for (let i = 0; i < e; i++) {
|
||||
const n = [];
|
||||
for (let h = 0; h < o; h++) {
|
||||
const c = t[i][h];
|
||||
n.push(new x({
|
||||
displayValue: c.displayValue,
|
||||
position: c.position,
|
||||
resultValue: c.resultValue,
|
||||
value: c.value,
|
||||
style: c.style
|
||||
}));
|
||||
}
|
||||
l.push(h);
|
||||
l.push(n);
|
||||
}
|
||||
const i = this.makeConfigFromData(l, this.config.view);
|
||||
return i.onCellChange = this.config.onCellChange, i.onCellClick = this.config.onCellClick, i.onCopy = this.config.onCopy, i.onSelectonChange = this.config.onSelectonChange, this.data = l, this.selection.selectedCell = null, this.selection.selectedRange = null, this.config = i, this.cache = this.getInitialCache(), this.scroller.updateScrollerSize(), this.viewport = new b(this, this.scroller.getViewportBoundlingRect()), this.renderSheet(), this;
|
||||
return this.data = l, this.selection.selectedCell = null, this.selection.selectedRange = null, this.config = this.makeConfigFromData(l, this.config.view), this.cache = this.getInitialCache(), this.scroller.updateScrollerSize(), this.viewport = new S(this, this.scroller.getViewportBoundlingRect()), this.renderSheet(), this;
|
||||
}
|
||||
makeConfigFromData(t, e) {
|
||||
const o = t.length - 1, l = t[0] ? t[0].length : 0, i = [];
|
||||
|
|
@ -966,7 +930,7 @@ class q {
|
|||
width: 150,
|
||||
title: String(c)
|
||||
}));
|
||||
return new x({
|
||||
return new p({
|
||||
view: e,
|
||||
rows: i,
|
||||
columns: n,
|
||||
|
|
@ -985,23 +949,23 @@ class q {
|
|||
}
|
||||
}
|
||||
export {
|
||||
C as CSS_PREFIX,
|
||||
m as CSS_PREFIX,
|
||||
Y as Cache,
|
||||
W as CachedColumn,
|
||||
F as CachedRow,
|
||||
p as Cell,
|
||||
F as CachedColumn,
|
||||
W as CachedRow,
|
||||
x as Cell,
|
||||
R as CellStyles,
|
||||
E as Column,
|
||||
x as Config,
|
||||
p as Config,
|
||||
M as Position,
|
||||
g as RenderBox,
|
||||
y as RenderBox,
|
||||
k as Row,
|
||||
S as Selection,
|
||||
v as Selection,
|
||||
_ as SerializableCell,
|
||||
O as Styles,
|
||||
b as Viewport,
|
||||
S as Viewport,
|
||||
K as createSampleConfig,
|
||||
L as createSampleData,
|
||||
B as createSampleData,
|
||||
q as default,
|
||||
U as makeSpreadsheetConfigAndData
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -49,6 +49,7 @@ export declare class Cell {
|
|||
getSerializableCell(): SerializableCell;
|
||||
changeStyles(styles: CellStyles): void;
|
||||
changeValues(values: Partial<Omit<CellConstructorProps, "position">>): void;
|
||||
private isCellInRange;
|
||||
render(root: Spreadsheet): void;
|
||||
}
|
||||
export {};
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "modern_spreadsheet",
|
||||
"private": false,
|
||||
"version": "0.0.33",
|
||||
"version": "0.0.31",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/main.js",
|
||||
|
|
@ -33,7 +33,6 @@
|
|||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:HTML": "tsc && cross-env BUILD_BROWSER=true vite build",
|
||||
"build:watch": "tsc && vite build --watch",
|
||||
"preview": "vite preview",
|
||||
"predeploy": "npm run dist",
|
||||
|
|
@ -47,7 +46,6 @@
|
|||
"@types/node": "^20.4.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||
"@typescript-eslint/parser": "^6.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"gh-pages": "^5.0.0",
|
||||
|
|
@ -56,10 +54,6 @@
|
|||
"sass": "^1.63.6",
|
||||
"tslib": "^2.6.0",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.0",
|
||||
"vite-plugin-html": "^3.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fast-formula-parser": "^1.0.19"
|
||||
"vite": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1886
pnpm-lock.yaml
1886
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
|
|
@ -82,7 +82,9 @@ export class ColumnsBar {
|
|||
|
||||
const isColSelected = this.isColumnSelected(column);
|
||||
|
||||
this.ctx.fillStyle = isColSelected ? "#c7ebff" : "white";
|
||||
this.ctx.fillStyle = isColSelected
|
||||
? this.root.styles.cells.selectedBackground
|
||||
: "white";
|
||||
this.ctx.strokeStyle = "black";
|
||||
this.ctx.lineWidth = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ export class Editor {
|
|||
});
|
||||
|
||||
this.hide();
|
||||
this.root.renderSelection();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,7 +69,9 @@ export class RowsBar {
|
|||
|
||||
const isRowSeleted = this.isRowSelected(column);
|
||||
|
||||
this.ctx.fillStyle = isRowSeleted ? "#c7ebff" : "white";
|
||||
this.ctx.fillStyle = isRowSeleted
|
||||
? this.root.styles.cells.selectedBackground
|
||||
: "white";
|
||||
this.ctx.strokeStyle = "black";
|
||||
this.ctx.lineWidth = this.resizerHeight;
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ export class Scroller {
|
|||
}
|
||||
|
||||
//* Start typings
|
||||
const keysRegex = /^([a-z]|[а-я]|[0-9]|=)$/;
|
||||
const keysRegex = /^([a-z]|[а-я]|[0-9])$/;
|
||||
if (!event.metaKey && !event.ctrlKey) {
|
||||
//* Prevent handle shortcutrs
|
||||
const isPressedLetterKey = keysRegex.test(event.key.toLowerCase());
|
||||
|
|
@ -193,6 +193,7 @@ export class Scroller {
|
|||
}
|
||||
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
console.log(event.code);
|
||||
if (event.code === "KeyC") {
|
||||
let cells: Cell[][] = undefined!;
|
||||
const selection = new Selection();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import Spreadsheet, { CSS_PREFIX, RenderBox } from "../main";
|
||||
import Spreadsheet, { CSS_PREFIX } from "../main";
|
||||
import { Position } from "../modules/cell";
|
||||
|
||||
/**
|
||||
|
|
@ -52,79 +52,12 @@ export class Sheet {
|
|||
this.root.data[row][column].render(this.root);
|
||||
}
|
||||
|
||||
private getSelectionRange() {
|
||||
const { selectedCell, selectedRange } = this.root.selection;
|
||||
|
||||
if (!selectedCell && !selectedRange) return;
|
||||
if (selectedRange) {
|
||||
const startRow = Math.min(selectedRange.from.row, selectedRange.to.row);
|
||||
const startCol = Math.min(
|
||||
selectedRange.from.column,
|
||||
selectedRange.to.column,
|
||||
);
|
||||
const lastRow = Math.max(selectedRange.from.row, selectedRange.to.row);
|
||||
const lastCol = Math.max(
|
||||
selectedRange.from.column,
|
||||
selectedRange.to.column,
|
||||
);
|
||||
|
||||
const startCellBox = new RenderBox(this.root.config, {
|
||||
row: startRow,
|
||||
column: startCol,
|
||||
});
|
||||
|
||||
let width = 0;
|
||||
for (let col = startCol; col <= lastCol; col++) {
|
||||
width += this.root.config.columns[col].width;
|
||||
}
|
||||
|
||||
let height = 0;
|
||||
for (let row = startRow; row <= lastRow; row++) {
|
||||
height += this.root.config.rows[row].height;
|
||||
}
|
||||
|
||||
const x = startCellBox.x - this.root.viewport.left;
|
||||
const y = startCellBox.y - this.root.viewport.top;
|
||||
|
||||
return { x, y, height, width };
|
||||
}
|
||||
if (!selectedRange && selectedCell) {
|
||||
const box = new RenderBox(this.root.config, selectedCell);
|
||||
box.x -= this.root.viewport.left;
|
||||
box.y -= this.root.viewport.top;
|
||||
return box;
|
||||
}
|
||||
}
|
||||
|
||||
private renderSelectionRange(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
) {
|
||||
this.ctx.save();
|
||||
this.ctx.strokeStyle = "#7da8ff";
|
||||
this.ctx.lineWidth = 3;
|
||||
this.ctx.strokeRect(x, y, width, height);
|
||||
this.ctx.fillStyle = "#7da8ff35";
|
||||
this.ctx.fillRect(x, y, width, height);
|
||||
this.ctx.restore();
|
||||
}
|
||||
|
||||
renderSelection() {
|
||||
const box = this.getSelectionRange();
|
||||
if (!box) return;
|
||||
const { height, width, x, y } = box;
|
||||
this.renderSelectionRange(x, y, width, height);
|
||||
}
|
||||
|
||||
renderSheet() {
|
||||
const firstRowIdx = this.root.viewport.firstRow;
|
||||
const lastColIdx = this.root.viewport.lastCol + 3;
|
||||
const lastRowIdx = this.root.viewport.lastRow + 3;
|
||||
const firstColIdx = this.root.viewport.firstCol;
|
||||
|
||||
|
||||
for (let row = firstRowIdx; row <= lastRowIdx; row++) {
|
||||
for (let col = firstColIdx; col <= lastColIdx; col++) {
|
||||
if (!this.root.config.columns[col] || !this.root.config.rows[row])
|
||||
|
|
@ -133,6 +66,5 @@ export class Sheet {
|
|||
this.renderCell({ column: col, row });
|
||||
}
|
||||
}
|
||||
this.renderSelection();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
src/main.ts
24
src/main.ts
|
|
@ -30,7 +30,6 @@ import { ColumnsBar } from "./components/columnsBar";
|
|||
import { RowsBar } from "./components/rowsBar";
|
||||
import { EventTypes, Events } from "./modules/events";
|
||||
import { Clipboard } from "./modules/clipboard";
|
||||
import { FormulaParser } from "./modules/formulaParser";
|
||||
|
||||
/*
|
||||
! Component structure
|
||||
|
|
@ -70,7 +69,6 @@ export default class Spreadsheet {
|
|||
public cache: Cache;
|
||||
public events: Events;
|
||||
public clipboard: Clipboard;
|
||||
public formulaParser: FormulaParser
|
||||
|
||||
constructor(
|
||||
target: string | HTMLElement,
|
||||
|
|
@ -107,7 +105,6 @@ export default class Spreadsheet {
|
|||
this.selection = new Selection();
|
||||
this.events = new Events(this);
|
||||
this.clipboard = new Clipboard(this);
|
||||
this.formulaParser = new FormulaParser(this)
|
||||
|
||||
this.data = data;
|
||||
this.styles = new Styles();
|
||||
|
|
@ -127,6 +124,7 @@ export default class Spreadsheet {
|
|||
private setColumnsBarPosition() {
|
||||
const top = this.toolbar.height;
|
||||
const left = this.rowsBar.width;
|
||||
console.log(top, left);
|
||||
this.columnsBar.setElementPosition(top, left);
|
||||
}
|
||||
|
||||
|
|
@ -165,6 +163,8 @@ export default class Spreadsheet {
|
|||
rows: cachedRows,
|
||||
});
|
||||
|
||||
console.log("CACHE: ", cache);
|
||||
console.log("CONFIG: ", this.config);
|
||||
return cache;
|
||||
}
|
||||
|
||||
|
|
@ -315,10 +315,6 @@ export default class Spreadsheet {
|
|||
this.sheet.renderSheet();
|
||||
}
|
||||
|
||||
renderSelection() {
|
||||
this.sheet.renderSelection();
|
||||
}
|
||||
|
||||
renderColumnsBar() {
|
||||
this.columnsBar.renderBar();
|
||||
}
|
||||
|
|
@ -333,11 +329,11 @@ export default class Spreadsheet {
|
|||
|
||||
public loadData(data: Cell[][] | SerializableCell[][]): Spreadsheet {
|
||||
const rowsLength = data.length;
|
||||
const colsLength = data[0] ? data[0].length : 0;
|
||||
const colsLength = data[0] ? this.data[0].length : 0;
|
||||
this.data = [];
|
||||
|
||||
const formattedData: Cell[][] = [];
|
||||
// Transform serialized objects to Cells
|
||||
|
||||
for (let row = 0; row < rowsLength; row++) {
|
||||
const innerRow: Cell[] = [];
|
||||
for (let col = 0; col < colsLength; col++) {
|
||||
|
|
@ -355,17 +351,11 @@ export default class Spreadsheet {
|
|||
formattedData.push(innerRow);
|
||||
}
|
||||
|
||||
const config = this.makeConfigFromData(formattedData, this.config.view);
|
||||
config.onCellChange = this.config.onCellChange
|
||||
config.onCellClick = this.config.onCellClick
|
||||
config.onCopy = this.config.onCopy
|
||||
config.onSelectonChange = this.config.onSelectonChange
|
||||
|
||||
|
||||
this.data = formattedData;
|
||||
|
||||
this.selection.selectedCell = null;
|
||||
this.selection.selectedRange = null;
|
||||
this.config = config
|
||||
this.config = this.makeConfigFromData(formattedData, this.config.view);
|
||||
this.cache = this.getInitialCache();
|
||||
this.scroller.updateScrollerSize();
|
||||
this.viewport = new Viewport(
|
||||
|
|
|
|||
|
|
@ -44,21 +44,21 @@ export class Cache {
|
|||
public getRowByYCoord(y: number): number {
|
||||
let rowIdx = 0;
|
||||
for (let i = 0; i < this.rows.length; i++) {
|
||||
rowIdx = i
|
||||
if (y <= this.rows[i].yPos) { //* Intersection detect
|
||||
if (y <= this.rows[i].yPos) {
|
||||
//* Intersection detect
|
||||
rowIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return rowIdx;
|
||||
}
|
||||
|
||||
public getColumnByXCoord(x: number): number {
|
||||
let colIdx = 0;
|
||||
for (let i = 0; i < this.columns.length; i++) {
|
||||
colIdx = i
|
||||
if (x <= this.columns[i].xPos) { //* Intersection detect
|
||||
if (x <= this.columns[i].xPos) {
|
||||
//* Intersection detect
|
||||
colIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import Spreadsheet from "../main";
|
||||
import { FormulaParser } from "./formulaParser";
|
||||
import { RenderBox } from "./renderBox";
|
||||
|
||||
export type CellConstructorProps = {
|
||||
|
|
@ -70,9 +69,6 @@ export class Cell {
|
|||
position: Position;
|
||||
style: CellStyles | null = null;
|
||||
|
||||
cellsDependsOnThisCell: Position[] = []
|
||||
dependedFromCells: Position[] = []
|
||||
|
||||
constructor(props: CellConstructorProps) {
|
||||
this.value = props.value;
|
||||
this.displayValue = props.displayValue;
|
||||
|
|
@ -100,53 +96,50 @@ export class Cell {
|
|||
Object.assign(this, values);
|
||||
}
|
||||
|
||||
evalFormula(parser: FormulaParser) {
|
||||
if (this.value.substring(0, 1) !== '=') return;
|
||||
private isCellInRange(root: Spreadsheet): boolean {
|
||||
const { column, row } = this.position;
|
||||
const { selectedRange } = root.selection;
|
||||
|
||||
this.resultValue = parser.parser.parse(this.value.slice(1), {
|
||||
col: this.position.column,
|
||||
row: this.position.row
|
||||
})
|
||||
if (!selectedRange) return false;
|
||||
|
||||
const isCellInRow =
|
||||
row >= Math.min(selectedRange.from.row, selectedRange.to.row) &&
|
||||
row <= Math.max(selectedRange.to.row, selectedRange.from.row);
|
||||
const isCellInCol =
|
||||
column >= Math.min(selectedRange.from.column, selectedRange.to.column) &&
|
||||
column <= Math.max(selectedRange.to.column, selectedRange.from.column);
|
||||
|
||||
return isCellInCol && isCellInRow;
|
||||
}
|
||||
|
||||
// private isCellInRange(root: Spreadsheet): boolean {
|
||||
// const { column, row } = this.position;
|
||||
// const { selectedRange } = root.selection;
|
||||
|
||||
// if (!selectedRange) return false;
|
||||
|
||||
// const isCellInRow =
|
||||
// row >= Math.min(selectedRange.from.row, selectedRange.to.row) &&
|
||||
// row <= Math.max(selectedRange.to.row, selectedRange.from.row);
|
||||
// const isCellInCol =
|
||||
// column >= Math.min(selectedRange.from.column, selectedRange.to.column) &&
|
||||
// column <= Math.max(selectedRange.to.column, selectedRange.from.column);
|
||||
|
||||
// return isCellInCol && isCellInRow;
|
||||
// }
|
||||
|
||||
render(root: Spreadsheet) {
|
||||
const renderBox = new RenderBox(root.config, this.position);
|
||||
let { x, y } = renderBox;
|
||||
const { height, width } = renderBox;
|
||||
const { ctx } = root;
|
||||
|
||||
// const isCellSelected =
|
||||
// root.selection.selectedCell?.row === this.position.row &&
|
||||
// root.selection.selectedCell.column === this.position.column;
|
||||
// const isCellInRange = this.isCellInRange(root);
|
||||
const isCellSelected =
|
||||
root.selection.selectedCell?.row === this.position.row &&
|
||||
root.selection.selectedCell.column === this.position.column;
|
||||
const isCellInRange = this.isCellInRange(root);
|
||||
y -= root.viewport.top;
|
||||
x -= root.viewport.left;
|
||||
|
||||
const styles = this.style ?? root.styles.cells;
|
||||
|
||||
ctx.clearRect(x, y, width, height);
|
||||
ctx.fillStyle = styles.background;
|
||||
ctx.fillStyle =
|
||||
isCellSelected || isCellInRange
|
||||
? styles.selectedBackground
|
||||
: styles.background;
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.fillRect(x, y, width - 1, height - 1);
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
|
||||
ctx.fillStyle = styles.fontColor;
|
||||
ctx.fillStyle =
|
||||
isCellSelected || isCellInRange
|
||||
? styles.selectedFontColor
|
||||
: styles.fontColor;
|
||||
ctx.textAlign = "left";
|
||||
ctx.font = `${styles.fontSize}px Arial`;
|
||||
ctx.textBaseline = "middle";
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export class Clipboard {
|
|||
} catch (err) {
|
||||
console.error("Cannot read clipboard. ", err);
|
||||
}
|
||||
root.renderSheet();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ export type ActionTypes =
|
|||
| ChangeCellEvent
|
||||
| CopyAction;
|
||||
|
||||
|
||||
export class Events {
|
||||
root: Spreadsheet;
|
||||
|
||||
|
|
@ -47,7 +46,7 @@ export class Events {
|
|||
this.root = root;
|
||||
}
|
||||
|
||||
async dispatch(action: ActionTypes) {
|
||||
dispatch(action: ActionTypes) {
|
||||
switch (action.type) {
|
||||
case EventTypes.CELL_CLICK: {
|
||||
const { event, scroller } = action;
|
||||
|
|
@ -69,20 +68,9 @@ export class Events {
|
|||
|
||||
case EventTypes.CELL_CHANGE: {
|
||||
const { cell, enableCallback } = action;
|
||||
if (cell.value.substring(0, 1).startsWith('=')) {
|
||||
try {
|
||||
await cell.evalFormula(this.root.formulaParser)
|
||||
cell.displayValue = cell.resultValue
|
||||
this.root.renderCell(cell.position.row, cell.position.column)
|
||||
this.changeCellValues(cell, enableCallback);
|
||||
return;
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
this.root.renderCell(cell.position.row, cell.position.column)
|
||||
//
|
||||
//* Here may be side effects
|
||||
//
|
||||
this.changeCellValues(cell, enableCallback);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
import Parser, { DepParser } from 'fast-formula-parser'
|
||||
import Spreadsheet from '../main'
|
||||
|
||||
export class FormulaParser {
|
||||
parser: Parser
|
||||
depParser: DepParser
|
||||
root: Spreadsheet
|
||||
constructor(root: Spreadsheet) {
|
||||
this.root = root
|
||||
|
||||
this.parser = new Parser({
|
||||
onCell: ({col, row}) => {
|
||||
const cell = this.root.data[row - 1][col - 1]
|
||||
const cellValue = cell.resultValue.length > 0 ? cell.resultValue : cell.value
|
||||
if( cellValue && isNaN(Number(cellValue)) === false) return Number(cellValue)
|
||||
return this.root.data[row - 1][col - 1].resultValue ?? ''
|
||||
},
|
||||
})
|
||||
|
||||
this.depParser = new DepParser({})
|
||||
this.depParser
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
declare module 'fast-formula-parser' {
|
||||
export type PositionWithSheet = {
|
||||
sheet?: string
|
||||
row: number
|
||||
col: number
|
||||
}
|
||||
|
||||
export type FunctionArgument = {
|
||||
isArray: boolean
|
||||
isCellRef: boolean
|
||||
isRangeRef: boolean
|
||||
value: string | number
|
||||
}
|
||||
|
||||
export type Position = {
|
||||
col: number
|
||||
row: number
|
||||
}
|
||||
|
||||
export type RangeReference = {
|
||||
sheet?: string
|
||||
from: Position,
|
||||
to: Position
|
||||
}
|
||||
|
||||
export type Config = {
|
||||
functions?: Record<string, (...args: FunctionArgument[]) => string>
|
||||
functionsNeedContext?: (context: Parser, ...args: FunctionArgument[]) => string
|
||||
onCell?: (position: PositionWithSheet) => number | string
|
||||
onRange?: (ref) => Array<string|number>[]
|
||||
onVariable?: (name: string, sheetName: string) => RangeReference
|
||||
}
|
||||
|
||||
export const Types = {
|
||||
NUMBER: 0,
|
||||
ARRAY: 1,
|
||||
BOOLEAN: 2,
|
||||
STRING: 3,
|
||||
RANGE_REF: 4, // can be 'A:C' or '1:4', not only 'A1:C3'
|
||||
CELL_REF: 5,
|
||||
COLLECTIONS: 6, // Unions of references
|
||||
NUMBER_NO_BOOLEAN: 10,
|
||||
};
|
||||
|
||||
export const Factorials: number[]
|
||||
|
||||
export default class Parser {
|
||||
constructor(config: Config)
|
||||
parse: (expression: string, position: PositionWithSheet) => string
|
||||
parseAsync: (expression: string, position: PositionWithSheet) => Promise<string>
|
||||
|
||||
}
|
||||
|
||||
|
||||
type FormulaHelpersType = {
|
||||
accept: (param: FunctionArgument, type?: number, defValue?: number | string, flat?: boolean, allowSingleValue?: boolean) => number | string
|
||||
type: (variable) => number
|
||||
isRangeRef: (param) => boolean
|
||||
isCellRef: (param) => boolean
|
||||
}
|
||||
|
||||
export class DepParser {
|
||||
constructor(config?: {onVariable?: (name: string, sheetName: string) => RangeReference})
|
||||
parse(expression: string, position: PositionWithSheet): PositionWithSheet[]
|
||||
}
|
||||
|
||||
export const FormulaHelpers: FormulaHelpersType
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
"types": ["vite/client", "node"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@ import { defineConfig } from "vite";
|
|||
import path from "path";
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import { typescriptPaths } from "rollup-plugin-typescript-paths";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const BROWSER_MODE = process.env.BUILD_BROWSER === 'true';
|
||||
console.log({ BROWSER_MODE });
|
||||
|
||||
const libConfig = defineConfig({
|
||||
export default defineConfig({
|
||||
base: "/modern_spreadsheet/",
|
||||
plugins: [],
|
||||
resolve: {},
|
||||
server: {
|
||||
port: 5179,
|
||||
port: 3000,
|
||||
open: true,
|
||||
},
|
||||
build: {
|
||||
|
|
@ -39,23 +37,3 @@ const libConfig = defineConfig({
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
const browserConfig = defineConfig({
|
||||
base: "/modern_spreadsheet/",
|
||||
resolve: {},
|
||||
build: {
|
||||
manifest: true,
|
||||
minify: true,
|
||||
reportCompressedSize: true,
|
||||
sourcemap: true,
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, 'index.html'),
|
||||
fileName: 'demo',
|
||||
formats: ['es']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const config = BROWSER_MODE ? browserConfig : libConfig;
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
Loading…
Reference in New Issue