From 2f78b7ec22918417bf6e9e7a972458b089e3df75 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 13 Jul 2022 21:40:09 +0500 Subject: [PATCH] add cells selection --- src/components/table/Table.ts | 14 +-- src/components/table/TableSelection.ts | 95 ++++++++++++++----- .../table/handlers/table.select.handler.ts | 13 +-- src/styles/components/table.scss | 14 +++ 4 files changed, 100 insertions(+), 36 deletions(-) diff --git a/src/components/table/Table.ts b/src/components/table/Table.ts index 575109a..1bb6a0e 100644 --- a/src/components/table/Table.ts +++ b/src/components/table/Table.ts @@ -62,7 +62,7 @@ export class Table extends ExcelComponent { this.initTable(); this.$onEventFromObserver('formula:input', this.updateCurrentText); - this.$onEventFromObserver('formula:enter-press', () => this.selection.current.focus()); + this.$onEventFromObserver('formula:enter-press', () => this.selection.$currentCell.focus()); this.$onEventFromObserver('toolbar:applyStyle', this.updateCurrentStyles); this.$onEventFromObserver('toolbar:add-row', this.addNewRowHandler); } @@ -126,12 +126,12 @@ export class Table extends ExcelComponent { } emitSelectCallback() { - this.$emitEventToObserver('table:select-cell', this.selection.current); + this.$emitEventToObserver('table:select-cell', this.selection.$currentCell); - const styles = this.selection.current?.getStyles(Object.keys(initialStyleState)); + const styles = this.selection.$currentCell?.getStyles(Object.keys(initialStyleState)); this.dispatchToStore(changeCurrentStyles(styles)); - this.dispatchToStore(changeCurrentText(this.selection.current.text)); + this.dispatchToStore(changeCurrentText(this.selection.$currentCell.text)); } async resizeTable(event: MouseEvent) { @@ -147,12 +147,12 @@ export class Table extends ExcelComponent { } updateCurrentText = (text: string) => { - this.selection.current.attr('data-value', text); - this.selection.current.text = parse(text); + this.selection.$currentCell.attr('data-value', text); + this.selection.$currentCell.text = parse(text); this.dispatchToStore(actions.changeText({ text, - id: this.selection.current.data.id || startCellId, + id: this.selection.$currentCell.data.id || startCellId, })); }; diff --git a/src/components/table/TableSelection.ts b/src/components/table/TableSelection.ts index 594b892..ecc45c4 100644 --- a/src/components/table/TableSelection.ts +++ b/src/components/table/TableSelection.ts @@ -4,17 +4,21 @@ import { getParamsFromCellId, startCellId } from 'components/table/table.functio export class TableSelection { static selectedClassName = 'selected'; - private group: Dom[]; - public current: Dom; + private 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.group = []; + this.selectedCellsGroup = []; this.rootTable = rootTable; } get selectedIds() { - return this.group.map(el => el.data.id); + return this.selectedCellsGroup.map(el => el.data.id); } // TODO make a focus manager @@ -32,12 +36,14 @@ export class TableSelection { } } - select($el: Dom) { + select($cell: Dom) { this.clearSelection(); - this.group = [$el]; - this.current = $el; - $el.addClass(TableSelection.selectedClassName); - this.focusToCell($el); + this.selectedCellsGroup = [$cell]; + this.$currentCell = $cell; + $cell.addClass(TableSelection.selectedClassName); + this.focusToCell($cell); + this.selectHeader($cell); + this.$focusedCell = $cell; } selectByCellId(cellID: { col: number, row: number }) { @@ -46,21 +52,19 @@ export class TableSelection { if (col <= 0) col = 0; if (row <= 0) row = 0; - this.clearSelection(); - this.current = $(`[data-id="${row}:${col}"]`); - this.focusToCell(this.current); - this.current.addClass(TableSelection.selectedClassName); + this.select($(`[data-id="${row}:${col}"]`)); } clearSelection() { - this.group.forEach(el => el?.removeClass(TableSelection.selectedClassName)); - this.current?.removeClass(TableSelection.selectedClassName); - this.group = []; + this.selectedCellsGroup.forEach(el => el?.removeClass(TableSelection.selectedClassName)); + this.$currentCell?.removeClass(TableSelection.selectedClassName); + this.clearHeaderSelection(); + this.selectedCellsGroup = []; } - selectTo($el: Dom) { - const startCellParams = getParamsFromCellId(this.current.data.id || startCellId); - const selectedCellParams = getParamsFromCellId($el.data.id || startCellId); + 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); @@ -71,8 +75,8 @@ export class TableSelection { for (let row = startRow; row <= endRow; row++) { for (let col = startCol; col <= endCol; col++) { - const $cell = $(`[data-id="${row}:${col}"]`); - this.addCellToSelection($cell); + const $target = $(`[data-id="${row}:${col}"]`); + this.addCellToSelection($target); } } } @@ -83,11 +87,56 @@ export class TableSelection { } addCellToSelection($cell: Dom) { - this.group.push($cell); + 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: CSSStyleDeclaration) { - this.group.forEach(el => el.css(style)); + 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); } } diff --git a/src/components/table/handlers/table.select.handler.ts b/src/components/table/handlers/table.select.handler.ts index 8511014..a668c02 100644 --- a/src/components/table/handlers/table.select.handler.ts +++ b/src/components/table/handlers/table.select.handler.ts @@ -29,7 +29,7 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl const target = $(event.target); if (isCell(event)) { - if (event.shiftKey) selection.selectTo(target); + if (event.shiftKey) selection.selectFromTo(selection.$currentCell, target); else if (event.ctrlKey) selection.addCellToSelection(target); else selection.select(target); } else { @@ -63,10 +63,10 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl 'Tab', ]; - if (!selection?.current || !handleKeys.includes(key)) return; + if (!selection?.$currentCell || !handleKeys.includes(key)) return; // If something goes wrong, go to start line - const currentCellId = selection.current.data.id || startCellId; + const currentCellId = selection.$currentCell.data.id || startCellId; let { row, col } = getParamsFromCellId(currentCellId); switch (key) { @@ -104,13 +104,14 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl default: break; } - selection.selectByCellId({ row, col }); + if (event.shiftKey) selection.addGroupToSelectionById({ row, col }); + else selection.selectByCellId({ row, col }); } function onMouseOverHandler() { - if (selection.current.$el) { + if (selection.$currentCell.$el) { // TODO find event type - selection.selectTo($((event as any).toElement)); + selection.selectFromTo(selection.$currentCell, $((event as any).toElement)); } } } diff --git a/src/styles/components/table.scss b/src/styles/components/table.scss index e1759a8..fb281e7 100644 --- a/src/styles/components/table.scss +++ b/src/styles/components/table.scss @@ -93,8 +93,22 @@ } [data-header="row"] { cursor: e-resize; + + &.selected { + border-right: 2px solid #3c74ff; + } } + [data-header="col"] { cursor: n-resize; + + &.selected { + border-bottom: 2px solid #3c74ff; + } + } + + [data-header="col"].selected, [data-header="row"].selected { + font-weight: bold; + background: rgba(60, 116, 255, 0.4); } }