parent
9dd64b9a77
commit
8596220e85
|
|
@ -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 = () => {
|
||||
|
|
|
|||
20
src/index.ts
20
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);
|
||||
|
|
|
|||
13
src/main.ts
13
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";
|
|||
</Table>
|
||||
*/
|
||||
|
||||
interface SpreadsheetConstructorProperties {
|
||||
config?: Omit<Config, "view">; // 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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue