Compare commits
No commits in common. "main" and "events" have entirely different histories.
30
README.md
30
README.md
|
|
@ -1,15 +1,9 @@
|
||||||
# Modern Spreadsheet
|
# 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.
|
- High performance spreadsheet based on CanvasAPI.
|
||||||
- TypeScript supported
|
- TypeScript supported
|
||||||
- Native scrolling
|
|
||||||
- Customizable
|
|
||||||
- Copy & Paste support
|
|
||||||
|
|
||||||
### Basic usage
|
## Basic usage
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import Spreadsheet from "modern_spreadsheet";
|
import Spreadsheet from "modern_spreadsheet";
|
||||||
|
|
@ -20,7 +14,7 @@ const sheet = new Spreadsheet(target);
|
||||||
//...
|
//...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Save and load data
|
## Save and load data
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function saveData() {
|
function saveData() {
|
||||||
|
|
@ -36,11 +30,10 @@ function loadData() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Supported events
|
## Supported events
|
||||||
- onCellClick
|
- onCellClick
|
||||||
- onSelectionChange
|
- onSelectionChange
|
||||||
- onCellChange
|
- onCellChange
|
||||||
- onCopy
|
|
||||||
|
|
||||||
### Using events examples
|
### Using events examples
|
||||||
```ts
|
```ts
|
||||||
|
|
@ -48,27 +41,23 @@ import Spreadsheet, { SpreadsheetConstructorProperties } from "./main";
|
||||||
|
|
||||||
const options: SpreadsheetConstructorProperties = {
|
const options: SpreadsheetConstructorProperties = {
|
||||||
onCellClick: (event, cell) => {
|
onCellClick: (event, cell) => {
|
||||||
console.log("Cell click", event, cell);
|
console.log('Cell click', event, cell)
|
||||||
},
|
},
|
||||||
onSelectionChange: (selection) => {
|
onSelectionChange: (selection) => {
|
||||||
console.log("Changed selection: ", selection);
|
console.log("Changed selection: ", selection)
|
||||||
},
|
},
|
||||||
onCellChange = (cell) => {
|
onCellChange(cell) {
|
||||||
console.log("Cell changed: ", cell);
|
console.log("Cell changed: ", cell)
|
||||||
},
|
},
|
||||||
onCopy: (range, data, dataAsString) => {
|
}
|
||||||
console.log("Copy event: ", range, data, dataAsString)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sheet = new Spreadsheet("#spreadsheet", options);
|
const sheet = new Spreadsheet("#spreadsheet", options);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- ~~Rows number and columns heading render~~
|
- ~~Rows number and columns heading render~~
|
||||||
- ~~Custom event functions (ex.: onSelectionChange, onCellEdit...). Full list of supported events will available on this page~~
|
- ~~Custom event functions (ex.: onSelectionChange, onCellEdit...). Full list of supported events will available on this page~~
|
||||||
- ~~Copy & Paste support~~
|
|
||||||
- Rows and columns resizing
|
- Rows and columns resizing
|
||||||
- Toolbar
|
- Toolbar
|
||||||
- Context menu
|
- Context menu
|
||||||
|
|
@ -76,3 +65,4 @@ const sheet = new Spreadsheet("#spreadsheet", options);
|
||||||
- Selected cell depends cells highlight
|
- Selected cell depends cells highlight
|
||||||
- Async formulas support
|
- Async formulas support
|
||||||
- Mutlisheets (?)
|
- Mutlisheets (?)
|
||||||
|
- Copy & Paste support
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ export declare class Scroller {
|
||||||
private root;
|
private root;
|
||||||
private isSelecting;
|
private isSelecting;
|
||||||
constructor(root: Spreadsheet);
|
constructor(root: Spreadsheet);
|
||||||
setSelectingMode(mode: boolean): void;
|
|
||||||
private handleMouseMove;
|
private handleMouseMove;
|
||||||
private handleMouseUp;
|
private handleMouseUp;
|
||||||
private handleDoubleClick;
|
private handleDoubleClick;
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,5 @@ export declare class Sheet {
|
||||||
constructor(root: Spreadsheet);
|
constructor(root: Spreadsheet);
|
||||||
getCellByCoords(x: number, y: number): Position;
|
getCellByCoords(x: number, y: number): Position;
|
||||||
renderCell(position: Position): void;
|
renderCell(position: Position): void;
|
||||||
private getSelectionRange;
|
|
||||||
private renderSelectionRange;
|
|
||||||
renderSelection(): void;
|
|
||||||
renderSheet(): 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
|
|
@ -1,18 +1,13 @@
|
||||||
import { Cell, CellConstructorProps, CellStyles, Position, SerializableCell } from "./modules/cell";
|
import { Cell, CellConstructorProps, CellStyles, Position, SerializableCell } from "./modules/cell";
|
||||||
import { CellChangeEvent, CellClickEvent, Config, CopyEvent, SelectionChangeEvent, ViewProperties } from "./modules/config";
|
import { Config, ViewProperties } from "./modules/config";
|
||||||
import { RangeSelectionType, Selection } from "./modules/selection";
|
import { RangeSelectionType, Selection } from "./modules/selection";
|
||||||
import { Styles } from "./modules/styles";
|
import { Styles } from "./modules/styles";
|
||||||
import { Viewport } from "./modules/viewport";
|
import { Viewport } from "./modules/viewport";
|
||||||
import "./scss/main.scss";
|
import "./scss/main.scss";
|
||||||
import { Cache } from "./modules/cache";
|
import { Cache } from "./modules/cache";
|
||||||
import { Events } from "./modules/events";
|
interface SpreadsheetConstructorProperties {
|
||||||
import { Clipboard } from "./modules/clipboard";
|
config?: Omit<Config, "view">;
|
||||||
export interface SpreadsheetConstructorProperties {
|
|
||||||
view?: ViewProperties;
|
view?: ViewProperties;
|
||||||
onCellClick?: CellClickEvent | null;
|
|
||||||
onSelectionChange?: SelectionChangeEvent | null;
|
|
||||||
onCellChange?: CellChangeEvent | null;
|
|
||||||
onCopy?: CopyEvent | null;
|
|
||||||
}
|
}
|
||||||
export declare const CSS_PREFIX = "modern_sc_";
|
export declare const CSS_PREFIX = "modern_sc_";
|
||||||
export default class Spreadsheet {
|
export default class Spreadsheet {
|
||||||
|
|
@ -29,8 +24,6 @@ export default class Spreadsheet {
|
||||||
viewport: Viewport;
|
viewport: Viewport;
|
||||||
selection: Selection;
|
selection: Selection;
|
||||||
cache: Cache;
|
cache: Cache;
|
||||||
events: Events;
|
|
||||||
clipboard: Clipboard;
|
|
||||||
constructor(target: string | HTMLElement, props?: SpreadsheetConstructorProperties);
|
constructor(target: string | HTMLElement, props?: SpreadsheetConstructorProperties);
|
||||||
private setRowsBarPosition;
|
private setRowsBarPosition;
|
||||||
private setColumnsBarPosition;
|
private setColumnsBarPosition;
|
||||||
|
|
@ -56,13 +49,12 @@ export default class Spreadsheet {
|
||||||
focusTable(): void;
|
focusTable(): void;
|
||||||
getCellByCoords(x: number, y: number): Position;
|
getCellByCoords(x: number, y: number): Position;
|
||||||
getCell(position: Position): Cell;
|
getCell(position: Position): Cell;
|
||||||
changeCellValues(position: Position, values: Partial<Omit<CellConstructorProps, "position">>, enableCallback?: boolean): void;
|
changeCellValues(position: Position, values: Partial<Omit<CellConstructorProps, "position">>): void;
|
||||||
changeCellStyles(position: Position, styles: CellStyles): void;
|
changeCellStyles(position: Position, styles: CellStyles): void;
|
||||||
applyActionToRange(range: RangeSelectionType, callback: (cell: Cell) => void): void;
|
applyActionToRange(range: RangeSelectionType, callback: (cell: Cell) => void): void;
|
||||||
deleteSelectedCellsValues(): void;
|
deleteSelectedCellsValues(): void;
|
||||||
showEditor(position: Position, initialString?: string): void;
|
showEditor(position: Position, initialString?: string): void;
|
||||||
renderSheet(): void;
|
renderSheet(): void;
|
||||||
renderSelection(): void;
|
|
||||||
renderColumnsBar(): void;
|
renderColumnsBar(): void;
|
||||||
renderRowsBar(): void;
|
renderRowsBar(): void;
|
||||||
renderCell(row: number, col: number): void;
|
renderCell(row: number, col: number): void;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -49,6 +49,7 @@ export declare class Cell {
|
||||||
getSerializableCell(): SerializableCell;
|
getSerializableCell(): SerializableCell;
|
||||||
changeStyles(styles: CellStyles): void;
|
changeStyles(styles: CellStyles): void;
|
||||||
changeValues(values: Partial<Omit<CellConstructorProps, "position">>): void;
|
changeValues(values: Partial<Omit<CellConstructorProps, "position">>): void;
|
||||||
|
private isCellInRange;
|
||||||
render(root: Spreadsheet): void;
|
render(root: Spreadsheet): void;
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import Spreadsheet, { RangeSelectionType } from "../main";
|
|
||||||
import { Cell, Position } from "./cell";
|
|
||||||
export declare class Clipboard {
|
|
||||||
saved: Cell[][] | null;
|
|
||||||
root: Spreadsheet;
|
|
||||||
constructor(root: Spreadsheet);
|
|
||||||
copy(data: Cell[][], range: RangeSelectionType): void;
|
|
||||||
paste(root: Spreadsheet, { column, row }: Position, event: ClipboardEvent): void;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
import { Cell } from "./cell";
|
|
||||||
import { Column } from "./column";
|
import { Column } from "./column";
|
||||||
import { Row } from "./row";
|
import { Row } from "./row";
|
||||||
import { RangeSelectionType, Selection } from "./selection";
|
|
||||||
export interface ViewProperties {
|
export interface ViewProperties {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
export type CellClickEvent = (event: MouseEvent, cell: Cell) => void;
|
|
||||||
export type SelectionChangeEvent = (selection: Selection) => void;
|
|
||||||
export type CellChangeEvent = (cell: Cell) => void;
|
|
||||||
export type CopyEvent = (range: RangeSelectionType, data: Cell[][], dataAsString: string) => void;
|
|
||||||
export type ConfigProperties = {
|
export type ConfigProperties = {
|
||||||
/** Please, end it with '_' symbol.
|
/** Please, end it with '_' symbol.
|
||||||
*
|
*
|
||||||
|
|
@ -20,10 +14,6 @@ export type ConfigProperties = {
|
||||||
rows: Row[];
|
rows: Row[];
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
view: ViewProperties;
|
view: ViewProperties;
|
||||||
onCellClick?: CellClickEvent | null;
|
|
||||||
onSelectionChange?: SelectionChangeEvent | null;
|
|
||||||
onCellChange?: CellChangeEvent | null;
|
|
||||||
onCopy?: CopyEvent | null;
|
|
||||||
};
|
};
|
||||||
export type SheetConfigConstructorProps = {
|
export type SheetConfigConstructorProps = {
|
||||||
rows: Row[];
|
rows: Row[];
|
||||||
|
|
@ -33,9 +23,5 @@ export declare class Config {
|
||||||
rows: Row[];
|
rows: Row[];
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
view: ViewProperties;
|
view: ViewProperties;
|
||||||
onCellClick: CellClickEvent | null;
|
|
||||||
onSelectonChange: SelectionChangeEvent | null;
|
|
||||||
onCellChange: CellChangeEvent | null;
|
|
||||||
onCopy: CopyEvent | null;
|
|
||||||
constructor(props: ConfigProperties);
|
constructor(props: ConfigProperties);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import { Scroller } from "../components/scroller";
|
|
||||||
import Spreadsheet, { Cell, RangeSelectionType, Selection } from "../main";
|
|
||||||
export declare enum EventTypes {
|
|
||||||
CELL_CLICK = "CELL_CLICK",
|
|
||||||
SELECTION_CHANGE = "CHANGE_SELECTION",
|
|
||||||
CELL_CHANGE = "CELL_CHANGE",
|
|
||||||
COPY_CELLS = "COPY_CELLS"
|
|
||||||
}
|
|
||||||
export type CellClickEvent = {
|
|
||||||
type: EventTypes.CELL_CLICK;
|
|
||||||
event: MouseEvent;
|
|
||||||
scroller: Scroller;
|
|
||||||
};
|
|
||||||
export type ChangeSelectionEvent = {
|
|
||||||
type: EventTypes.SELECTION_CHANGE;
|
|
||||||
selection: Selection;
|
|
||||||
enableCallback?: boolean;
|
|
||||||
};
|
|
||||||
export type ChangeCellEvent = {
|
|
||||||
type: EventTypes.CELL_CHANGE;
|
|
||||||
cell: Cell;
|
|
||||||
enableCallback?: boolean;
|
|
||||||
};
|
|
||||||
export type CopyAction = {
|
|
||||||
type: EventTypes.COPY_CELLS;
|
|
||||||
range: RangeSelectionType;
|
|
||||||
data: Cell[][];
|
|
||||||
dataAsString: string;
|
|
||||||
};
|
|
||||||
export type ActionTypes = CellClickEvent | ChangeSelectionEvent | ChangeCellEvent | CopyAction;
|
|
||||||
export declare class Events {
|
|
||||||
root: Spreadsheet;
|
|
||||||
constructor(root: Spreadsheet);
|
|
||||||
dispatch(action: ActionTypes): void;
|
|
||||||
private cellClick;
|
|
||||||
private changeSelection;
|
|
||||||
private changeCellValues;
|
|
||||||
private copy;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
body{padding:0;margin:0}.modern_sc_spreadsheet_container{position:relative;isolation:isolate;border:2px solid black}.modern_sc_content{position:absolute}.modern_sc_sheet{display:block;contain:strict}.modern_sc_scroller{position:absolute;overflow:scroll;box-sizing:border-box;transform:translateZ(0)}.modern_sc_scroller:focus{outline:none}.modern_sc_editor{position:absolute;box-sizing:border-box;font-size:16px;font-family:Arial,Helvetica,sans-serif}.modern_sc_hide{visibility:hidden}
|
body{padding:0;margin:0}.modern_sc_content{position:absolute}.modern_sc_spreadsheet_container{position:relative;isolation:isolate;border:2px solid black}.modern_sc_sheet{display:block;contain:strict}.modern_sc_scroller{position:absolute;overflow:scroll;box-sizing:border-box;transform:translateZ(0)}.modern_sc_scroller:focus{outline:none}.modern_sc_editor{position:absolute;box-sizing:border-box;font-size:16px;font-family:Arial,Helvetica,sans-serif}.modern_sc_hide{visibility:hidden}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import { BaseSelectionType, RangeSelectionType } from "../main";
|
|
||||||
export declare function checkEqualRanges(range1: RangeSelectionType, range2: RangeSelectionType): boolean;
|
|
||||||
export declare function checkEqualCellSelections(selection1: BaseSelectionType, selection2: BaseSelectionType): boolean;
|
|
||||||
13
package.json
13
package.json
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "modern_spreadsheet",
|
"name": "modern_spreadsheet",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.0.33",
|
"version": "0.0.29",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./dist/main.js",
|
"import": "./dist/main.js",
|
||||||
|
|
@ -33,21 +33,18 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build:HTML": "tsc && cross-env BUILD_BROWSER=true vite build",
|
|
||||||
"build:watch": "tsc && vite build --watch",
|
"build:watch": "tsc && vite build --watch",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"predeploy": "npm run dist",
|
"predeploy": "npm run dist",
|
||||||
"deploy": "gh-pages -d dist",
|
"deploy": "gh-pages -d dist",
|
||||||
"lint": "eslint src --ext .ts",
|
"lint": "eslint src --ext .ts",
|
||||||
"lint:fix": "eslint src --ext .ts --fix",
|
"lint:fix": "eslint src --ext .ts --fix"
|
||||||
"format": "prettier --write ./src/**/*.ts"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-typescript": "^11.1.2",
|
"@rollup/plugin-typescript": "^11.1.2",
|
||||||
"@types/node": "^20.4.4",
|
"@types/node": "^20.4.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||||
"@typescript-eslint/parser": "^6.2.0",
|
"@typescript-eslint/parser": "^6.2.0",
|
||||||
"cross-env": "^7.0.3",
|
|
||||||
"eslint": "^8.45.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"gh-pages": "^5.0.0",
|
"gh-pages": "^5.0.0",
|
||||||
|
|
@ -56,10 +53,6 @@
|
||||||
"sass": "^1.63.6",
|
"sass": "^1.63.6",
|
||||||
"tslib": "^2.6.0",
|
"tslib": "^2.6.0",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.4.0",
|
"vite": "^4.4.0"
|
||||||
"vite-plugin-html": "^3.2.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"fast-formula-parser": "^1.0.19"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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);
|
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.strokeStyle = "black";
|
||||||
this.ctx.lineWidth = 1;
|
this.ctx.lineWidth = 1;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,19 +54,17 @@ export class Editor {
|
||||||
}
|
}
|
||||||
case "Enter": {
|
case "Enter": {
|
||||||
if (!this.root.selection.selectedCell) return;
|
if (!this.root.selection.selectedCell) return;
|
||||||
|
|
||||||
this.root.changeCellValues(this.root.selection.selectedCell, {
|
|
||||||
value: this.element.value,
|
|
||||||
displayValue: this.element.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.root.events.dispatch({
|
this.root.events.dispatch({
|
||||||
type: EventTypes.CELL_CHANGE,
|
type: EventTypes.CELL_CHANGE,
|
||||||
cell: this.root.getCell(this.root.selection.selectedCell),
|
cell: this.root.getCell(this.root.selection.selectedCell),
|
||||||
});
|
values: {
|
||||||
|
value: this.element.value,
|
||||||
|
displayValue: this.element.value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.hide();
|
this.hide();
|
||||||
this.root.renderSelection();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,9 @@ export class RowsBar {
|
||||||
|
|
||||||
const isRowSeleted = this.isRowSelected(column);
|
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.strokeStyle = "black";
|
||||||
this.ctx.lineWidth = this.resizerHeight;
|
this.ctx.lineWidth = this.resizerHeight;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Spreadsheet, { CSS_PREFIX, Cell, Selection } from "../main";
|
import Spreadsheet, { CSS_PREFIX } from "../main";
|
||||||
import { EventTypes } from "../modules/events";
|
import { EventTypes } from "../modules/events";
|
||||||
import { checkEqualCellSelections } from "../utils/position";
|
import { checkEqualCellSelections } from "../utils/position";
|
||||||
|
|
||||||
|
|
@ -40,18 +40,10 @@ export class Scroller {
|
||||||
this.element.addEventListener("dblclick", this.handleDoubleClick);
|
this.element.addEventListener("dblclick", this.handleDoubleClick);
|
||||||
|
|
||||||
this.element.addEventListener("keydown", this.handleKeydown);
|
this.element.addEventListener("keydown", this.handleKeydown);
|
||||||
this.element.addEventListener("paste", (event) => {
|
|
||||||
if (!this.root.selection.selectedCell) return;
|
|
||||||
this.root.clipboard.paste(
|
|
||||||
this.root,
|
|
||||||
this.root.selection.selectedCell,
|
|
||||||
event,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSelectingMode(mode: boolean) {
|
public setSelectingMode(mode: boolean) {
|
||||||
this.isSelecting = mode;
|
this.isSelecting = mode
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleMouseMove = (event: MouseEvent) => {
|
private handleMouseMove = (event: MouseEvent) => {
|
||||||
|
|
@ -59,42 +51,37 @@ export class Scroller {
|
||||||
const { offsetX, offsetY } = event;
|
const { offsetX, offsetY } = event;
|
||||||
const lastSelectedCell = this.root.getCellByCoords(offsetX, offsetY);
|
const lastSelectedCell = this.root.getCellByCoords(offsetX, offsetY);
|
||||||
|
|
||||||
let isRangeChanged = false;
|
let isRangeChanged = false
|
||||||
|
|
||||||
if (this.root.selection.selectedRange) {
|
if (this.root.selection.selectedRange) {
|
||||||
isRangeChanged = !checkEqualCellSelections(
|
isRangeChanged = !checkEqualCellSelections(this.root.selection.selectedRange.to, lastSelectedCell)
|
||||||
this.root.selection.selectedRange.to,
|
|
||||||
lastSelectedCell,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isRangeChanged) {
|
if (isRangeChanged) {
|
||||||
this.root.selection.selectedRange.to = lastSelectedCell;
|
this.root.selection.selectedRange.to = lastSelectedCell;
|
||||||
this.root.events.dispatch({
|
this.root.events.dispatch({
|
||||||
type: EventTypes.SELECTION_CHANGE,
|
type: EventTypes.SELECTION_CHANGE,
|
||||||
selection: this.root.selection,
|
selection: this.root.selection,
|
||||||
enableCallback: true,
|
enableCallback: true
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleMouseUp = () => {
|
private handleMouseUp = () => {
|
||||||
this.isSelecting = false;
|
this.isSelecting = false;
|
||||||
const newSelection = { ...this.root.selection };
|
const newSelection = {...this.root.selection}
|
||||||
|
|
||||||
if (this.root.selection.selectedRange) {
|
if (this.root.selection.selectedRange) {
|
||||||
if (
|
if (
|
||||||
checkEqualCellSelections(
|
checkEqualCellSelections(this.root.selection.selectedRange.from, this.root.selection.selectedRange.to)
|
||||||
this.root.selection.selectedRange.from,
|
|
||||||
this.root.selection.selectedRange.to,
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
newSelection.selectedRange = null;
|
newSelection.selectedRange = null;
|
||||||
this.root.events.dispatch({
|
this.root.events.dispatch({
|
||||||
type: EventTypes.SELECTION_CHANGE,
|
type: EventTypes.SELECTION_CHANGE,
|
||||||
selection: newSelection,
|
selection: newSelection,
|
||||||
enableCallback: false,
|
enableCallback: false
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +98,6 @@ export class Scroller {
|
||||||
|
|
||||||
private handleKeydown = (event: KeyboardEvent) => {
|
private handleKeydown = (event: KeyboardEvent) => {
|
||||||
//* Navigation
|
//* Navigation
|
||||||
|
|
||||||
if (
|
if (
|
||||||
["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)
|
["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)
|
||||||
) {
|
) {
|
||||||
|
|
@ -132,7 +118,7 @@ export class Scroller {
|
||||||
if (
|
if (
|
||||||
this.root.selection.selectedCell &&
|
this.root.selection.selectedCell &&
|
||||||
this.root.selection.selectedCell.column <
|
this.root.selection.selectedCell.column <
|
||||||
this.root.config.columns.length - 1
|
this.root.config.columns.length - 1
|
||||||
) {
|
) {
|
||||||
this.root.selection.selectedCell.column += 1;
|
this.root.selection.selectedCell.column += 1;
|
||||||
// this.root.renderSheet();
|
// this.root.renderSheet();
|
||||||
|
|
@ -153,7 +139,7 @@ export class Scroller {
|
||||||
if (
|
if (
|
||||||
this.root.selection.selectedCell &&
|
this.root.selection.selectedCell &&
|
||||||
this.root.selection.selectedCell.row <
|
this.root.selection.selectedCell.row <
|
||||||
this.root.config.rows.length - 1
|
this.root.config.rows.length - 1
|
||||||
) {
|
) {
|
||||||
this.root.selection.selectedCell.row += 1;
|
this.root.selection.selectedCell.row += 1;
|
||||||
// this.root.renderSheet();
|
// this.root.renderSheet();
|
||||||
|
|
@ -164,12 +150,12 @@ export class Scroller {
|
||||||
this.root.events.dispatch({
|
this.root.events.dispatch({
|
||||||
type: EventTypes.SELECTION_CHANGE,
|
type: EventTypes.SELECTION_CHANGE,
|
||||||
selection: this.root.selection,
|
selection: this.root.selection,
|
||||||
enableCallback: true,
|
enableCallback: true
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//* Start typings
|
//* Start typings
|
||||||
const keysRegex = /^([a-z]|[а-я]|[0-9]|=)$/;
|
const keysRegex = /^([a-z]|[а-я])$/;
|
||||||
if (!event.metaKey && !event.ctrlKey) {
|
if (!event.metaKey && !event.ctrlKey) {
|
||||||
//* Prevent handle shortcutrs
|
//* Prevent handle shortcutrs
|
||||||
const isPressedLetterKey = keysRegex.test(event.key.toLowerCase());
|
const isPressedLetterKey = keysRegex.test(event.key.toLowerCase());
|
||||||
|
|
@ -191,51 +177,14 @@ export class Scroller {
|
||||||
this.root.deleteSelectedCellsValues();
|
this.root.deleteSelectedCellsValues();
|
||||||
this.root.renderSheet();
|
this.root.renderSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.metaKey || event.ctrlKey) {
|
|
||||||
if (event.code === "KeyC") {
|
|
||||||
let cells: Cell[][] = undefined!;
|
|
||||||
const selection = new Selection();
|
|
||||||
|
|
||||||
if (this.root.selection.selectedRange) {
|
|
||||||
const { from, to } = this.root.selection.selectedRange;
|
|
||||||
|
|
||||||
selection.selectedRange = this.root.selection.selectedRange;
|
|
||||||
|
|
||||||
const subArrByRows = this.root.data.slice(from.row, to.row + 1);
|
|
||||||
|
|
||||||
const subArrByCols = subArrByRows.map((row) => {
|
|
||||||
return row.slice(from.column, to.column + 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
cells = [...subArrByCols];
|
|
||||||
} else if (this.root.selection.selectedCell) {
|
|
||||||
const { column, row } = this.root.selection.selectedCell;
|
|
||||||
cells = [[this.root.data[row][column]]];
|
|
||||||
selection.selectedRange = {
|
|
||||||
from: this.root.selection.selectedCell,
|
|
||||||
to: this.root.selection.selectedCell,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.root.clipboard.copy(cells, selection.selectedRange);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.code === "KeyV") {
|
|
||||||
// if (!this.root.selection.selectedCell) return;
|
|
||||||
// this.root.clipboard.paste(this.root, this.root.selection.selectedCell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleClick = (event: MouseEvent) => {
|
private handleClick = (event: MouseEvent) => {
|
||||||
this.root.events.dispatch({
|
this.root.events.dispatch({
|
||||||
type: EventTypes.CELL_CLICK,
|
type: EventTypes.CELL_CLICK,
|
||||||
event,
|
event,
|
||||||
scroller: this,
|
scroller: this
|
||||||
});
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleScroll = () => {
|
private handleScroll = () => {
|
||||||
|
|
@ -282,7 +231,6 @@ export class Scroller {
|
||||||
this.verticalScroller = verticalScroller;
|
this.verticalScroller = verticalScroller;
|
||||||
this.horizontalScroller = horizontalScroller;
|
this.horizontalScroller = horizontalScroller;
|
||||||
scroller.appendChild(groupScrollers);
|
scroller.appendChild(groupScrollers);
|
||||||
scroller.contentEditable = "false";
|
|
||||||
scroller.classList.add(CSS_PREFIX + "scroller");
|
scroller.classList.add(CSS_PREFIX + "scroller");
|
||||||
|
|
||||||
return { scroller, verticalScroller, horizontalScroller };
|
return { scroller, verticalScroller, horizontalScroller };
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Spreadsheet, { CSS_PREFIX, RenderBox } from "../main";
|
import Spreadsheet, { CSS_PREFIX } from "../main";
|
||||||
import { Position } from "../modules/cell";
|
import { Position } from "../modules/cell";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,79 +52,12 @@ export class Sheet {
|
||||||
this.root.data[row][column].render(this.root);
|
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() {
|
renderSheet() {
|
||||||
const firstRowIdx = this.root.viewport.firstRow;
|
const firstRowIdx = this.root.viewport.firstRow;
|
||||||
const lastColIdx = this.root.viewport.lastCol + 3;
|
const lastColIdx = this.root.viewport.lastCol + 3;
|
||||||
const lastRowIdx = this.root.viewport.lastRow + 3;
|
const lastRowIdx = this.root.viewport.lastRow + 3;
|
||||||
const firstColIdx = this.root.viewport.firstCol;
|
const firstColIdx = this.root.viewport.firstCol;
|
||||||
|
|
||||||
|
|
||||||
for (let row = firstRowIdx; row <= lastRowIdx; row++) {
|
for (let row = firstRowIdx; row <= lastRowIdx; row++) {
|
||||||
for (let col = firstColIdx; col <= lastColIdx; col++) {
|
for (let col = firstColIdx; col <= lastColIdx; col++) {
|
||||||
if (!this.root.config.columns[col] || !this.root.config.rows[row])
|
if (!this.root.config.columns[col] || !this.root.config.rows[row])
|
||||||
|
|
@ -133,6 +66,5 @@ export class Sheet {
|
||||||
this.renderCell({ column: col, row });
|
this.renderCell({ column: col, row });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.renderSelection();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/index.ts
12
src/index.ts
|
|
@ -2,21 +2,19 @@ import Spreadsheet, { SpreadsheetConstructorProperties } from "./main";
|
||||||
|
|
||||||
const options: SpreadsheetConstructorProperties = {
|
const options: SpreadsheetConstructorProperties = {
|
||||||
onCellClick: (event, cell) => {
|
onCellClick: (event, cell) => {
|
||||||
console.log("Cell click", event, cell);
|
console.log('Cell click', event, cell)
|
||||||
},
|
},
|
||||||
onSelectionChange: (selection) => {
|
onSelectionChange: (selection) => {
|
||||||
console.log("Changed selection: ", selection);
|
console.log("Changed selection: ", selection)
|
||||||
},
|
},
|
||||||
onCellChange(cell) {
|
onCellChange(cell) {
|
||||||
console.log("Cell changed: ", cell);
|
console.log("Cell changed: ", cell)
|
||||||
},
|
},
|
||||||
onCopy: (range, data, dataAsString) => {
|
}
|
||||||
console.log("Copy event: ", range, data, dataAsString)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sheet = new Spreadsheet("#spreadsheet", options);
|
const sheet = new Spreadsheet("#spreadsheet", options);
|
||||||
|
|
||||||
|
|
||||||
function saveDataToLS() {
|
function saveDataToLS() {
|
||||||
const serializableData = sheet.serializeData();
|
const serializableData = sheet.serializeData();
|
||||||
localStorage.setItem("sheet", JSON.stringify(serializableData));
|
localStorage.setItem("sheet", JSON.stringify(serializableData));
|
||||||
|
|
|
||||||
68
src/main.ts
68
src/main.ts
|
|
@ -10,14 +10,7 @@ import {
|
||||||
Position,
|
Position,
|
||||||
SerializableCell,
|
SerializableCell,
|
||||||
} from "./modules/cell";
|
} from "./modules/cell";
|
||||||
import {
|
import { CellChangeEvent, CellClickEvent, Config, SelectionChangeEvent, ViewProperties } from "./modules/config";
|
||||||
CellChangeEvent,
|
|
||||||
CellClickEvent,
|
|
||||||
Config,
|
|
||||||
CopyEvent,
|
|
||||||
SelectionChangeEvent,
|
|
||||||
ViewProperties,
|
|
||||||
} from "./modules/config";
|
|
||||||
import { RangeSelectionType, Selection } from "./modules/selection";
|
import { RangeSelectionType, Selection } from "./modules/selection";
|
||||||
import { Styles } from "./modules/styles";
|
import { Styles } from "./modules/styles";
|
||||||
import { Viewport } from "./modules/viewport";
|
import { Viewport } from "./modules/viewport";
|
||||||
|
|
@ -28,9 +21,7 @@ import { Row } from "./modules/row";
|
||||||
import { Column } from "./modules/column";
|
import { Column } from "./modules/column";
|
||||||
import { ColumnsBar } from "./components/columnsBar";
|
import { ColumnsBar } from "./components/columnsBar";
|
||||||
import { RowsBar } from "./components/rowsBar";
|
import { RowsBar } from "./components/rowsBar";
|
||||||
import { EventTypes, Events } from "./modules/events";
|
import { Events } from "./modules/events";
|
||||||
import { Clipboard } from "./modules/clipboard";
|
|
||||||
import { FormulaParser } from "./modules/formulaParser";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
! Component structure
|
! Component structure
|
||||||
|
|
@ -46,10 +37,9 @@ import { FormulaParser } from "./modules/formulaParser";
|
||||||
|
|
||||||
export interface SpreadsheetConstructorProperties {
|
export interface SpreadsheetConstructorProperties {
|
||||||
view?: ViewProperties;
|
view?: ViewProperties;
|
||||||
onCellClick?: CellClickEvent | null;
|
onCellClick?: CellClickEvent | null
|
||||||
onSelectionChange?: SelectionChangeEvent | null;
|
onSelectionChange?: SelectionChangeEvent | null
|
||||||
onCellChange?: CellChangeEvent | null;
|
onCellChange?: CellChangeEvent | null
|
||||||
onCopy?: CopyEvent | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CSS_PREFIX = "modern_sc_";
|
export const CSS_PREFIX = "modern_sc_";
|
||||||
|
|
@ -68,9 +58,7 @@ export default class Spreadsheet {
|
||||||
public viewport: Viewport;
|
public viewport: Viewport;
|
||||||
public selection: Selection;
|
public selection: Selection;
|
||||||
public cache: Cache;
|
public cache: Cache;
|
||||||
public events: Events;
|
public events: Events
|
||||||
public clipboard: Clipboard;
|
|
||||||
public formulaParser: FormulaParser
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
target: string | HTMLElement,
|
target: string | HTMLElement,
|
||||||
|
|
@ -87,10 +75,9 @@ export default class Spreadsheet {
|
||||||
|
|
||||||
this.config = new Config(config);
|
this.config = new Config(config);
|
||||||
|
|
||||||
this.config.onCellClick = props?.onCellClick ?? null;
|
this.config.onCellClick = props?.onCellClick ?? null
|
||||||
this.config.onSelectonChange = props?.onSelectionChange ?? null;
|
this.config.onSelectonChange = props?.onSelectionChange ?? null
|
||||||
this.config.onCellChange = props?.onCellChange ?? null;
|
this.config.onCellChange = props?.onCellChange ?? null
|
||||||
this.config.onCopy = props?.onCopy ?? null
|
|
||||||
|
|
||||||
this.rowsBar = new RowsBar(this);
|
this.rowsBar = new RowsBar(this);
|
||||||
this.columnsBar = new ColumnsBar(this);
|
this.columnsBar = new ColumnsBar(this);
|
||||||
|
|
@ -105,9 +92,8 @@ export default class Spreadsheet {
|
||||||
this.scroller.getViewportBoundlingRect(),
|
this.scroller.getViewportBoundlingRect(),
|
||||||
);
|
);
|
||||||
this.selection = new Selection();
|
this.selection = new Selection();
|
||||||
this.events = new Events(this);
|
this.events = new Events(this)
|
||||||
this.clipboard = new Clipboard(this);
|
|
||||||
this.formulaParser = new FormulaParser(this)
|
|
||||||
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.styles = new Styles();
|
this.styles = new Styles();
|
||||||
|
|
@ -127,6 +113,7 @@ export default class Spreadsheet {
|
||||||
private setColumnsBarPosition() {
|
private setColumnsBarPosition() {
|
||||||
const top = this.toolbar.height;
|
const top = this.toolbar.height;
|
||||||
const left = this.rowsBar.width;
|
const left = this.rowsBar.width;
|
||||||
|
console.log(top, left);
|
||||||
this.columnsBar.setElementPosition(top, left);
|
this.columnsBar.setElementPosition(top, left);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +152,8 @@ export default class Spreadsheet {
|
||||||
rows: cachedRows,
|
rows: cachedRows,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("CACHE: ", cache);
|
||||||
|
console.log("CONFIG: ", this.config);
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,19 +237,10 @@ export default class Spreadsheet {
|
||||||
changeCellValues(
|
changeCellValues(
|
||||||
position: Position,
|
position: Position,
|
||||||
values: Partial<Omit<CellConstructorProps, "position">>,
|
values: Partial<Omit<CellConstructorProps, "position">>,
|
||||||
enableCallback: boolean = true
|
|
||||||
) {
|
) {
|
||||||
const { column, row } = position;
|
const { column, row } = position;
|
||||||
|
|
||||||
|
|
||||||
this.data[row][column].changeValues(values);
|
this.data[row][column].changeValues(values);
|
||||||
|
|
||||||
this.events.dispatch({
|
|
||||||
type: EventTypes.CELL_CHANGE,
|
|
||||||
cell: this.data[row][column],
|
|
||||||
enableCallback: enableCallback
|
|
||||||
})
|
|
||||||
|
|
||||||
this.renderCell(row, column);
|
this.renderCell(row, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,10 +295,6 @@ export default class Spreadsheet {
|
||||||
this.sheet.renderSheet();
|
this.sheet.renderSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSelection() {
|
|
||||||
this.sheet.renderSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderColumnsBar() {
|
renderColumnsBar() {
|
||||||
this.columnsBar.renderBar();
|
this.columnsBar.renderBar();
|
||||||
}
|
}
|
||||||
|
|
@ -333,11 +309,11 @@ export default class Spreadsheet {
|
||||||
|
|
||||||
public loadData(data: Cell[][] | SerializableCell[][]): Spreadsheet {
|
public loadData(data: Cell[][] | SerializableCell[][]): Spreadsheet {
|
||||||
const rowsLength = data.length;
|
const rowsLength = data.length;
|
||||||
const colsLength = data[0] ? data[0].length : 0;
|
const colsLength = data[0] ? this.data[0].length : 0;
|
||||||
this.data = [];
|
this.data = [];
|
||||||
|
|
||||||
const formattedData: Cell[][] = [];
|
const formattedData: Cell[][] = [];
|
||||||
// Transform serialized objects to Cells
|
|
||||||
for (let row = 0; row < rowsLength; row++) {
|
for (let row = 0; row < rowsLength; row++) {
|
||||||
const innerRow: Cell[] = [];
|
const innerRow: Cell[] = [];
|
||||||
for (let col = 0; col < colsLength; col++) {
|
for (let col = 0; col < colsLength; col++) {
|
||||||
|
|
@ -355,17 +331,11 @@ export default class Spreadsheet {
|
||||||
formattedData.push(innerRow);
|
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.data = formattedData;
|
||||||
|
|
||||||
this.selection.selectedCell = null;
|
this.selection.selectedCell = null;
|
||||||
this.selection.selectedRange = null;
|
this.selection.selectedRange = null;
|
||||||
this.config = config
|
this.config = this.makeConfigFromData(formattedData, this.config.view);
|
||||||
this.cache = this.getInitialCache();
|
this.cache = this.getInitialCache();
|
||||||
this.scroller.updateScrollerSize();
|
this.scroller.updateScrollerSize();
|
||||||
this.viewport = new Viewport(
|
this.viewport = new Viewport(
|
||||||
|
|
@ -406,7 +376,7 @@ export default class Spreadsheet {
|
||||||
view,
|
view,
|
||||||
rows,
|
rows,
|
||||||
columns,
|
columns,
|
||||||
onCellClick: null,
|
onCellClick: null
|
||||||
});
|
});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
|
|
||||||
|
|
@ -44,21 +44,21 @@ export class Cache {
|
||||||
public getRowByYCoord(y: number): number {
|
public getRowByYCoord(y: number): number {
|
||||||
let rowIdx = 0;
|
let rowIdx = 0;
|
||||||
for (let i = 0; i < this.rows.length; i++) {
|
for (let i = 0; i < this.rows.length; i++) {
|
||||||
rowIdx = i
|
if (y <= this.rows[i].yPos) {
|
||||||
if (y <= this.rows[i].yPos) { //* Intersection detect
|
//* Intersection detect
|
||||||
|
rowIdx = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rowIdx;
|
return rowIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getColumnByXCoord(x: number): number {
|
public getColumnByXCoord(x: number): number {
|
||||||
let colIdx = 0;
|
let colIdx = 0;
|
||||||
for (let i = 0; i < this.columns.length; i++) {
|
for (let i = 0; i < this.columns.length; i++) {
|
||||||
colIdx = i
|
if (x <= this.columns[i].xPos) {
|
||||||
if (x <= this.columns[i].xPos) { //* Intersection detect
|
//* Intersection detect
|
||||||
|
colIdx = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import Spreadsheet from "../main";
|
import Spreadsheet from "../main";
|
||||||
import { FormulaParser } from "./formulaParser";
|
|
||||||
import { RenderBox } from "./renderBox";
|
import { RenderBox } from "./renderBox";
|
||||||
|
|
||||||
export type CellConstructorProps = {
|
export type CellConstructorProps = {
|
||||||
|
|
@ -70,9 +69,6 @@ export class Cell {
|
||||||
position: Position;
|
position: Position;
|
||||||
style: CellStyles | null = null;
|
style: CellStyles | null = null;
|
||||||
|
|
||||||
cellsDependsOnThisCell: Position[] = []
|
|
||||||
dependedFromCells: Position[] = []
|
|
||||||
|
|
||||||
constructor(props: CellConstructorProps) {
|
constructor(props: CellConstructorProps) {
|
||||||
this.value = props.value;
|
this.value = props.value;
|
||||||
this.displayValue = props.displayValue;
|
this.displayValue = props.displayValue;
|
||||||
|
|
@ -100,53 +96,50 @@ export class Cell {
|
||||||
Object.assign(this, values);
|
Object.assign(this, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
evalFormula(parser: FormulaParser) {
|
private isCellInRange(root: Spreadsheet): boolean {
|
||||||
if (this.value.substring(0, 1) !== '=') return;
|
const { column, row } = this.position;
|
||||||
|
const { selectedRange } = root.selection;
|
||||||
|
|
||||||
this.resultValue = parser.parser.parse(this.value.slice(1), {
|
if (!selectedRange) return false;
|
||||||
col: this.position.column,
|
|
||||||
row: this.position.row
|
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) {
|
render(root: Spreadsheet) {
|
||||||
const renderBox = new RenderBox(root.config, this.position);
|
const renderBox = new RenderBox(root.config, this.position);
|
||||||
let { x, y } = renderBox;
|
let { x, y } = renderBox;
|
||||||
const { height, width } = renderBox;
|
const { height, width } = renderBox;
|
||||||
const { ctx } = root;
|
const { ctx } = root;
|
||||||
|
|
||||||
// const isCellSelected =
|
const isCellSelected =
|
||||||
// root.selection.selectedCell?.row === this.position.row &&
|
root.selection.selectedCell?.row === this.position.row &&
|
||||||
// root.selection.selectedCell.column === this.position.column;
|
root.selection.selectedCell.column === this.position.column;
|
||||||
// const isCellInRange = this.isCellInRange(root);
|
const isCellInRange = this.isCellInRange(root);
|
||||||
y -= root.viewport.top;
|
y -= root.viewport.top;
|
||||||
x -= root.viewport.left;
|
x -= root.viewport.left;
|
||||||
|
|
||||||
const styles = this.style ?? root.styles.cells;
|
const styles = this.style ?? root.styles.cells;
|
||||||
|
|
||||||
ctx.clearRect(x, y, width, height);
|
ctx.clearRect(x, y, width, height);
|
||||||
ctx.fillStyle = styles.background;
|
ctx.fillStyle =
|
||||||
|
isCellSelected || isCellInRange
|
||||||
|
? styles.selectedBackground
|
||||||
|
: styles.background;
|
||||||
ctx.strokeStyle = "black";
|
ctx.strokeStyle = "black";
|
||||||
ctx.fillRect(x, y, width - 1, height - 1);
|
ctx.fillRect(x, y, width - 1, height - 1);
|
||||||
ctx.strokeRect(x, y, width, height);
|
ctx.strokeRect(x, y, width, height);
|
||||||
|
|
||||||
ctx.fillStyle = styles.fontColor;
|
ctx.fillStyle =
|
||||||
|
isCellSelected || isCellInRange
|
||||||
|
? styles.selectedFontColor
|
||||||
|
: styles.fontColor;
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
ctx.font = `${styles.fontSize}px Arial`;
|
ctx.font = `${styles.fontSize}px Arial`;
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
|
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
import Spreadsheet, { RangeSelectionType } from "../main";
|
|
||||||
import { Cell, CellConstructorProps, CellStyles, Position } from "./cell";
|
|
||||||
import { EventTypes } from "./events";
|
|
||||||
|
|
||||||
export class Clipboard {
|
|
||||||
saved: Cell[][] | null = null;
|
|
||||||
root: Spreadsheet;
|
|
||||||
constructor(root: Spreadsheet) {
|
|
||||||
this.root = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(data: Cell[][], range: RangeSelectionType) {
|
|
||||||
const mapedData = data
|
|
||||||
.map((row) => {
|
|
||||||
return row
|
|
||||||
.map((item) => {
|
|
||||||
return item.displayValue;
|
|
||||||
})
|
|
||||||
.join("\t");
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
this.saved = data;
|
|
||||||
navigator.clipboard.writeText(mapedData);
|
|
||||||
|
|
||||||
this.root.events.dispatch({
|
|
||||||
type: EventTypes.COPY_CELLS,
|
|
||||||
data,
|
|
||||||
dataAsString: mapedData,
|
|
||||||
range,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
paste(root: Spreadsheet, { column, row }: Position, event: ClipboardEvent) {
|
|
||||||
if (!this.saved) {
|
|
||||||
if (!event.clipboardData) return;
|
|
||||||
const data = event.clipboardData.getData("text");
|
|
||||||
try {
|
|
||||||
const arr = data.split("\n").map((item) => item.split("\t"));
|
|
||||||
const arrayOfCells = arr.map((innerRow) => {
|
|
||||||
return innerRow.map((item) => {
|
|
||||||
const cellProps: CellConstructorProps = {
|
|
||||||
displayValue: item,
|
|
||||||
position: {
|
|
||||||
column,
|
|
||||||
row,
|
|
||||||
},
|
|
||||||
resultValue: item,
|
|
||||||
style: new CellStyles(),
|
|
||||||
value: item,
|
|
||||||
};
|
|
||||||
return new Cell(cellProps);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const rowsLength = arrayOfCells.length;
|
|
||||||
const colsLength = arrayOfCells[0] ? arrayOfCells[0].length : 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < rowsLength; i++) {
|
|
||||||
for (let j = 0; j < colsLength; j++) {
|
|
||||||
const savedCell = arrayOfCells[i][j];
|
|
||||||
|
|
||||||
const position = {
|
|
||||||
column: column + j,
|
|
||||||
row: row + i,
|
|
||||||
};
|
|
||||||
|
|
||||||
const values = {
|
|
||||||
displayValue: savedCell.displayValue,
|
|
||||||
value: savedCell.value,
|
|
||||||
style: savedCell.style,
|
|
||||||
};
|
|
||||||
|
|
||||||
root.changeCellValues(position, values, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Cannot read clipboard. ", err);
|
|
||||||
}
|
|
||||||
root.renderSheet();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowsLength = this.saved.length;
|
|
||||||
const colsLength = this.saved[0] ? this.saved[0].length : 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < rowsLength; i++) {
|
|
||||||
for (let j = 0; j < colsLength; j++) {
|
|
||||||
const savedCell = this.saved[i][j];
|
|
||||||
|
|
||||||
const position = {
|
|
||||||
column: column + j,
|
|
||||||
row: row + i,
|
|
||||||
};
|
|
||||||
|
|
||||||
const values = {
|
|
||||||
displayValue: savedCell.displayValue,
|
|
||||||
value: savedCell.value,
|
|
||||||
style: savedCell.style,
|
|
||||||
};
|
|
||||||
|
|
||||||
root.changeCellValues(position, values, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
import { Cell } from "./cell";
|
import { Cell } from "./cell";
|
||||||
import { Column } from "./column";
|
import { Column } from "./column";
|
||||||
import { Row } from "./row";
|
import { Row } from "./row";
|
||||||
import { RangeSelectionType, Selection } from "./selection";
|
import { Selection } from "./selection";
|
||||||
|
|
||||||
export interface ViewProperties {
|
export interface ViewProperties {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
export type CellClickEvent = (event: MouseEvent, cell: Cell) => void;
|
export type CellClickEvent = (event: MouseEvent, cell: Cell) => void
|
||||||
export type SelectionChangeEvent = (selection: Selection) => void;
|
export type SelectionChangeEvent = (selection: Selection) => void
|
||||||
export type CellChangeEvent = (cell: Cell) => void;
|
export type CellChangeEvent = (cell: Cell) => void
|
||||||
export type CopyEvent = (
|
|
||||||
range: RangeSelectionType,
|
|
||||||
data: Cell[][],
|
|
||||||
dataAsString: string,
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
export type ConfigProperties = {
|
export type ConfigProperties = {
|
||||||
/** Please, end it with '_' symbol.
|
/** Please, end it with '_' symbol.
|
||||||
|
|
@ -26,10 +21,9 @@ export type ConfigProperties = {
|
||||||
rows: Row[];
|
rows: Row[];
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
view: ViewProperties;
|
view: ViewProperties;
|
||||||
onCellClick?: CellClickEvent | null;
|
onCellClick?: CellClickEvent | null
|
||||||
onSelectionChange?: SelectionChangeEvent | null;
|
onSelectionChange?: SelectionChangeEvent | null
|
||||||
onCellChange?: CellChangeEvent | null;
|
onCellChange?: CellChangeEvent | null
|
||||||
onCopy?: CopyEvent | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SheetConfigConstructorProps = {
|
export type SheetConfigConstructorProps = {
|
||||||
|
|
@ -45,19 +39,17 @@ export class Config {
|
||||||
height: 600,
|
height: 600,
|
||||||
};
|
};
|
||||||
|
|
||||||
onCellClick: CellClickEvent | null = null;
|
onCellClick: ((event: MouseEvent, cell: Cell) => void) | null = null
|
||||||
onSelectonChange: SelectionChangeEvent | null = null;
|
onSelectonChange: SelectionChangeEvent | null = null
|
||||||
onCellChange: CellChangeEvent | null = null;
|
onCellChange: CellChangeEvent | null = null
|
||||||
onCopy: CopyEvent | null;
|
|
||||||
|
|
||||||
constructor(props: ConfigProperties) {
|
constructor(props: ConfigProperties) {
|
||||||
this.columns = props.columns;
|
this.columns = props.columns;
|
||||||
this.rows = props.rows;
|
this.rows = props.rows;
|
||||||
this.view = props.view;
|
this.view = props.view;
|
||||||
|
|
||||||
this.onCellClick = props.onCellClick ?? null;
|
this.onCellClick = props.onCellClick ?? null
|
||||||
this.onSelectonChange = props.onSelectionChange ?? null;
|
this.onSelectonChange = props.onSelectionChange ?? null
|
||||||
this.onCellChange = props.onCellChange ?? null;
|
this.onCellChange = props.onCellChange ?? null
|
||||||
this.onCopy = props.onCopy ?? null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,142 +1,107 @@
|
||||||
import { Scroller } from "../components/scroller";
|
import { Scroller } from "../components/scroller";
|
||||||
import Spreadsheet, { Cell, RangeSelectionType, Selection } from "../main";
|
import Spreadsheet, { Cell, CellConstructorProps, Selection } from "../main";
|
||||||
|
|
||||||
export enum EventTypes {
|
export enum EventTypes {
|
||||||
CELL_CLICK = "CELL_CLICK",
|
CELL_CLICK = "CELL_CLICK",
|
||||||
SELECTION_CHANGE = "CHANGE_SELECTION",
|
SELECTION_CHANGE = "CHANGE_SELECTION",
|
||||||
CELL_CHANGE = "CELL_CHANGE",
|
CELL_CHANGE = "CELL_CHANGE"
|
||||||
COPY_CELLS = "COPY_CELLS",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CellClickEvent = {
|
export type CellClickEvent = {
|
||||||
type: EventTypes.CELL_CLICK;
|
type: EventTypes.CELL_CLICK
|
||||||
event: MouseEvent;
|
event: MouseEvent
|
||||||
scroller: Scroller;
|
scroller: Scroller
|
||||||
};
|
}
|
||||||
|
|
||||||
export type ChangeSelectionEvent = {
|
export type ChangeSelectionEvent = {
|
||||||
type: EventTypes.SELECTION_CHANGE;
|
type: EventTypes.SELECTION_CHANGE,
|
||||||
selection: Selection;
|
selection: Selection
|
||||||
enableCallback?: boolean;
|
enableCallback?: boolean
|
||||||
};
|
}
|
||||||
|
|
||||||
export type ChangeCellEvent = {
|
export type ChangeCellEvent = {
|
||||||
type: EventTypes.CELL_CHANGE;
|
type: EventTypes.CELL_CHANGE,
|
||||||
cell: Cell;
|
cell: Cell,
|
||||||
enableCallback?: boolean;
|
values: Partial<Omit<CellConstructorProps, "position">>
|
||||||
};
|
}
|
||||||
|
|
||||||
export type CopyAction = {
|
|
||||||
type: EventTypes.COPY_CELLS;
|
|
||||||
range: RangeSelectionType;
|
|
||||||
data: Cell[][];
|
|
||||||
dataAsString: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ActionTypes =
|
|
||||||
| CellClickEvent
|
|
||||||
| ChangeSelectionEvent
|
|
||||||
| ChangeCellEvent
|
|
||||||
| CopyAction;
|
|
||||||
|
|
||||||
|
export type ActionTypes = CellClickEvent | ChangeSelectionEvent | ChangeCellEvent
|
||||||
|
|
||||||
export class Events {
|
export class Events {
|
||||||
root: Spreadsheet;
|
|
||||||
|
|
||||||
constructor(root: Spreadsheet) {
|
root: Spreadsheet
|
||||||
this.root = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
async dispatch(action: ActionTypes) {
|
constructor(root: Spreadsheet) {
|
||||||
switch (action.type) {
|
this.root = root
|
||||||
case EventTypes.CELL_CLICK: {
|
|
||||||
const { event, scroller } = action;
|
|
||||||
//
|
|
||||||
//* Here may be side effects
|
|
||||||
//
|
|
||||||
this.cellClick(event, scroller);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EventTypes.SELECTION_CHANGE: {
|
|
||||||
const { selection, enableCallback } = action;
|
|
||||||
//
|
|
||||||
//* Here may be side effects
|
|
||||||
//
|
|
||||||
this.changeSelection(selection, enableCallback);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
this.changeCellValues(cell, enableCallback);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EventTypes.COPY_CELLS: {
|
|
||||||
const { data, dataAsString, range } = action;
|
|
||||||
this.copy(range, data, dataAsString);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private cellClick = (event: MouseEvent, scroller: Scroller) => {
|
dispatch(action: ActionTypes) {
|
||||||
if (event.button !== 0) return; // Left mouse button
|
switch (action.type) {
|
||||||
const { offsetX, offsetY } = event;
|
case EventTypes.CELL_CLICK: {
|
||||||
const clickedCell = this.root.getCellByCoords(offsetX, offsetY);
|
const { event, scroller } = action
|
||||||
const cell = this.root.getCell(clickedCell);
|
//
|
||||||
|
//* Here may be side effects
|
||||||
|
//
|
||||||
|
this.cellClick(event, scroller)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const selection = new Selection();
|
case EventTypes.SELECTION_CHANGE: {
|
||||||
selection.selectedCell = clickedCell;
|
const { selection, enableCallback } = action
|
||||||
selection.selectedRange = {
|
//
|
||||||
from: clickedCell,
|
//* Here may be side effects
|
||||||
to: clickedCell,
|
//
|
||||||
};
|
this.changeSelection(selection, enableCallback)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
scroller.setSelectingMode(true);
|
case EventTypes.CELL_CHANGE: {
|
||||||
|
const { cell, values } = action
|
||||||
|
//
|
||||||
|
//* Here may be side effects
|
||||||
|
//
|
||||||
|
this.changeCellValues(cell, values)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
this.changeSelection(selection, true);
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.root.config.onCellClick?.(event, cell);
|
private cellClick = (event: MouseEvent, scroller: Scroller) => {
|
||||||
};
|
if (event.button !== 0) return; // Left mouse button
|
||||||
|
const { offsetX, offsetY } = event;
|
||||||
|
const clickedCell = this.root.getCellByCoords(offsetX, offsetY);
|
||||||
|
const cell = this.root.getCell(clickedCell)
|
||||||
|
|
||||||
private changeSelection = (selection: Selection, enableCallback = false) => {
|
const selection = new Selection()
|
||||||
this.root.selection = selection;
|
selection.selectedCell = clickedCell
|
||||||
|
selection.selectedRange = {
|
||||||
|
from: clickedCell,
|
||||||
|
to: clickedCell,
|
||||||
|
};
|
||||||
|
|
||||||
if (enableCallback) this.root.config.onSelectonChange?.(selection);
|
scroller.setSelectingMode(true);
|
||||||
this.root.renderSheet();
|
|
||||||
this.root.renderColumnsBar();
|
|
||||||
this.root.renderRowsBar();
|
|
||||||
};
|
|
||||||
|
|
||||||
private changeCellValues(cell: Cell, enableCallback: boolean = true) {
|
this.changeSelection(selection, true)
|
||||||
if (enableCallback) this.root.config.onCellChange?.(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
private copy = (
|
this.root.config.onCellClick?.(event, cell)
|
||||||
range: RangeSelectionType,
|
}
|
||||||
data: Cell[][],
|
|
||||||
dataAsString: string,
|
private changeSelection = (selection: Selection, enableCallback = false) => {
|
||||||
) => {
|
this.root.selection = selection
|
||||||
this.root.config.onCopy?.(range, data, dataAsString);
|
|
||||||
};
|
if (enableCallback) this.root.config.onSelectonChange?.(selection)
|
||||||
}
|
this.root.renderSheet();
|
||||||
|
this.root.renderColumnsBar();
|
||||||
|
this.root.renderRowsBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
private changeCellValues(cell: Cell, values: Partial<Omit<CellConstructorProps, "position">>) {
|
||||||
|
this.root.changeCellValues(cell.position, values)
|
||||||
|
|
||||||
|
this.root.config.onCellChange?.(cell)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,12 @@
|
||||||
import { BaseSelectionType, RangeSelectionType } from "../main";
|
import { BaseSelectionType, RangeSelectionType } from "../main";
|
||||||
|
|
||||||
export function checkEqualRanges(
|
export function checkEqualRanges(range1: RangeSelectionType, range2: RangeSelectionType) {
|
||||||
range1: RangeSelectionType,
|
const equalRows = range1.from.row === range2.to.row
|
||||||
range2: RangeSelectionType,
|
const equalColumns = range1.from.column === range2.to.column
|
||||||
) {
|
|
||||||
const equalRows = range1.from.row === range2.to.row;
|
|
||||||
const equalColumns = range1.from.column === range2.to.column;
|
|
||||||
|
|
||||||
return equalRows && equalColumns;
|
return equalRows && equalColumns
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkEqualCellSelections(
|
export function checkEqualCellSelections(selection1: BaseSelectionType, selection2: BaseSelectionType) {
|
||||||
selection1: BaseSelectionType,
|
return selection1.column === selection2.column && selection1.row === selection2.row
|
||||||
selection2: BaseSelectionType,
|
}
|
||||||
) {
|
|
||||||
return (
|
|
||||||
selection1.column === selection2.column && selection1.row === selection2.row
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"types": ["vite/client", "node"],
|
"types": ["vite/client", "node"],
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": false,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,14 @@ import { defineConfig } from "vite";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import typescript from "@rollup/plugin-typescript";
|
import typescript from "@rollup/plugin-typescript";
|
||||||
import { typescriptPaths } from "rollup-plugin-typescript-paths";
|
import { typescriptPaths } from "rollup-plugin-typescript-paths";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const BROWSER_MODE = process.env.BUILD_BROWSER === 'true';
|
export default defineConfig({
|
||||||
console.log({ BROWSER_MODE });
|
|
||||||
|
|
||||||
const libConfig = defineConfig({
|
|
||||||
base: "/modern_spreadsheet/",
|
base: "/modern_spreadsheet/",
|
||||||
plugins: [],
|
plugins: [],
|
||||||
resolve: {},
|
resolve: {},
|
||||||
server: {
|
server: {
|
||||||
port: 5179,
|
port: 3000,
|
||||||
open: true,
|
open: true,
|
||||||
},
|
},
|
||||||
build: {
|
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