parent
9dd64b9a77
commit
8596220e85
|
|
@ -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 = () => {
|
||||||
|
|
|
||||||
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 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);
|
||||||
|
|
|
||||||
13
src/main.ts
13
src/main.ts
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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