diff --git a/index.html b/index.html
index 0d15dc6e..7393928a 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
-
+
diff --git a/index.js b/index.js
index 13870b71..5517329d 100644
--- a/index.js
+++ b/index.js
@@ -1,23 +1,40 @@
import Lightsheet from "./src/main.ts";
var data = [
- ["", "=1+2/3*6+A1+test(1,2)", "img/nophoto.jpg", "Marketing"],
- ["2", "Jorge", "img/nophoto.jpg", "Marketing", "3120"],
- ["3", "Jorge", "img/nophoto.jpg", "Marketing", "3120"],
+ ["1", "=1+2/3*6+A1+test(1,2)", "img/nophoto.jpg", "Marketing"],
+ ["2.44445", "400000.000000", "img/nophoto.jpg", "Marketing", "3120"],
+ ["3.555555", "312", "43", "64", "3120"],
];
const toolbar = ["undo", "redo", "save"];
new Lightsheet(
{
- sheetName: "Sheet1",
data,
onCellChange: (colIndex, rowIndex, newValue) => {
console.log(colIndex, rowIndex, newValue);
},
toolbarOptions: {
- showToolbar: true,
+ showToolbar: false,
items: toolbar,
+ element: document.getElementById("toolbar-dom-id"),
},
+ style: [
+ {
+ position: "A",
+ css: "font-weight: bold;",
+ format: { type: "number", options: { decimal: 2 } },
+ },
+ {
+ position: "B2",
+ css: "background-color: yellow;",
+ format: { type: "number", options: { decimal: 2 } },
+ },
+ {
+ position: "3",
+ css: "background-color: gray;",
+ format: { type: "number", options: { decimal: 0 } },
+ },
+ ],
},
document.getElementById("lightsheet"),
);
diff --git a/src/core/evaluation/expressionHandler.ts b/src/core/evaluation/expressionHandler.ts
index 3ece47e9..88c6acb8 100644
--- a/src/core/evaluation/expressionHandler.ts
+++ b/src/core/evaluation/expressionHandler.ts
@@ -18,8 +18,8 @@ import {
} from "./expressionHandler.types.ts";
import { CellState } from "../structure/cell/cellState.ts";
-import LightsheetHelper from "../../utils/helpers.ts";
-import { Coordinate } from "../../utils/common.types.ts";
+import { GenerateColumnLabel } from "../../utils/helpers.ts";
+import { IndexPosition } from "../../utils/common.types.ts";
import { CellReference } from "../structure/cell/types.cell.ts";
import SheetHolder from "../structure/sheetHolder.ts";
@@ -86,16 +86,16 @@ export default class ExpressionHandler {
}
}
- updatePositionalReferences(from: Coordinate, to: Coordinate) {
+ updatePositionalReferences(from: IndexPosition, to: IndexPosition) {
if (!this.rawValue.startsWith("=")) return this.rawValue;
const expression = this.rawValue.substring(1);
const parseResult = math.parse(expression);
const fromSymbol =
- LightsheetHelper.generateColumnLabel(from.column + 1) + (from.row + 1);
+ GenerateColumnLabel(from.columnIndex! + 1) + (from.rowIndex! + 1);
const toSymbol =
- LightsheetHelper.generateColumnLabel(to.column + 1) + (to.row + 1);
+ GenerateColumnLabel(to.columnIndex! + 1) + (to.rowIndex! + 1);
// Update each symbol in the expression.
const transform = parseResult.transform((node) =>
@@ -212,7 +212,7 @@ export default class ExpressionHandler {
this.cellRefHolder.push({
sheetKey: targetSheet.key,
- position: { column: j, row: i },
+ position: { columnIndex: j, rowIndex: i },
});
values.push(cellInfo?.resolvedValue ?? "");
}
@@ -230,8 +230,8 @@ export default class ExpressionHandler {
this.cellRefHolder.push({
sheetKey: targetSheet.key,
position: {
- column: colIndex,
- row: rowIndex,
+ columnIndex: colIndex,
+ rowIndex: rowIndex,
},
});
return cellInfo?.resolvedValue ?? "";
diff --git a/src/core/evaluation/expressionHandler.types.ts b/src/core/evaluation/expressionHandler.types.ts
index 4b8b56a7..e88bc6ef 100644
--- a/src/core/evaluation/expressionHandler.types.ts
+++ b/src/core/evaluation/expressionHandler.types.ts
@@ -1,9 +1,9 @@
+import { IndexPosition } from "../../utils/common.types.ts";
import { SheetKey } from "../structure/key/keyTypes.ts";
-import { Coordinate } from "../../utils/common.types.ts";
export type CellSheetPosition = {
sheetKey: SheetKey;
- position: Coordinate;
+ position: IndexPosition;
};
export type EvaluationResult = {
diff --git a/src/core/event/event.ts b/src/core/event/event.ts
index eda0cfc6..78d78dd9 100644
--- a/src/core/event/event.ts
+++ b/src/core/event/event.ts
@@ -1,5 +1,5 @@
-import EventType from "./eventType";
import EventState from "./eventState";
+import { EventType } from "./events.types";
export default class Event {
eventType: EventType;
diff --git a/src/core/event/eventType.ts b/src/core/event/eventType.ts
deleted file mode 100644
index 90abeaf2..00000000
--- a/src/core/event/eventType.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-enum EventType {
- UI_SET_CELL = 0,
- CORE_SET_CELL = 1,
-}
-
-export default EventType;
diff --git a/src/core/event/events.ts b/src/core/event/events.ts
index 6852ad69..876687a8 100644
--- a/src/core/event/events.ts
+++ b/src/core/event/events.ts
@@ -1,6 +1,6 @@
-import EventType from "./eventType";
import Event from "./event";
import EventState from "./eventState";
+import { EventType } from "./events.types";
export type ListenerFunction = (event: Event) => void;
diff --git a/src/core/event/events.types.ts b/src/core/event/events.types.ts
index ec442865..c80724f3 100644
--- a/src/core/event/events.types.ts
+++ b/src/core/event/events.types.ts
@@ -1,18 +1,29 @@
-import { PositionInfo } from "../structure/sheet.types.ts";
-import { Coordinate } from "../../utils/common.types.ts";
+import { IndexPosition } from "../../utils/common.types.ts";
+import { KeyPosition } from "../structure/sheet.types.ts";
export type UISetCellPayload = {
- keyPosition?: PositionInfo;
- indexPosition?: Coordinate;
+ keyPosition?: KeyPosition;
+ indexPosition?: IndexPosition;
rawValue: string;
};
export type CoreSetCellPayload = {
- keyPosition: PositionInfo;
- indexPosition: Coordinate;
+ keyPosition?: KeyPosition;
+ indexPosition: IndexPosition;
rawValue: string;
formattedValue: string;
- clearCell: boolean;
- clearRow: boolean;
+ clearCell?: boolean;
+ clearRow?: boolean;
};
+
+export type CoreSetStylePayload = {
+ indexPosition: IndexPosition;
+ value: string;
+};
+
+export enum EventType {
+ VIEW_SET_CELL = 0,
+ CORE_SET_CELL = 1,
+ VIEW_SET_STYLE = 2,
+}
diff --git a/src/core/structure/cellStyle.ts b/src/core/structure/cellStyle.ts
index 92babfae..5a714a2d 100644
--- a/src/core/structure/cellStyle.ts
+++ b/src/core/structure/cellStyle.ts
@@ -3,24 +3,24 @@ import Cloneable from "../cloneable.ts";
export default class CellStyle extends Cloneable {
formatter: Formatter | null;
- styling: Map;
+ css: Map;
constructor(
- styling: Map | null = null,
+ css: Map | null = null,
formatter: Formatter | null = null,
) {
super();
this.formatter = formatter;
- this.styling = new Map(styling);
+ this.css = new Map(css);
}
applyStylesOf(other: CellStyle | null): CellStyle {
if (!other) return this;
// If a style is set in other but not in this, apply it to this.
- for (const [key, value] of other.styling) {
- if (!this.styling.has(key)) {
- this.styling.set(key, value);
+ for (const [key, value] of other.css) {
+ if (!this.css.has(key)) {
+ this.css.set(key, value);
}
}
@@ -31,19 +31,30 @@ export default class CellStyle extends Cloneable {
return this;
}
+ applyCss(css: Map): CellStyle {
+ // If a style is set in other but not in this, apply it to this.
+ for (const [key, value] of css) {
+ if (!this.css.has(key)) {
+ this.css.set(key, value);
+ }
+ }
+
+ return this;
+ }
+
clearStylingSetBy(other: CellStyle | null) {
if (!other) return false;
let isEmpty = true;
// If a property is set in other, clear it from this.
- for (const key in other.styling) {
- if (this.styling.has(key)) {
- this.styling.delete(key);
+ for (const key in other.css) {
+ if (this.css.has(key)) {
+ this.css.delete(key);
}
if (other.formatter) this.formatter = null;
- if (isEmpty && (this.styling.has(key) || this.formatter)) isEmpty = false;
+ if (isEmpty && (this.css.has(key) || this.formatter)) isEmpty = false;
}
return isEmpty;
diff --git a/src/core/structure/sheet.ts b/src/core/structure/sheet.ts
index 113b054d..21dc88d8 100644
--- a/src/core/structure/sheet.ts
+++ b/src/core/structure/sheet.ts
@@ -8,19 +8,31 @@ import {
import Cell from "./cell/cell.ts";
import Column from "./group/column.ts";
import Row from "./group/row.ts";
-import { CellInfo, PositionInfo, ShiftDirection } from "./sheet.types.ts";
+import {
+ CellInfo,
+ GroupType,
+ GroupTypes,
+ KeyPosition,
+ ShiftDirection,
+} from "./sheet.types.ts";
import ExpressionHandler from "../evaluation/expressionHandler.ts";
import CellStyle from "./cellStyle.ts";
import CellGroup from "./group/cellGroup.ts";
import Events from "../event/events.ts";
import LightsheetEvent from "../event/event.ts";
-import { CoreSetCellPayload, UISetCellPayload } from "../event/events.types.ts";
-import EventType from "../event/eventType.ts";
+import {
+ CoreSetCellPayload,
+ CoreSetStylePayload,
+ EventType,
+ UISetCellPayload,
+} from "../event/events.types.ts";
import { CellState } from "./cell/cellState.ts";
import { EvaluationResult } from "../evaluation/expressionHandler.types.ts";
+import Formatter from "../evaluation/formatter.ts";
import SheetHolder from "./sheetHolder.ts";
import { CellReference } from "./cell/types.cell.ts";
-import { Coordinate } from "../../utils/common.types.ts";
+import { GenerateStyleStringFromMap } from "../../utils/helpers.ts";
+import { IndexPosition } from "../../utils/common.types.ts";
export default class Sheet {
readonly key: SheetKey;
@@ -98,7 +110,7 @@ export default class Sheet {
this.resolveCell(cell!, colKey, rowKey);
}
- this.emitSetCellEvent(colKey, rowKey, colIndex, rowIndex, cell);
+ this.emitSetCellEvent(colIndex, rowIndex, cell);
return {
rawValue: cell ? cell.rawValue : undefined,
@@ -113,17 +125,23 @@ export default class Sheet {
}
public moveCell(
- from: Coordinate,
- to: Coordinate,
+ from: IndexPosition,
+ to: IndexPosition,
moveStyling: boolean = true,
) {
- const fromPosition = this.getCellInfoAt(from.column, from.row)?.position;
- let toPosition = this.getCellInfoAt(to.column, to.row)?.position;
+ const fromPosition = this.getCellInfoAt(
+ from.columnIndex!,
+ from.rowIndex!,
+ )?.position;
+ let toPosition = this.getCellInfoAt(
+ to.columnIndex!,
+ to.rowIndex!,
+ )?.position;
if (!fromPosition) return false;
if (!toPosition) {
- toPosition = this.initializePosition(to.column, to.row);
+ toPosition = this.initializePosition(to.columnIndex!, to.rowIndex!);
} else {
this.deleteCell(toPosition.columnKey!, toPosition.rowKey!);
}
@@ -331,14 +349,14 @@ export default class Sheet {
? this.rows.get(oppositeKey as RowKey)!.position
: this.columns.get(oppositeKey as ColumnKey)!.position;
- const fromCoord: Coordinate = {
- column: group instanceof Column ? from : oppositeGroupPos!,
- row: group instanceof Row ? from : oppositeGroupPos!,
+ const fromCoord: IndexPosition = {
+ columnIndex: group instanceof Column ? from : oppositeGroupPos!,
+ rowIndex: group instanceof Row ? from : oppositeGroupPos!,
};
- const toCoord: Coordinate = {
- column: group instanceof Column ? to : oppositeGroupPos!,
- row: group instanceof Row ? to : oppositeGroupPos!,
+ const toCoord: IndexPosition = {
+ columnIndex: group instanceof Column ? to : oppositeGroupPos!,
+ rowIndex: group instanceof Row ? to : oppositeGroupPos!,
};
this.updateCellReferenceSymbols(cell, fromCoord, toCoord);
@@ -347,8 +365,8 @@ export default class Sheet {
private updateCellReferenceSymbols(
cell: Cell,
- from: Coordinate,
- to: Coordinate,
+ from: IndexPosition,
+ to: IndexPosition,
) {
// Update reference symbols for all cell formulas that refer to the cell being moved.
for (const [refCellKey, refInfo] of cell.referencesIn) {
@@ -364,8 +382,6 @@ export default class Sheet {
// Emit event for the rawValue change.
refSheet.emitSetCellEvent(
- refInfo.column,
- refInfo.row,
refSheet.getColumnIndex(refInfo.column)!,
refSheet.getRowIndex(refInfo.row)!,
refCell,
@@ -416,7 +432,10 @@ export default class Sheet {
return true;
}
- getCellStyle(colKey?: ColumnKey, rowKey?: RowKey): CellStyle {
+ getMergedCellStyle(
+ colKey: ColumnKey | null = null,
+ rowKey: RowKey | null = null,
+ ): CellStyle {
const col = colKey ? this.columns.get(colKey) : null;
const row = rowKey ? this.rows.get(rowKey) : null;
if (!col && !row) return this.defaultStyle;
@@ -433,46 +452,96 @@ export default class Sheet {
return cellStyle;
}
- setCellStyle(
- colKey: ColumnKey,
- rowKey: RowKey,
- style: CellStyle | null,
- ): boolean {
- const col = this.columns.get(colKey);
- const row = this.rows.get(rowKey);
- if (!col || !row) return false;
-
- if (style == null) {
- return this.clearCellStyle(colKey, rowKey);
- }
-
- // TODO Style could be non-null but empty; should we allow this?
- style = new CellStyle().clone(style);
+ setCellFormatter(
+ columnIndex: number,
+ rowIndex: number,
+ formatter: Formatter | null = null,
+ ): void {
+ const { columnKey, rowKey } = this.initializePosition(
+ columnIndex,
+ rowIndex,
+ );
+ const column = this.columns.get(columnKey!);
+ const row = this.rows.get(rowKey!);
- col.cellFormatting.set(row.key, style);
- row.cellFormatting.set(col.key, style);
+ if (!column || !row) return;
- if (style.formatter) {
- this.applyCellFormatter(this.getCell(colKey, rowKey)!, colKey, rowKey);
- }
+ const newStyle = new CellStyle().clone(column.cellFormatting.get(row.key));
+ newStyle.formatter = formatter;
+ column.cellFormatting.set(row.key, newStyle);
+ row.cellFormatting.set(column.key, newStyle);
- return true;
- }
+ const cell = this.getCell(columnKey!, rowKey!);
- setColumnStyle(colKey: ColumnKey, style: CellStyle | null): boolean {
- const col = this.columns.get(colKey);
- if (!col) return false;
+ this.applyCellFormatter(cell!, columnKey!, rowKey!);
- this.setCellGroupStyle(col, style);
- return true;
+ this.deleteCellIfUnused(columnKey!, rowKey!);
+ this.emitSetCellEvent(columnIndex, rowIndex, cell);
}
- setRowStyle(rowKey: RowKey, style: CellStyle | null): boolean {
- const row = this.rows.get(rowKey);
- if (!row) return false;
-
- this.setCellGroupStyle(row, style);
- return true;
+ setCellCss(
+ columnIndex: number,
+ rowIndex: number,
+ css: Map = new Map(),
+ ): void {
+ const { columnKey, rowKey } = this.initializePosition(
+ columnIndex,
+ rowIndex,
+ );
+ const column = this.columns.get(columnKey!);
+ const row = this.rows.get(rowKey!);
+
+ if (!column || !row) return;
+
+ const newStyle = new CellStyle().clone(column.cellFormatting.get(row.key));
+ newStyle.css = css;
+ column.cellFormatting.set(row.key, newStyle);
+ row.cellFormatting.set(column.key, newStyle);
+
+ this.deleteCellIfUnused(columnKey!, rowKey!);
+ this.emitSetStyleEvent(columnIndex, rowIndex, columnKey, rowKey);
+ }
+
+ setGroupCss(
+ groupIndex: number,
+ groupType: GroupType,
+ css: Map = new Map(),
+ ): void {
+ const isColumnGroup = groupType == GroupTypes.Column;
+ const groupKey = isColumnGroup
+ ? this.columnPositions.get(groupIndex)
+ : this.rowPositions.get(groupIndex);
+ if (!groupKey) return;
+
+ const group = isColumnGroup
+ ? this.columns.get(groupKey as ColumnKey)
+ : this.rows.get(groupKey as RowKey);
+ if (!group) return;
+
+ group.defaultStyle = new CellStyle(css, group.defaultStyle?.formatter);
+ isColumnGroup
+ ? this.emitSetStyleEvent(groupIndex, null, groupKey as ColumnKey, null)
+ : this.emitSetStyleEvent(null, groupIndex, null, groupKey as RowKey);
+ }
+
+ setGroupFormatter(
+ groupIndex: number,
+ groupType: GroupType,
+ formatter: Formatter | null = null,
+ ): void {
+ const isColumnGroup: boolean = groupType == GroupTypes.Column;
+ const groupKey = isColumnGroup
+ ? this.columnPositions.get(groupIndex)
+ : this.rowPositions.get(groupIndex);
+ if (!groupKey) return;
+ const group = isColumnGroup
+ ? this.columns.get(groupKey as ColumnKey)
+ : this.rows.get(groupKey as RowKey);
+ if (!group) return;
+
+ const cellStyle = new CellStyle(group.defaultStyle?.css, formatter);
+
+ this.setCellGroupStyle(group, cellStyle);
}
private setCellGroupStyle(
@@ -482,7 +551,6 @@ export default class Sheet {
style = style ? new CellStyle().clone(style) : null;
const formatterChanged = style?.formatter != group.defaultStyle?.formatter;
group.defaultStyle = style;
-
// Iterate through formatted cells in this group and clear any styling properties set by the new style.
for (const [opposingKey, cellStyle] of group.cellFormatting) {
const shouldClear = cellStyle.clearStylingSetBy(style);
@@ -497,18 +565,18 @@ export default class Sheet {
}
if (!formatterChanged) return;
-
// Apply new formatter to all cells in this group.
for (const [opposingKey] of group.cellIndex) {
const cell = this.cellData.get(group.cellIndex.get(opposingKey)!)!;
- if (group instanceof Column) {
- this.applyCellFormatter(cell, group.key, opposingKey as RowKey);
- continue;
- }
- this.applyCellFormatter(
+ const colKey = (
+ group instanceof Column ? group.key : opposingKey
+ ) as ColumnKey;
+ const rowKey = (group instanceof Row ? group.key : opposingKey) as RowKey;
+ this.applyCellFormatter(cell, colKey, rowKey);
+ this.emitSetCellEvent(
+ this.getColumnIndex(colKey)!,
+ this.getRowIndex(rowKey)!,
cell,
- opposingKey as ColumnKey,
- group.key as RowKey,
);
}
}
@@ -637,8 +705,6 @@ export default class Sheet {
// Emit event if the referred cell's value has changed (for the referring sheet's events).
if (refUpdated) {
referringSheet.emitSetCellEvent(
- refInfo.column,
- refInfo.row,
referringSheet.getColumnIndex(refInfo.column)!,
referringSheet.getRowIndex(refInfo.row)!,
referringCell,
@@ -649,12 +715,9 @@ export default class Sheet {
return valueChanged;
}
- private applyCellFormatter(
- cell: Cell,
- colKey: ColumnKey,
- rowKey: RowKey,
- ): boolean {
- const style = this.getCellStyle(colKey, rowKey);
+ private applyCellFormatter(cell: Cell, colKey: ColumnKey, rowKey: RowKey) {
+ if (!cell) return;
+ const style = this.getMergedCellStyle(colKey, rowKey);
let formattedValue: string | null = cell.resolvedValue;
if (style?.formatter) {
formattedValue = style.formatter.format(formattedValue);
@@ -665,14 +728,15 @@ export default class Sheet {
}
cell.formattedValue = formattedValue;
- return true;
+ return;
}
/**
* Delete a cell if it's empty, has no formatting and is not referenced by any other cell.
*/
private deleteCellIfUnused(colKey: ColumnKey, rowKey: RowKey): boolean {
- const cell = this.getCell(colKey, rowKey)!;
+ const cell = this.getCell(colKey, rowKey);
+ if (!cell) return false;
if (cell.rawValue != "") return false;
// Check if this cell is referenced by anything.
@@ -702,10 +766,15 @@ export default class Sheet {
// Initialize the referred cell if it doesn't exist yet.
const position = refSheet.initializePosition(
- ref.position.column,
- ref.position.row,
+ ref.position.columnIndex!,
+ ref.position.rowIndex!,
);
- if (!refSheet.getCellInfoAt(ref.position.column, ref.position.row)) {
+ if (
+ !refSheet.getCellInfoAt(
+ ref.position.columnIndex!,
+ ref.position.rowIndex!,
+ )
+ ) {
refSheet.createCell(position.columnKey!, position.rowKey!, "");
}
@@ -771,9 +840,9 @@ export default class Sheet {
return false;
}
- private initializePosition(colPos: number, rowPos: number): PositionInfo {
+ private initializePosition(columnPos: number, rowPos: number): KeyPosition {
let rowKey;
- let colKey;
+ let columnKey;
// Create row and column if they don't exist yet.
if (!this.rowPositions.has(rowPos)) {
@@ -786,47 +855,58 @@ export default class Sheet {
rowKey = this.rowPositions.get(rowPos)!;
}
- if (!this.columnPositions.has(colPos)) {
+ if (!this.columnPositions.has(columnPos)) {
// Create a new column
- const col = new Column(this.defaultWidth, colPos);
+ const col = new Column(this.defaultWidth, columnPos);
this.columns.set(col.key, col);
- this.columnPositions.set(colPos, col.key);
+ this.columnPositions.set(columnPos, col.key);
- colKey = col.key;
+ columnKey = col.key;
} else {
- colKey = this.columnPositions.get(colPos)!;
+ columnKey = this.columnPositions.get(columnPos)!;
}
- return { rowKey: rowKey, columnKey: colKey };
+ return { columnKey, rowKey };
}
private emitSetCellEvent(
- colKey: ColumnKey,
- rowKey: RowKey,
- colPos: number,
- rowPos: number,
+ columnIndex: number,
+ rowIndex: number,
cell: Cell | null,
) {
const payload: CoreSetCellPayload = {
- keyPosition: {
- rowKey: rowKey,
- columnKey: colKey,
- },
indexPosition: {
- column: colPos,
- row: rowPos,
+ columnIndex,
+ rowIndex,
},
rawValue: cell ? cell.rawValue : "",
formattedValue: cell ? cell.formattedValue : "",
- clearCell: cell == null,
- clearRow: this.rows.get(rowKey) == null,
};
this.events.emit(new LightsheetEvent(EventType.CORE_SET_CELL, payload));
}
+ private emitSetStyleEvent(
+ columnIndex: number | null,
+ rowIndex: number | null,
+ columnKey: ColumnKey | null = null,
+ rowKey: RowKey | null = null,
+ ) {
+ const payload: CoreSetStylePayload = {
+ indexPosition: {
+ rowIndex,
+ columnIndex,
+ },
+ value: GenerateStyleStringFromMap(
+ this.getMergedCellStyle(columnKey, rowKey).css,
+ ),
+ };
+
+ this.events.emit(new LightsheetEvent(EventType.VIEW_SET_STYLE, payload));
+ }
+
private registerEvents() {
- this.events.on(EventType.UI_SET_CELL, (event) =>
+ this.events.on(EventType.VIEW_SET_CELL, (event) =>
this.handleUISetCell(event),
);
}
@@ -842,8 +922,8 @@ export default class Sheet {
);
} else if (payload.indexPosition) {
this.setCellAt(
- payload.indexPosition.column,
- payload.indexPosition.row,
+ payload.indexPosition.columnIndex!,
+ payload.indexPosition.rowIndex!,
payload.rawValue,
);
} else {
diff --git a/src/core/structure/sheet.types.ts b/src/core/structure/sheet.types.ts
index b8a5f715..64ed5da6 100644
--- a/src/core/structure/sheet.types.ts
+++ b/src/core/structure/sheet.types.ts
@@ -1,20 +1,34 @@
import { ColumnKey, RowKey } from "./key/keyTypes.ts";
import { CellState } from "./cell/cellState.ts";
-export type PositionInfo = {
+export type KeyPosition = {
columnKey?: ColumnKey;
rowKey?: RowKey;
};
export type CellInfo = {
- position: PositionInfo;
+ position: KeyPosition;
rawValue?: string;
resolvedValue?: string;
formattedValue?: string;
state?: CellState;
};
+export enum GroupTypes {
+ Column = 1,
+ Row,
+}
+export type GroupType = GroupTypes;
+
export enum ShiftDirection {
forward = "forward",
backward = "backward",
}
+
+export type Format = { type: string; options?: any };
+
+export type StyleInfo = {
+ position: string;
+ css?: string;
+ format?: Format;
+};
diff --git a/src/main.ts b/src/main.ts
index 5ea38eb8..b2c1844e 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,18 +1,26 @@
-import UI from "./ui/render.ts";
-import { LightsheetOptions } from "./main.types.ts";
+import UI from "./view/view.ts";
import Sheet from "./core/structure/sheet.ts";
-import { CellInfo } from "./core/structure/sheet.types.ts";
+import {
+ CellInfo,
+ Format,
+ GroupTypes,
+ StyleInfo,
+} from "./core/structure/sheet.types.ts";
import Events from "./core/event/events.ts";
import { ListenerFunction } from "./core/event/events.ts";
import EventState from "./core/event/eventState.ts";
-import EventType from "./core/event/eventType.ts";
import SheetHolder from "./core/structure/sheetHolder.ts";
import { DefaultColCount, DefaultRowCount } from "./utils/constants.ts";
import ExpressionHandler from "./core/evaluation/expressionHandler.ts";
import { CellReference } from "./core/structure/cell/types.cell.ts";
-import { RowKey, ColumnKey } from "./core/structure/key/keyTypes.ts";
-import CellStyle from "./core/structure/cellStyle.ts";
-import { Coordinate } from "./utils/common.types.ts";
+import NumberFormatter from "./core/evaluation/numberFormatter.ts";
+import {
+ GenerateStyleMapFromString,
+ GetRowColFromCellRef,
+} from "./utils/helpers.ts";
+import { LightsheetOptions } from "./main.types.ts";
+import { EventType } from "./core/event/events.types.ts";
+import { IndexPosition } from "./utils/common.types.ts";
export default class Lightsheet {
private ui: UI | undefined;
@@ -20,6 +28,7 @@ export default class Lightsheet {
private sheet: Sheet;
sheetHolder: SheetHolder;
private events: Events;
+ style?: any = null;
onCellChange?;
isReady: boolean = false;
@@ -37,7 +46,7 @@ export default class Lightsheet {
this.events = new Events();
this.sheetHolder = SheetHolder.getInstance();
this.sheet = new Sheet(options.sheetName, this.events);
-
+ this.style = options.style;
if (targetElement) {
this.ui = new UI(targetElement, this.options, this.events);
@@ -61,7 +70,7 @@ export default class Lightsheet {
if (options.onCellChange) {
this.onCellChange = options.onCellChange;
}
-
+ this.initializeStyle();
if (options.onReady) options.onReady = this.options.onReady;
this.onTableReady();
}
@@ -100,56 +109,76 @@ export default class Lightsheet {
this.options.isReadOnly = isReadOnly;
}
- showToolbar(isShown: boolean) {
- this.ui?.showToolbar(isShown);
- }
-
- getKey() {
- return this.sheet.key;
+ getFormatter(type: string, options?: any) {
+ if (type == "number") {
+ return new NumberFormatter(options.decimal);
+ }
+ return;
+ }
+
+ setCss(position: string, css: string) {
+ const { rowIndex, columnIndex } = GetRowColFromCellRef(position);
+ const mappedCss = css ? GenerateStyleMapFromString(css) : null;
+ if (rowIndex == null && columnIndex == null) {
+ return;
+ } else if (rowIndex != null && columnIndex != null) {
+ this.sheet.setCellCss(columnIndex, rowIndex, mappedCss!);
+ } else if (rowIndex != null) {
+ this.sheet.setGroupCss(rowIndex, GroupTypes.Row, mappedCss!);
+ } else if (columnIndex != null) {
+ this.sheet.setGroupCss(columnIndex, GroupTypes.Column, mappedCss!);
+ }
}
- getName() {
- return this.options.sheetName;
+ clearCss(position: string) {
+ this.setCss(position, "");
}
- setCellAt(columnIndex: number, rowIndex: number, value: any): CellInfo {
- return this.sheet.setCellAt(columnIndex, rowIndex, value.toString());
+ setFormatting(position: string, format: Format) {
+ this.processFormatting(position, format);
}
- setCell(colKey: ColumnKey, rowKey: RowKey, formula: string): CellInfo | null {
- return this.sheet.setCell(colKey, rowKey, formula);
+ clearFormatter(position: string) {
+ this.processFormatting(position, null);
}
- getCellInfoAt(colPos: number, rowPos: number): CellInfo | null {
- return this.sheet.getCellInfoAt(colPos, rowPos);
- }
-
- getRowIndex(rowKey: RowKey): number | undefined {
- return this.sheet.getRowIndex(rowKey);
+ private processFormatting(position: string, format: Format | null) {
+ const { rowIndex, columnIndex } = GetRowColFromCellRef(position);
+ const formatter = format
+ ? this.getFormatter(format.type, format.options)
+ : null;
+ if (!formatter) return;
+ if (rowIndex == null && columnIndex == null) {
+ return;
+ } else if (rowIndex != null && columnIndex != null) {
+ this.sheet.setCellFormatter(columnIndex, rowIndex, formatter);
+ } else if (rowIndex != null) {
+ this.sheet.setGroupFormatter(rowIndex, GroupTypes.Row, formatter);
+ } else if (columnIndex != null) {
+ this.sheet.setGroupFormatter(columnIndex, GroupTypes.Column, formatter);
+ }
}
- getColumnIndex(colKey: ColumnKey): number | undefined {
- return this.sheet.getColumnIndex(colKey);
+ private initializeStyle() {
+ this.style?.forEach((item: StyleInfo) => {
+ if (item.css) this.setCss(item.position, item.css!);
+ if (item.format) this.setFormatting(item.position, item.format);
+ });
}
-
- getCellStyle(colKey?: ColumnKey, rowKey?: RowKey): CellStyle {
- return this.sheet.getCellStyle(colKey, rowKey);
+ showToolbar(isShown: boolean) {
+ this.ui?.showToolbar(isShown);
}
- setCellStyle(
- colKey: ColumnKey,
- rowKey: RowKey,
- style: CellStyle | null,
- ): boolean {
- return this.sheet.setCellStyle(colKey, rowKey, style);
+ getName() {
+ return this.options.sheetName;
}
- setRowStyle(rowkey: RowKey, cellStyle: CellStyle): boolean {
- return this.sheet.setRowStyle(rowkey, cellStyle);
+ setCell(columnIndex: number, rowIndex: number, value: any): CellInfo {
+ return this.sheet.setCellAt(columnIndex, rowIndex, value.toString());
}
- setColumnStyle(columnKey: ColumnKey, cellStyle: CellStyle): boolean {
- return this.sheet.setColumnStyle(columnKey, cellStyle);
+ getCellInfoAt(colPos: number, rowPos: number): CellInfo | null {
+ return this.sheet.getCellInfoAt(colPos, rowPos);
}
moveColumn(from: number, to: number): boolean {
@@ -160,7 +189,11 @@ export default class Lightsheet {
return this.sheet.moveRow(from, to);
}
- moveCell(from: Coordinate, to: Coordinate, moveStyling: boolean = true) {
+ moveCell(
+ from: IndexPosition,
+ to: IndexPosition,
+ moveStyling: boolean = true,
+ ) {
this.sheet.moveCell(from, to, moveStyling);
}
diff --git a/src/main.types.ts b/src/main.types.ts
index ddc0665a..36795595 100644
--- a/src/main.types.ts
+++ b/src/main.types.ts
@@ -2,6 +2,8 @@
export type LightsheetOptions = {
sheetName: string;
data?: any[];
+
+ style?: any;
onCellChange?: (colIndex: number, rowIndex: number, value: any) => void;
onCellClick?: (colIndex: number, rowIndex: number) => void;
onReady?: () => void;
diff --git a/src/ui/render.types.ts b/src/ui/render.types.ts
deleted file mode 100644
index 1a3f6fee..00000000
--- a/src/ui/render.types.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Coordinate } from "../utils/common.types.ts";
-
-export type CellIdInfo = {
- keyParts: string[];
- isIndex: boolean;
-};
-
-export type SelectionContainer = {
- selectionStart: Coordinate | null;
- selectionEnd: Coordinate | null;
-};
diff --git a/src/utils/common.types.ts b/src/utils/common.types.ts
index e3f6075b..8c2d54b5 100644
--- a/src/utils/common.types.ts
+++ b/src/utils/common.types.ts
@@ -1,4 +1,4 @@
-export type Coordinate = {
- row: number;
- column: number;
+export type IndexPosition = {
+ columnIndex?: number | null;
+ rowIndex?: number | null;
};
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index 34d6c7cc..614991c8 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -1,12 +1,69 @@
-export default class LightsheetHelper {
- static generateColumnLabel = (rowIndex: number) => {
- let label = "";
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- while (rowIndex > 0) {
- rowIndex--; // Adjust index to start from 0
- label = alphabet[rowIndex % 26] + label;
- rowIndex = Math.floor(rowIndex / 26);
+import { IndexPosition } from "./common.types";
+
+export function GenerateRowLabel(rowIndex: number): string {
+ let label = "";
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ while (rowIndex > 0) {
+ rowIndex--; // Adjust index to start from 0
+ label = alphabet[rowIndex % 26] + label;
+ rowIndex = Math.floor(rowIndex / 26);
+ }
+ return label || "A"; // Return "A" if index is 0
+}
+
+export function GetRowColFromCellRef(cellRef: string): IndexPosition {
+ // Regular expression to extract the column and row indexes
+ const matches = cellRef.match(/^([A-Z]+)?(\d+)?$/);
+ if (matches) {
+ const colStr = matches[1] || ""; // If column letter is not provided, default to empty string
+ const rowStr = matches[2] || ""; // If row number is not provided, default to empty string
+
+ // Convert column string to index
+ let colIndex = 0;
+ if (colStr !== "") {
+ for (let i = 0; i < colStr.length; i++) {
+ colIndex = colIndex * 26 + (colStr.charCodeAt(i) - 64);
+ }
}
- return label || "A"; // Return "A" if index is 0
- };
+
+ // Convert row string to index
+ const rowIndex = rowStr ? parseInt(rowStr, 10) : null;
+
+ return {
+ rowIndex: rowIndex ? rowIndex - 1 : null,
+ columnIndex: colIndex ? colIndex - 1 : null,
+ };
+ } else {
+ // Invalid cell reference
+ return { rowIndex: null, columnIndex: null };
+ }
+}
+
+export function GenerateColumnLabel(rowIndex: number) {
+ let label = "";
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ while (rowIndex > 0) {
+ rowIndex--; // Adjust index to start from 0
+ label = alphabet[rowIndex % 26] + label;
+ rowIndex = Math.floor(rowIndex / 26);
+ }
+ return label || "A"; // Return "A" if index is 0
+}
+
+export function GenerateStyleMapFromString(style: string): Map {
+ const mappedStyle = new Map();
+ style.split(";").forEach((item: string) => {
+ const [key, value] = item.split(":");
+ if (!key || !value) return;
+ mappedStyle.set(key.trim(), value.trim());
+ });
+ return mappedStyle;
+}
+
+export function GenerateStyleStringFromMap(style: Map) {
+ let result = "";
+ for (const [key, value] of style) {
+ result += `${key}:${value};`;
+ }
+ return result;
}
diff --git a/src/ui/ui.css b/src/view/view.css
similarity index 96%
rename from src/ui/ui.css
rename to src/view/view.css
index e017d73b..d504a577 100644
--- a/src/ui/ui.css
+++ b/src/view/view.css
@@ -61,8 +61,8 @@
.lightsheet_table_td {
border-top: 1px solid var(--lightsheet-silver-400);
border-left: 1px solid var(--lightsheet-silver-400);
- border-right: 0px;
- border-bottom: 0px;
+ border-right: 1px solid transparent;
+ border-bottom: 1px solid transparent;
background-color: white;
}
@@ -100,6 +100,7 @@ td:first-child {
margin: 0px;
padding: 2px;
background-color: transparent;
+ font-weight: inherit;
}
.lightsheet_table_row_number {
diff --git a/src/ui/render.ts b/src/view/view.ts
similarity index 78%
rename from src/ui/render.ts
rename to src/view/view.ts
index 9d932301..c9b7788d 100644
--- a/src/ui/render.ts
+++ b/src/view/view.ts
@@ -1,15 +1,16 @@
-import { SelectionContainer } from "./render.types.ts";
-import LightsheetEvent from "../core/event/event.ts";
+import Event from "../core/event/event";
+import Events from "../core/event/events";
import {
- CoreSetCellPayload,
UISetCellPayload,
-} from "../core/event/events.types.ts";
-import EventType from "../core/event/eventType.ts";
-import { LightsheetOptions, ToolbarOptions } from "../main.types";
-import LightsheetHelper from "../utils/helpers.ts";
-import { ToolbarItems } from "../utils/constants.ts";
-import { Coordinate } from "../utils/common.types.ts";
-import Events from "../core/event/events.ts";
+ EventType,
+ CoreSetStylePayload,
+ CoreSetCellPayload,
+} from "../core/event/events.types";
+import { ToolbarOptions, LightsheetOptions } from "../main.types";
+import { IndexPosition } from "../utils/common.types";
+import { ToolbarItems } from "../utils/constants";
+import { GenerateColumnLabel } from "../utils/helpers";
+import { SelectionContainer } from "./view.types";
export default class UI {
tableEl!: Element;
@@ -25,7 +26,7 @@ export default class UI {
selectedCellsContainer: SelectionContainer;
toolbarOptions: ToolbarOptions;
isReadOnly: boolean;
- singleSelectedCell: Coordinate | undefined;
+ singleSelectedCell: IndexPosition | undefined;
tableContainerDom: Element;
private events: Events;
@@ -166,8 +167,8 @@ export default class UI {
const newValue = this.formulaInput.value;
if (event.key === "Enter") {
if (this.singleSelectedCell) {
- const colIndex = this.singleSelectedCell.column;
- const rowIndex = this.singleSelectedCell.row;
+ const colIndex = this.singleSelectedCell.columnIndex!;
+ const rowIndex = this.singleSelectedCell.rowIndex!;
this.onUICellValueChange(newValue, colIndex, rowIndex);
}
this.formulaInput.blur();
@@ -184,8 +185,8 @@ export default class UI {
this.formulaInput.onblur = () => {
const newValue = this.formulaInput.value;
if (this.singleSelectedCell) {
- const colIndex = this.singleSelectedCell.column;
- const rowIndex = this.singleSelectedCell.row;
+ const colIndex = this.singleSelectedCell.columnIndex!;
+ const rowIndex = this.singleSelectedCell.rowIndex!;
this.onUICellValueChange(newValue, colIndex, rowIndex);
}
};
@@ -206,8 +207,7 @@ export default class UI {
);
const newColumnNumber = this.getColumnCount() + 1;
- const newHeaderValue =
- LightsheetHelper.generateColumnLabel(newColumnNumber);
+ const newHeaderValue = GenerateColumnLabel(newColumnNumber);
headerCellDom.textContent = newHeaderValue;
headerCellDom.onclick = (e: MouseEvent) =>
@@ -215,8 +215,7 @@ export default class UI {
const rowCount = this.getRowCount();
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
- const rowDom = this.tableBodyDom.children[rowIndex];
- this.addCell(rowDom, newColumnNumber - 1, rowIndex, "");
+ this.addCell(newColumnNumber - 1, rowIndex, "");
}
this.tableHeadDom.children[0].appendChild(headerCellDom);
@@ -250,7 +249,7 @@ export default class UI {
const columnCount = this.getColumnCount();
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
- this.addCell(rowDom, columnIndex, rowCount, "");
+ this.addCell(columnIndex, rowCount, "");
}
return rowDom;
}
@@ -295,23 +294,20 @@ export default class UI {
return rowDom;
}
- getRow(rowKey: string): HTMLElement | null {
- return document.getElementById(rowKey);
+ getRowDom(rowIndex: number) {
+ return this.tableBodyDom.children.length < rowIndex + 1
+ ? null
+ : this.tableBodyDom.children[rowIndex];
}
- addCell(
- rowDom: Element,
- colIndex: number,
- rowIndex: number,
- value: any,
- columnKey?: string,
- ): HTMLElement {
+ addCell(colIndex: number, rowIndex: number, value: any): HTMLElement {
const cellDom = document.createElement("td");
cellDom.classList.add(
"lightsheet_table_cell",
"lightsheet_table_row_cell",
"lightsheet_table_td",
);
+ const rowDom = this.tableBodyDom.children[rowIndex];
rowDom.appendChild(cellDom);
cellDom.id = `${colIndex}_${rowIndex}`;
cellDom.setAttribute("column-index", `${colIndex}` || "");
@@ -324,7 +320,6 @@ export default class UI {
cellDom.appendChild(inputDom);
if (value) {
- cellDom.id = `${columnKey}_${rowDom.id}`;
inputDom.value = value;
}
@@ -359,8 +354,8 @@ export default class UI {
}
this.singleSelectedCell = {
- column: Number(colIndex),
- row: Number(rowIndex),
+ columnIndex: Number(colIndex),
+ rowIndex: Number(rowIndex),
};
if (this.formulaBarDom) {
@@ -405,83 +400,100 @@ export default class UI {
}
}
- onUICellValueChange(rawValue: string, colIndex: number, rowIndex: number) {
+ onUICellValueChange(rawValue: string, columnIndex: number, rowIndex: number) {
const payload: UISetCellPayload = {
- indexPosition: { column: colIndex, row: rowIndex },
+ indexPosition: { columnIndex, rowIndex },
rawValue,
};
- this.events.emit(new LightsheetEvent(EventType.UI_SET_CELL, payload));
+ this.events.emit(new Event(EventType.VIEW_SET_CELL, payload));
}
private registerEvents() {
this.events.on(EventType.CORE_SET_CELL, (event) => {
this.onCoreSetCell(event);
});
+ this.events.on(EventType.VIEW_SET_STYLE, (event) => {
+ this.onCoreSetStyle(event.payload);
+ });
+ }
+
+ private onCoreSetStyle(event: CoreSetStylePayload) {
+ const { indexPosition, value } = event;
+ if (
+ indexPosition.columnIndex != undefined &&
+ indexPosition.rowIndex != undefined
+ ) {
+ const cellDom =
+ this.tableBodyDom.children[indexPosition.rowIndex].children[
+ indexPosition.columnIndex + 1
+ ];
+ const inputElement = cellDom! as HTMLElement;
+ inputElement.setAttribute("style", value);
+ } else if (indexPosition.columnIndex || indexPosition.columnIndex === 0) {
+ for (let i = 0; i < this.tableBodyDom.children.length; i++) {
+ this.tableBodyDom.children[i].children[
+ indexPosition.columnIndex + 1
+ ].setAttribute("style", value);
+ }
+ } else {
+ for (
+ let i = 1;
+ i < this.tableBodyDom.children[indexPosition.rowIndex!].children.length;
+ i++
+ ) {
+ this.tableBodyDom.children[indexPosition.rowIndex!].children[
+ i
+ ].setAttribute("style", value);
+ }
+ }
}
private onCoreSetCell(event: LightsheetEvent) {
const payload = event.payload as CoreSetCellPayload;
// Create new columns if the column index is greater than the current column count.
- const newColumns = payload.indexPosition.column - this.getColumnCount() + 1;
+ const newColumns =
+ payload.indexPosition.columnIndex! - this.getColumnCount() + 1;
for (let i = 0; i < newColumns; i++) {
this.addColumn();
}
- const newRows = payload.indexPosition.row - this.getRowCount() + 1;
+ const newRows = payload.indexPosition.rowIndex! - this.getRowCount() + 1;
for (let i = 0; i < newRows; i++) {
this.addRow();
}
// Get HTML elements and (new) IDs for the payload's cell and row.
- const elInfo = this.getElementInfoForSetCell(payload);
-
- elInfo.cellDom!.id = elInfo.cellDomId;
- elInfo.rowDom!.id = elInfo.rowDomId;
+ const cellInputDom = this.getElementInfoForSetCell(
+ payload.indexPosition.columnIndex!,
+ payload.indexPosition.rowIndex!,
+ payload.formattedValue,
+ );
- // Update input element with values from the core.
- const inputEl = elInfo.cellDom!.firstChild! as HTMLInputElement;
- inputEl.setAttribute("rawValue", payload.rawValue);
- inputEl.setAttribute("resolvedValue", payload.formattedValue);
- inputEl.value = payload.formattedValue;
+ cellInputDom.setAttribute("rawValue", payload.rawValue);
+ cellInputDom.setAttribute("resolvedValue", payload.formattedValue);
+ cellInputDom.value = payload.formattedValue;
}
- private getElementInfoForSetCell = (payload: CoreSetCellPayload) => {
- const colKey = payload.keyPosition.columnKey?.toString();
- const rowKey = payload.keyPosition.rowKey?.toString();
-
- const columnIndex = payload.indexPosition.column;
- const rowIndex = payload.indexPosition.row;
-
- const cellDomKey =
- colKey && rowKey ? `${colKey!.toString()}_${rowKey!.toString()}` : null;
-
- // Get the cell by either column and row key or position.
- // TODO Index-based ID may not be unique if there are multiple sheets.
- const cellDom =
- (cellDomKey && document.getElementById(cellDomKey)) ||
- document.getElementById(`${columnIndex}_${rowIndex}`);
-
- const newCellDomId = payload.clearCell
- ? `${columnIndex}_${rowIndex}`
- : `${colKey}_${rowKey}`;
-
- const newRowDomId = payload.clearRow ? `row_${rowIndex}` : rowKey!;
-
- let rowDom: HTMLElement | null = null;
- if (rowKey) {
- rowDom = document.getElementById(rowKey);
- }
- if (!rowDom) {
- const rowId = `row_${rowIndex}`;
- rowDom = document.getElementById(rowId);
+ private getElementInfoForSetCell = (
+ columnIndex: number,
+ rowIndex: number,
+ formattedValue: string,
+ ) => {
+ let cellDom;
+ if (this.tableBodyDom.children.length < rowIndex + 1) {
+ this.addRow();
+ cellDom = this.addCell(columnIndex, rowIndex, formattedValue);
+ } else {
+ if (
+ this.tableBodyDom.children[rowIndex].children.length <
+ columnIndex + 2
+ ) {
+ cellDom = this.addCell(columnIndex, rowIndex, formattedValue);
+ } else
+ cellDom =
+ this.tableBodyDom.children[rowIndex].children[columnIndex + 1];
}
-
- return {
- cellDom: cellDom,
- cellDomId: newCellDomId,
- rowDom: rowDom,
- rowDomId: newRowDomId,
- };
+ return cellDom.firstChild! as HTMLInputElement;
};
getColumnCount() {
@@ -551,14 +563,15 @@ export default class UI {
return false;
const withinX =
- (cellColumnIndex >= selectionStart.column &&
- cellColumnIndex <= selectionEnd.column) ||
- (cellColumnIndex <= selectionStart.column &&
- cellColumnIndex >= selectionEnd.column);
+ (cellColumnIndex >= selectionStart.columnIndex! &&
+ cellColumnIndex <= selectionEnd.columnIndex!) ||
+ (cellColumnIndex <= selectionStart.columnIndex! &&
+ cellColumnIndex >= selectionEnd.columnIndex!);
const withinY =
- (cellRowIndex >= selectionStart.row &&
- cellRowIndex <= selectionEnd.row) ||
- (cellRowIndex <= selectionStart.row && cellRowIndex >= selectionEnd.row);
+ (cellRowIndex >= selectionStart.rowIndex! &&
+ cellRowIndex <= selectionEnd.rowIndex!) ||
+ (cellRowIndex <= selectionStart.rowIndex! &&
+ cellRowIndex >= selectionEnd.rowIndex!);
return withinX && withinY;
}
@@ -584,8 +597,8 @@ export default class UI {
this.selectedCellsContainer.selectionStart =
(colIndex != null || undefined) && (rowIndex != null || undefined)
? {
- row: rowIndex,
- column: colIndex,
+ rowIndex: rowIndex,
+ columnIndex: colIndex,
}
: null;
}
@@ -597,8 +610,8 @@ export default class UI {
this.selectedCellsContainer.selectionEnd =
(colIndex != null || undefined) && (rowIndex != null || undefined)
? {
- row: rowIndex,
- column: colIndex,
+ rowIndex: rowIndex,
+ columnIndex: colIndex,
}
: null;
if (
diff --git a/src/view/view.types.ts b/src/view/view.types.ts
new file mode 100644
index 00000000..ea92b0ba
--- /dev/null
+++ b/src/view/view.types.ts
@@ -0,0 +1,11 @@
+import { IndexPosition } from "../utils/common.types";
+
+export type CellIdInfo = {
+ keyParts: string[];
+ isIndex: boolean;
+};
+
+export type SelectionContainer = {
+ selectionStart: IndexPosition | null;
+ selectionEnd: IndexPosition | null;
+};
diff --git a/tests/core/event/event.test.ts b/tests/core/event/event.test.ts
index f68f8d31..6450245c 100644
--- a/tests/core/event/event.test.ts
+++ b/tests/core/event/event.test.ts
@@ -1,7 +1,7 @@
import Event from "../../../src/core/event/event";
-import EventType from "../../../src/core/event/eventType";
import EventState from "../../../src/core/event/eventState";
import Events from "../../../src/core/event/events";
+import { EventType } from "../../../src/core/event/events.types";
describe("Events", () => {
let events: Events;
@@ -12,9 +12,9 @@ describe("Events", () => {
test("should register and trigger an event", () => {
const mockCallback = jest.fn();
- events.on(EventType.UI_SET_CELL, mockCallback);
+ events.on(EventType.VIEW_SET_CELL, mockCallback);
- const event = new Event(EventType.UI_SET_CELL, "test payload", false);
+ const event = new Event(EventType.VIEW_SET_CELL, "test payload", false);
events.emit(event);
expect(mockCallback).toHaveBeenCalledWith(event);
@@ -96,10 +96,10 @@ describe("Events", () => {
test("should remove an event listener", () => {
const mockCallback = jest.fn();
- events.on(EventType.UI_SET_CELL, mockCallback);
- events.removeEventListener(EventType.UI_SET_CELL, mockCallback);
+ events.on(EventType.VIEW_SET_CELL, mockCallback);
+ events.removeEventListener(EventType.VIEW_SET_CELL, mockCallback);
- const event = new Event(EventType.UI_SET_CELL, "test payload");
+ const event = new Event(EventType.VIEW_SET_CELL, "test payload");
events.emit(event);
expect(mockCallback).not.toHaveBeenCalled();
@@ -107,17 +107,17 @@ describe("Events", () => {
test("should remove an event listener only with correct state", () => {
const mockCallback = jest.fn();
- events.on(EventType.UI_SET_CELL, mockCallback);
- events.on(EventType.UI_SET_CELL, mockCallback, EventState.PRE_EVENT);
+ events.on(EventType.VIEW_SET_CELL, mockCallback);
+ events.on(EventType.VIEW_SET_CELL, mockCallback, EventState.PRE_EVENT);
events.removeEventListener(
- EventType.UI_SET_CELL,
+ EventType.VIEW_SET_CELL,
mockCallback,
EventState.PRE_EVENT,
);
const event = new Event(
- EventType.UI_SET_CELL,
+ EventType.VIEW_SET_CELL,
"test payload",
false,
EventState.PRE_EVENT,
@@ -130,10 +130,10 @@ describe("Events", () => {
test("should handle multiple listeners for the same event", () => {
const firstCallback = jest.fn();
const secondCallback = jest.fn();
- events.on(EventType.UI_SET_CELL, firstCallback);
- events.on(EventType.UI_SET_CELL, secondCallback);
+ events.on(EventType.VIEW_SET_CELL, firstCallback);
+ events.on(EventType.VIEW_SET_CELL, secondCallback);
- const event = new Event(EventType.UI_SET_CELL, "test payload");
+ const event = new Event(EventType.VIEW_SET_CELL, "test payload");
events.emit(event);
expect(firstCallback).toHaveBeenCalledWith(event);
@@ -142,7 +142,7 @@ describe("Events", () => {
test("should not trigger listeners of a different event type", () => {
const mockCallback = jest.fn();
- events.on(EventType.UI_SET_CELL, mockCallback);
+ events.on(EventType.VIEW_SET_CELL, mockCallback);
const event = new Event(EventType.CORE_SET_CELL, "test payload");
events.emit(event);
@@ -152,10 +152,10 @@ describe("Events", () => {
test("should not trigger listeners after they are removed", () => {
const mockCallback = jest.fn();
- events.on(EventType.UI_SET_CELL, mockCallback);
- events.removeEventListener(EventType.UI_SET_CELL, mockCallback);
+ events.on(EventType.VIEW_SET_CELL, mockCallback);
+ events.removeEventListener(EventType.VIEW_SET_CELL, mockCallback);
- const event = new Event(EventType.UI_SET_CELL, "test payload");
+ const event = new Event(EventType.VIEW_SET_CELL, "test payload");
events.emit(event);
expect(mockCallback).not.toHaveBeenCalled();
@@ -167,11 +167,11 @@ describe("Events", () => {
});
const anotherCallback = jest.fn();
- events.on(EventType.UI_SET_CELL, mockCallback, EventState.PRE_EVENT);
- events.addEventListener(EventType.UI_SET_CELL, anotherCallback);
+ events.on(EventType.VIEW_SET_CELL, mockCallback, EventState.PRE_EVENT);
+ events.addEventListener(EventType.VIEW_SET_CELL, anotherCallback);
const event = new Event(
- EventType.UI_SET_CELL,
+ EventType.VIEW_SET_CELL,
"test payload",
false,
EventState.PRE_EVENT,
@@ -188,20 +188,24 @@ describe("Events", () => {
const postEventCallback = jest.fn();
events.addEventListener(
- EventType.UI_SET_CELL,
+ EventType.VIEW_SET_CELL,
preEventCallback,
EventState.PRE_EVENT,
);
- events.on(EventType.UI_SET_CELL, postEventCallback, EventState.POST_EVENT);
+ events.on(
+ EventType.VIEW_SET_CELL,
+ postEventCallback,
+ EventState.POST_EVENT,
+ );
const preEvent = new Event(
- EventType.UI_SET_CELL,
+ EventType.VIEW_SET_CELL,
"pre payload",
false,
EventState.PRE_EVENT,
);
const postEvent = new Event(
- EventType.UI_SET_CELL,
+ EventType.VIEW_SET_CELL,
"post payload",
false,
EventState.POST_EVENT,
diff --git a/tests/core/structure/cellReferences.test.ts b/tests/core/structure/cellReferences.test.ts
index bb3668e5..c1c4e72c 100644
--- a/tests/core/structure/cellReferences.test.ts
+++ b/tests/core/structure/cellReferences.test.ts
@@ -1,7 +1,6 @@
import Sheet from "../../../src/core/structure/sheet";
import { CellState } from "../../../src/core/structure/cell/cellState.ts";
import { CellInfo } from "../../../src/core/structure/sheet.types.ts";
-import CellStyle from "../../../src/core/structure/cellStyle.ts";
describe("Cell references", () => {
let sheet: Sheet;
@@ -107,17 +106,12 @@ describe("Cell references", () => {
});
it("should create an empty cell with styling", () => {
- const b2 = sheet.getCellInfoAt(1, 1)!;
- sheet.setCellStyle(
- b2.position!.columnKey!,
- b2.position!.rowKey!,
- new CellStyle(new Map([["width", "50px"]])),
- );
+ sheet.setCellCss(1, 1, new Map([["width", "50px;"]]));
sheet.setCellAt(1, 1, "");
// Clearing the style should result in the cell being deleted.
- sheet.setCellStyle(b2!.position.columnKey!, b2!.position.rowKey!, null);
+ sheet.setCellCss(1, 1, new Map());
expect(sheet.getCellInfoAt(1, 1)).toBeNull();
});
diff --git a/tests/core/structure/cellStyle.test.ts b/tests/core/structure/cellStyle.test.ts
index 176c08b0..c4755e3c 100644
--- a/tests/core/structure/cellStyle.test.ts
+++ b/tests/core/structure/cellStyle.test.ts
@@ -1,5 +1,6 @@
import Sheet from "../../../src/core/structure/sheet.ts";
import CellStyle from "../../../src/core/structure/cellStyle.ts";
+import { GroupTypes } from "../../../src/core/structure/sheet.types.ts";
describe("CellStyle", () => {
let sheet: Sheet;
@@ -24,43 +25,46 @@ describe("CellStyle", () => {
new CellStyle(new Map([["border", "1px solid black"]])),
];
- sheet.setCellStyle(pos.columnKey!, pos.rowKey!, styles[0]);
- expect(sheet.getCellStyle(pos.columnKey!, pos.rowKey!)!).toEqual(styles[0]);
-
- sheet.setCellStyle(pos.columnKey!, pos.rowKey!, null);
- expect(sheet.getCellStyle(pos.columnKey!, pos.rowKey!)).toEqual(
- sheet["defaultStyle"],
+ sheet.setCellCss(1, 1, styles[0].css);
+ expect(sheet.getMergedCellStyle(pos.columnKey, pos.rowKey).css!).toEqual(
+ styles[0].css,
);
- sheet.setRowStyle(pos.rowKey!, styles[1]);
- expect(sheet.getCellStyle(pos.columnKey!, pos.rowKey!)!).toEqual(styles[1]);
+ sheet.setCellCss(1, 1, new Map());
+ expect(sheet.getMergedCellStyle(pos.columnKey, pos.rowKey).css).toEqual(
+ sheet.defaultStyle.css,
+ );
+ sheet.setGroupCss(1, GroupTypes.Row, styles[1].css);
+ expect(sheet.getMergedCellStyle(pos.columnKey, pos.rowKey).css).toEqual(
+ styles[1].css,
+ );
- sheet.setColumnStyle(pos.columnKey!, styles[2]);
- expect(sheet.getCellStyle(pos.columnKey!, pos.rowKey!)!).toEqual(
+ sheet.setGroupCss(1!, GroupTypes.Column, styles[2].css);
+ expect(sheet.getMergedCellStyle(pos.columnKey, pos.rowKey).css).toEqual(
new CellStyle(
new Map([
["width", "50px"],
["color", "0xff0000"],
]),
- ),
+ ).css,
);
- sheet.setCellStyle(pos.columnKey!, pos.rowKey!, styles[3]);
- expect(sheet.getCellStyle(pos.columnKey!, pos.rowKey!)!).toEqual(
+ sheet.setCellCss(1, 1, styles[3].css);
+ expect(sheet.getMergedCellStyle(pos.columnKey, pos.rowKey).css).toEqual(
new CellStyle(
new Map([
["width", "50px"],
["color", "0xff0000"],
["border", "1px solid black"],
]),
- ),
+ ).css,
);
- sheet.setCellStyle(pos.columnKey!, pos.rowKey!, null);
- sheet.setRowStyle(pos.rowKey!, null);
- sheet.setColumnStyle(pos.columnKey!, null);
- expect(sheet.getCellStyle(pos.columnKey!, pos.rowKey!)).toEqual(
- sheet["defaultStyle"],
+ sheet.setCellCss(1, 1, new Map());
+ sheet.setGroupCss(1, GroupTypes.Row, new Map());
+ sheet.setGroupCss(1, GroupTypes.Column, new Map());
+ expect(sheet.getMergedCellStyle(pos.columnKey, pos.rowKey).css).toEqual(
+ sheet.defaultStyle.css,
);
});
});
diff --git a/tests/core/structure/formatter.test.ts b/tests/core/structure/formatter.test.ts
index ce0fbd2f..a5590b79 100644
--- a/tests/core/structure/formatter.test.ts
+++ b/tests/core/structure/formatter.test.ts
@@ -2,6 +2,7 @@ import Sheet from "../../../src/core/structure/sheet.ts";
import CellStyle from "../../../src/core/structure/cellStyle.ts";
import NumberFormatter from "../../../src/core/evaluation/numberFormatter.ts";
import { CellState } from "../../../src/core/structure/cell/cellState.ts";
+import { GroupTypes } from "../../../src/core/structure/sheet.types.ts";
describe("Formatter test", () => {
let sheet: Sheet;
@@ -19,24 +20,23 @@ describe("Formatter test", () => {
it("Should round a fraction correctly", () => {
const oneDigit = new CellStyle(null, new NumberFormatter(1));
- sheet.setColumnStyle(sheet["columnPositions"].get(1)!, oneDigit);
+ sheet.setGroupFormatter(1, GroupTypes.Column, oneDigit.formatter);
expect(sheet.getCellInfoAt(1, 1)!.formattedValue).toBe("0.8");
});
it("Should apply two different formatting rules to the same cell value", () => {
const noDigits = new CellStyle(null, new NumberFormatter(0));
const twoDigits = new CellStyle(null, new NumberFormatter(2));
+ sheet.setGroupFormatter(1, GroupTypes.Column, noDigits.formatter);
+ sheet.setGroupFormatter(2, GroupTypes.Column, twoDigits.formatter);
- sheet.setColumnStyle(sheet["columnPositions"].get(0)!, noDigits);
- sheet.setColumnStyle(sheet["columnPositions"].get(2)!, twoDigits);
-
- expect(sheet.getCellInfoAt(0, 1)!.formattedValue).toBe("12");
+ expect(sheet.getCellInfoAt(1, 1)!.formattedValue).toBe("1");
expect(sheet.getCellInfoAt(2, 1)!.formattedValue).toBe("12.30");
});
it("Should format a string value as a number and result in an invalid cell state", () => {
const style = new CellStyle(null, new NumberFormatter(0));
- sheet.setColumnStyle(sheet["columnPositions"].get(0)!, style);
+ sheet.setGroupFormatter(0, GroupTypes.Column, style.formatter);
expect(sheet.getCellInfoAt(0, 0)!.state).toBe(CellState.INVALID_FORMAT);
});
diff --git a/tests/core/structure/moveCell.test.ts b/tests/core/structure/moveCell.test.ts
index 6ac2716e..2a64358b 100644
--- a/tests/core/structure/moveCell.test.ts
+++ b/tests/core/structure/moveCell.test.ts
@@ -21,7 +21,10 @@ describe("Cell moving tests", () => {
});
it("should move a single cell and not invalidate incoming references", () => {
- sheet.moveCell({ column: 0, row: 0 }, { column: 3, row: 3 });
+ sheet.moveCell(
+ { columnIndex: 0, rowIndex: 0 },
+ { columnIndex: 3, rowIndex: 3 },
+ );
const referringCell = sheet.getCellInfoAt(1, 0);
expect(sheet.getCellInfoAt(0, 0)).toBe(null);
expect(referringCell!.resolvedValue).toBe("1");
@@ -29,9 +32,18 @@ describe("Cell moving tests", () => {
});
it("should move multiple cells and not invalidate references", () => {
- sheet.moveCell({ column: 0, row: 0 }, { column: 3, row: 0 });
- sheet.moveCell({ column: 1, row: 1 }, { column: 4, row: 1 });
- sheet.moveCell({ column: 2, row: 2 }, { column: 5, row: 2 });
+ sheet.moveCell(
+ { columnIndex: 0, rowIndex: 0 },
+ { columnIndex: 3, rowIndex: 0 },
+ );
+ sheet.moveCell(
+ { columnIndex: 1, rowIndex: 1 },
+ { columnIndex: 4, rowIndex: 1 },
+ );
+ sheet.moveCell(
+ { columnIndex: 2, rowIndex: 2 },
+ { columnIndex: 5, rowIndex: 2 },
+ );
expect(sheet.getCellInfoAt(0, 0)).toBe(null);
expect(sheet.getCellInfoAt(1, 0)?.rawValue).toBe("=D1");
@@ -47,15 +59,15 @@ describe("Cell moving tests", () => {
it("should move a single cell with its styling", () => {
const style = new CellStyle(new Map([["color", "red"]]));
const fromCell = sheet.getCellInfoAt(0, 0)!;
- sheet.setCellStyle(
- fromCell.position.columnKey!,
- fromCell.position.rowKey!,
- style,
- );
- sheet.moveCell({ column: 0, row: 0 }, { column: 3, row: 3 });
+ sheet.setCellCss(0, 0, style.css);
+
+ sheet.moveCell(
+ { columnIndex: 0, rowIndex: 0 },
+ { columnIndex: 3, rowIndex: 3 },
+ );
expect(
- sheet.getCellStyle(
+ sheet.getMergedCellStyle(
fromCell.position.columnKey!,
fromCell.position.rowKey!,
),
@@ -63,7 +75,10 @@ describe("Cell moving tests", () => {
const toCell = sheet.getCellInfoAt(3, 3)!;
expect(
- sheet.getCellStyle(toCell.position.columnKey!, toCell.position.rowKey!),
+ sheet.getMergedCellStyle(
+ toCell.position.columnKey!,
+ toCell.position.rowKey!,
+ ),
).toEqual(style);
});
});
diff --git a/tests/ui/setCellAt.test.ts b/tests/ui/setCellAt.test.ts
index 49d55ad3..2348d0d8 100644
--- a/tests/ui/setCellAt.test.ts
+++ b/tests/ui/setCellAt.test.ts
@@ -22,20 +22,8 @@ describe("Lightsheet setCellAt", () => {
document.body.removeChild(targetElementMock);
});
- it("Should set the cell and use col and row keys", () => {
- const cellInfo = lightSheet.setCellAt(1, 1, "test");
-
- //Query the HTML via document else it will not be able to find the element
- const cellId = cellInfo.position.columnKey + "_" + cellInfo.position.rowKey;
- const cellElement = document.getElementById(cellId);
-
- const cellInput = cellElement?.children[0] as HTMLInputElement;
- expect(cellInput.value).toBe("test");
- });
-
it("Should set the cell at the correct position in the DOM", () => {
lightSheet.setCellAt(2, 3, "test");
-
//Query the HTML via document else it will not be able to find the element
const tableElement = document.querySelector("table");
//This Table API accept position as 1 based index
diff --git a/tests/ui/setCss.test.ts b/tests/ui/setCss.test.ts
new file mode 100644
index 00000000..c6e125ff
--- /dev/null
+++ b/tests/ui/setCss.test.ts
@@ -0,0 +1,80 @@
+import LightSheet from "../../src/main";
+
+describe("LightSheet", () => {
+ let targetElementMock: HTMLElement;
+
+ beforeEach(() => {
+ window.sheetHolder?.clear();
+ targetElementMock = document.createElement("div");
+ document.body.appendChild(targetElementMock);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(targetElementMock);
+ });
+
+ test("Should be able to render table based on provided styles", () => {
+ const styleString = "font-weight: bold;";
+
+ new LightSheet(
+ {
+ data: [
+ ["1", "=1+2/3*6+A1+test(1,2)", "img/nophoto.jpg", "Marketing"],
+ ["2.44445", "400000.000000", "img/nophoto.jpg", "Marketing", "3120"],
+ ["3.555555", "Jorge", "img/nophoto.jpg", "Marketing", "3120"],
+ ],
+ sheetName: "Sheet",
+ style: [
+ {
+ position: "A1",
+ css: styleString,
+ },
+ ],
+ },
+ targetElementMock,
+ );
+
+ const tableBody = targetElementMock.querySelector("tbody");
+ if (!tableBody) {
+ // If tbody is not found, fail the test or log an error
+ fail("tbody element not found in the table.");
+ }
+
+ expect(
+ (tableBody.rows[0].children[1] as HTMLElement).style.cssText,
+ ).toEqual(styleString);
+ });
+
+ test("Should be able to clear existing table style", () => {
+ const styleString = "font-weight: bold;";
+
+ const ls = new LightSheet(
+ {
+ data: [
+ ["1", "=1+2/3*6+A1+test(1,2)", "img/nophoto.jpg", "Marketing"],
+ ["2.44445", "400000.000000", "img/nophoto.jpg", "Marketing", "3120"],
+ ["3.555555", "Jorge", "img/nophoto.jpg", "Marketing", "3120"],
+ ],
+ sheetName: "Sheet",
+ style: [
+ {
+ position: "A1",
+ css: styleString,
+ },
+ ],
+ },
+ targetElementMock,
+ );
+ ls.clearCss("A");
+
+ const tableBody = targetElementMock.querySelector("tbody");
+ if (!tableBody) {
+ // If tbody is not found, fail the test or log an error
+ fail("tbody element not found in the table.");
+ }
+
+ expect(
+ (tableBody.rows[0].children[1] as HTMLElement).style.cssText,
+ ).toEqual("");
+ });
+});
diff --git a/tests/ui/setFromatter.test.ts b/tests/ui/setFromatter.test.ts
new file mode 100644
index 00000000..d34d03a1
--- /dev/null
+++ b/tests/ui/setFromatter.test.ts
@@ -0,0 +1,101 @@
+import LightSheet from "../../src/main";
+
+describe("LightSheet", () => {
+ let targetElementMock: HTMLElement;
+
+ beforeEach(() => {
+ window.sheetHolder?.clear();
+ targetElementMock = document.createElement("div");
+ document.body.appendChild(targetElementMock);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(targetElementMock);
+ });
+
+ test("Should be able to render table based on provided formatters", () => {
+ new LightSheet(
+ {
+ data: [
+ ["1", "=1+2/3*6+A1+test(1,2)", "img/nophoto.jpg", "Marketing"],
+ ["2.44445", "400000.000000", "img/nophoto.jpg", "Marketing", "3120"],
+ ["3.555555", "Jorge", "img/nophoto.jpg", "Marketing", "3120"],
+ ],
+ sheetName: "Sheet",
+ style: [
+ {
+ position: "A",
+ css: "font-weight: bold;",
+ format: { type: "number", options: { decimal: 2 } },
+ },
+ ],
+ },
+ targetElementMock,
+ );
+
+ const tableBody = targetElementMock.querySelector("tbody");
+ if (!tableBody) {
+ // If tbody is not found, fail the test or log an error
+ fail("tbody element not found in the table.");
+ }
+ expect(
+ (tableBody.rows[1].children[1].children[0] as HTMLInputElement).value,
+ ).toEqual("2.44");
+ });
+
+ test("Should be able to set formatter to existing table", () => {
+ const ls = new LightSheet(
+ {
+ data: [
+ ["1", "=1+2/3*6+A1+test(1,2)", "img/nophoto.jpg", "Marketing"],
+ ["2.44445", "400000.000000", "img/nophoto.jpg", "Marketing", "3120"],
+ ["3.555555", "Jorge", "img/nophoto.jpg", "Marketing", "3120"],
+ ],
+ sheetName: "Sheet",
+ },
+ targetElementMock,
+ );
+ ls.setFormatting("A2", { type: "number", options: { decimal: 2 } });
+
+ const tableBody = targetElementMock.querySelector("tbody");
+ if (!tableBody) {
+ // If tbody is not found, fail the test or log an error
+ fail("tbody element not found in the table.");
+ }
+ expect(
+ (tableBody.rows[1].children[1].children[0] as HTMLInputElement).value,
+ ).toEqual("2.44");
+ });
+
+ test("Should be able to clear formatter to from table", () => {
+ const ls = new LightSheet(
+ {
+ data: [
+ ["1", "=1+2/3*6+A1+test(1,2)", "img/nophoto.jpg", "Marketing"],
+ ["2.44445", "400000.000000", "img/nophoto.jpg", "Marketing", "3120"],
+ ["3.555555", "Jorge", "img/nophoto.jpg", "Marketing", "3120"],
+ ],
+ sheetName: "Sheet",
+ style: [
+ {
+ position: "A",
+ css: "font-weight: bold;",
+ format: { type: "number", options: { decimal: 2 } },
+ },
+ ],
+ },
+ targetElementMock,
+ );
+ ls.clearFormatter("A");
+
+ const tableBody = targetElementMock.querySelector("tbody");
+ if (!tableBody) {
+ // If tbody is not found, fail the test or log an error
+ fail("tbody element not found in the table.");
+ }
+
+ expect(
+ (tableBody.rows[1].children[1].children[0] as HTMLInputElement).value,
+ ).toEqual("2.44445");
+ });
+});