modern_spreadsheet/src/components/scroller.ts

253 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Spreadsheet, { CSS_PREFIX } from "../main";
export interface ViewportRect {
top: number;
left: number;
right: number;
bottom: number;
}
export class Scroller {
element: HTMLDivElement;
private verticalScroller: HTMLDivElement;
private horizontalScroller: HTMLDivElement;
private root: Spreadsheet;
private isSelecting = false;
constructor(root: Spreadsheet) {
this.root = root;
const { horizontalScroller, scroller, verticalScroller } =
this.buildComponent();
this.element = scroller;
this.verticalScroller = verticalScroller;
this.horizontalScroller = horizontalScroller;
this.element.style.height = this.root.config.view.height + "px";
this.element.style.width = this.root.config.view.width + "px";
this.element.style.top = this.root.columnsBarHeight + 'px'
this.element.style.left = this.root.rowsBarWidth + 'px'
this.element.tabIndex = -1;
this.updateScrollerSize(); //* Init size set
this.element.addEventListener("scroll", this.handleScroll);
this.element.addEventListener("mousedown", this.handleClick);
this.element.addEventListener("mousemove", this.handleMouseMove);
this.element.addEventListener("mouseup", this.handleMouseUp);
this.element.addEventListener("dblclick", this.handleDoubleClick);
this.element.addEventListener("keydown", this.handleKeydown);
}
private handleMouseMove = (event: MouseEvent) => {
if (!this.isSelecting) return;
const { offsetX, offsetY } = event;
const lastSelectedCell = this.root.getCellByCoords(offsetX, offsetY);
if (this.root.selection.selectedRange) {
this.root.selection.selectedRange.to = lastSelectedCell;
}
this.root.renderSheet();
this.root.renderColumnsBar();
this.root.renderRowsBar();
};
private handleMouseUp = () => {
this.isSelecting = false;
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
) {
this.root.selection.selectedRange = null;
}
}
this.root.renderSheet();
this.root.renderColumnsBar();
this.root.renderRowsBar();
};
private handleDoubleClick = (event: MouseEvent) => {
event.preventDefault();
const position = this.root.getCellByCoords(event.offsetX, event.offsetY);
this.root.showEditor(position);
};
private handleKeydown = (event: KeyboardEvent) => {
console.log(event);
//* Navigation
if (
["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)
) {
event.preventDefault();
this.root.selection.selectedRange = null;
switch (event.key) {
case "ArrowLeft": {
if (
this.root.selection.selectedCell &&
this.root.selection.selectedCell.column > 0
) {
console.log("tick");
this.root.selection.selectedCell.column -= 1;
this.root.renderSheet();
}
break;
}
case "ArrowRight": {
if (
this.root.selection.selectedCell &&
this.root.selection.selectedCell.column <
this.root.config.columns.length - 1
) {
this.root.selection.selectedCell.column += 1;
this.root.renderSheet();
}
break;
}
case "ArrowUp": {
if (
this.root.selection.selectedCell &&
this.root.selection.selectedCell.row > 0
) {
this.root.selection.selectedCell.row -= 1;
this.root.renderSheet();
}
break;
}
case "ArrowDown": {
if (
this.root.selection.selectedCell &&
this.root.selection.selectedCell.row <
this.root.config.rows.length - 1
) {
this.root.selection.selectedCell.row += 1;
this.root.renderSheet();
}
break;
}
}
}
const keysRegex = /^([a-z]|[а-я])$/;
if (!event.metaKey && !event.ctrlKey) {
//* Prevent handle shortcutrs
const isPressedLetterKey = keysRegex.test(event.key.toLowerCase());
if (event.key === "F2" || isPressedLetterKey) {
//* English and Russian keyboard. Or F2 button
event.preventDefault();
if (!this.root.selection.selectedCell) return;
this.root.showEditor(
this.root.selection.selectedCell,
isPressedLetterKey ? event.key : undefined,
);
}
}
if (event.key === "Delete") {
event.preventDefault();
this.root.deleteSelectedCellsValues();
this.root.renderSheet();
}
};
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(); };
private handleScroll = () => {
const rect = this.getViewportBoundlingRect();
this.root.viewport.updateValues(rect);
this.root.renderSheet();
this.root.renderColumnsBar()
this.root.renderRowsBar(); };
public getViewportBoundlingRect(): ViewportRect {
const { scrollTop, scrollLeft } = this.element;
const { height, width } = this.element.getBoundingClientRect();
const bottom = scrollTop + height;
const right = scrollLeft + width;
return {
top: scrollTop,
left: scrollLeft,
bottom,
right,
};
}
private buildComponent() {
const scroller = document.createElement("div");
const verticalScroller = document.createElement("div");
const horizontalScroller = document.createElement("div");
const groupScrollers = document.createElement("div");
const stack = document.createElement("div");
verticalScroller.style.width = "0px";
verticalScroller.style.pointerEvents = "none";
horizontalScroller.style.pointerEvents = "none";
groupScrollers.style.display = "flex";
stack.appendChild(verticalScroller);
stack.appendChild(horizontalScroller);
groupScrollers.appendChild(stack);
this.verticalScroller = verticalScroller;
this.horizontalScroller = horizontalScroller;
scroller.appendChild(groupScrollers);
scroller.classList.add(CSS_PREFIX + "scroller");
return { scroller, verticalScroller, horizontalScroller };
}
private getActualHeight() {
return this.root.config.rows.reduce((acc, curr) => {
acc += curr.height;
return acc;
}, 0);
}
private getActualWidth() {
return this.root.config.columns.reduce((acc, curr) => {
acc += curr.width;
return acc;
}, 0);
}
updateScrollerSize() {
const totalHeight = this.getActualHeight();
const totalWidth = this.getActualWidth();
this.setScrollerHeight(totalHeight);
this.setScrollerWidth(totalWidth);
}
private setScrollerHeight(height: number) {
this.verticalScroller.style.height = height + "px";
}
private setScrollerWidth(width: number) {
this.horizontalScroller.style.width = width + "px";
}
}