Added click on cell event

Added selection change event
This commit is contained in:
Eugene 2023-07-26 14:29:51 +03:00
parent 9dd64b9a77
commit 8596220e85
6 changed files with 175 additions and 39 deletions

View File

@ -1,4 +1,6 @@
import Spreadsheet, { CSS_PREFIX } from "../main"; import Spreadsheet, { CSS_PREFIX } from "../main";
import { EventTypes } from "../modules/events";
import { checkEqualCellSelections } from "../utils/position";
export interface ViewportRect { export interface ViewportRect {
top: number; top: number;
@ -12,7 +14,6 @@ export class Scroller {
private verticalScroller: HTMLDivElement; private verticalScroller: HTMLDivElement;
private horizontalScroller: HTMLDivElement; private horizontalScroller: HTMLDivElement;
private root: Spreadsheet; private root: Spreadsheet;
private isSelecting = false; private isSelecting = false;
constructor(root: Spreadsheet) { constructor(root: Spreadsheet) {
@ -41,29 +42,46 @@ export class Scroller {
this.element.addEventListener("keydown", this.handleKeydown); this.element.addEventListener("keydown", this.handleKeydown);
} }
public setSelectingMode(mode: boolean) {
this.isSelecting = mode
}
private handleMouseMove = (event: MouseEvent) => { private handleMouseMove = (event: MouseEvent) => {
if (!this.isSelecting) return; if (!this.isSelecting) return;
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
if (this.root.selection.selectedRange) { if (this.root.selection.selectedRange) {
this.root.selection.selectedRange.to = lastSelectedCell; isRangeChanged = !checkEqualCellSelections(this.root.selection.selectedRange.to, lastSelectedCell)
if (isRangeChanged) {
this.root.selection.selectedRange.to = lastSelectedCell;
this.root.events.dispatch({
type: EventTypes.CHANGE_SELECTION,
selection: this.root.selection,
enableCallback: true
})
}
} }
this.root.renderSheet();
this.root.renderColumnsBar();
this.root.renderRowsBar();
}; };
private handleMouseUp = () => { private handleMouseUp = () => {
this.isSelecting = false; this.isSelecting = false;
const newSelection = {...this.root.selection}
if (this.root.selection.selectedRange) { if (this.root.selection.selectedRange) {
if ( if (
this.root.selection.selectedRange.from.row === checkEqualCellSelections(this.root.selection.selectedRange.from, this.root.selection.selectedRange.to)
this.root.selection.selectedRange.to.row &&
this.root.selection.selectedRange.from.column ===
this.root.selection.selectedRange.to.column
) { ) {
this.root.selection.selectedRange = null; newSelection.selectedRange = null;
this.root.events.dispatch({
type: EventTypes.CHANGE_SELECTION,
selection: newSelection,
enableCallback: false
})
} }
} }
@ -79,7 +97,6 @@ export class Scroller {
}; };
private handleKeydown = (event: KeyboardEvent) => { private handleKeydown = (event: KeyboardEvent) => {
console.log(event);
//* Navigation //* Navigation
if ( if (
["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key) ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)
@ -92,9 +109,8 @@ export class Scroller {
this.root.selection.selectedCell && this.root.selection.selectedCell &&
this.root.selection.selectedCell.column > 0 this.root.selection.selectedCell.column > 0
) { ) {
console.log("tick");
this.root.selection.selectedCell.column -= 1; this.root.selection.selectedCell.column -= 1;
this.root.renderSheet(); // this.root.renderSheet();
} }
break; break;
} }
@ -102,10 +118,10 @@ 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();
} }
break; break;
} }
@ -115,7 +131,7 @@ export class Scroller {
this.root.selection.selectedCell.row > 0 this.root.selection.selectedCell.row > 0
) { ) {
this.root.selection.selectedCell.row -= 1; this.root.selection.selectedCell.row -= 1;
this.root.renderSheet(); // this.root.renderSheet();
} }
break; break;
} }
@ -123,15 +139,22 @@ 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();
} }
break; break;
} }
} }
this.root.events.dispatch({
type: EventTypes.CHANGE_SELECTION,
selection: this.root.selection,
enableCallback: true
})
} }
//* Start typings
const keysRegex = /^([a-z]|[а-я])$/; const keysRegex = /^([a-z]|[а-я])$/;
if (!event.metaKey && !event.ctrlKey) { if (!event.metaKey && !event.ctrlKey) {
//* Prevent handle shortcutrs //* Prevent handle shortcutrs
@ -157,20 +180,11 @@ export class Scroller {
}; };
private handleClick = (event: MouseEvent) => { private handleClick = (event: MouseEvent) => {
if (event.button !== 0) return; // Left mouse button this.root.events.dispatch({
const { offsetX, offsetY } = event; type: EventTypes.CELL_CLICK,
const clickedCell = this.root.getCellByCoords(offsetX, offsetY); event,
this.isSelecting = true; scroller: this
this.root.selection.selectedRange = { })
from: clickedCell,
to: clickedCell,
};
this.root.selection.selectedCell = clickedCell;
this.root.renderSheet();
this.root.renderColumnsBar();
this.root.renderRowsBar();
}; };
private handleScroll = () => { private handleScroll = () => {

View File

@ -1,11 +1,16 @@
import Spreadsheet from "./main"; import Spreadsheet, { SpreadsheetConstructorProperties } from "./main";
const saveButton = document.querySelector("#save_button"); const options: SpreadsheetConstructorProperties = {
const loadButton = document.querySelector("#load_button"); onCellClick: (event, cell) => {
console.log('Callback from instance', event, cell)
},
onSelectionChange: (selection) => {
console.log("Changed selection: ", selection)
}
}
if (!saveButton || !loadButton) throw new Error("LOST"); const sheet = new Spreadsheet("#spreadsheet", options);
const sheet = new Spreadsheet("#spreadsheet");
function saveDataToLS() { function saveDataToLS() {
const serializableData = sheet.serializeData(); const serializableData = sheet.serializeData();
@ -19,5 +24,10 @@ function loadDataFromLS() {
sheet.loadData(json); sheet.loadData(json);
} }
const saveButton = document.querySelector("#save_button");
const loadButton = document.querySelector("#load_button");
if (!saveButton || !loadButton) throw new Error("LOST");
saveButton.addEventListener("click", saveDataToLS); saveButton.addEventListener("click", saveDataToLS);
loadButton.addEventListener("click", loadDataFromLS); loadButton.addEventListener("click", loadDataFromLS);

View File

@ -21,6 +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 { Events } from "./modules/events";
/* /*
! Component structure ! Component structure
@ -34,9 +35,10 @@ import { RowsBar } from "./components/rowsBar";
</Table> </Table>
*/ */
interface SpreadsheetConstructorProperties { export interface SpreadsheetConstructorProperties {
config?: Omit<Config, "view">; // Not optional.
view?: ViewProperties; view?: ViewProperties;
onCellClick?: ((event: MouseEvent, cell: Cell) => void) | null
onSelectionChange?: ((selection: Selection) => void) | null
} }
export const CSS_PREFIX = "modern_sc_"; export const CSS_PREFIX = "modern_sc_";
@ -55,6 +57,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
constructor( constructor(
target: string | HTMLElement, target: string | HTMLElement,
@ -71,6 +74,9 @@ export default class Spreadsheet {
this.config = new Config(config); this.config = new Config(config);
this.config.onCellClick = props?.onCellClick ?? null
this.config.onSelectonChange = props?.onSelectionChange ?? null
this.rowsBar = new RowsBar(this); this.rowsBar = new RowsBar(this);
this.columnsBar = new ColumnsBar(this); this.columnsBar = new ColumnsBar(this);
this.sheet = new Sheet(this); this.sheet = new Sheet(this);
@ -84,6 +90,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.data = data; this.data = data;
this.styles = new Styles(); this.styles = new Styles();
@ -366,6 +374,7 @@ export default class Spreadsheet {
view, view,
rows, rows,
columns, columns,
onCellClick: null
}); });
return config; return config;

View File

@ -1,5 +1,7 @@
import { Cell } from "./cell";
import { Column } from "./column"; import { Column } from "./column";
import { Row } from "./row"; import { Row } from "./row";
import { Selection } from "./selection";
export interface ViewProperties { export interface ViewProperties {
width: number; width: number;
@ -16,6 +18,8 @@ export type ConfigProperties = {
rows: Row[]; rows: Row[];
columns: Column[]; columns: Column[];
view: ViewProperties; view: ViewProperties;
onCellClick?: ((event: MouseEvent, cell: Cell) => void) | null
onSelectionChange?: ((selection: Selection) => void) | null
}; };
export type SheetConfigConstructorProps = { export type SheetConfigConstructorProps = {
@ -30,9 +34,16 @@ export class Config {
width: 800, width: 800,
height: 600, height: 600,
}; };
onCellClick: ( (event: MouseEvent, cell: Cell) => void ) | null = null
onSelectonChange: ((selection: Selection) => void) | null = 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.onSelectonChange = props.onSelectionChange ?? null
} }
} }

80
src/modules/events.ts Normal file
View File

@ -0,0 +1,80 @@
import { Scroller } from "../components/scroller";
import Spreadsheet, { Selection } from "../main";
export enum EventTypes {
CELL_CLICK = "CELL_CLICK",
CHANGE_SELECTION = "CHANGE_SELECTION"
}
export type CellClickEvent = {
type: EventTypes.CELL_CLICK
event: MouseEvent
scroller: Scroller
}
export type ChangeSelectionEvent = {
type: EventTypes.CHANGE_SELECTION,
selection: Selection
enableCallback?: boolean
}
export type ActionTypes = CellClickEvent | ChangeSelectionEvent
export class Events {
root: Spreadsheet
constructor(root: Spreadsheet) {
this.root = root
}
dispatch(action: ActionTypes) {
switch (action.type) {
case EventTypes.CELL_CLICK: {
const { event, scroller } = action
this.cellClick(event, scroller)
break;
}
case EventTypes.CHANGE_SELECTION: {
const { selection, enableCallback } = action
this.changeSelection(selection, enableCallback)
break;
}
default: {
break;
}
}
}
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)
const selection = new Selection()
selection.selectedCell = clickedCell
selection.selectedRange = {
from: clickedCell,
to: clickedCell,
};
scroller.setSelectingMode(true);
this.changeSelection(selection, true)
this.root.config.onCellClick?.(event, cell)
}
private changeSelection = (selection: Selection, enableCallback = false) => {
this.root.selection = selection
if (enableCallback) this.root.config.onSelectonChange?.(selection)
this.root.renderSheet();
this.root.renderColumnsBar();
this.root.renderRowsBar();
}
}

12
src/utils/position.ts Normal file
View File

@ -0,0 +1,12 @@
import { BaseSelectionType, RangeSelectionType } from "../main";
export function checkEqualRanges(range1: RangeSelectionType, range2: RangeSelectionType) {
const equalRows = range1.from.row === range2.to.row
const equalColumns = range1.from.column === range2.to.column
return equalRows && equalColumns
}
export function checkEqualCellSelections(selection1: BaseSelectionType, selection2: BaseSelectionType) {
return selection1.column === selection2.column && selection1.row === selection2.row
}