From 8596220e85ff3bf4c471286024adfb80477ca6ff Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 26 Jul 2023 14:29:51 +0300 Subject: [PATCH] Added click on cell event Added selection change event --- src/components/scroller.ts | 78 ++++++++++++++++++++++--------------- src/index.ts | 20 +++++++--- src/main.ts | 13 ++++++- src/modules/config.ts | 11 ++++++ src/modules/events.ts | 80 ++++++++++++++++++++++++++++++++++++++ src/utils/position.ts | 12 ++++++ 6 files changed, 175 insertions(+), 39 deletions(-) create mode 100644 src/modules/events.ts create mode 100644 src/utils/position.ts diff --git a/src/components/scroller.ts b/src/components/scroller.ts index 0ecc36c..ef51fee 100644 --- a/src/components/scroller.ts +++ b/src/components/scroller.ts @@ -1,4 +1,6 @@ import Spreadsheet, { CSS_PREFIX } from "../main"; +import { EventTypes } from "../modules/events"; +import { checkEqualCellSelections } from "../utils/position"; export interface ViewportRect { top: number; @@ -12,7 +14,6 @@ export class Scroller { private verticalScroller: HTMLDivElement; private horizontalScroller: HTMLDivElement; private root: Spreadsheet; - private isSelecting = false; constructor(root: Spreadsheet) { @@ -41,29 +42,46 @@ export class Scroller { this.element.addEventListener("keydown", this.handleKeydown); } + public setSelectingMode(mode: boolean) { + this.isSelecting = mode + } + private handleMouseMove = (event: MouseEvent) => { if (!this.isSelecting) return; const { offsetX, offsetY } = event; const lastSelectedCell = this.root.getCellByCoords(offsetX, offsetY); + + let isRangeChanged = false + 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 = () => { this.isSelecting = false; + const newSelection = {...this.root.selection} if (this.root.selection.selectedRange) { if ( - this.root.selection.selectedRange.from.row === - this.root.selection.selectedRange.to.row && - this.root.selection.selectedRange.from.column === - this.root.selection.selectedRange.to.column + checkEqualCellSelections(this.root.selection.selectedRange.from, this.root.selection.selectedRange.to) ) { - 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) => { - console.log(event); //* Navigation if ( ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key) @@ -92,9 +109,8 @@ export class Scroller { this.root.selection.selectedCell && this.root.selection.selectedCell.column > 0 ) { - console.log("tick"); this.root.selection.selectedCell.column -= 1; - this.root.renderSheet(); + // this.root.renderSheet(); } break; } @@ -102,10 +118,10 @@ export class Scroller { if ( this.root.selection.selectedCell && 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.renderSheet(); + // this.root.renderSheet(); } break; } @@ -115,7 +131,7 @@ export class Scroller { this.root.selection.selectedCell.row > 0 ) { this.root.selection.selectedCell.row -= 1; - this.root.renderSheet(); + // this.root.renderSheet(); } break; } @@ -123,15 +139,22 @@ export class Scroller { if ( this.root.selection.selectedCell && 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.renderSheet(); + // this.root.renderSheet(); } break; } } + this.root.events.dispatch({ + type: EventTypes.CHANGE_SELECTION, + selection: this.root.selection, + enableCallback: true + }) } + + //* Start typings const keysRegex = /^([a-z]|[а-я])$/; if (!event.metaKey && !event.ctrlKey) { //* Prevent handle shortcutrs @@ -157,20 +180,11 @@ export class Scroller { }; private handleClick = (event: MouseEvent) => { - if (event.button !== 0) return; // Left mouse button - const { offsetX, offsetY } = event; - const clickedCell = this.root.getCellByCoords(offsetX, offsetY); - this.isSelecting = true; - this.root.selection.selectedRange = { - from: clickedCell, - to: clickedCell, - }; - this.root.selection.selectedCell = clickedCell; - - this.root.renderSheet(); - this.root.renderColumnsBar(); - - this.root.renderRowsBar(); + this.root.events.dispatch({ + type: EventTypes.CELL_CLICK, + event, + scroller: this + }) }; private handleScroll = () => { diff --git a/src/index.ts b/src/index.ts index 06698cb..3b0838a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,16 @@ -import Spreadsheet from "./main"; +import Spreadsheet, { SpreadsheetConstructorProperties } from "./main"; -const saveButton = document.querySelector("#save_button"); -const loadButton = document.querySelector("#load_button"); +const options: SpreadsheetConstructorProperties = { + 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() { const serializableData = sheet.serializeData(); @@ -19,5 +24,10 @@ function loadDataFromLS() { 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); loadButton.addEventListener("click", loadDataFromLS); diff --git a/src/main.ts b/src/main.ts index d9ed316..c20432c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,7 @@ import { Row } from "./modules/row"; import { Column } from "./modules/column"; import { ColumnsBar } from "./components/columnsBar"; import { RowsBar } from "./components/rowsBar"; +import { Events } from "./modules/events"; /* ! Component structure @@ -34,9 +35,10 @@ import { RowsBar } from "./components/rowsBar"; */ -interface SpreadsheetConstructorProperties { - config?: Omit; // Not optional. +export interface SpreadsheetConstructorProperties { view?: ViewProperties; + onCellClick?: ((event: MouseEvent, cell: Cell) => void) | null + onSelectionChange?: ((selection: Selection) => void) | null } export const CSS_PREFIX = "modern_sc_"; @@ -55,6 +57,7 @@ export default class Spreadsheet { public viewport: Viewport; public selection: Selection; public cache: Cache; + public events: Events constructor( target: string | HTMLElement, @@ -71,6 +74,9 @@ export default class Spreadsheet { this.config = new Config(config); + this.config.onCellClick = props?.onCellClick ?? null + this.config.onSelectonChange = props?.onSelectionChange ?? null + this.rowsBar = new RowsBar(this); this.columnsBar = new ColumnsBar(this); this.sheet = new Sheet(this); @@ -84,6 +90,8 @@ export default class Spreadsheet { this.scroller.getViewportBoundlingRect(), ); this.selection = new Selection(); + this.events = new Events(this) + this.data = data; this.styles = new Styles(); @@ -366,6 +374,7 @@ export default class Spreadsheet { view, rows, columns, + onCellClick: null }); return config; diff --git a/src/modules/config.ts b/src/modules/config.ts index 83f55b1..e16f922 100644 --- a/src/modules/config.ts +++ b/src/modules/config.ts @@ -1,5 +1,7 @@ +import { Cell } from "./cell"; import { Column } from "./column"; import { Row } from "./row"; +import { Selection } from "./selection"; export interface ViewProperties { width: number; @@ -16,6 +18,8 @@ export type ConfigProperties = { rows: Row[]; columns: Column[]; view: ViewProperties; + onCellClick?: ((event: MouseEvent, cell: Cell) => void) | null + onSelectionChange?: ((selection: Selection) => void) | null }; export type SheetConfigConstructorProps = { @@ -30,9 +34,16 @@ export class Config { width: 800, height: 600, }; + + onCellClick: ( (event: MouseEvent, cell: Cell) => void ) | null = null + onSelectonChange: ((selection: Selection) => void) | null = null + constructor(props: ConfigProperties) { this.columns = props.columns; this.rows = props.rows; this.view = props.view; + + this.onCellClick = props.onCellClick ?? null + this.onSelectonChange = props.onSelectionChange ?? null } } diff --git a/src/modules/events.ts b/src/modules/events.ts new file mode 100644 index 0000000..be158de --- /dev/null +++ b/src/modules/events.ts @@ -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(); + } + +} \ No newline at end of file diff --git a/src/utils/position.ts b/src/utils/position.ts new file mode 100644 index 0000000..1f33091 --- /dev/null +++ b/src/utils/position.ts @@ -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 +} \ No newline at end of file