refactor, new selection/focus logic
This commit is contained in:
parent
3bb80c1ddf
commit
0d17d35fe8
@ -29,7 +29,7 @@ export class Formula extends ExcelComponent {
|
|||||||
this.formulaInput = this.$root.find('#formula-input');
|
this.formulaInput = this.$root.find('#formula-input');
|
||||||
|
|
||||||
this.$onEventFromObserver('table:select-cell', (cell: Dom) => {
|
this.$onEventFromObserver('table:select-cell', (cell: Dom) => {
|
||||||
this.formulaInput.text = cell.data.value || '';
|
this.formulaInput.text = cell.dataValue || '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
src/components/table/FocusManager.ts
Normal file
59
src/components/table/FocusManager.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { Table } from 'components/table/Table';
|
||||||
|
import { $, Dom } from 'core/Dom';
|
||||||
|
import { parse } from 'core/utils';
|
||||||
|
|
||||||
|
export class FocusManager {
|
||||||
|
private rootTable: Table;
|
||||||
|
public $currentFocusedCell: Dom | null;
|
||||||
|
|
||||||
|
constructor(rootTable: Table) {
|
||||||
|
this.rootTable = rootTable;
|
||||||
|
|
||||||
|
this.initManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initManager() {
|
||||||
|
this.$currentFocusedCell = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetFocus() {
|
||||||
|
window.getSelection()?.removeAllRanges();
|
||||||
|
(document.activeElement as HTMLElement)?.blur();
|
||||||
|
this.$currentFocusedCell = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusOnTable() {
|
||||||
|
this.rootTable.$root.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
focusCell($cell: Dom) {
|
||||||
|
try {
|
||||||
|
const r = new Range();
|
||||||
|
const innerText = $cell.$el.childNodes[$cell.$el.childNodes.length - 1];
|
||||||
|
|
||||||
|
if (innerText !== undefined) {
|
||||||
|
r.setEndAfter(innerText);
|
||||||
|
r.setStartBefore(innerText);
|
||||||
|
r.collapse(false);
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection?.removeAllRanges();
|
||||||
|
selection?.addRange(r);
|
||||||
|
} else {
|
||||||
|
$cell.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$currentFocusedCell = $cell;
|
||||||
|
} catch (e) {
|
||||||
|
console.log('FocusManager: ', e.message);
|
||||||
|
this.rootTable.selectionManager.clearAllSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocusOut(event: FocusEvent) {
|
||||||
|
const $target = $(event.target);
|
||||||
|
if (!this.$currentFocusedCell?.isEqual($target)) return;
|
||||||
|
|
||||||
|
this.rootTable.updateTextInCell(parse($target.dataValue), $target, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
398
src/components/table/SelectionManager.ts
Normal file
398
src/components/table/SelectionManager.ts
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
import { Table } from 'components/table/Table';
|
||||||
|
import {
|
||||||
|
getCellIdFromParams,
|
||||||
|
getParamsFromCellId,
|
||||||
|
isCell2,
|
||||||
|
isSelectionKey,
|
||||||
|
startCellId,
|
||||||
|
} from 'components/table/table.functions';
|
||||||
|
import { $, Dom } from 'core/Dom';
|
||||||
|
import { getCellById, getIdByCell } from 'core/utils';
|
||||||
|
|
||||||
|
export class SelectionManager {
|
||||||
|
private rootTable: Table;
|
||||||
|
private $selection: Dom[];
|
||||||
|
public $currentSelectedCell: Dom | null;
|
||||||
|
private $firstSelectedCell: Dom | null;
|
||||||
|
|
||||||
|
private isMouseDowned: boolean;
|
||||||
|
private static selectedClassName = 'selected';
|
||||||
|
|
||||||
|
private static addSelectedCellClass($cell: Dom) {
|
||||||
|
if (!$cell || !$cell.isExist) return;
|
||||||
|
$cell.addClass('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static removeSelectedCellClass($cell: Dom) {
|
||||||
|
if (!$cell || !$cell.isExist) return;
|
||||||
|
$cell.removeClass('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
private addCurrentCellClass() {
|
||||||
|
if (!this.$firstSelectedCell?.isExist) this.$currentSelectedCell?.addClass('current');
|
||||||
|
else this.$firstSelectedCell?.addClass('current');
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeCurrentCellClass() {
|
||||||
|
if (!this.$firstSelectedCell?.isExist) this.$currentSelectedCell?.removeClass('current');
|
||||||
|
else this.$firstSelectedCell?.removeClass('current');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static selectionKeys = [
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'ArrowUp',
|
||||||
|
'Enter',
|
||||||
|
'Tab',
|
||||||
|
'Delete',
|
||||||
|
|
||||||
|
'Control',
|
||||||
|
'Shift',
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(rootTable: Table) {
|
||||||
|
this.rootTable = rootTable;
|
||||||
|
|
||||||
|
this.initManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initManager() {
|
||||||
|
this.clearAllSelection();
|
||||||
|
|
||||||
|
this.isMouseDowned = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedIds() {
|
||||||
|
return this.$selection.map($cell => getCellIdFromParams(getIdByCell($cell)));
|
||||||
|
}
|
||||||
|
|
||||||
|
addCellToSelection($target: Dom) {
|
||||||
|
this.$selection.push($target);
|
||||||
|
this.currentSelectedCell = $target;
|
||||||
|
this.selectHeader($target);
|
||||||
|
|
||||||
|
SelectionManager.addSelectedCellClass($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private set currentSelectedCell($cell: Dom | null) {
|
||||||
|
this.removeCurrentCellClass();
|
||||||
|
if ($cell) {
|
||||||
|
this.$currentSelectedCell = $cell;
|
||||||
|
this.addCurrentCellClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectFromTo($from: Dom, $cell: Dom) {
|
||||||
|
this.clearAllSelection(false);
|
||||||
|
|
||||||
|
const startCellParams = getParamsFromCellId($from.data.id || startCellId);
|
||||||
|
const selectedCellParams = getParamsFromCellId($cell.data.id || startCellId);
|
||||||
|
|
||||||
|
const startCol = Math.min(startCellParams.col, selectedCellParams.col);
|
||||||
|
const endCol = Math.max(startCellParams.col, selectedCellParams.col);
|
||||||
|
const startRow = Math.min(startCellParams.row, selectedCellParams.row);
|
||||||
|
const endRow = Math.max(startCellParams.row, selectedCellParams.row);
|
||||||
|
|
||||||
|
for (let row = startRow; row <= endRow; row++) {
|
||||||
|
for (let col = startCol; col <= endCol; col++) {
|
||||||
|
const $target = $(`[data-id="${row}:${col}"]`);
|
||||||
|
this.addCellToSelection($target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rootTable.focusManager.focusOnTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectCell($cell: Dom) {
|
||||||
|
this.clearAllSelection();
|
||||||
|
this.rootTable.focusManager.focusOnTable();
|
||||||
|
this.$selection = [$cell];
|
||||||
|
this.currentSelectedCell = $cell;
|
||||||
|
this.$firstSelectedCell = $cell;
|
||||||
|
this.selectHeader($cell);
|
||||||
|
|
||||||
|
this.rootTable.emitSelectCallback($cell);
|
||||||
|
|
||||||
|
SelectionManager.addSelectedCellClass($cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectCells($cells: Dom[]) {
|
||||||
|
this.clearAllSelection();
|
||||||
|
$cells.forEach($cell => this.addCellToSelection($cell));
|
||||||
|
}
|
||||||
|
|
||||||
|
addGroupToSelectionById(cellID: { col: number | string, row: number | string }) {
|
||||||
|
if (this.$selection.length === 1) {
|
||||||
|
this.$firstSelectedCell = this.$selection[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { col, row } = cellID;
|
||||||
|
const $cell = $(`[data-id="${row}:${col}"]`);
|
||||||
|
const $lastCell = this.$selection[this.$selection.length - 1];
|
||||||
|
|
||||||
|
if (!$lastCell.isExist || !this.$firstSelectedCell?.isExist) {
|
||||||
|
this.selectCell($lastCell);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectFromTo(this.$firstSelectedCell, $cell);
|
||||||
|
|
||||||
|
this.$currentSelectedCell = $cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllSelection(clearFirstSelectedCell = true) {
|
||||||
|
this.$selection?.forEach($cell => SelectionManager.removeSelectedCellClass($cell));
|
||||||
|
this.$selection = [];
|
||||||
|
this.currentSelectedCell = null;
|
||||||
|
if (clearFirstSelectedCell) this.$firstSelectedCell = null;
|
||||||
|
this.clearHeaderSelection();
|
||||||
|
this.rootTable.focusManager?.resetFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectHeadRowCol($target: Dom) {
|
||||||
|
const row = $target.closest('[data-header="row"]');
|
||||||
|
const col = $target.closest('[data-header="col"]');
|
||||||
|
const resizer = $target.closest('[data-resize]');
|
||||||
|
|
||||||
|
if (resizer?.isExist) return;
|
||||||
|
|
||||||
|
let $cells: Dom[] = [];
|
||||||
|
if (row?.isExist) {
|
||||||
|
const cells = row.closest('[data-row]')?.findAll('[data-type="cell"]');
|
||||||
|
if (!cells) return;
|
||||||
|
|
||||||
|
$cells = Array.from(cells).map(cell => $(cell as HTMLElement));
|
||||||
|
} else if (col?.isExist) {
|
||||||
|
const colNumber = col.data.col;
|
||||||
|
const columns = this.rootTable.$root.findAll(`[data-col="${colNumber}"]`);
|
||||||
|
|
||||||
|
$cells = Array.from(columns).filter(el => !col.isEqual(el as HTMLElement)).map(el => $(el as HTMLElement));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectCells($cells);
|
||||||
|
this.currentSelectedCell = $cells[0];
|
||||||
|
this.$currentSelectedCell?.isExist && this.rootTable.emitSelectCallback(this.$currentSelectedCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectHeader($cell: Dom) {
|
||||||
|
if (!$cell.isExist) return;
|
||||||
|
|
||||||
|
const { headerCol, headerRow } = this.findHeadOfCell($cell);
|
||||||
|
|
||||||
|
headerRow?.addClass(SelectionManager.selectedClassName);
|
||||||
|
headerCol?.addClass(SelectionManager.selectedClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearHeaderSelection() {
|
||||||
|
this.rootTable.$root.findAll('[data-header]').forEach(header => {
|
||||||
|
header.classList.remove(SelectionManager.selectedClassName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getNeighbourCellBySide(side: 'left' | 'right' | 'down' | 'up', $cell = this.$currentSelectedCell): Dom | undefined {
|
||||||
|
if (!$cell?.isExist) {
|
||||||
|
console.error('Нет стартовой ячейки');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { row, col } = getIdByCell($cell);
|
||||||
|
if (row === undefined || col === undefined) return;
|
||||||
|
|
||||||
|
switch (side) {
|
||||||
|
case 'down':
|
||||||
|
row++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'up':
|
||||||
|
row--;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'left':
|
||||||
|
col--;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'right':
|
||||||
|
col++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $neighbourCell = getCellById({ col, row });
|
||||||
|
if (!$neighbourCell || !$neighbourCell.isExist) {
|
||||||
|
console.error('Не получилось найти ячейку');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
return $neighbourCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove
|
||||||
|
moveSelectionTo(side: 'left' | 'right' | 'down' | 'up') {
|
||||||
|
const $current = this.$currentSelectedCell || this.rootTable.focusManager.$currentFocusedCell || this.$currentSelectedCell?.[0];
|
||||||
|
if (!$current?.isExist) return;
|
||||||
|
|
||||||
|
const $movingCell = this.getNeighbourCellBySide(side, $current);
|
||||||
|
if (!$movingCell?.isExist || !$movingCell) {
|
||||||
|
console.error('Ошибка, не найдена ячейка для перемещения');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.selectCell($movingCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
findHeadOfCell($cell: Dom): { headerRow: Dom | undefined, headerCol: Dom | undefined } {
|
||||||
|
const headerRow = $cell.closest('[data-row]')?.find("[data-header='row']");
|
||||||
|
const headerCol = this.rootTable.$root.find(`[data-col="${$cell.data.id?.split(':')[1]}"]`);
|
||||||
|
|
||||||
|
return { headerRow, headerCol };
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDownHandler(event: MouseEvent) {
|
||||||
|
const target = $(event.target);
|
||||||
|
const header = target.closest('[data-header]');
|
||||||
|
const resizer = target.closest('[data-resize]');
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (header?.isExist && !isCell2(target) && !resizer?.isExist) {
|
||||||
|
this.selectHeadRowCol(header);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isCell2(target)) return;
|
||||||
|
if (this.$currentSelectedCell && target.isEqual(this.$currentSelectedCell)) return;
|
||||||
|
|
||||||
|
this.isMouseDowned = true;
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case event.ctrlKey:
|
||||||
|
this.addCellToSelection(target);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case event.shiftKey:
|
||||||
|
if (!this.$currentSelectedCell) this.selectCell(target);
|
||||||
|
else {
|
||||||
|
const id = getIdByCell(target);
|
||||||
|
if (!id) return;
|
||||||
|
const { row, col } = id;
|
||||||
|
if (row === undefined || col === undefined) return;
|
||||||
|
|
||||||
|
this.addGroupToSelectionById({ row, col });
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: this.selectCell(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDownHandler(event: KeyboardEvent) {
|
||||||
|
if (!isSelectionKey(event.key) || this.rootTable.focusManager.$currentFocusedCell?.isExist) {
|
||||||
|
// TODO improve check
|
||||||
|
if (event.key.length === 1) {
|
||||||
|
this.$currentSelectedCell && this.rootTable.focusManager.focusCell(this.$currentSelectedCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let $cell: Dom | undefined;
|
||||||
|
let side: 'left' | 'right' | 'down' | 'up' = 'right';
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Shift':
|
||||||
|
case 'Control':
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 'ArrowDown':
|
||||||
|
side = 'down';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowUp':
|
||||||
|
side = 'up';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowLeft':
|
||||||
|
side = 'left';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowRight':
|
||||||
|
side = 'right';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
side = event.shiftKey ? 'up' : 'down';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Tab':
|
||||||
|
side = event.shiftKey ? 'left' : 'right';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Delete': {
|
||||||
|
this.$selection.forEach($el => {
|
||||||
|
this.rootTable.updateTextInCell('', $el);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!side) return;
|
||||||
|
|
||||||
|
// $cell = this.getNeighbourCellBySide(side);
|
||||||
|
$cell = this.getNeighbourCellBySide(side, this.$currentSelectedCell || this.$firstSelectedCell);
|
||||||
|
|
||||||
|
if (event.ctrlKey && $cell?.isExist) {
|
||||||
|
this.addCellToSelection($cell);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.shiftKey && $cell?.isExist && event.key !== 'Enter' && event.key !== 'Tab') {
|
||||||
|
const id = getIdByCell($cell);
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const { row, col } = id;
|
||||||
|
if (row === undefined || col === undefined) return;
|
||||||
|
|
||||||
|
this.addGroupToSelectionById({ row, col });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cell = this.getNeighbourCellBySide(side, this.$firstSelectedCell || this.$currentSelectedCell);
|
||||||
|
if (!$cell || !$cell.isExist) return;
|
||||||
|
|
||||||
|
this.selectCell($cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
doubleClickHandler(event: MouseEvent) {
|
||||||
|
const target = $(event.target);
|
||||||
|
if (!isCell2(target)) return;
|
||||||
|
|
||||||
|
this.rootTable.focusManager.focusCell(target);
|
||||||
|
this.rootTable.updateTextInCell(target.dataValue, target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseOverHandler(event: MouseEvent) {
|
||||||
|
if (!this.isMouseDowned) return;
|
||||||
|
const $target = $((event as any).toElement);
|
||||||
|
|
||||||
|
if (this.$firstSelectedCell?.isExist && isCell2($target)) {
|
||||||
|
this.selectFromTo(this.$firstSelectedCell, $target);
|
||||||
|
this.$currentSelectedCell = $target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseUpHandler() {
|
||||||
|
this.isMouseDowned = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyStyle(style: Partial<CSSStyleDeclaration>) {
|
||||||
|
this.$selection.forEach($cell => {
|
||||||
|
$cell.css(style);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,38 +1,36 @@
|
|||||||
import { ContextSelectType } from 'components/ContextMenu/ContextMenu';
|
import { ContextSelectType } from 'components/ContextMenu/ContextMenu';
|
||||||
import { BaseComponentOption } from 'components/excel/Excel';
|
import { BaseComponentOption } from 'components/excel/Excel';
|
||||||
|
import { FocusManager } from 'components/table/FocusManager';
|
||||||
|
import { SelectionManager } from 'components/table/SelectionManager';
|
||||||
import { startCellId } from 'components/table/table.functions';
|
import { startCellId } from 'components/table/table.functions';
|
||||||
import * as actions from 'redux/action-creators';
|
import * as actions from 'redux/action-creators';
|
||||||
import { $, Dom } from 'core/Dom';
|
import { $, Dom } from 'core/Dom';
|
||||||
import { ExcelComponent } from 'core/ExcelComponent';
|
import { ExcelComponent } from 'core/ExcelComponent';
|
||||||
import { TableSelection } from 'components/table/TableSelection';
|
|
||||||
import {
|
import {
|
||||||
addCol,
|
addCol,
|
||||||
addRow,
|
addRow, changeCurrentStyles, changeCurrentText,
|
||||||
changeCurrentStyles,
|
|
||||||
changeCurrentText,
|
|
||||||
changeTableSize, removeColFromTable,
|
changeTableSize, removeColFromTable,
|
||||||
removeRowFromTable,
|
removeRowFromTable,
|
||||||
} from 'redux/action-creators';
|
} from 'redux/action-creators';
|
||||||
import { createTable } from 'components/table/table.template';
|
import { createTable } from 'components/table/table.template';
|
||||||
import { TableSizeType } from 'redux/types';
|
import { TableSizeType } from 'redux/types';
|
||||||
import { initialStyleState } from 'src/constants';
|
import { parse } from 'core/utils';
|
||||||
import { getCellId, parse } from 'core/utils';
|
|
||||||
import { resizeHandler } from 'components/table/handlers/table.resize';
|
import { resizeHandler } from 'components/table/handlers/table.resize';
|
||||||
import { selectHandler } from 'components/table/handlers/table.select.handler';
|
import { initialStyleState } from 'src/constants';
|
||||||
|
|
||||||
export class Table extends ExcelComponent {
|
export class Table extends ExcelComponent {
|
||||||
static className = 'excel__table';
|
static className = 'excel__table';
|
||||||
|
|
||||||
private selection: TableSelection;
|
|
||||||
private isMouseDowned: boolean;
|
|
||||||
private tableResizing: boolean;
|
private tableResizing: boolean;
|
||||||
public tableSize: TableSizeType;
|
public tableSize: TableSizeType;
|
||||||
|
selectionManager: SelectionManager;
|
||||||
|
focusManager: FocusManager;
|
||||||
|
|
||||||
constructor($root: Dom, options: BaseComponentOption) {
|
constructor($root: Dom, options: BaseComponentOption) {
|
||||||
super($root, {
|
super($root, {
|
||||||
...options,
|
...options,
|
||||||
name: 'Table',
|
name: 'Table',
|
||||||
eventListeners: ['mousedown', 'keydown', 'input', 'mouseover', 'mouseup', 'contextmenu'],
|
eventListeners: ['mousedown', 'keydown', 'input', 'mouseover', 'mouseup', 'contextmenu', 'dblclick', 'focusout'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,10 +39,10 @@ export class Table extends ExcelComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeRender() {
|
beforeRender() {
|
||||||
this.selection = new TableSelection(this);
|
|
||||||
this.tableResizing = false;
|
this.tableResizing = false;
|
||||||
this.isMouseDowned = false;
|
|
||||||
this.tableSize = this.getTableSize();
|
this.tableSize = this.getTableSize();
|
||||||
|
this.selectionManager = new SelectionManager(this);
|
||||||
|
this.focusManager = new FocusManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
afterRender() {
|
afterRender() {
|
||||||
@ -53,7 +51,7 @@ export class Table extends ExcelComponent {
|
|||||||
this.initTable();
|
this.initTable();
|
||||||
|
|
||||||
this.$onEventFromObserver('formula:input', this.updateTextInCell);
|
this.$onEventFromObserver('formula:input', this.updateTextInCell);
|
||||||
this.$onEventFromObserver('formula:enter-press', () => this.selection.$currentCell.focus());
|
// this.$onEventFromObserver('formula:enter-press', () => this.selection.$currentCell.focus());
|
||||||
this.$onEventFromObserver('toolbar:applyStyle', this.updateCurrentStyles);
|
this.$onEventFromObserver('toolbar:applyStyle', this.updateCurrentStyles);
|
||||||
this.$onEventFromObserver('toolbar:add-row', this.addNewRowHandler);
|
this.$onEventFromObserver('toolbar:add-row', this.addNewRowHandler);
|
||||||
this.$onEventFromObserver('toolbar:remove-row', this.removeRowHandler);
|
this.$onEventFromObserver('toolbar:remove-row', this.removeRowHandler);
|
||||||
@ -129,24 +127,24 @@ export class Table extends ExcelComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initStartCellFocus() {
|
initStartCellFocus() {
|
||||||
const $cell = this.$root.find(`[data-id="${startCellId}"]`);
|
// const $cell = this.$root.find(`[data-id="${startCellId}"]`);
|
||||||
this.selection.select($cell);
|
// this.selection.select($cell);
|
||||||
|
//
|
||||||
this.$emitEventToObserver('table:select-cell', $cell);
|
// this.$emitEventToObserver('table:select-cell', $cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitSelectCallback() {
|
emitSelectCallback($cell: Dom) {
|
||||||
this.$emitEventToObserver('table:select-cell', this.selection.$currentCell);
|
this.$emitEventToObserver('table:select-cell', $cell);
|
||||||
|
|
||||||
const styles = this.selection.$currentCell?.getStyles(Object.keys(initialStyleState));
|
const styles = $cell.getStyles(Object.keys(initialStyleState));
|
||||||
|
|
||||||
this.dispatchToStore(changeCurrentStyles(styles));
|
this.dispatchToStore(changeCurrentStyles(styles));
|
||||||
this.dispatchToStore(changeCurrentText(this.selection.$currentCell.text));
|
this.dispatchToStore(changeCurrentText($cell.dataValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
async resizeTable(event: MouseEvent) {
|
async resizeTable(event: MouseEvent) {
|
||||||
try {
|
try {
|
||||||
if (!$(event.target).closest('[data-resize]').isExist) return;
|
if (!$(event.target).closest('[data-resize]')?.isExist) return;
|
||||||
this.tableResizing = true;
|
this.tableResizing = true;
|
||||||
const resizeData = await resizeHandler(this.$root, event);
|
const resizeData = await resizeHandler(this.$root, event);
|
||||||
this.dispatchToStore(actions.tableResize(resizeData));
|
this.dispatchToStore(actions.tableResize(resizeData));
|
||||||
@ -157,23 +155,27 @@ export class Table extends ExcelComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateCurrentStyles = (style: Partial<CSSStyleDeclaration>) => {
|
updateCurrentStyles = (style: Partial<CSSStyleDeclaration>) => {
|
||||||
this.selection.applyStyle(style);
|
this.selectionManager.applyStyle(style);
|
||||||
this.dispatchToStore(actions.applyStyle({
|
this.dispatchToStore(actions.applyStyle({
|
||||||
value: style,
|
value: style,
|
||||||
ids: this.selection.selectedIds,
|
ids: this.selectionManager.selectedIds,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
updateTextInCell = (text: string, $cell = this.selection.$focusedCell) => {
|
updateTextInCell = (text: string, $cell?: Dom, changeVisibleText?: boolean) => {
|
||||||
$cell.attr('data-value', text);
|
if (!$cell || !$cell.isExist) return;
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
$cell.text = parse(text);
|
|
||||||
|
|
||||||
this.dispatchToStore(actions.changeText({
|
if (!changeVisibleText) {
|
||||||
text,
|
$cell.attr('data-value', text);
|
||||||
id: $cell.data.id || startCellId,
|
|
||||||
}));
|
this.dispatchToStore(actions.changeText({
|
||||||
this.selection.focusToCell($cell);
|
text: $cell.dataValue.toString(),
|
||||||
|
id: $cell.data.id || startCellId,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
$cell.text = text || '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addNewRowHandler = () => {
|
addNewRowHandler = () => {
|
||||||
@ -190,12 +192,12 @@ export class Table extends ExcelComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeRowHandler = () => {
|
removeRowHandler = () => {
|
||||||
const cell = this.selection.$focusedCell;
|
// const cell = this.selection.$focusedCell;
|
||||||
const cellId = getCellId(cell);
|
// const cellId = getCellId(cell);
|
||||||
if (!cellId) return;
|
// if (!cellId) return;
|
||||||
|
//
|
||||||
const { row } = cellId;
|
// const { row } = cellId;
|
||||||
this.removeColRow('row', +row);
|
// this.removeColRow('row', +row);
|
||||||
};
|
};
|
||||||
|
|
||||||
removeColRow = (type: 'col' | 'row', index: number) => {
|
removeColRow = (type: 'col' | 'row', index: number) => {
|
||||||
@ -215,7 +217,6 @@ export class Table extends ExcelComponent {
|
|||||||
const idx = target.closest('[data-row]').data.row;
|
const idx = target.closest('[data-row]').data.row;
|
||||||
if (!idx) return;
|
if (!idx) return;
|
||||||
this.addNewColRow('row', 'before', +idx);
|
this.addNewColRow('row', 'before', +idx);
|
||||||
// this.selection.selectByCellId({ col: 2, row: 2 });
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,33 +262,43 @@ export class Table extends ExcelComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMousedown(event: MouseEvent) {
|
onMousedown(event: MouseEvent) {
|
||||||
this.isMouseDowned = true;
|
|
||||||
selectHandler(event, this.selection, this.emitSelectCallback.bind(this));
|
|
||||||
this.resizeTable(event);
|
this.resizeTable(event);
|
||||||
|
this.selectionManager.onMouseDownHandler(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeydown(event: KeyboardEvent) {
|
onKeydown(event: KeyboardEvent) {
|
||||||
selectHandler(event, this.selection, this.emitSelectCallback.bind(this));
|
this.selectionManager.onKeyDownHandler(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
onInput(event: InputEvent) {
|
onInput(event: InputEvent) {
|
||||||
this.updateTextInCell((event.target as HTMLElement).innerText);
|
const $target = $(event.target);
|
||||||
|
if (!this.focusManager.$currentFocusedCell) return;
|
||||||
|
|
||||||
|
this.updateTextInCell($target.text, this.focusManager.$currentFocusedCell);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseover(event: MouseEvent) {
|
onMouseover(event: MouseEvent) {
|
||||||
this.isMouseDowned && !this.tableResizing && selectHandler(event, this.selection);
|
this.selectionManager.onMouseOverHandler(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseup() {
|
onMouseup() {
|
||||||
this.isMouseDowned = false;
|
this.selectionManager.onMouseUpHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
onContextmenu(event: MouseEvent) {
|
onContextmenu(event: MouseEvent) {
|
||||||
const $target = $(event.target);
|
const $target = $(event.target);
|
||||||
|
|
||||||
this.selection.clearSelection();
|
this.selectionManager.clearAllSelection();
|
||||||
this.selection.selectHeadRowCol($target);
|
this.selectionManager.selectHeadRowCol($target);
|
||||||
|
|
||||||
this.$emitEventToObserver('table:contextmenu', event);
|
this.$emitEventToObserver('table:contextmenu', event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDblclick(event: MouseEvent) {
|
||||||
|
this.selectionManager.doubleClickHandler(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocusout(event: FocusEvent) {
|
||||||
|
this.focusManager.onFocusOut(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,165 +0,0 @@
|
|||||||
import { Table } from 'components/table/Table';
|
|
||||||
import { $, Dom } from 'core/Dom';
|
|
||||||
import { getParamsFromCellId, startCellId } from 'components/table/table.functions';
|
|
||||||
import { initialStyleState } from 'src/constants';
|
|
||||||
|
|
||||||
export class TableSelection {
|
|
||||||
static selectedClassName = 'selected';
|
|
||||||
public selectedCellsGroup: Dom[];
|
|
||||||
// TODO remove focus, when selection
|
|
||||||
// $focusedCell and $currentCell can be different, f.e. if select cells with Shift key, currentCell
|
|
||||||
// will be last cell, focusedCell will start cell, and can be different from selectedCellsGroup first item
|
|
||||||
public $currentCell: Dom;
|
|
||||||
public $focusedCell: Dom;
|
|
||||||
public rootTable: Table;
|
|
||||||
|
|
||||||
constructor(rootTable: Table) {
|
|
||||||
this.selectedCellsGroup = [];
|
|
||||||
this.rootTable = rootTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedIds() {
|
|
||||||
return this.selectedCellsGroup.map(el => el.data.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO make a focus manager
|
|
||||||
focusToCell($cell: Dom) {
|
|
||||||
try {
|
|
||||||
this.$focusedCell = $cell;
|
|
||||||
|
|
||||||
const range = new Range();
|
|
||||||
const node = $cell.$el;
|
|
||||||
|
|
||||||
range.setStartAfter(node.childNodes[node.childNodes.length - 1]);
|
|
||||||
|
|
||||||
window.getSelection()?.removeAllRanges();
|
|
||||||
window.getSelection()?.addRange(range);
|
|
||||||
} catch (e) {
|
|
||||||
$cell.$el.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select($cell: Dom) {
|
|
||||||
this.clearSelection();
|
|
||||||
this.selectedCellsGroup = [$cell];
|
|
||||||
this.$currentCell = $cell;
|
|
||||||
this.$focusedCell = $cell;
|
|
||||||
$cell.addClass(TableSelection.selectedClassName);
|
|
||||||
this.focusToCell($cell);
|
|
||||||
this.selectHeader($cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectByCellId(cellID: { col: number, row: number }) {
|
|
||||||
let { col, row } = cellID;
|
|
||||||
|
|
||||||
if (col <= 0) col = 0;
|
|
||||||
if (row <= 0) row = 0;
|
|
||||||
|
|
||||||
this.select($(`[data-id="${row}:${col}"]`));
|
|
||||||
}
|
|
||||||
|
|
||||||
clearSelection() {
|
|
||||||
this.selectedCellsGroup.forEach(el => el?.removeClass(TableSelection.selectedClassName));
|
|
||||||
this.$currentCell?.removeClass(TableSelection.selectedClassName);
|
|
||||||
this.clearHeaderSelection();
|
|
||||||
this.selectedCellsGroup = [];
|
|
||||||
this.rootTable.updateCurrentStyles(initialStyleState);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectFromTo($from: Dom, $cell: Dom) {
|
|
||||||
const startCellParams = getParamsFromCellId($from.data.id || startCellId);
|
|
||||||
const selectedCellParams = getParamsFromCellId($cell.data.id || startCellId);
|
|
||||||
|
|
||||||
const startCol = Math.min(startCellParams.col, selectedCellParams.col);
|
|
||||||
const endCol = Math.max(startCellParams.col, selectedCellParams.col);
|
|
||||||
const startRow = Math.min(startCellParams.row, selectedCellParams.row);
|
|
||||||
const endRow = Math.max(startCellParams.row, selectedCellParams.row);
|
|
||||||
|
|
||||||
this.clearSelection();
|
|
||||||
|
|
||||||
for (let row = startRow; row <= endRow; row++) {
|
|
||||||
for (let col = startCol; col <= endCol; col++) {
|
|
||||||
const $target = $(`[data-id="${row}:${col}"]`);
|
|
||||||
this.addCellToSelection($target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectGroupies($cells: Dom[]) {
|
|
||||||
this.clearSelection();
|
|
||||||
$cells.forEach($cell => this.addCellToSelection($cell));
|
|
||||||
}
|
|
||||||
|
|
||||||
addCellToSelection($cell: Dom) {
|
|
||||||
this.selectedCellsGroup.push($cell);
|
|
||||||
$cell.addClass(TableSelection.selectedClassName);
|
|
||||||
this.selectHeader($cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
addGroupToSelectionById(cellID: { col: number, row: number }) {
|
|
||||||
if (this.selectedCellsGroup.length === 1) {
|
|
||||||
this.$focusedCell = this.selectedCellsGroup[0];
|
|
||||||
}
|
|
||||||
const { col, row } = cellID;
|
|
||||||
const $cell = $(`[data-id="${row}:${col}"]`);
|
|
||||||
const $lastCell = this.selectedCellsGroup[this.selectedCellsGroup.length - 1];
|
|
||||||
|
|
||||||
if (!$lastCell.isExist) {
|
|
||||||
this.select($lastCell);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectFromTo(this.$focusedCell, $cell);
|
|
||||||
|
|
||||||
this.$currentCell = $cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyStyle(style: Partial<CSSStyleDeclaration>) {
|
|
||||||
this.selectedCellsGroup.forEach(el => el.css(style));
|
|
||||||
}
|
|
||||||
|
|
||||||
selectHeader($cell: Dom) {
|
|
||||||
if (!$cell) return;
|
|
||||||
|
|
||||||
const { headerCol, headerRow } = this.findHeadOfCell($cell);
|
|
||||||
|
|
||||||
headerRow.addClass(TableSelection.selectedClassName);
|
|
||||||
headerCol.addClass(TableSelection.selectedClassName);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearHeaderSelection() {
|
|
||||||
this.rootTable.$root.findAll('[data-header]').forEach(header => {
|
|
||||||
header.classList.remove(TableSelection.selectedClassName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
findHeadOfCell($cell: Dom): { headerRow: Dom, headerCol: Dom } {
|
|
||||||
const headerRow = $cell.closest('[data-row]').find("[data-header='row']");
|
|
||||||
const headerCol = this.rootTable.$root.find(`[data-col="${$cell.data.id?.split(':')[1]}"]`);
|
|
||||||
|
|
||||||
return { headerRow, headerCol };
|
|
||||||
}
|
|
||||||
|
|
||||||
isCellInSelection($cell: Dom) {
|
|
||||||
return this.selectedCellsGroup.includes($cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectHeadRowCol($target: Dom) {
|
|
||||||
const row = $target.closest('[data-header="row"]');
|
|
||||||
const col = $target.closest('[data-header="col"]');
|
|
||||||
const resizer = $target.closest('[data-resize]');
|
|
||||||
|
|
||||||
if (row.$el && !resizer.$el) {
|
|
||||||
const cells = row.closest('[data-row]').findAll('[data-type="cell"]');
|
|
||||||
const $cells = Array.from(cells).map(cell => $(cell as HTMLElement));
|
|
||||||
|
|
||||||
this.selectGroupies($cells);
|
|
||||||
} else if (col.$el && !resizer.$el) {
|
|
||||||
const colNumber = col.data.col;
|
|
||||||
const columns = this.rootTable.$root.findAll(`[data-col="${colNumber}"]`);
|
|
||||||
const $cells = Array.from(columns).filter(el => el !== col.$el).map(el => $(el as HTMLElement));
|
|
||||||
|
|
||||||
this.selectGroupies($cells);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
import { $ } from 'core/Dom';
|
|
||||||
import { TableSelection } from 'components/table/TableSelection';
|
|
||||||
import { getParamsFromCellId, isCell, startCellId } from 'components/table/table.functions';
|
|
||||||
|
|
||||||
export function selectHandler(event: MouseEvent | KeyboardEvent, selection: TableSelection, callback?: () => void) {
|
|
||||||
switch (event.type) {
|
|
||||||
case 'mousedown': {
|
|
||||||
onMouseDownHandler();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'keydown': {
|
|
||||||
onKeyDownHandler();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'mouseover': {
|
|
||||||
onMouseOverHandler();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Analog callback && callback();
|
|
||||||
callback?.();
|
|
||||||
|
|
||||||
function onMouseDownHandler() {
|
|
||||||
const target = $(event.target);
|
|
||||||
|
|
||||||
if (isCell(event)) {
|
|
||||||
if (event.shiftKey) selection.selectFromTo(selection.$currentCell, target);
|
|
||||||
else if (event.ctrlKey) selection.addCellToSelection(target);
|
|
||||||
else selection.select(target);
|
|
||||||
} else {
|
|
||||||
selection.selectHeadRowCol(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeyDownHandler() {
|
|
||||||
const { key } = event as KeyboardEvent;
|
|
||||||
const handleKeys = [
|
|
||||||
'ArrowDown',
|
|
||||||
'ArrowUp',
|
|
||||||
'ArrowRight',
|
|
||||||
'ArrowLeft',
|
|
||||||
'Enter',
|
|
||||||
'Tab',
|
|
||||||
'Delete',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!selection?.$currentCell || !handleKeys.includes(key)) return;
|
|
||||||
|
|
||||||
// If something goes wrong, go to start line
|
|
||||||
const currentCellId = selection.$currentCell.data.id || startCellId;
|
|
||||||
let { row, col } = getParamsFromCellId(currentCellId);
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case 'ArrowDown': {
|
|
||||||
if (selection.rootTable.tableSize.row === row + 1) return;
|
|
||||||
row++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ArrowUp': {
|
|
||||||
row--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ArrowRight': {
|
|
||||||
if (selection.rootTable.tableSize.col === col + 1) return;
|
|
||||||
col++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ArrowLeft': {
|
|
||||||
col--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Enter': {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (event.shiftKey) row--;
|
|
||||||
else row++;
|
|
||||||
if (row === selection.rootTable.tableSize.row) selection.rootTable.addNewRowHandler();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Tab': {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Tab in selection must save focus in inner selection cells
|
|
||||||
if (selection.selectedCellsGroup.length > 1) {
|
|
||||||
const idxInSelection = selection.selectedCellsGroup.findIndex(el => el.$el === selection.$focusedCell.$el);
|
|
||||||
const nextIdx = idxInSelection + 1 === selection.selectedCellsGroup.length ? 0 : idxInSelection + 1;
|
|
||||||
|
|
||||||
selection.focusToCell(selection.selectedCellsGroup[nextIdx]);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.shiftKey) col--;
|
|
||||||
else col++;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Delete': {
|
|
||||||
if (selection.selectedCellsGroup.length > 1) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
selection.selectedCellsGroup.forEach($cell => selection.rootTable.updateTextInCell('', $cell));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.shiftKey && key !== 'Tab' && key !== 'Enter') selection.addGroupToSelectionById({ row, col });
|
|
||||||
else selection.selectByCellId({ row, col });
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseOverHandler() {
|
|
||||||
if (selection.$currentCell.$el) {
|
|
||||||
// TODO find event type
|
|
||||||
selection.selectFromTo(selection.$currentCell, $((event as any).toElement));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,18 @@
|
|||||||
import { $ } from 'core/Dom';
|
import { SelectionManager } from 'components/table/SelectionManager';
|
||||||
|
import { $, Dom } from 'core/Dom';
|
||||||
|
|
||||||
export function isCell(event: Event): boolean {
|
export function isCell(event: Event): boolean {
|
||||||
return $(event.target).data.type === 'cell';
|
return $(event.target).data.type === 'cell';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCell2($cell: Dom): boolean {
|
||||||
|
return $cell.data.type === 'cell';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSelectionKey(key: string): boolean {
|
||||||
|
return SelectionManager.selectionKeys.includes(key);
|
||||||
|
}
|
||||||
|
|
||||||
export function getParamsFromCellId(cellId: string) {
|
export function getParamsFromCellId(cellId: string) {
|
||||||
const row = +cellId.split(':')[0];
|
const row = +cellId.split(':')[0];
|
||||||
const col = +cellId.split(':')[1];
|
const col = +cellId.split(':')[1];
|
||||||
@ -11,4 +20,8 @@ export function getParamsFromCellId(cellId: string) {
|
|||||||
return { col, row };
|
return { col, row };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCellIdFromParams(params: { row?: string, col?: string }) {
|
||||||
|
return `${params.row}:${params.col}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const startCellId = '0:0';
|
export const startCellId = '0:0';
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export class ComponentManager {
|
|||||||
|
|
||||||
createComponent(Component: ComponentType, componentOptions: BaseComponentOption) {
|
createComponent(Component: ComponentType, componentOptions: BaseComponentOption) {
|
||||||
const $el = $.create('div', Component.className);
|
const $el = $.create('div', Component.className);
|
||||||
|
$el.attr('tabindex', '-1');
|
||||||
const componentInstance: ExcelComponent = new Component($el, componentOptions);
|
const componentInstance: ExcelComponent = new Component($el, componentOptions);
|
||||||
|
|
||||||
$el.html(componentInstance.toHTML());
|
$el.html(componentInstance.toHTML());
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export class Dom implements DomClass {
|
|||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
if (this.$el.closest('input')) return (this.$el as HTMLInputElement).value;
|
if (this.$el.closest('input')) return (this.$el as HTMLInputElement).value;
|
||||||
return (this.$el as HTMLElement).innerText;
|
return (this.$el as HTMLElement).innerText || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
@ -70,10 +70,18 @@ export class Dom implements DomClass {
|
|||||||
this.$el.focus();
|
this.$el.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blur() {
|
||||||
|
this.$el.blur();
|
||||||
|
}
|
||||||
|
|
||||||
get data() {
|
get data() {
|
||||||
return this.$el.dataset || '';
|
return this.$el.dataset || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dataValue(): string {
|
||||||
|
return this.$el.dataset.value || '';
|
||||||
|
}
|
||||||
|
|
||||||
setData(name: string, value: string) {
|
setData(name: string, value: string) {
|
||||||
this.$el.setAttribute(`data-${name}`, value);
|
this.$el.setAttribute(`data-${name}`, value);
|
||||||
}
|
}
|
||||||
@ -124,7 +132,7 @@ export class Dom implements DomClass {
|
|||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
attr(name: string, value: string) {
|
attr(name: string, value?: string) {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
this.$el.setAttribute(name, value);
|
this.$el.setAttribute(name, value);
|
||||||
return this;
|
return this;
|
||||||
@ -144,6 +152,12 @@ export class Dom implements DomClass {
|
|||||||
get isExist(): boolean {
|
get isExist(): boolean {
|
||||||
return !!this.$el;
|
return !!this.$el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEqual($target: Dom | HTMLElement): boolean {
|
||||||
|
if ($target instanceof Dom) return this.$el === $target.$el;
|
||||||
|
|
||||||
|
return this.$el === $target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $(selector: SelectorType) {
|
export function $(selector: SelectorType) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Dom } from 'core/Dom';
|
import { $, Dom } from 'core/Dom';
|
||||||
import { CallbackType, StateType } from 'redux/types';
|
import { CallbackType, StateType } from 'redux/types';
|
||||||
import { fontSizes } from 'src/constants';
|
import { fontSizes } from 'src/constants';
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ export function debounce(fn: CallbackType, wait: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parse(value: string) {
|
export function parse(value: string) {
|
||||||
if (value.startsWith('=')) {
|
if (value.toString().startsWith('=')) {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-eval
|
// eslint-disable-next-line no-eval
|
||||||
return eval(value.slice(1));
|
return eval(value.slice(1));
|
||||||
@ -68,12 +68,15 @@ export function getMethodNameByEventName(eventName: string): string {
|
|||||||
return `on${capitalize(eventName)}`;
|
return `on${capitalize(eventName)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCellId($cell: Dom): { row: string, col: string } | false {
|
export function getIdByCell($cell: Dom): { row?: string, col?: string } {
|
||||||
if (!$cell.isExist) return false;
|
|
||||||
const id = $cell.data.id?.split(':');
|
const id = $cell.data.id?.split(':');
|
||||||
if (!Array.isArray(id)) return false;
|
return { row: id?.[0], col: id?.[1] };
|
||||||
const [row, col] = id;
|
}
|
||||||
return { row, col };
|
|
||||||
|
export function getCellById(id: { row: string | number, col: string | number }): Dom | false {
|
||||||
|
const $cell = $(`[data-id="${id.row}:${id.col}"]`);
|
||||||
|
if (!$cell.isExist) return false;
|
||||||
|
return $cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function storageName(param: string) {
|
export function storageName(param: string) {
|
||||||
|
|||||||
@ -60,8 +60,12 @@
|
|||||||
&.selected {
|
&.selected {
|
||||||
border: none;
|
border: none;
|
||||||
outline: 2px solid $primary-color;
|
outline: 2px solid $primary-color;
|
||||||
|
background: #cccccc;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
&.current {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.col-resize{
|
.col-resize{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -71,7 +75,7 @@
|
|||||||
width: 4px;
|
width: 4px;
|
||||||
background: $primary-color;
|
background: $primary-color;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
z-index: 2;
|
z-index: 50;
|
||||||
|
|
||||||
&:hover{
|
&:hover{
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
@ -86,7 +90,7 @@
|
|||||||
height: 4px;
|
height: 4px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background: $primary-color;
|
background: $primary-color;
|
||||||
z-index: 2;
|
z-index: 50;
|
||||||
|
|
||||||
&:hover{
|
&:hover{
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user