From e99c624886f27b53a826e783c0849186fe1d1af3 Mon Sep 17 00:00:00 2001 From: bennyboer Date: Fri, 22 Jul 2022 17:18:08 +0200 Subject: [PATCH 1/5] Formatting code with prettier + introducing barrels --- .prettierrc | 4 + example/src/app/app.component.html | 485 ++-- example/src/app/app.component.scss | 157 +- example/src/app/app.component.ts | 641 +++-- example/src/app/app.module.ts | 35 +- .../src/app/renderer/debug-cell-renderer.ts | 67 +- example/src/environments/environment.prod.ts | 6 +- example/src/environments/environment.ts | 4 +- example/src/index.html | 34 +- example/src/main.ts | 7 +- example/src/polyfills.ts | 3 +- example/src/styles.scss | 49 +- package.json | 2 + src/border/border-side.ts | 6 +- src/border/border-style.ts | 4 +- src/border/border.ts | 4 +- src/border/index.ts | 5 + src/border/model/border-mask.ts | 2 - src/border/model/border-model.interface.ts | 17 +- src/border/model/border-model.ts | 180 +- src/border/model/index.ts | 3 + src/border/options.ts | 24 +- src/cell/cell.ts | 4 +- src/cell/index.ts | 3 + src/cell/model/cell-model.interface.ts | 38 +- src/cell/model/cell-model.spec.ts | 543 ++-- src/cell/model/cell-model.ts | 712 +++-- src/cell/model/event/before-delete-event.ts | 6 +- src/cell/model/event/cell-model-change.ts | 4 +- src/cell/model/event/cell-model-event-type.ts | 4 +- src/cell/model/event/hidden-event.ts | 6 +- src/cell/model/event/index.ts | 4 + src/cell/model/index.ts | 3 + src/cell/range/cell-range-util.spec.ts | 93 +- src/cell/range/cell-range-util.ts | 50 +- src/cell/range/cell-range.ts | 2 +- src/cell/range/index.ts | 2 + src/event/event-type.ts | 4 +- src/event/event.ts | 4 +- src/event/index.ts | 2 + src/index.ts | 8 + src/options.ts | 10 +- src/overlay/index.ts | 2 + src/overlay/overlay-manager.ts | 4 +- src/overlay/overlay.ts | 4 +- src/renderer/canvas/canvas-renderer.ts | 2551 ++++++++++++----- .../canvas/cell/canvas-cell-renderer.ts | 9 +- .../checkbox-cell-renderer-options.ts | 56 +- .../checkbox/checkbox-cell-renderer-value.ts | 4 +- .../cell/checkbox/checkbox-cell-renderer.ts | 222 +- src/renderer/canvas/cell/checkbox/index.ts | 6 + .../combobox-cell-renderer-options.ts | 62 +- .../combobox/combobox-cell-renderer-value.ts | 2 +- .../cell/combobox/combobox-cell-renderer.ts | 222 +- src/renderer/canvas/cell/combobox/index.ts | 14 + .../canvas/cell/dom/dom-cell-renderer.ts | 61 +- src/renderer/canvas/cell/dom/index.ts | 1 + src/renderer/canvas/cell/header/index.ts | 1 + .../cell/header/row-column-header-renderer.ts | 108 +- .../canvas/cell/image/image-cell-renderer.ts | 103 +- src/renderer/canvas/cell/image/index.ts | 4 + src/renderer/canvas/cell/index.ts | 9 + src/renderer/canvas/cell/loading/index.ts | 4 + .../cell/loading/loading-cell-renderer.ts | 63 +- src/renderer/canvas/cell/rating/index.ts | 6 + .../rating/rating-cell-renderer-options.ts | 32 +- .../cell/rating/rating-cell-renderer-value.ts | 4 +- .../cell/rating/rating-cell-renderer.ts | 194 +- src/renderer/canvas/cell/text/index.ts | 6 + .../canvas/cell/text/line-wrap/index.ts | 4 + .../cell/text/line-wrap/line-wrapper.ts | 4 +- .../canvas/cell/text/line-wrap/line.ts | 2 - .../canvas/cell/text/line-wrap/paragraph.ts | 4 +- .../text/line-wrap/trivial-line-wrapper.ts | 49 +- .../cell/text/text-cell-renderer-options.ts | 39 +- .../cell/text/text-cell-renderer-value.ts | 4 +- .../canvas/cell/text/text-cell-renderer.ts | 232 +- src/renderer/canvas/index.ts | 6 + src/renderer/canvas/options.ts | 30 +- src/renderer/cell/cell-renderer.ts | 12 +- .../event/cell-renderer-event-listener.ts | 8 +- .../cell/event/cell-renderer-event.ts | 5 +- .../cell/event/cell-renderer-focus-event.ts | 6 +- .../event/cell-renderer-keyboard-event.ts | 4 +- .../cell/event/cell-renderer-mouse-event.ts | 6 +- src/renderer/cell/event/index.ts | 5 + src/renderer/cell/index.ts | 2 + src/renderer/index.ts | 12 + src/renderer/options/index.ts | 18 + src/renderer/options/row-column-resizing.ts | 78 +- src/renderer/options/scrollbar.ts | 13 +- src/renderer/options/scrolling.ts | 19 +- src/renderer/options/selection.ts | 101 +- src/renderer/renderer-factory.ts | 39 +- .../{options.ts => renderer-options.ts} | 32 +- src/renderer/renderer.ts | 19 +- src/renderer/renderers.ts | 22 +- src/renderer/util/canvas.ts | 39 +- src/renderer/util/index.ts | 2 + src/renderer/util/scroll.ts | 15 +- src/selection/index.ts | 8 + src/selection/model/index.ts | 2 + .../model/selection-model.interface.ts | 34 +- src/selection/model/selection-model.ts | 713 ++++- src/selection/options.ts | 43 +- src/selection/selection.ts | 6 +- src/table-engine.ts | 54 +- src/util/alignment/alignment-util.ts | 26 +- src/util/alignment/horizontal-alignment.ts | 2 +- src/util/alignment/index.ts | 3 + src/util/alignment/vertical-alignment.ts | 2 +- src/util/clipboard/clipboard-util.ts | 37 +- src/util/clipboard/index.ts | 1 + src/util/color.ts | 2 - src/util/colors.ts | 74 +- src/util/index.ts | 8 + .../impl/copy-performance-warning.ts | 8 +- src/util/notification/impl/copy.ts | 10 +- src/util/notification/impl/index.ts | 2 + src/util/notification/index.ts | 5 + src/util/notification/notification-ids.ts | 7 +- src/util/notification/notification-service.ts | 4 +- src/util/notification/notification-type.ts | 4 +- src/util/notification/notification.ts | 4 +- src/util/point.ts | 2 - src/util/rect.ts | 2 - webpack.config.js | 25 +- yarn.lock | 10 + 128 files changed, 5947 insertions(+), 2977 deletions(-) create mode 100644 .prettierrc create mode 100644 src/border/index.ts create mode 100644 src/border/model/index.ts create mode 100644 src/cell/index.ts create mode 100644 src/cell/model/event/index.ts create mode 100644 src/cell/model/index.ts create mode 100644 src/cell/range/index.ts create mode 100644 src/event/index.ts create mode 100644 src/index.ts create mode 100644 src/overlay/index.ts create mode 100644 src/renderer/canvas/cell/checkbox/index.ts create mode 100644 src/renderer/canvas/cell/combobox/index.ts create mode 100644 src/renderer/canvas/cell/dom/index.ts create mode 100644 src/renderer/canvas/cell/header/index.ts create mode 100644 src/renderer/canvas/cell/image/index.ts create mode 100644 src/renderer/canvas/cell/index.ts create mode 100644 src/renderer/canvas/cell/loading/index.ts create mode 100644 src/renderer/canvas/cell/rating/index.ts create mode 100644 src/renderer/canvas/cell/text/index.ts create mode 100644 src/renderer/canvas/cell/text/line-wrap/index.ts create mode 100644 src/renderer/canvas/index.ts create mode 100644 src/renderer/cell/event/index.ts create mode 100644 src/renderer/cell/index.ts create mode 100644 src/renderer/index.ts create mode 100644 src/renderer/options/index.ts rename src/renderer/{options.ts => renderer-options.ts} (71%) create mode 100644 src/renderer/util/index.ts create mode 100644 src/selection/index.ts create mode 100644 src/selection/model/index.ts create mode 100644 src/util/alignment/index.ts create mode 100644 src/util/clipboard/index.ts create mode 100644 src/util/index.ts create mode 100644 src/util/notification/impl/index.ts create mode 100644 src/util/notification/index.ts diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..86e9e7d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "singleQuote": true +} diff --git a/example/src/app/app.component.html b/example/src/app/app.component.html index df8ee79..650570e 100644 --- a/example/src/app/app.component.html +++ b/example/src/app/app.component.html @@ -1,229 +1,292 @@
+
+
+ -
-
- + Table Engine Demo - Table Engine Demo + + {{ libraryVersion }} + +
- - {{ libraryVersion }} - -
+
+
+ -
- -
- - - - - - - - - - - - - -
- palette - - line_weight - - line_style -
- - Color - - - - - Size - - - - - Style - - - {{ borderStyle }} - - Solid - Dashed - Dotted - - -
- - - - - - - - - - - - - - - - - -
- -
- - - -
- -
-
- -
- - -
- -   - -
-
- -
- - -
- - Add count - - -   - -   - -
-
- -   - -
-
- -
- - -
- -   - -
-
+ + + + + + + + + + + +
+ palette + + line_weight + + line_style +
+ + Color + + + + + Size + + + + + Style + + + {{ borderStyle }} + + Solid + Dashed + Dotted + + +
-
- + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +
+
-
- -   - -
-
- -
-
+
+ -
- +
+ +   + +
+
-
- -
+
+ -
- - -
-
+
+ + Add count + + +   + +   + +
+
+ +   + +
+
-
- +
+ -
- -
-
+
+ +   + +
+
-
+
+ - -
+
+ +   + +
+
+ +
+
+ +
+ + +
+ +
+ +
+ + +
+
+ +
+ -
-
- +
+ +
+
+
+ +
-
+
+
+ +
+
diff --git a/example/src/app/app.component.scss b/example/src/app/app.component.scss index 6db4254..497e1cb 100644 --- a/example/src/app/app.component.scss +++ b/example/src/app/app.component.scss @@ -1,115 +1,114 @@ .content-wrapper { - display: flex; - position: relative; - height: 100%; - width: 100%; - flex-direction: row; - box-sizing: border-box; + display: flex; + position: relative; + height: 100%; + width: 100%; + flex-direction: row; + box-sizing: border-box; } .table-container-wrapper { - flex: 1 1 auto; - border: 1px solid #EAEAEA; - border-radius: 2px; - margin: 25px; - position: relative; + flex: 1 1 auto; + border: 1px solid #eaeaea; + border-radius: 2px; + margin: 25px; + position: relative; } .table-container { - position: absolute; - width: 100%; - height: 100%; + position: absolute; + width: 100%; + height: 100%; } .controls-container { - width: 400px; - flex: 0 0 auto; - display: flex; - flex-direction: column; - border-right: 1px solid #EAEAEA; + width: 400px; + flex: 0 0 auto; + display: flex; + flex-direction: column; + border-right: 1px solid #eaeaea; } .border-table { - width: 100%; - text-align: center; - border-collapse: collapse; - - td { - width: 33.3%; - - .mat-icon { - color: #333333; - font-size: 20px; - width: 20px; - height: 20px; + width: 100%; + text-align: center; + border-collapse: collapse; + + td { + width: 33.3%; + + .mat-icon { + color: #333333; + font-size: 20px; + width: 20px; + height: 20px; + } } - } } .border-cross-table { - margin: 0 auto; - border-collapse: collapse; + margin: 0 auto; + border-collapse: collapse; } .controls-group { - margin-top: 10px; - margin-bottom: 20px; + margin-top: 10px; + margin-bottom: 20px; } .color-input { - border-radius: 100%; - width: 30px; - height: 30px; - border: none; - cursor: pointer; - outline: none; - color: transparent; + border-radius: 100%; + width: 30px; + height: 30px; + border: none; + cursor: pointer; + outline: none; + color: transparent; } .controls-label { - font-size: 1.4em; - display: block; - margin-bottom: 16px; - text-align: center; + font-size: 1.4em; + display: block; + margin-bottom: 16px; + text-align: center; } .headline { - flex: 0 0 auto; - padding: 25px 20px; - border-bottom: 1px solid #EAEAEA; - font-size: 2.0em; - text-align: center; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - line-height: 1.2; - - .version-info { - font-size: 12px; - } + flex: 0 0 auto; + padding: 25px 20px; + border-bottom: 1px solid #eaeaea; + font-size: 2em; + text-align: center; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + line-height: 1.2; + + .version-info { + font-size: 12px; + } } .controls-content { - flex: 1 1 auto; - display: flex; - flex-direction: column; - padding: 15px 10px; - overflow-y: auto; - overflow-x: hidden; + flex: 1 1 auto; + display: flex; + flex-direction: column; + padding: 15px 10px; + overflow-y: auto; + overflow-x: hidden; } .footer { - flex: 0 0 auto; - border-top: 1px solid #EAEAEA; - padding: 15px 10px; - display: flex; - flex-flow: row; - justify-content: center; - align-items: center; - - .zoom-label { - margin: 0 10px; - } + flex: 0 0 auto; + border-top: 1px solid #eaeaea; + padding: 15px 10px; + display: flex; + flex-flow: row; + justify-content: center; + align-items: center; + + .zoom-label { + margin: 0 10px; + } } - diff --git a/example/src/app/app.component.ts b/example/src/app/app.component.ts index 89a8f5b..95a7f11 100644 --- a/example/src/app/app.component.ts +++ b/example/src/app/app.component.ts @@ -5,55 +5,55 @@ import { ElementRef, NgZone, OnDestroy, - ViewChild -} from "@angular/core"; -import {ICellModel} from "../../../src/cell/model/cell-model.interface"; -import {CellModel} from "../../../src/cell/model/cell-model"; -import {CellRange} from "../../../src/cell/range/cell-range"; -import {TableEngine} from "../../../src/table-engine"; -import {RowColumnHeaderRenderer} from "../../../src/renderer/canvas/cell/header/row-column-header-renderer"; -import {ROW_COLUMN_HEADER_TRANSFORM} from "../../../src/selection/options"; -import {ISelection} from "../../../src/selection/selection"; -import {IImageCellRendererValue, ImageCellRenderer} from "../../../src/renderer/canvas/cell/image/image-cell-renderer"; -import {BorderStyle} from "../../../src/border/border-style"; -import {IColor} from "../../../src/util/color"; + ViewChild, +} from '@angular/core'; import { + BorderStyle, + CellModel, + CellRange, + CheckboxCellRenderer, + ComboBoxCellRenderer, + CopyPerformanceWarningNotification, + DOMCellRenderer, + HorizontalAlignment, + ICell, + ICellModel, + ICheckboxCellRendererValue, + IColor, + IComboBoxCellRendererValue, + IImageCellRendererValue, ILoadingCellRendererValue, - LoadingCellRenderer -} from "../../../src/renderer/canvas/cell/loading/loading-cell-renderer"; -import {environment} from "../environments/environment"; -import {INotification} from "../../../src/util/notification/notification"; -import {NotificationIDs} from "../../../src/util/notification/notification-ids"; -import {CopyPerformanceWarningNotification} from "../../../src/util/notification/impl/copy-performance-warning"; -import {MatSnackBar} from "@angular/material/snack-bar"; -import {TextCellRenderer} from "../../../src/renderer/canvas/cell/text/text-cell-renderer"; -import {ITextCellRendererValue} from "../../../src/renderer/canvas/cell/text/text-cell-renderer-value"; -import {HorizontalAlignment} from "../../../src/util/alignment/horizontal-alignment"; -import {VerticalAlignment} from "../../../src/util/alignment/vertical-alignment"; -import {ICell} from "../../../src/cell/cell"; -import {DebugCellRenderer} from "./renderer/debug-cell-renderer"; -import {CheckboxCellRenderer} from "../../../src/renderer/canvas/cell/checkbox/checkbox-cell-renderer"; -import {ICheckboxCellRendererValue} from "../../../src/renderer/canvas/cell/checkbox/checkbox-cell-renderer-value"; -import {DOMCellRenderer} from "../../../src/renderer/canvas/cell/dom/dom-cell-renderer"; -import {RatingCellRenderer} from "../../../src/renderer/canvas/cell/rating/rating-cell-renderer"; -import {ComboBoxCellRenderer} from "../../../src/renderer/canvas/cell/combobox/combobox-cell-renderer"; -import {IComboBoxCellRendererValue} from "../../../src/renderer/canvas/cell/combobox/combobox-cell-renderer-value"; + ImageCellRenderer, + INotification, + ISelection, + ITextCellRendererValue, + LoadingCellRenderer, + NotificationIDs, + RatingCellRenderer, + ROW_COLUMN_HEADER_TRANSFORM, + RowColumnHeaderRenderer, + TableEngine, + TextCellRenderer, + VerticalAlignment, +} from '../../../src'; +import { environment } from '../environments/environment'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { DebugCellRenderer } from './renderer/debug-cell-renderer'; @Component({ - selector: "app-root", - templateUrl: "./app.component.html", - styleUrls: ["./app.component.scss"], - changeDetection: ChangeDetectionStrategy.OnPush + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent implements AfterViewInit, OnDestroy { - - @ViewChild("tableContainer") + @ViewChild('tableContainer') public tableContainer!: ElementRef; /** * Currently selected border color. */ - public borderColor: string = "rgba(255, 0, 0, 1.0)"; + public borderColor: string = 'rgba(255, 0, 0, 1.0)'; /** * Currently selected border size. @@ -63,7 +63,7 @@ export class AppComponent implements AfterViewInit, OnDestroy { /** * Currently selected border style. */ - public borderStyle: string = "Solid"; + public borderStyle: string = 'Solid'; /** * Amount of rows/columns to add. @@ -80,8 +80,7 @@ export class AppComponent implements AfterViewInit, OnDestroy { constructor( private readonly zone: NgZone, private readonly snackbar: MatSnackBar - ) { - } + ) {} public addOneFixedRowsColumns(): void { this.engine.getOptions().renderer.view.fixedRows += 1; @@ -90,8 +89,14 @@ export class AppComponent implements AfterViewInit, OnDestroy { } public removeOneFixedRowsColumns(): void { - this.engine.getOptions().renderer.view.fixedRows = Math.max(this.engine.getOptions().renderer.view.fixedRows - 1, 0); - this.engine.getOptions().renderer.view.fixedColumns = Math.max(this.engine.getOptions().renderer.view.fixedColumns - 1, 0); + this.engine.getOptions().renderer.view.fixedRows = Math.max( + this.engine.getOptions().renderer.view.fixedRows - 1, + 0 + ); + this.engine.getOptions().renderer.view.fixedColumns = Math.max( + this.engine.getOptions().renderer.view.fixedColumns - 1, + 0 + ); this.engine.repaint(); } @@ -106,7 +111,9 @@ export class AppComponent implements AfterViewInit, OnDestroy { size = 500; } - const selections: ISelection[] = this.engine.getSelectionModel().getSelections(); + const selections: ISelection[] = this.engine + .getSelectionModel() + .getSelections(); if (selections.length <= 0) { return; } @@ -114,7 +121,11 @@ export class AppComponent implements AfterViewInit, OnDestroy { // Gather all selected rows const rows: Set = new Set(); for (const selection of selections) { - for (let row = selection.range.startRow; row <= selection.range.endRow; row++) { + for ( + let row = selection.range.startRow; + row <= selection.range.endRow; + row++ + ) { rows.add(row); } } @@ -131,7 +142,9 @@ export class AppComponent implements AfterViewInit, OnDestroy { size = 500; } - const selections: ISelection[] = this.engine.getSelectionModel().getSelections(); + const selections: ISelection[] = this.engine + .getSelectionModel() + .getSelections(); if (selections.length <= 0) { return; } @@ -139,7 +152,11 @@ export class AppComponent implements AfterViewInit, OnDestroy { // Gather all selected columns const columns: Set = new Set(); for (const selection of selections) { - for (let column = selection.range.startColumn; column <= selection.range.endColumn; column++) { + for ( + let column = selection.range.startColumn; + column <= selection.range.endColumn; + column++ + ) { columns.add(column); } } @@ -150,7 +167,9 @@ export class AppComponent implements AfterViewInit, OnDestroy { } public mergeSelection(): void { - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { this.engine.getCellModel().mergeCells(primary.range); this.engine.repaint(); @@ -158,9 +177,13 @@ export class AppComponent implements AfterViewInit, OnDestroy { } public splitSelection(): void { - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { - this.engine.getCellModel().splitCell(primary.range.startRow, primary.range.startColumn); + this.engine + .getCellModel() + .splitCell(primary.range.startRow, primary.range.startColumn); this.engine.repaint(); } } @@ -171,7 +194,9 @@ export class AppComponent implements AfterViewInit, OnDestroy { } public hideRows(): void { - const selections: ISelection[] = this.engine.getSelectionModel().getSelections(); + const selections: ISelection[] = this.engine + .getSelectionModel() + .getSelections(); const toHide: number[] = []; for (const s of selections) { for (let row = s.range.startRow; row <= s.range.endRow; row++) { @@ -186,10 +211,16 @@ export class AppComponent implements AfterViewInit, OnDestroy { } public hideColumns(): void { - const selections: ISelection[] = this.engine.getSelectionModel().getSelections(); + const selections: ISelection[] = this.engine + .getSelectionModel() + .getSelections(); const toHide: number[] = []; for (const s of selections) { - for (let column = s.range.startColumn; column <= s.range.endColumn; column++) { + for ( + let column = s.range.startColumn; + column <= s.range.endColumn; + column++ + ) { toHide.push(column); } } @@ -201,9 +232,9 @@ export class AppComponent implements AfterViewInit, OnDestroy { } private static _borderStyleNameToStyle(styleName: string): BorderStyle { - if (styleName === "Solid") { + if (styleName === 'Solid') { return BorderStyle.SOLID; - } else if (styleName === "Dotted") { + } else if (styleName === 'Dotted') { return BorderStyle.DOTTED; } else { return BorderStyle.DASHED; @@ -211,72 +242,110 @@ export class AppComponent implements AfterViewInit, OnDestroy { } private static _rgbaStringToColor(rgbaStr: string): IColor { - rgbaStr = rgbaStr.startsWith("rgba") ? rgbaStr.substring(5, rgbaStr.length - 1) : rgbaStr.substring(4, rgbaStr.length - 1); - const parts = rgbaStr.split(","); + rgbaStr = rgbaStr.startsWith('rgba') + ? rgbaStr.substring(5, rgbaStr.length - 1) + : rgbaStr.substring(4, rgbaStr.length - 1); + const parts = rgbaStr.split(','); return { red: parseInt(parts[0].trim()), green: parseInt(parts[1].trim()), blue: parseInt(parts[2].trim()), - alpha: parts.length === 4 ? parseFloat(parts[3].trim()) : 1.0 - } + alpha: parts.length === 4 ? parseFloat(parts[3].trim()) : 1.0, + }; } public setBorderTop(): void { - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { - this.engine.getBorderModel().setBorder({ - top: { - size: this.borderSize, - style: AppComponent._borderStyleNameToStyle(this.borderStyle), - color: AppComponent._rgbaStringToColor(this.borderColor) - } - }, primary.range); + this.engine.getBorderModel().setBorder( + { + top: { + size: this.borderSize, + style: AppComponent._borderStyleNameToStyle( + this.borderStyle + ), + color: AppComponent._rgbaStringToColor( + this.borderColor + ), + }, + }, + primary.range + ); this.engine.repaint(); } } public setBorderLeft(): void { - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { - this.engine.getBorderModel().setBorder({ - left: { - size: this.borderSize, - style: AppComponent._borderStyleNameToStyle(this.borderStyle), - color: AppComponent._rgbaStringToColor(this.borderColor) - } - }, primary.range); + this.engine.getBorderModel().setBorder( + { + left: { + size: this.borderSize, + style: AppComponent._borderStyleNameToStyle( + this.borderStyle + ), + color: AppComponent._rgbaStringToColor( + this.borderColor + ), + }, + }, + primary.range + ); this.engine.repaint(); } } public setBorderBottom(): void { - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { - this.engine.getBorderModel().setBorder({ - bottom: { - size: this.borderSize, - style: AppComponent._borderStyleNameToStyle(this.borderStyle), - color: AppComponent._rgbaStringToColor(this.borderColor) - } - }, primary.range); + this.engine.getBorderModel().setBorder( + { + bottom: { + size: this.borderSize, + style: AppComponent._borderStyleNameToStyle( + this.borderStyle + ), + color: AppComponent._rgbaStringToColor( + this.borderColor + ), + }, + }, + primary.range + ); this.engine.repaint(); } } public setBorderRight(): void { - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { - this.engine.getBorderModel().setBorder({ - right: { - size: this.borderSize, - style: AppComponent._borderStyleNameToStyle(this.borderStyle), - color: AppComponent._rgbaStringToColor(this.borderColor) - } - }, primary.range); + this.engine.getBorderModel().setBorder( + { + right: { + size: this.borderSize, + style: AppComponent._borderStyleNameToStyle( + this.borderStyle + ), + color: AppComponent._rgbaStringToColor( + this.borderColor + ), + }, + }, + primary.range + ); this.engine.repaint(); } @@ -296,84 +365,108 @@ export class AppComponent implements AfterViewInit, OnDestroy { public insertRows(count: number): void { let beforeIndex: number; - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { beforeIndex = primary.initial.row + 1; } else { beforeIndex = this.engine.getCellModel().getRowCount(); } - this.engine.getCellModel().insertRows(beforeIndex, count, (row, column) => { - if (column === 0) { - return { - range: { - startRow: row, - endRow: row, - startColumn: column, - endColumn: column - }, - rendererName: RowColumnHeaderRenderer.NAME, - value: null - }; - } + this.engine + .getCellModel() + .insertRows(beforeIndex, count, (row, column) => { + if (column === 0) { + return { + range: { + startRow: row, + endRow: row, + startColumn: column, + endColumn: column, + }, + rendererName: RowColumnHeaderRenderer.NAME, + value: null, + }; + } - return null; - }); + return null; + }); this.engine.repaint(); } public insertColumns(count: number): void { let beforeIndex: number; - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { beforeIndex = primary.initial.column + 1; } else { beforeIndex = this.engine.getCellModel().getColumnCount(); } - this.engine.getCellModel().insertColumns(beforeIndex, count, (row, column) => { - if (row === 0) { - return { - range: { - startRow: row, - endRow: row, - startColumn: column, - endColumn: column - }, - rendererName: RowColumnHeaderRenderer.NAME, - value: null - }; - } + this.engine + .getCellModel() + .insertColumns(beforeIndex, count, (row, column) => { + if (row === 0) { + return { + range: { + startRow: row, + endRow: row, + startColumn: column, + endColumn: column, + }, + rendererName: RowColumnHeaderRenderer.NAME, + value: null, + }; + } - return null; - }); + return null; + }); this.engine.repaint(); } public deleteRows(): void { - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { let count: number = 0; - for (let row = primary.range.startRow; row <= primary.range.endRow; row++) { + for ( + let row = primary.range.startRow; + row <= primary.range.endRow; + row++ + ) { if (!this.engine.getCellModel().isRowHidden(row)) { count++; } } - this.engine.getCellModel().deleteRows(primary.range.startRow, count); + this.engine + .getCellModel() + .deleteRows(primary.range.startRow, count); this.engine.repaint(); } } public deleteColumns(): void { - const primary: ISelection | null = this.engine.getSelectionModel().getPrimary(); + const primary: ISelection | null = this.engine + .getSelectionModel() + .getPrimary(); if (!!primary) { let count: number = 0; - for (let column = primary.range.startColumn; column <= primary.range.endColumn; column++) { + for ( + let column = primary.range.startColumn; + column <= primary.range.endColumn; + column++ + ) { if (!this.engine.getCellModel().isColumnHidden(column)) { count++; } } - this.engine.getCellModel().deleteColumns(primary.range.startColumn, count); + this.engine + .getCellModel() + .deleteColumns(primary.range.startColumn, count); this.engine.repaint(); } } @@ -384,18 +477,25 @@ export class AppComponent implements AfterViewInit, OnDestroy { public ngAfterViewInit(): void { this.zone.runOutsideAngular(() => { const cellModel = AppComponent.initializeCellModel(); - this.engine = new TableEngine(this.tableContainer.nativeElement, cellModel); + this.engine = new TableEngine( + this.tableContainer.nativeElement, + cellModel + ); this.engine.getOptions().misc.debug = false; // Enable/Disable debug mode // Setup row/column resizing - this.engine.getOptions().renderer.canvas.rowColumnResizing.allowResizing = true; + this.engine.getOptions().renderer.canvas.rowColumnResizing.allowResizing = + true; this.engine.getOptions().renderer.canvas.rowColumnResizing.rowCount = 1; this.engine.getOptions().renderer.canvas.rowColumnResizing.columnCount = 1; // Setup copy-handle this.engine.getOptions().selection.copyHandle.showCopyHandle = true; - this.engine.getOptions().selection.copyHandle.copyHandler = (origin, target) => { + this.engine.getOptions().selection.copyHandle.copyHandler = ( + origin, + target + ) => { /* NOTE: The copy-handler is very simple and will just copy the value of the initial cell of the origin cell range to all the cells of the target cell range. @@ -403,22 +503,33 @@ export class AppComponent implements AfterViewInit, OnDestroy { */ // Only allow copying when all involved cells have the same size - const originCells: ICell[] = this.engine.getCellModel().getCells(origin); - const width: number = originCells[0].range.endColumn - originCells[0].range.startColumn; - const height: number = originCells[0].range.endRow - originCells[0].range.startRow; + const originCells: ICell[] = this.engine + .getCellModel() + .getCells(origin); + const width: number = + originCells[0].range.endColumn - + originCells[0].range.startColumn; + const height: number = + originCells[0].range.endRow - originCells[0].range.startRow; for (const cell of originCells) { - const cellWidth: number = cell.range.endColumn - cell.range.startColumn; - const cellHeight: number = cell.range.endRow - cell.range.startRow; + const cellWidth: number = + cell.range.endColumn - cell.range.startColumn; + const cellHeight: number = + cell.range.endRow - cell.range.startRow; if (cellWidth !== width || cellHeight !== height) { return; // Abort! } } - const targetCells: ICell[] = this.engine.getCellModel().getCells(target); + const targetCells: ICell[] = this.engine + .getCellModel() + .getCells(target); for (const cell of targetCells) { - const cellWidth: number = cell.range.endColumn - cell.range.startColumn; - const cellHeight: number = cell.range.endRow - cell.range.startRow; + const cellWidth: number = + cell.range.endColumn - cell.range.startColumn; + const cellHeight: number = + cell.range.endRow - cell.range.startRow; if (cellWidth !== width || cellHeight !== height) { return; // Abort! @@ -426,8 +537,15 @@ export class AppComponent implements AfterViewInit, OnDestroy { } // Copy cells in a very simple way, this can be made very elaborate, but we will simply copy the value of the initial cell to all involved cells - const initialCell: ICell = this.engine.getCellModel().getCell(this.engine.getSelectionModel().getPrimary().initial.row, this.engine.getSelectionModel().getPrimary().initial.column); - const valueToCopy: any = !!initialCell ? initialCell.value : ""; + const initialCell: ICell = this.engine + .getCellModel() + .getCell( + this.engine.getSelectionModel().getPrimary().initial + .row, + this.engine.getSelectionModel().getPrimary().initial + .column + ); + const valueToCopy: any = !!initialCell ? initialCell.value : ''; for (const cell of targetCells) { cell.value = JSON.parse(JSON.stringify(valueToCopy)); @@ -437,7 +555,8 @@ export class AppComponent implements AfterViewInit, OnDestroy { }; // Set row/column header selection transform - this.engine.getOptions().selection.selectionTransform = ROW_COLUMN_HEADER_TRANSFORM; + this.engine.getOptions().selection.selectionTransform = + ROW_COLUMN_HEADER_TRANSFORM; // Set initial state of fixed rows/columns this.engine.getOptions().renderer.view.fixedRows = 1; @@ -448,77 +567,97 @@ export class AppComponent implements AfterViewInit, OnDestroy { this.engine.getOptions().renderer.notificationService = { notify: (notification: INotification) => { this.zone.run(() => { - if (notification.id === NotificationIDs.COPY_PERFORMANCE_WARNING) { - this.snackbar.open(notification.message, "Copy anyway", { - duration: 6000, - verticalPosition: "bottom", - horizontalPosition: "center" - }).afterDismissed().subscribe((dismiss) => { - console.log(dismiss.dismissedByAction); - (notification as CopyPerformanceWarningNotification).callback(dismiss.dismissedByAction); - }); + if ( + notification.id === + NotificationIDs.COPY_PERFORMANCE_WARNING + ) { + this.snackbar + .open(notification.message, 'Copy anyway', { + duration: 6000, + verticalPosition: 'bottom', + horizontalPosition: 'center', + }) + .afterDismissed() + .subscribe((dismiss) => { + console.log(dismiss.dismissedByAction); + ( + notification as CopyPerformanceWarningNotification + ).callback(dismiss.dismissedByAction); + }); } else if (notification.id === NotificationIDs.COPY) { - this.snackbar.open(notification.message, "OK", { + this.snackbar.open(notification.message, 'OK', { duration: 3000, - verticalPosition: "bottom", - horizontalPosition: "center" + verticalPosition: 'bottom', + horizontalPosition: 'center', }); } }); - } + }, }; // Register needed cell renderers - this.engine.registerCellRenderer(new TextCellRenderer({ - editable: true, - horizontalAlignment: HorizontalAlignment.CENTER, - verticalAlignment: VerticalAlignment.MIDDLE - })); + this.engine.registerCellRenderer( + new TextCellRenderer({ + editable: true, + horizontalAlignment: HorizontalAlignment.CENTER, + verticalAlignment: VerticalAlignment.MIDDLE, + }) + ); this.engine.registerCellRenderer(new DebugCellRenderer()); this.engine.registerCellRenderer(new RowColumnHeaderRenderer()); this.engine.registerCellRenderer(new ImageCellRenderer()); this.engine.registerCellRenderer(new LoadingCellRenderer()); this.engine.registerCellRenderer(new CheckboxCellRenderer()); this.engine.registerCellRenderer(new DOMCellRenderer()); - this.engine.registerCellRenderer(new RatingCellRenderer({ - editable: true - })); + this.engine.registerCellRenderer( + new RatingCellRenderer({ + editable: true, + }) + ); this.engine.registerCellRenderer(new ComboBoxCellRenderer()); // Set an example border - this.engine.getBorderModel().setBorder({ - right: { - style: BorderStyle.SOLID, - size: 1, - color: {red: 255, blue: 0, green: 0, alpha: 1}, - }, - bottom: { - style: BorderStyle.SOLID, - size: 2, - color: {red: 0, blue: 0, green: 255, alpha: 1}, - }, - left: { - style: BorderStyle.SOLID, - size: 3, - color: {red: 0, blue: 255, green: 0, alpha: 1}, + this.engine.getBorderModel().setBorder( + { + right: { + style: BorderStyle.SOLID, + size: 1, + color: { red: 255, blue: 0, green: 0, alpha: 1 }, + }, + bottom: { + style: BorderStyle.SOLID, + size: 2, + color: { red: 0, blue: 0, green: 255, alpha: 1 }, + }, + left: { + style: BorderStyle.SOLID, + size: 3, + color: { red: 0, blue: 255, green: 0, alpha: 1 }, + }, + top: { + style: BorderStyle.SOLID, + size: 4, + color: { red: 255, blue: 0, green: 100, alpha: 1 }, + }, }, - top: { - style: BorderStyle.SOLID, - size: 4, - color: {red: 255, blue: 0, green: 100, alpha: 1}, + { + startRow: 2, + endRow: 4, + startColumn: 2, + endColumn: 3, } - }, { - startRow: 2, - endRow: 4, - startColumn: 2, - endColumn: 3 - }); + ); - this.engine.getBorderModel().setBorderLine(10, 2, { - style: BorderStyle.DOTTED, - size: 3, - color: {red: 140, blue: 180, green: 160, alpha: 1.0} - }, {bottom: true, top: true}); + this.engine.getBorderModel().setBorderLine( + 10, + 2, + { + style: BorderStyle.DOTTED, + size: 3, + color: { red: 140, blue: 180, green: 160, alpha: 1.0 }, + }, + { bottom: true, top: true } + ); this.engine.initialize(); }); @@ -544,45 +683,50 @@ export class AppComponent implements AfterViewInit, OnDestroy { startRow: 5, endRow: 10, startColumn: 5, - endColumn: 8 + endColumn: 8, }, rendererName: LoadingCellRenderer.NAME, - value: - { - cellRenderer: ImageCellRenderer.NAME, - promiseSupplier: async () => { - return new Promise(resolve => setTimeout(() => resolve({ - src: "assets/sloth.svg" - } as IImageCellRendererValue), 3000)) - }, - } as ILoadingCellRendererValue + value: { + cellRenderer: ImageCellRenderer.NAME, + promiseSupplier: async () => { + return new Promise((resolve) => + setTimeout( + () => + resolve({ + src: 'assets/sloth.svg', + } as IImageCellRendererValue), + 3000 + ) + ); + }, + } as ILoadingCellRendererValue, }, { range: CellRange.fromSingleRowColumn(25, 4), rendererName: TextCellRenderer.NAME, value: { - text: "This is a cell for which we enabled line wrapping since this text is pretty long and will not fit into the cells available space.", + text: 'This is a cell for which we enabled line wrapping since this text is pretty long and will not fit into the cells available space.', options: { useLineWrapping: true, horizontalAlignment: HorizontalAlignment.LEFT, - verticalAlignment: VerticalAlignment.BOTTOM - } - } as ITextCellRendererValue + verticalAlignment: VerticalAlignment.BOTTOM, + }, + } as ITextCellRendererValue, }, { range: CellRange.fromSingleRowColumn(1000, 1000), rendererName: TextCellRenderer.NAME, - value: "Last cell with more text than normally" + value: 'Last cell with more text than normally', }, { range: { startRow: 22, endRow: 26, startColumn: 9, - endColumn: 11 + endColumn: 11, }, rendererName: DebugCellRenderer.NAME, - value: null + value: null, }, { range: CellRange.fromSingleRowColumn(10, 2), @@ -590,54 +734,56 @@ export class AppComponent implements AfterViewInit, OnDestroy { value: { cellRenderer: TextCellRenderer.NAME, promiseSupplier: async () => { - return new Promise(resolve => setTimeout(() => resolve("Done 😀"), 2000)) + return new Promise((resolve) => + setTimeout(() => resolve('Done 😀'), 2000) + ); }, - } as ILoadingCellRendererValue + } as ILoadingCellRendererValue, }, { range: CellRange.fromSingleRowColumn(13, 3), rendererName: CheckboxCellRenderer.NAME, value: { - checked: false - } as ICheckboxCellRendererValue + checked: false, + } as ICheckboxCellRendererValue, }, { range: CellRange.fromSingleRowColumn(14, 3), rendererName: CheckboxCellRenderer.NAME, value: { checked: false, - label: "with Label" - } as ICheckboxCellRendererValue + label: 'with Label', + } as ICheckboxCellRendererValue, }, { range: { startRow: 30, endRow: 33, startColumn: 3, - endColumn: 3 + endColumn: 3, }, rendererName: TextCellRenderer.NAME, value: { - text: "HTML/DOM cell renderer:", + text: 'HTML/DOM cell renderer:', options: { horizontalAlignment: HorizontalAlignment.RIGHT, verticalAlignment: VerticalAlignment.MIDDLE, fontSize: 18, useLineWrapping: true, - editable: false - } - } as ITextCellRendererValue + editable: false, + }, + } as ITextCellRendererValue, }, { range: { startRow: 30, endRow: 40, startColumn: 4, - endColumn: 5 + endColumn: 5, }, rendererName: DOMCellRenderer.NAME, value: ` -
+

The Table-Engine is able to render HTML in a cell

    @@ -645,44 +791,44 @@ export class AppComponent implements AfterViewInit, OnDestroy {
  • a
  • list!
-

Nevertheless use these types of renderers sparingly, as they may result in poor performance

+

Nevertheless use these types of renderers sparingly, as they may result in poor performance

-` +`, }, { range: CellRange.fromSingleRowColumn(16, 3), rendererName: RatingCellRenderer.NAME, - value: 3.5 + value: 3.5, }, { range: CellRange.fromSingleRowColumn(18, 3), rendererName: ComboBoxCellRenderer.NAME, value: { select_options: { - apple: {label: "Apple"}, - banana: {label: "Banana"}, - orange: {label: "Orange"} - } - } as IComboBoxCellRendererValue + apple: { label: 'Apple' }, + banana: { label: 'Banana' }, + orange: { label: 'Orange' }, + }, + } as IComboBoxCellRendererValue, }, { range: CellRange.fromSingleRowColumn(19, 3), rendererName: ComboBoxCellRenderer.NAME, value: { - selected_option_id: "cow", + selected_option_id: 'cow', select_options: { - cat: {label: "Meow"}, - dog: {label: "Woof"}, - wolf: {label: "Howl"}, - chicken: {label: "Bah-gawk"}, - cow: {label: "Moo"}, - bear: {label: "Roar"}, - bee: {label: "Buzz"}, - cricket: {label: "Chirp"}, - duck: {label: "Quack"} - } - } as IComboBoxCellRendererValue - } + cat: { label: 'Meow' }, + dog: { label: 'Woof' }, + wolf: { label: 'Howl' }, + chicken: { label: 'Bah-gawk' }, + cow: { label: 'Moo' }, + bear: { label: 'Roar' }, + bee: { label: 'Buzz' }, + cricket: { label: 'Chirp' }, + duck: { label: 'Quack' }, + }, + } as IComboBoxCellRendererValue, + }, ], (row, column) => row * column, (row, column) => { @@ -693,7 +839,7 @@ export class AppComponent implements AfterViewInit, OnDestroy { } }, (row) => 25, - (column) => column === 0 ? 50 : 120, + (column) => (column === 0 ? 50 : 120), new Set(), new Set() ); @@ -702,7 +848,7 @@ export class AppComponent implements AfterViewInit, OnDestroy { startRow: 2, endRow: 4, startColumn: 2, - endColumn: 3 + endColumn: 3, }); // Resize row with index 25 @@ -710,5 +856,4 @@ export class AppComponent implements AfterViewInit, OnDestroy { return model; } - } diff --git a/example/src/app/app.module.ts b/example/src/app/app.module.ts index 5d5f97f..6266e9a 100644 --- a/example/src/app/app.module.ts +++ b/example/src/app/app.module.ts @@ -1,21 +1,19 @@ -import {NgModule} from "@angular/core"; -import {BrowserModule} from "@angular/platform-browser"; +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; -import {AppComponent} from "./app.component"; -import {ColorPickerModule} from "ngx-color-picker"; -import {FormsModule} from "@angular/forms"; -import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; -import {MatButtonModule} from "@angular/material/button"; -import {MatIconModule} from "@angular/material/icon"; -import {MatInputModule} from "@angular/material/input"; -import {MatSelectModule} from "@angular/material/select"; -import {MatTooltipModule} from "@angular/material/tooltip"; -import {MatSnackBarModule} from "@angular/material/snack-bar"; +import { AppComponent } from './app.component'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { FormsModule } from '@angular/forms'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; @NgModule({ - declarations: [ - AppComponent - ], + declarations: [AppComponent], imports: [ BrowserModule, ColorPickerModule, @@ -26,10 +24,9 @@ import {MatSnackBarModule} from "@angular/material/snack-bar"; MatInputModule, MatSelectModule, MatTooltipModule, - MatSnackBarModule + MatSnackBarModule, ], providers: [], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { -} +export class AppModule {} diff --git a/example/src/app/renderer/debug-cell-renderer.ts b/example/src/app/renderer/debug-cell-renderer.ts index 62859b7..6b33c7e 100644 --- a/example/src/app/renderer/debug-cell-renderer.ts +++ b/example/src/app/renderer/debug-cell-renderer.ts @@ -1,17 +1,16 @@ -import {ICanvasCellRenderer} from "../../../../src/renderer/canvas/cell/canvas-cell-renderer"; -import {ICell} from "../../../../src/cell/cell"; -import {IRenderContext} from "../../../../src/renderer/canvas/canvas-renderer"; -import {ICellRendererEventListener} from "../../../../src/renderer/cell/event/cell-renderer-event-listener"; -import {TableEngine} from "../../../../src/table-engine"; -import {IRectangle} from "../../../../src/util/rect"; -import {Colors} from "../../../../src/util/colors"; -import {AlignmentUtil} from "../../../../src/util/alignment/alignment-util"; -import {VerticalAlignment} from "../../../../src/util/alignment/vertical-alignment"; -import {HorizontalAlignment} from "../../../../src/util/alignment/horizontal-alignment"; +import { ICanvasCellRenderer } from '../../../../src/renderer/canvas/cell/canvas-cell-renderer'; +import { ICell } from '../../../../src/cell/cell'; +import { IRenderContext } from '../../../../src/renderer/canvas/canvas-renderer'; +import { ICellRendererEventListener } from '../../../../src/renderer/cell/event/cell-renderer-event-listener'; +import { TableEngine } from '../../../../src/table-engine'; +import { IRectangle } from '../../../../src/util/rect'; +import { Colors } from '../../../../src/util/colors'; +import { AlignmentUtil } from '../../../../src/util/alignment/alignment-util'; +import { VerticalAlignment } from '../../../../src/util/alignment/vertical-alignment'; +import { HorizontalAlignment } from '../../../../src/util/alignment/horizontal-alignment'; export class DebugCellRenderer implements ICanvasCellRenderer { - - public static readonly NAME: string = "debug"; + public static readonly NAME: string = 'debug'; /** * Event listeners on cells rendered with this cell renderer. @@ -30,7 +29,7 @@ export class DebugCellRenderer implements ICanvasCellRenderer { this._engine.repaint(); }, onMouseOut: (event) => { - event.cell.value = "mouse out"; + event.cell.value = 'mouse out'; this._engine.repaint(); }, onKeyDown: (event) => { @@ -42,32 +41,37 @@ export class DebugCellRenderer implements ICanvasCellRenderer { this._engine.repaint(); }, onFocus: (event) => { - event.cell.value = "focused"; + event.cell.value = 'focused'; this._engine.repaint(); }, onBlur: (event) => { - event.cell.value = "blurred"; + event.cell.value = 'blurred'; this._engine.repaint(); - } + }, }; private _engine: TableEngine; - public after(ctx: CanvasRenderingContext2D): void { - } + public after(ctx: CanvasRenderingContext2D): void {} - public before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { - ctx.font = "10px sans-serif"; + public before( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { + ctx.font = '10px sans-serif'; ctx.fillStyle = Colors.toStyleStr(Colors.BLACK); - ctx.textBaseline = AlignmentUtil.verticalAlignmentToStyleStr(VerticalAlignment.MIDDLE) as CanvasTextBaseline; - ctx.textAlign = AlignmentUtil.horizontalAlignmentToStyleStr(HorizontalAlignment.CENTER) as CanvasTextAlign; + ctx.textBaseline = AlignmentUtil.verticalAlignmentToStyleStr( + VerticalAlignment.MIDDLE + ) as CanvasTextBaseline; + ctx.textAlign = AlignmentUtil.horizontalAlignmentToStyleStr( + HorizontalAlignment.CENTER + ) as CanvasTextAlign; } - public cleanup(): void { - } + public cleanup(): void {} public getCopyValue(cell: ICell): string { - return ""; + return ''; } public getEventListener(): ICellRendererEventListener | null { @@ -82,8 +86,16 @@ export class DebugCellRenderer implements ICanvasCellRenderer { this._engine = engine; } - public render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { - ctx.fillText(cell.value ?? "[No event yet]", Math.round(bounds.left + bounds.width / 2), Math.round(bounds.top + bounds.height / 2)); + public render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { + ctx.fillText( + cell.value ?? '[No event yet]', + Math.round(bounds.left + bounds.width / 2), + Math.round(bounds.top + bounds.height / 2) + ); } /** @@ -93,5 +105,4 @@ export class DebugCellRenderer implements ICanvasCellRenderer { public onDisappearing(cell: ICell): void { // Do nothing } - } diff --git a/example/src/environments/environment.prod.ts b/example/src/environments/environment.prod.ts index 46acc08..9ac6cc4 100644 --- a/example/src/environments/environment.prod.ts +++ b/example/src/environments/environment.prod.ts @@ -1,6 +1,6 @@ -import {version} from '../../../package.json'; +import { version } from '../../../package.json'; export const environment = { - production: true, - tableEngineVersion: version + production: true, + tableEngineVersion: version, }; diff --git a/example/src/environments/environment.ts b/example/src/environments/environment.ts index 62a4274..f1cb2b3 100644 --- a/example/src/environments/environment.ts +++ b/example/src/environments/environment.ts @@ -5,8 +5,8 @@ import PackageJson from '../../../package.json'; // The list of file replacements can be found in `angular.json`. export const environment = { - production: false, - tableEngineVersion: PackageJson.version + production: false, + tableEngineVersion: PackageJson.version, }; /* diff --git a/example/src/index.html b/example/src/index.html index aca0d38..68fb83d 100644 --- a/example/src/index.html +++ b/example/src/index.html @@ -1,16 +1,22 @@ - + - - - Example - - - - - - - - - - + + + Example + + + + + + + + + + diff --git a/example/src/main.ts b/example/src/main.ts index c7b673c..9bd6fb6 100644 --- a/example/src/main.ts +++ b/example/src/main.ts @@ -5,8 +5,9 @@ import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { - enableProdMode(); + enableProdMode(); } -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => console.error(err)); diff --git a/example/src/polyfills.ts b/example/src/polyfills.ts index c70a18d..c342e52 100644 --- a/example/src/polyfills.ts +++ b/example/src/polyfills.ts @@ -45,8 +45,7 @@ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ -import 'zone.js/dist/zone'; // Included with Angular CLI. - +import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS diff --git a/example/src/styles.scss b/example/src/styles.scss index 36d6556..43f0add 100644 --- a/example/src/styles.scss +++ b/example/src/styles.scss @@ -1,38 +1,39 @@ -html, body { - height: 100%; - user-select: none; +html, +body { + height: 100%; + user-select: none; } body { - margin: 0; - position: relative; - font-family: Roboto, "Helvetica Neue", sans-serif; + margin: 0; + position: relative; + font-family: Roboto, 'Helvetica Neue', sans-serif; } .small-form-field .mat-form-field-infix { - padding: 0.5em 0; + padding: 0.5em 0; } .table-engine-combobox-dropdown-list { - background: white; - border-radius: 0 0 2px 2px; - box-shadow: 2px 2px 4px #999; + background: white; + border-radius: 0 0 2px 2px; + box-shadow: 2px 2px 4px #999; - ul { - list-style-type: none; - margin: 0; - padding: 0; + ul { + list-style-type: none; + margin: 0; + padding: 0; - li { - line-height: 1.0; - font-size: 12px; - box-sizing: border-box; - padding: 4px 8px; - border-bottom: 1px solid #EAEAEA; + li { + line-height: 1; + font-size: 12px; + box-sizing: border-box; + padding: 4px 8px; + border-bottom: 1px solid #eaeaea; - &:hover { - background-color: #EAEAEA; - } + &:hover { + background-color: #eaeaea; + } + } } - } } diff --git a/package.json b/package.json index f5a807d..6f7ddad 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "homepage": "https://github.com/bennyboer/table-engine#readme", "devDependencies": { "@types/jest": "^26.0.24", + "circular-dependency-plugin": "^5.2.2", "jest": "^27.0.6", + "prettier": "^2.7.1", "ts-jest": "^27.0.4", "ts-loader": "^9.2.4", "typescript": "^4.6.4", diff --git a/src/border/border-side.ts b/src/border/border-side.ts index 06d697f..52e876a 100644 --- a/src/border/border-side.ts +++ b/src/border/border-side.ts @@ -1,11 +1,10 @@ -import {BorderStyle} from "./border-style"; -import {IColor} from "../util/color"; +import { BorderStyle } from './border-style'; +import { IColor } from '../util'; /** * A single side of a border. */ export interface IBorderSide { - /** * Priority of the border side. * This can be used to resolve border collision conflicts when @@ -38,5 +37,4 @@ export interface IBorderSide { * Color of the line. */ color: IColor; - } diff --git a/src/border/border-style.ts b/src/border/border-style.ts index f52c2ff..864c32b 100644 --- a/src/border/border-style.ts +++ b/src/border/border-style.ts @@ -2,9 +2,7 @@ * Available border styles. */ export enum BorderStyle { - SOLID, DASHED, - DOTTED - + DOTTED, } diff --git a/src/border/border.ts b/src/border/border.ts index 1a1b8a5..14a532a 100644 --- a/src/border/border.ts +++ b/src/border/border.ts @@ -1,10 +1,9 @@ -import {IBorderSide} from "./border-side"; +import { IBorderSide } from './border-side'; /** * Representation of a border in the table-engine. */ export interface IBorder { - /** * Top border side. */ @@ -24,5 +23,4 @@ export interface IBorder { * Top border side. */ bottom?: IBorderSide; - } diff --git a/src/border/index.ts b/src/border/index.ts new file mode 100644 index 0000000..8fd366e --- /dev/null +++ b/src/border/index.ts @@ -0,0 +1,5 @@ +export { IBorder } from './border'; +export { IBorderSide } from './border-side'; +export { BorderStyle } from './border-style'; +export { IBorderOptions, fillOptions as fillBorderOptions } from './options'; +export * from './model'; diff --git a/src/border/model/border-mask.ts b/src/border/model/border-mask.ts index d3c8e33..85a225e 100644 --- a/src/border/model/border-mask.ts +++ b/src/border/model/border-mask.ts @@ -2,7 +2,6 @@ * Mask used to set a border. */ export interface IBorderMask { - /** * Whether to set the border on top. */ @@ -22,5 +21,4 @@ export interface IBorderMask { * Whether to set the border to the right. */ right?: boolean; - } diff --git a/src/border/model/border-model.interface.ts b/src/border/model/border-model.interface.ts index 22a9060..32e0c93 100644 --- a/src/border/model/border-model.interface.ts +++ b/src/border/model/border-model.interface.ts @@ -1,13 +1,12 @@ -import {ICellRange} from "../../cell/range/cell-range"; -import {IBorder} from "../border"; -import {IBorderMask} from "./border-mask"; -import {IBorderSide} from "../border-side"; +import { ICellRange } from '../../cell'; +import { IBorder } from '../border'; +import { IBorderMask } from './border-mask'; +import { IBorderSide } from '../border-side'; /** * Representation of the table-engines border model. */ export interface IBorderModel { - /** * Set a border on the outlines of the passed range. * @param border to set @@ -22,12 +21,16 @@ export interface IBorderModel { * @param borderSide to set * @param mask to apply border with */ - setBorderLine(row: number, column: number, borderSide: IBorderSide, mask: IBorderMask): void; + setBorderLine( + row: number, + column: number, + borderSide: IBorderSide, + mask: IBorderMask + ): void; /** * Get borders for the given cell range. * @param range to get borders for */ getBorders(range: ICellRange): IBorder[][]; - } diff --git a/src/border/model/border-model.ts b/src/border/model/border-model.ts index 6bcddd2..0398395 100644 --- a/src/border/model/border-model.ts +++ b/src/border/model/border-model.ts @@ -1,17 +1,14 @@ -import {IBorder} from "../border"; -import {IBorderModel} from "./border-model.interface"; -import {ICellRange} from "../../cell/range/cell-range"; -import {ICellModel} from "../../cell/model/cell-model.interface"; -import {ICell} from "../../cell/cell"; -import {ITableEngineOptions} from "../../options"; -import {IBorderSide} from "../border-side"; -import {IBorderMask} from "./border-mask"; +import { IBorder } from '../border'; +import { IBorderModel } from './border-model.interface'; +import { ICell, ICellModel, ICellRange } from '../../cell'; +import { ITableEngineOptions } from '../../options'; +import { IBorderSide } from '../border-side'; +import { IBorderMask } from './border-mask'; /** * Border model of the table-engine. */ export class BorderModel implements IBorderModel { - /** * Cell model of the table-engine. */ @@ -42,48 +39,68 @@ export class BorderModel implements IBorderModel { for (let row = range.startRow; row <= range.endRow; row++) { if (!!border.left) { - const leftCellBorder: IBorder = this._getCellBorder(row, range.startColumn, true); + const leftCellBorder: IBorder = this._getCellBorder( + row, + range.startColumn, + true + ); if (leftCellBorder.left?.priority !== priority) { leftCellBorder.left = { priority, color: border.left.color, size: border.left.size, - style: border.left.style + style: border.left.style, }; } } if (!!border.right) { - const rightCellBorder: IBorder = this._getCellBorder(row, range.endColumn, true); + const rightCellBorder: IBorder = this._getCellBorder( + row, + range.endColumn, + true + ); if (rightCellBorder.right?.priority !== priority) { rightCellBorder.right = { priority, color: border.right.color, size: border.right.size, - style: border.right.style + style: border.right.style, }; } } } - for (let column = range.startColumn; column <= range.endColumn; column++) { + for ( + let column = range.startColumn; + column <= range.endColumn; + column++ + ) { if (!!border.top) { - const topCellBorder: IBorder = this._getCellBorder(range.startRow, column, true); + const topCellBorder: IBorder = this._getCellBorder( + range.startRow, + column, + true + ); if (topCellBorder.top?.priority !== priority) { topCellBorder.top = { priority, color: border.top.color, size: border.top.size, - style: border.top.style + style: border.top.style, }; } } if (!!border.bottom) { - const bottomCellBorder: IBorder = this._getCellBorder(range.endRow, column, true); + const bottomCellBorder: IBorder = this._getCellBorder( + range.endRow, + column, + true + ); if (bottomCellBorder.bottom?.priority !== priority) { bottomCellBorder.bottom = { priority, color: border.bottom.color, size: border.bottom.size, - style: border.bottom.style + style: border.bottom.style, }; } } @@ -97,7 +114,12 @@ export class BorderModel implements IBorderModel { * @param borderSide to set * @param mask to apply border with */ - public setBorderLine(row: number, column: number, borderSide: IBorderSide, mask: IBorderMask): void { + public setBorderLine( + row: number, + column: number, + borderSide: IBorderSide, + mask: IBorderMask + ): void { const border: IBorder = this._getCellBorder(row, column, true); const priority: number = ++this._priorityCounter; @@ -106,7 +128,7 @@ export class BorderModel implements IBorderModel { priority, color: borderSide.color, size: borderSide.size, - style: borderSide.style + style: borderSide.style, }; } if (mask.bottom) { @@ -114,7 +136,7 @@ export class BorderModel implements IBorderModel { priority, color: borderSide.color, size: borderSide.size, - style: borderSide.style + style: borderSide.style, }; } if (mask.left) { @@ -122,7 +144,7 @@ export class BorderModel implements IBorderModel { priority, color: borderSide.color, size: borderSide.size, - style: borderSide.style + style: borderSide.style, }; } if (mask.right) { @@ -130,7 +152,7 @@ export class BorderModel implements IBorderModel { priority, color: borderSide.color, size: borderSide.size, - style: borderSide.style + style: borderSide.style, }; } } @@ -141,8 +163,16 @@ export class BorderModel implements IBorderModel { * @param columnIndex of the cell to get border at * @param fill whether to fill the cell border is there is none yet */ - private _getCellBorder(rowIndex: number, columnIndex: number, fill: boolean): IBorder | null { - const cell: ICell = this._cellModel.getCell(rowIndex, columnIndex, fill); + private _getCellBorder( + rowIndex: number, + columnIndex: number, + fill: boolean + ): IBorder | null { + const cell: ICell = this._cellModel.getCell( + rowIndex, + columnIndex, + fill + ); if (!cell) { return null; } @@ -164,7 +194,9 @@ export class BorderModel implements IBorderModel { private _getDefaultBorder(row: number, column: number): IBorder { return !!this._options.border.defaultBorderSupplier ? this._options.border.defaultBorderSupplier(row, column) - : (!!this._options.border.defaultBorder ? this._options.border.defaultBorder : {}); + : !!this._options.border.defaultBorder + ? this._options.border.defaultBorder + : {}; } /** @@ -183,21 +215,41 @@ export class BorderModel implements IBorderModel { // Fill matrix for cells in cell model first const cells: ICell[] = this._cellModel.getCells(range); for (const cell of cells) { - const cellBorder: IBorder = this._getCellBorder(cell.range.startRow, cell.range.startColumn, true); + const cellBorder: IBorder = this._getCellBorder( + cell.range.startRow, + cell.range.startColumn, + true + ); // Fill with defaults (if not set) - const defaultBorder: IBorder = this._getDefaultBorder(cell.range.startRow, cell.range.startColumn); + const defaultBorder: IBorder = this._getDefaultBorder( + cell.range.startRow, + cell.range.startColumn + ); const border: IBorder = { top: !!cellBorder.top ? cellBorder.top : defaultBorder.top, - bottom: !!cellBorder.bottom ? cellBorder.bottom : defaultBorder.bottom, + bottom: !!cellBorder.bottom + ? cellBorder.bottom + : defaultBorder.bottom, left: !!cellBorder.left ? cellBorder.left : defaultBorder.left, - right: !!cellBorder.right ? cellBorder.right : defaultBorder.right + right: !!cellBorder.right + ? cellBorder.right + : defaultBorder.right, }; - const startRow: number = Math.max(cell.range.startRow, range.startRow); + const startRow: number = Math.max( + cell.range.startRow, + range.startRow + ); const endRow: number = Math.min(cell.range.endRow, range.endRow); - const startColumn: number = Math.max(cell.range.startColumn, range.startColumn) - const endColumn: number = Math.min(cell.range.endColumn, range.endColumn); + const startColumn: number = Math.max( + cell.range.startColumn, + range.startColumn + ); + const endColumn: number = Math.min( + cell.range.endColumn, + range.endColumn + ); for (let row = startRow; row <= endRow; row++) { const rowOffset: number = row - range.startRow; @@ -215,9 +267,17 @@ export class BorderModel implements IBorderModel { // Check for border collision if (row > 0) { - const upper: IBorder = this._getCellBorder(row - 1, column, false); + const upper: IBorder = this._getCellBorder( + row - 1, + column, + false + ); if (!!upper && !!upper.bottom) { - b.top = this._options.border.borderCollisionResolver(border.top, upper.bottom); // Resolve border collision + b.top = + this._options.border.borderCollisionResolver( + border.top, + upper.bottom + ); // Resolve border collision } } } @@ -227,9 +287,17 @@ export class BorderModel implements IBorderModel { // Check for border collision if (row < this._cellModel.getRowCount() - 1) { - const lower: IBorder = this._getCellBorder(row + 1, column, false); + const lower: IBorder = this._getCellBorder( + row + 1, + column, + false + ); if (!!lower && !!lower.top) { - b.bottom = this._options.border.borderCollisionResolver(border.bottom, lower.top); // Resolve border collision + b.bottom = + this._options.border.borderCollisionResolver( + border.bottom, + lower.top + ); // Resolve border collision } } } @@ -239,9 +307,17 @@ export class BorderModel implements IBorderModel { // Check for border collision if (row > 0) { - const leftCell: IBorder = this._getCellBorder(row, column - 1, false); + const leftCell: IBorder = this._getCellBorder( + row, + column - 1, + false + ); if (!!leftCell && !!leftCell.right) { - b.left = this._options.border.borderCollisionResolver(border.left, leftCell.right); // Resolve border collision + b.left = + this._options.border.borderCollisionResolver( + border.left, + leftCell.right + ); // Resolve border collision } } } @@ -251,9 +327,17 @@ export class BorderModel implements IBorderModel { // Check for border collision if (row < this._cellModel.getColumnCount() - 1) { - const rightCell: IBorder = this._getCellBorder(row, column + 1, false); + const rightCell: IBorder = this._getCellBorder( + row, + column + 1, + false + ); if (!!rightCell && !!rightCell.left) { - b.right = this._options.border.borderCollisionResolver(border.right, rightCell.left); // Resolve border collision + b.right = + this._options.border.borderCollisionResolver( + border.right, + rightCell.left + ); // Resolve border collision } } } @@ -265,16 +349,23 @@ export class BorderModel implements IBorderModel { for (let rowOffset = 0; rowOffset < result.length; rowOffset++) { const rowBorders: IBorder[] = result[rowOffset]; - for (let columnOffset = 0; columnOffset < rowBorders.length; columnOffset++) { + for ( + let columnOffset = 0; + columnOffset < rowBorders.length; + columnOffset++ + ) { let border: IBorder | null = rowBorders[columnOffset]; if (!border) { // Fill with defaults - const defaultBorder: IBorder = this._getDefaultBorder(range.startRow + rowOffset, range.startColumn + columnOffset); + const defaultBorder: IBorder = this._getDefaultBorder( + range.startRow + rowOffset, + range.startColumn + columnOffset + ); rowBorders[columnOffset] = { top: defaultBorder.top, bottom: defaultBorder.bottom, left: defaultBorder.left, - right: defaultBorder.right + right: defaultBorder.right, }; } } @@ -282,5 +373,4 @@ export class BorderModel implements IBorderModel { return result; } - } diff --git a/src/border/model/index.ts b/src/border/model/index.ts new file mode 100644 index 0000000..9dbce0f --- /dev/null +++ b/src/border/model/index.ts @@ -0,0 +1,3 @@ +export { IBorderModel } from './border-model.interface'; +export { BorderModel } from './border-model'; +export { IBorderMask } from './border-mask'; diff --git a/src/border/options.ts b/src/border/options.ts index 085c363..33f23e8 100644 --- a/src/border/options.ts +++ b/src/border/options.ts @@ -1,13 +1,16 @@ -import {IBorderSide} from "./border-side"; -import {IBorder} from "./border"; -import {BorderStyle} from "./border-style"; +import { IBorderSide } from './border-side'; +import { IBorder } from './border'; +import { BorderStyle } from './border-style'; /** * The default border collision resolver. * @param sideA first side * @param sideB second side */ -const DEFAULT_BORDER_COLLISION_RESOLVER: (sideA: IBorderSide, sideB: IBorderSide) => IBorderSide = (sideA, sideB) => { +const DEFAULT_BORDER_COLLISION_RESOLVER: ( + sideA: IBorderSide, + sideB: IBorderSide +) => IBorderSide = (sideA, sideB) => { if (!!sideA && !!sideB) { return sideA.priority > sideB.priority ? sideA : sideB; } else if (!!sideA) { @@ -25,9 +28,9 @@ const DEFAULT_BORDER_COLLISION_RESOLVER: (sideA: IBorderSide, sideB: IBorderSide const DEFAULT_BORDER_SIDE: IBorderSide = { priority: 0, isDefault: true, - color: {red: 230, green: 230, blue: 230, alpha: 1.0}, + color: { red: 230, green: 230, blue: 230, alpha: 1.0 }, size: 1, - style: BorderStyle.SOLID + style: BorderStyle.SOLID, }; /** @@ -37,14 +40,13 @@ const DEFAULT_BORDER: IBorder = { left: DEFAULT_BORDER_SIDE, right: DEFAULT_BORDER_SIDE, bottom: DEFAULT_BORDER_SIDE, - top: DEFAULT_BORDER_SIDE + top: DEFAULT_BORDER_SIDE, }; /** * Options for the border model. */ export interface IBorderOptions { - /** * Resolver for a border collision. * It may happen that two neighbouring cells define different border sides @@ -55,7 +57,10 @@ export interface IBorderOptions { * @param sideA first side * @param sideB second side */ - borderCollisionResolver?: (sideA: IBorderSide, sideB: IBorderSide) => IBorderSide; + borderCollisionResolver?: ( + sideA: IBorderSide, + sideB: IBorderSide + ) => IBorderSide; /** * Default border to use when no border has been specified for a cell. @@ -71,7 +76,6 @@ export interface IBorderOptions { * @param column to display border at */ defaultBorderSupplier?: (row: number, column: number) => IBorder; - } /** diff --git a/src/cell/cell.ts b/src/cell/cell.ts index 3f4b09a..4b75885 100644 --- a/src/cell/cell.ts +++ b/src/cell/cell.ts @@ -1,5 +1,5 @@ -import {ICellRange} from "./range/cell-range"; -import {IBorder} from "../border/border"; +import { ICellRange } from './range'; +import { IBorder } from '../border'; /** * Cell representation of the table engine. diff --git a/src/cell/index.ts b/src/cell/index.ts new file mode 100644 index 0000000..46ed07a --- /dev/null +++ b/src/cell/index.ts @@ -0,0 +1,3 @@ +export { ICell } from './cell'; +export * from './model'; +export * from './range'; diff --git a/src/cell/model/cell-model.interface.ts b/src/cell/model/cell-model.interface.ts index 0ea2a27..20ee521 100644 --- a/src/cell/model/cell-model.interface.ts +++ b/src/cell/model/cell-model.interface.ts @@ -1,21 +1,24 @@ -import {ICell} from "../cell"; -import {ICellRange} from "../range/cell-range"; -import {IRectangle} from "../../util/rect"; -import {Observable} from "rxjs"; -import {ICellModelEvent} from "./event/cell-model-change"; +import { ICell } from '../cell'; +import { ICellRange } from '../range'; +import { IRectangle } from '../../util'; +import { Observable } from 'rxjs'; +import { ICellModelEvent } from './event'; /** * Representation of a cell model. */ export interface ICellModel { - /** * Get a cell at the given coordinates. * @param rowIndex to get cell at * @param columnIndex to get cell at * @param fill whether to fill the cell lookup with a new cell instance, if it is currently null (Default: false) */ - getCell(rowIndex: number, columnIndex: number, fill?: boolean): ICell | null; + getCell( + rowIndex: number, + columnIndex: number, + fill?: boolean + ): ICell | null; /** * Set a value to the cell at the given row and column. @@ -31,7 +34,11 @@ export interface ICellModel { * @param columnIndex index of the column * @param rendererName name of the renderer to set */ - setRenderer(rowIndex: number, columnIndex: number, rendererName: string): void; + setRenderer( + rowIndex: number, + columnIndex: number, + rendererName: string + ): void; /** * Get all cells in the provided range. @@ -176,7 +183,11 @@ export interface ICellModel { * @param count of rows to insert * @param cellInitializer initializer for the new cells */ - insertRows(insertBeforeIndex: number, count: number, cellInitializer?: (row: number, column: number) => ICell): void; + insertRows( + insertBeforeIndex: number, + count: number, + cellInitializer?: (row: number, column: number) => ICell + ): void; /** * Insert columns before the given index. @@ -184,7 +195,11 @@ export interface ICellModel { * @param count of columns to insert * @param cellInitializer initializer for the new cells */ - insertColumns(insertBeforeIndex: number, count: number, cellInitializer?: (row: number, column: number) => ICell): void; + insertColumns( + insertBeforeIndex: number, + count: number, + cellInitializer?: (row: number, column: number) => ICell + ): void; /** * Delete rows starting with the given index. @@ -267,17 +282,14 @@ export interface ICellModel { * Cleanup when the cell model is no more needed. */ cleanup(): void; - } /** * Options for the getCells method. */ export interface IGetCellsOptions { - /** * Whether to include hidden cells in the result. */ includeHidden?: boolean; - } diff --git a/src/cell/model/cell-model.spec.ts b/src/cell/model/cell-model.spec.ts index 6579082..e67f4ba 100644 --- a/src/cell/model/cell-model.spec.ts +++ b/src/cell/model/cell-model.spec.ts @@ -1,19 +1,19 @@ -import {CellModel} from "./cell-model"; -import {CellRange} from "../range/cell-range"; -import {ICell} from "../cell"; -import {IRectangle} from "../../util/rect"; +import { CellModel } from './cell-model'; +import { CellRange } from '../range'; +import { ICell } from '../cell'; +import { IRectangle } from '../../util'; -test("[CellModel.generate] Validate row/column sizes - I", () => { +test('[CellModel.generate] Validate row/column sizes - I', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => row * 2 + 10, (column) => column * 4 + 20, new Set(), @@ -30,17 +30,17 @@ test("[CellModel.generate] Validate row/column sizes - I", () => { } }); -test("[CellModel.generate] Validate row/column sizes - II", () => { +test('[CellModel.generate] Validate row/column sizes - II', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -58,17 +58,17 @@ test("[CellModel.generate] Validate row/column sizes - II", () => { expect(model.getHeight()).toBe(30 * 6); }); -test("[CellModel.generate] Validate row/column offsets", () => { +test('[CellModel.generate] Validate row/column offsets', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -82,7 +82,7 @@ test("[CellModel.generate] Validate row/column offsets", () => { expect(model.getColumnOffset(3)).toBe(100 * 3); }); -test("[CellModel.generate] Validate row/column offsets and hidden rows/columns", () => { +test('[CellModel.generate] Validate row/column offsets and hidden rows/columns', () => { const hiddenRows = new Set(); hiddenRows.add(1); hiddenRows.add(3); @@ -94,12 +94,12 @@ test("[CellModel.generate] Validate row/column offsets and hidden rows/columns", [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, hiddenRows, @@ -113,17 +113,17 @@ test("[CellModel.generate] Validate row/column offsets and hidden rows/columns", expect(model.getColumnOffset(5)).toBe(100 * 4); }); -test("[CellModel.generate] Validate cell values and ranges", () => { +test('[CellModel.generate] Validate cell values and ranges', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -135,7 +135,7 @@ test("[CellModel.generate] Validate cell values and ranges", () => { const cell: ICell = model.getCell(row, column); if (row === 5 && column === 5) { - expect(cell.value).toBe("Last cell"); + expect(cell.value).toBe('Last cell'); } else { expect(cell.value).toBe(row * column); } @@ -148,17 +148,17 @@ test("[CellModel.generate] Validate cell values and ranges", () => { } }); -test("[CellModel.resize] Resize a single row and column", () => { +test('[CellModel.resize] Resize a single row and column', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -183,17 +183,17 @@ test("[CellModel.resize] Resize a single row and column", () => { expect(model.getHeight()).toBe(30 * 5 + 100); }); -test("[CellModel.resize] Resize multiple rows and columns", () => { +test('[CellModel.resize] Resize multiple rows and columns', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -220,7 +220,7 @@ test("[CellModel.resize] Resize multiple rows and columns", () => { expect(model.getHeight()).toBe(30 * 3 + 3 * 100); }); -test("[CellModel.resize] Resize rows and columns with hidden rows/columns", () => { +test('[CellModel.resize] Resize rows and columns with hidden rows/columns', () => { const hiddenRows = new Set(); hiddenRows.add(1); hiddenRows.add(3); @@ -232,12 +232,12 @@ test("[CellModel.resize] Resize rows and columns with hidden rows/columns", () = [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, hiddenRows, @@ -259,17 +259,17 @@ test("[CellModel.resize] Resize rows and columns with hidden rows/columns", () = expect(model.getHeight()).toBe(30 * 4 + 60); }); -test("[CellModel.hide] Hide rows and columns", () => { +test('[CellModel.hide] Hide rows and columns', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -304,7 +304,7 @@ test("[CellModel.hide] Hide rows and columns", () => { expect(model.getColumnOffset(1)).toBe(100); }); -test("[CellModel.hide] Hide rows and columns - with already hidden rows/columns", () => { +test('[CellModel.hide] Hide rows and columns - with already hidden rows/columns', () => { const hiddenRows = new Set(); hiddenRows.add(1); hiddenRows.add(3); @@ -316,12 +316,12 @@ test("[CellModel.hide] Hide rows and columns - with already hidden rows/columns" [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, hiddenRows, @@ -342,7 +342,7 @@ test("[CellModel.hide] Hide rows and columns - with already hidden rows/columns" expect(model.getWidth()).toBe(300); }); -test("[CellModel.show] Show single row and column", () => { +test('[CellModel.show] Show single row and column', () => { const hiddenRows = new Set(); hiddenRows.add(1); hiddenRows.add(3); @@ -354,12 +354,12 @@ test("[CellModel.show] Show single row and column", () => { [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, hiddenRows, @@ -381,17 +381,17 @@ test("[CellModel.show] Show single row and column", () => { expect(model.getColumnOffset(1)).toBe(100); }); -test("[CellModel.hide] Hide all", () => { +test('[CellModel.hide] Hide all', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -405,17 +405,17 @@ test("[CellModel.hide] Hide all", () => { expect(model.getHeight()).toBe(0); }); -test("[CellModel.hide] Hide all and show all again", () => { +test('[CellModel.hide] Hide all and show all again', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -435,17 +435,17 @@ test("[CellModel.hide] Hide all and show all again", () => { expect(model.getHeight()).toBe(180); }); -test("[CellModel.show] Show multiple rows and columns", () => { +test('[CellModel.show] Show multiple rows and columns', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -492,17 +492,17 @@ test("[CellModel.show] Show multiple rows and columns", () => { expect(model.getColumnOffset(5)).toBe(300); }); -test("[CellModel.getCell] Get a cell", () => { +test('[CellModel.getCell] Get a cell', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -515,7 +515,7 @@ test("[CellModel.getCell] Get a cell", () => { expect(lastCell.range.endRow).toBe(5); expect(lastCell.range.startColumn).toBe(5); expect(lastCell.range.endColumn).toBe(5); - expect(lastCell.value).toBe("Last cell"); + expect(lastCell.value).toBe('Last cell'); const anotherCell = model.getCell(2, 3); @@ -526,17 +526,17 @@ test("[CellModel.getCell] Get a cell", () => { expect(anotherCell.value).toBe(2 * 3); }); -test("[CellModel.merge] Merge a cell range", () => { +test('[CellModel.merge] Merge a cell range', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -547,7 +547,7 @@ test("[CellModel.merge] Merge a cell range", () => { startRow: 2, endRow: 4, startColumn: 2, - endColumn: 4 + endColumn: 4, }); expect(success).toBe(true); @@ -559,17 +559,17 @@ test("[CellModel.merge] Merge a cell range", () => { expect(cell.range.endColumn).toBe(4); }); -test("[CellModel.merge] Merge a cell range - impossible", () => { +test('[CellModel.merge] Merge a cell range - impossible', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -580,14 +580,14 @@ test("[CellModel.merge] Merge a cell range - impossible", () => { startRow: 2, endRow: 4, startColumn: 2, - endColumn: 4 + endColumn: 4, }); const success = model.mergeCells({ startRow: 1, endRow: 4, startColumn: 2, - endColumn: 4 + endColumn: 4, }); expect(success).toBe(false); @@ -599,17 +599,17 @@ test("[CellModel.merge] Merge a cell range - impossible", () => { expect(cell.range.endColumn).toBe(4); }); -test("[CellModel.split] Split a cell range", () => { +test('[CellModel.split] Split a cell range', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -620,7 +620,7 @@ test("[CellModel.split] Split a cell range", () => { startRow: 2, endRow: 4, startColumn: 2, - endColumn: 4 + endColumn: 4, }); model.splitCell(3, 4); // The indices do not really matter and just need to be in the merged cell range @@ -641,17 +641,17 @@ test("[CellModel.split] Split a cell range", () => { } }); -test("[CellModel.split] Try to split a single row column cell", () => { +test('[CellModel.split] Try to split a single row column cell', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -668,17 +668,17 @@ test("[CellModel.split] Try to split a single row column cell", () => { expect(cell.range.endColumn).toBe(2); }); -test("[CellModel.insert] Insert rows/columns at the beginning", () => { +test('[CellModel.insert] Insert rows/columns at the beginning', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -740,17 +740,17 @@ test("[CellModel.insert] Insert rows/columns at the beginning", () => { expect(model.getColumnOffset(7)).toBe(700); }); -test("[CellModel.insert] Insert rows/columns at the end", () => { +test('[CellModel.insert] Insert rows/columns at the end', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -778,31 +778,35 @@ test("[CellModel.insert] Insert rows/columns at the end", () => { // Check whether the last 5 columns are empty cells (null) for (let row = 0; row < model.getRowCount(); row++) { - for (let column = model.getColumnCount() - 5; column < model.getColumnCount(); column++) { + for ( + let column = model.getColumnCount() - 5; + column < model.getColumnCount(); + column++ + ) { expect(model.getCell(row, column)).toBe(null); } } // Check the state of the cell with content "Last cell" const lastCell: ICell = model.getCell(5, 5); - expect(lastCell.value).toBe("Last cell"); + expect(lastCell.value).toBe('Last cell'); expect(lastCell.range.startRow).toBe(5); expect(lastCell.range.endRow).toBe(5); expect(lastCell.range.startColumn).toBe(5); expect(lastCell.range.endColumn).toBe(5); }); -test("[CellModel.insert] Insert rows/columns somewhere in between", () => { +test('[CellModel.insert] Insert rows/columns somewhere in between', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -836,7 +840,7 @@ test("[CellModel.insert] Insert rows/columns somewhere in between", () => { } }); -test("[CellModel.insert] Insert rows/columns with hidden rows/columns", () => { +test('[CellModel.insert] Insert rows/columns with hidden rows/columns', () => { const hiddenRows: Set = new Set(); const hiddenColumns: Set = new Set(); @@ -850,12 +854,12 @@ test("[CellModel.insert] Insert rows/columns with hidden rows/columns", () => { [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, hiddenRows, @@ -875,7 +879,7 @@ test("[CellModel.insert] Insert rows/columns with hidden rows/columns", () => { expect(model.getHeight()).toBe(210); }); -test("[CellModel.insert] Insert rows/columns with merged cells and hidden rows/columns", () => { +test('[CellModel.insert] Insert rows/columns with merged cells and hidden rows/columns', () => { const hiddenRows: Set = new Set(); const hiddenColumns: Set = new Set(); @@ -889,12 +893,12 @@ test("[CellModel.insert] Insert rows/columns with merged cells and hidden rows/c [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, hiddenRows, @@ -906,7 +910,7 @@ test("[CellModel.insert] Insert rows/columns with merged cells and hidden rows/c startRow: 1, endRow: 4, startColumn: 1, - endColumn: 4 + endColumn: 4, }); // Insert some rows @@ -934,17 +938,17 @@ test("[CellModel.insert] Insert rows/columns with merged cells and hidden rows/c expect(model.getRowOffset(4 + 4)).toBe(180); }); -test("[CellModel.delete] Delete rows/columns from the beginning", () => { +test('[CellModel.delete] Delete rows/columns from the beginning', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -970,8 +974,11 @@ test("[CellModel.delete] Delete rows/columns from the beginning", () => { const cell: ICell = model.getCell(row, column); - if (row === model.getRowCount() - 1 && column === model.getColumnCount() - 1) { - expect(cell.value).toBe("Last cell"); + if ( + row === model.getRowCount() - 1 && + column === model.getColumnCount() - 1 + ) { + expect(cell.value).toBe('Last cell'); } else { expect(cell.value).toBe((row + 2) * (column + 2)); } @@ -983,17 +990,17 @@ test("[CellModel.delete] Delete rows/columns from the beginning", () => { } }); -test("[CellModel.delete] Delete rows/columns at the end", () => { +test('[CellModel.delete] Delete rows/columns at the end', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1027,17 +1034,17 @@ test("[CellModel.delete] Delete rows/columns at the end", () => { } }); -test("[CellModel.delete] Delete rows/columns in between", () => { +test('[CellModel.delete] Delete rows/columns in between', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1066,8 +1073,11 @@ test("[CellModel.delete] Delete rows/columns in between", () => { const columnValue: number = column < 2 ? column : column + 2; const cell: ICell = model.getCell(row, column); - if (row === model.getRowCount() - 1 && column === model.getColumnCount() - 1) { - expect(cell.value).toBe("Last cell"); + if ( + row === model.getRowCount() - 1 && + column === model.getColumnCount() - 1 + ) { + expect(cell.value).toBe('Last cell'); } else { expect(cell.value).toBe(rowValue * columnValue); } @@ -1079,7 +1089,7 @@ test("[CellModel.delete] Delete rows/columns in between", () => { } }); -test("[CellModel.delete] Delete rows/columns with hidden rows/columns", () => { +test('[CellModel.delete] Delete rows/columns with hidden rows/columns', () => { const hiddenRows: Set = new Set(); const hiddenColumns: Set = new Set(); @@ -1091,12 +1101,12 @@ test("[CellModel.delete] Delete rows/columns with hidden rows/columns", () => { [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, hiddenRows, @@ -1120,15 +1130,22 @@ test("[CellModel.delete] Delete rows/columns with hidden rows/columns", () => { const rowValue: number = row < 2 ? row : row + 2; for (let column = 0; column < model.getColumnCount(); column++) { - expect(model.getColumnOffset(column)).toBe(column * 100 - (column > 1 ? 100 : 0)); + expect(model.getColumnOffset(column)).toBe( + column * 100 - (column > 1 ? 100 : 0) + ); expect(model.getColumnSize(column)).toBe(100); - expect(model.isColumnHidden(column)).toBe(column === 1 || column === 3); + expect(model.isColumnHidden(column)).toBe( + column === 1 || column === 3 + ); const columnValue: number = column < 2 ? column : column + 2; const cell: ICell = model.getCell(row, column); - if (row === model.getRowCount() - 1 && column === model.getColumnCount() - 1) { - expect(cell.value).toBe("Last cell"); + if ( + row === model.getRowCount() - 1 && + column === model.getColumnCount() - 1 + ) { + expect(cell.value).toBe('Last cell'); } else { expect(cell.value).toBe(rowValue * columnValue); } @@ -1140,17 +1157,17 @@ test("[CellModel.delete] Delete rows/columns with hidden rows/columns", () => { } }); -test("[CellModel.delete] Delete rows/columns with merged cells - I", () => { +test('[CellModel.delete] Delete rows/columns with merged cells - I', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1161,7 +1178,7 @@ test("[CellModel.delete] Delete rows/columns with merged cells - I", () => { startRow: 0, endRow: model.getRowCount() - 1, startColumn: 0, - endColumn: 0 + endColumn: 0, }); model.mergeCells({ startRow: 1, @@ -1183,7 +1200,7 @@ test("[CellModel.delete] Delete rows/columns with merged cells - I", () => { [0, 0, 0, 0], [0, 1, 1, 5], [0, 4, 16, 20], - [0, 5, 20, "Last cell"] + [0, 5, 20, 'Last cell'], ]; for (let row = 0; row < model.getRowCount(); row++) { for (let column = 0; column < model.getColumnCount(); column++) { @@ -1206,17 +1223,17 @@ test("[CellModel.delete] Delete rows/columns with merged cells - I", () => { expect(cell.range.endColumn).toBe(2); }); -test("[CellModel.delete] Delete rows/columns with merged cells - II", () => { +test('[CellModel.delete] Delete rows/columns with merged cells - II', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1228,28 +1245,28 @@ test("[CellModel.delete] Delete rows/columns with merged cells - II", () => { startRow: 2, endRow: model.getRowCount() - 1, startColumn: 1, - endColumn: 1 + endColumn: 1, }); // Merge cell starting under the in the row to delete model.mergeCells({ startRow: 4, endRow: model.getRowCount() - 1, startColumn: 2, - endColumn: 2 + endColumn: 2, }); // Merge cell ranging over the row to delete model.mergeCells({ startRow: 0, endRow: model.getRowCount() - 1, startColumn: 3, - endColumn: 3 + endColumn: 3, }); // Merge cell ranging only to the first row to delete model.mergeCells({ startRow: 0, endRow: 2, startColumn: 4, - endColumn: 4 + endColumn: 4, }); // Delete some rows/columns from the beginning @@ -1264,7 +1281,7 @@ test("[CellModel.delete] Delete rows/columns with merged cells - II", () => { [0, 1, 2, 0, 0, 5], [0, 2, 6, 0, 12, 15], [0, 2, 8, 0, 16, 20], - [0, 2, 8, 0, 20, "Last cell"] + [0, 2, 8, 0, 20, 'Last cell'], ]; for (let row = 0; row < model.getRowCount(); row++) { for (let column = 0; column < model.getColumnCount(); column++) { @@ -1299,17 +1316,17 @@ test("[CellModel.delete] Delete rows/columns with merged cells - II", () => { expect(cell.range.endColumn).toBe(4); }); -test("[CellModel.delete] Delete rows/columns with merged cells - III", () => { +test('[CellModel.delete] Delete rows/columns with merged cells - III', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1321,28 +1338,28 @@ test("[CellModel.delete] Delete rows/columns with merged cells - III", () => { startRow: 1, endRow: 1, startColumn: 2, - endColumn: model.getColumnCount() - 1 + endColumn: model.getColumnCount() - 1, }); // Merge cell starting next to the column to delete model.mergeCells({ startRow: 2, endRow: 2, startColumn: 4, - endColumn: model.getColumnCount() - 1 + endColumn: model.getColumnCount() - 1, }); // Merge cell ranging over the column to delete model.mergeCells({ startRow: 3, endRow: 3, startColumn: 0, - endColumn: model.getColumnCount() - 1 + endColumn: model.getColumnCount() - 1, }); // Merge cell ranging only to the first column to delete model.mergeCells({ startRow: 4, endRow: 4, startColumn: 0, - endColumn: 2 + endColumn: 2, }); // Delete some rows/columns from the beginning @@ -1358,7 +1375,7 @@ test("[CellModel.delete] Delete rows/columns with merged cells - III", () => { [0, 2, 6, 8, 8], [0, 0, 0, 0, 0], [0, 0, 12, 16, 20], - [0, 5, 15, 20, "Last cell"] + [0, 5, 15, 20, 'Last cell'], ]; for (let row = 0; row < model.getRowCount(); row++) { for (let column = 0; column < model.getColumnCount(); column++) { @@ -1393,17 +1410,17 @@ test("[CellModel.delete] Delete rows/columns with merged cells - III", () => { expect(cell.range.endColumn).toBe(1); }); -test("[CellModel.get] Get cell at offset", () => { +test('[CellModel.get] Get cell at offset', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1432,17 +1449,17 @@ test("[CellModel.get] Get cell at offset", () => { expect(cell.range.startColumn).toBe(0); }); -test("[CellModel.get] Get cells for rectangle", () => { +test('[CellModel.get] Get cells for rectangle', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1453,7 +1470,7 @@ test("[CellModel.get] Get cells for rectangle", () => { left: 150, top: 130, width: 200, - height: 100 + height: 100, }); const expectedValues: Set = new Set(); @@ -1472,17 +1489,17 @@ test("[CellModel.get] Get cells for rectangle", () => { expect(expectedValues.size).toBe(0); }); -test("[CellModel.getBounds] Get bounds of a cell", () => { +test('[CellModel.getBounds] Get bounds of a cell', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1493,7 +1510,7 @@ test("[CellModel.getBounds] Get bounds of a cell", () => { startRow: 1, endRow: 4, startColumn: 1, - endColumn: 3 + endColumn: 3, }); let bounds: IRectangle = model.getBounds(model.getCell(0, 0).range); @@ -1527,17 +1544,17 @@ test("[CellModel.getBounds] Get bounds of a cell", () => { expect(bounds.width).toBe(200); }); -test("[CellModel.getRange] Get cell range for rectangle", () => { +test('[CellModel.getRange] Get cell range for rectangle', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1548,7 +1565,7 @@ test("[CellModel.getRange] Get cell range for rectangle", () => { left: 100, top: 100, width: 300, - height: 50 + height: 50, }); expect(range.startRow).toBe(3); @@ -1557,70 +1574,78 @@ test("[CellModel.getRange] Get cell range for rectangle", () => { expect(range.endColumn).toBe(4); }); -test("[CellModel.isRangeVisible] Check whether a range is visible", () => { +test('[CellModel.isRangeVisible] Check whether a range is visible', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), new Set() ); - expect(model.isRangeVisible({ - startRow: 2, - endRow: 4, - startColumn: 2, - endColumn: 4 - })).toBe(true); + expect( + model.isRangeVisible({ + startRow: 2, + endRow: 4, + startColumn: 2, + endColumn: 4, + }) + ).toBe(true); model.hideRows([2, 4]); model.hideColumns([2, 3]); - expect(model.isRangeVisible({ - startRow: 2, - endRow: 4, - startColumn: 2, - endColumn: 4 - })).toBe(true); + expect( + model.isRangeVisible({ + startRow: 2, + endRow: 4, + startColumn: 2, + endColumn: 4, + }) + ).toBe(true); model.hideColumns([4]); - expect(model.isRangeVisible({ - startRow: 2, - endRow: 4, - startColumn: 2, - endColumn: 4 - })).toBe(false); + expect( + model.isRangeVisible({ + startRow: 2, + endRow: 4, + startColumn: 2, + endColumn: 4, + }) + ).toBe(false); model.hideRows([3]); - expect(model.isRangeVisible({ - startRow: 2, - endRow: 4, - startColumn: 2, - endColumn: 4 - })).toBe(false); + expect( + model.isRangeVisible({ + startRow: 2, + endRow: 4, + startColumn: 2, + endColumn: 4, + }) + ).toBe(false); }); -test("[CellModel.find] Find next/previous visible row/column", () => { +test('[CellModel.find] Find next/previous visible row/column', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => row * column, - (row, column) => "text", + (row, column) => 'text', (row) => 30, (column) => 100, new Set(), @@ -1662,14 +1687,14 @@ test("[CellModel.find] Find next/previous visible row/column", () => { expect(model.findPreviousVisibleColumn(5)).toBe(5); }); -test("[CellModel.generate] Test generating null cells and filling", () => { +test('[CellModel.generate] Test generating null cells and filling', () => { const model = CellModel.generate( [ { range: CellRange.fromSingleRowColumn(5, 5), - rendererName: "text", - value: "Last cell" - } + rendererName: 'text', + value: 'Last cell', + }, ], (row, column) => null, (row, column) => null, @@ -1684,23 +1709,23 @@ test("[CellModel.generate] Test generating null cells and filling", () => { startRow: 1, endRow: 2, startColumn: 1, - endColumn: 2 + endColumn: 2, }); - expect(model.getCell(1, 1).rendererName).toBe("text"); - expect(model.getCell(1, 2).rendererName).toBe("text"); - expect(model.getCell(2, 1).rendererName).toBe("text"); - expect(model.getCell(2, 2).rendererName).toBe("text"); + expect(model.getCell(1, 1).rendererName).toBe('text'); + expect(model.getCell(1, 2).rendererName).toBe('text'); + expect(model.getCell(2, 1).rendererName).toBe('text'); + expect(model.getCell(2, 2).rendererName).toBe('text'); expect(model.getCell(0, 0)).toBe(null); expect(model.getCell(3, 3)).toBe(null); // Fill a cell value and/or renderer name - model.setRenderer(0, 0, "hello"); - expect(model.getCell(0, 0).rendererName).toBe("hello"); + model.setRenderer(0, 0, 'hello'); + expect(model.getCell(0, 0).rendererName).toBe('hello'); expect(model.getCell(0, 0).value).toBe(null); - model.setValue(3, 3, "Hello world!"); - expect(model.getCell(3, 3).value).toBe("Hello world!"); - expect(model.getCell(3, 3).rendererName).toBe("text"); + model.setValue(3, 3, 'Hello world!'); + expect(model.getCell(3, 3).value).toBe('Hello world!'); + expect(model.getCell(3, 3).rendererName).toBe('text'); }); diff --git a/src/cell/model/cell-model.ts b/src/cell/model/cell-model.ts index b114d1a..228f2bf 100644 --- a/src/cell/model/cell-model.ts +++ b/src/cell/model/cell-model.ts @@ -1,24 +1,19 @@ -import {ICell} from "../cell"; -import {CellRange, ICellRange} from "../range/cell-range"; -import {CellRangeUtil} from "../range/cell-range-util"; -import {IRectangle} from "../../util/rect"; -import {ICellModel, IGetCellsOptions} from "./cell-model.interface"; -import {IBorder} from "../../border/border"; -import {TextCellRenderer} from "../../renderer/canvas/cell/text/text-cell-renderer"; -import {Observable, Subject} from "rxjs"; -import {ICellModelEvent} from "./event/cell-model-change"; -import {BeforeDeleteEvent} from "./event/before-delete-event"; -import {HiddenEvent} from "./event/hidden-event"; +import { ICell } from '../cell'; +import { CellRange, CellRangeUtil, ICellRange } from '../range'; +import { IRectangle } from '../../util'; +import { ICellModel, IGetCellsOptions } from './cell-model.interface'; +import { IBorder } from '../../border'; +import { Observable, Subject } from 'rxjs'; +import { BeforeDeleteEvent, HiddenEvent, ICellModelEvent } from './event'; /** * Model managing cells and their position and size in the table. */ export class CellModel implements ICellModel { - /** * The default cell renderer name to use. */ - private static readonly DEFAULT_CELL_RENDERER_NAME: string = TextCellRenderer.NAME; + private static readonly DEFAULT_CELL_RENDERER_NAME: string = 'text'; /** * The default row size. @@ -81,9 +76,16 @@ export class CellModel implements ICellModel { /** * Subject emitting certain events in the cell model. */ - private readonly _events: Subject = new Subject(); + private readonly _events: Subject = + new Subject(); - constructor(cellLookup: Array>, rowSizes: number[], columnSizes: number[], hiddenRows: Set, hiddenColumns: Set) { + constructor( + cellLookup: Array>, + rowSizes: number[], + columnSizes: number[], + hiddenRows: Set, + hiddenColumns: Set + ) { this._cellLookup = cellLookup; this._rowSizes = rowSizes; this._columnSizes = columnSizes; @@ -91,7 +93,10 @@ export class CellModel implements ICellModel { this._hiddenColumns = hiddenColumns; this._rowOffsets = CellModel._calculateOffsets(rowSizes, hiddenRows); - this._columnOffsets = CellModel._calculateOffsets(columnSizes, hiddenColumns); + this._columnOffsets = CellModel._calculateOffsets( + columnSizes, + hiddenColumns + ); } /** @@ -165,28 +170,44 @@ export class CellModel implements ICellModel { // Fill cell lookup with cells for (let row = 0; row < rowCount; row++) { for (let column = 0; column < columnCount; column++) { - const cellAlreadyFilledInLookup: boolean = !!cellLookup[row][column]; + const cellAlreadyFilledInLookup: boolean = + !!cellLookup[row][column]; if (!cellAlreadyFilledInLookup) { // Create new cell and fill lookup const cell: ICell = cellsByKey.get(`${row}_${column}`); if (!!cell) { // Fill cell into lookup - for (let r = cell.range.startRow; r <= cell.range.endRow; r++) { - for (let c = cell.range.startColumn; c <= cell.range.endColumn; c++) { + for ( + let r = cell.range.startRow; + r <= cell.range.endRow; + r++ + ) { + for ( + let c = cell.range.startColumn; + c <= cell.range.endColumn; + c++ + ) { cellLookup[r][c] = cell; } } } else { // Encountered empty cell const value: any = emptyCellValueSupplier(row, column); - const rendererName: string = emptyCellRendererSupplier(row, column); + const rendererName: string = emptyCellRendererSupplier( + row, + column + ); - const hasValue: boolean = value !== null && value !== undefined; + const hasValue: boolean = + value !== null && value !== undefined; if (hasValue || !!rendererName) { cellLookup[row][column] = { value, rendererName, - range: CellRange.fromSingleRowColumn(row, column) + range: CellRange.fromSingleRowColumn( + row, + column + ), }; } else { /* @@ -200,7 +221,13 @@ export class CellModel implements ICellModel { } } - return new CellModel(cellLookup, rowSizes, columnSizes, hiddenRows, hiddenColumns); + return new CellModel( + cellLookup, + rowSizes, + columnSizes, + hiddenRows, + hiddenColumns + ); } /** @@ -208,7 +235,10 @@ export class CellModel implements ICellModel { * @param sizes to calculate offsets from * @param hidden rows/columns that need to be left out */ - private static _calculateOffsets(sizes: number[], hidden: Set): number[] { + private static _calculateOffsets( + sizes: number[], + hidden: Set + ): number[] { const result = new Array(sizes.length); let currentOffset = 0; @@ -230,7 +260,11 @@ export class CellModel implements ICellModel { * @param columnIndex to get cell at * @param fill whether to fill the cell lookup with a new cell instance, if it is currently null (Default: false) */ - public getCell(rowIndex: number, columnIndex: number, fill?: boolean): ICell | null { + public getCell( + rowIndex: number, + columnIndex: number, + fill?: boolean + ): ICell | null { if (fill === undefined || fill === null) { fill = false; } @@ -257,7 +291,7 @@ export class CellModel implements ICellModel { const newCell: ICell = { range: CellRange.fromSingleRowColumn(rowIndex, columnIndex), value: null, - rendererName: CellModel.DEFAULT_CELL_RENDERER_NAME + rendererName: CellModel.DEFAULT_CELL_RENDERER_NAME, }; // Fill in matrix @@ -284,7 +318,11 @@ export class CellModel implements ICellModel { * @param columnIndex index of the column * @param rendererName name of the renderer to set */ - public setRenderer(rowIndex: number, columnIndex: number, rendererName: string): void { + public setRenderer( + rowIndex: number, + columnIndex: number, + rendererName: string + ): void { const cell: ICell = this._getCellOrFill(rowIndex, columnIndex); cell.rendererName = rendererName; } @@ -295,19 +333,28 @@ export class CellModel implements ICellModel { * @param options for the method */ public getCells(range: ICellRange, options?: IGetCellsOptions): ICell[] { - const cells: ICell[] = new Array((range.endRow - range.startRow + 1) * (range.endColumn - range.startColumn + 1)); + const cells: ICell[] = new Array( + (range.endRow - range.startRow + 1) * + (range.endColumn - range.startColumn + 1) + ); - const includeHidden: boolean = !!options ? options.includeHidden : false; + const includeHidden: boolean = !!options + ? options.includeHidden + : false; let cellCount: number = 0; - this._forEachCellInRange(range, (cell, row, column) => { - if (!!cell) { - cells[cellCount++] = cell; + this._forEachCellInRange( + range, + (cell, row, column) => { + if (!!cell) { + cells[cellCount++] = cell; + } + }, + { + unique: true, + includeHidden, } - }, { - unique: true, - includeHidden - }); + ); // Adjust result list length that may not be correct due to merged cells or empty (null) cells cells.length = cellCount; @@ -350,7 +397,12 @@ export class CellModel implements ICellModel { * @param offset to get nearest row at */ public getRowAtOffset(offset: number): number { - return CellModel._calculateIndexForOffset(offset, this._rowOffsets, this._rowSizes, this._hiddenRows); + return CellModel._calculateIndexForOffset( + offset, + this._rowOffsets, + this._rowSizes, + this._hiddenRows + ); } /** @@ -358,7 +410,12 @@ export class CellModel implements ICellModel { * @param offset to get nearest column at */ public getColumnAtOffset(offset: number): number { - return CellModel._calculateIndexForOffset(offset, this._columnOffsets, this._columnSizes, this._hiddenColumns); + return CellModel._calculateIndexForOffset( + offset, + this._columnOffsets, + this._columnSizes, + this._hiddenColumns + ); } /** @@ -376,7 +433,11 @@ export class CellModel implements ICellModel { } let isAtLeastOneColumnVisible: boolean = false; - for (let column = range.startColumn; column <= range.endColumn; column++) { + for ( + let column = range.startColumn; + column <= range.endColumn; + column++ + ) { if (!this.isColumnHidden(column)) { isAtLeastOneColumnVisible = true; break; @@ -392,7 +453,11 @@ export class CellModel implements ICellModel { * @returns the next visible row or -1 */ public findNextVisibleRow(from: number): number { - return CellModel._findNextVisibleIndex(from, this.getRowCount() - 1, this._hiddenRows); + return CellModel._findNextVisibleIndex( + from, + this.getRowCount() - 1, + this._hiddenRows + ); } /** @@ -401,7 +466,11 @@ export class CellModel implements ICellModel { * @returns the next visible column or -1 */ public findNextVisibleColumn(from: number): number { - return CellModel._findNextVisibleIndex(from, this.getColumnCount() - 1, this._hiddenColumns); + return CellModel._findNextVisibleIndex( + from, + this.getColumnCount() - 1, + this._hiddenColumns + ); } /** @@ -411,7 +480,11 @@ export class CellModel implements ICellModel { * @param hidden all hidden indices * @returns the next visible index or -1 */ - private static _findNextVisibleIndex(from: number, maxIndex: number, hidden: Set): number { + private static _findNextVisibleIndex( + from: number, + maxIndex: number, + hidden: Set + ): number { for (let i = from; i <= maxIndex; i++) { if (!hidden.has(i)) { return i; @@ -445,7 +518,10 @@ export class CellModel implements ICellModel { * @param hidden all hidden indices * @returns the previous visible index or -1 */ - private static _findPreviousVisibleIndex(from: number, hidden: Set): number { + private static _findPreviousVisibleIndex( + from: number, + hidden: Set + ): number { for (let i = from; i >= 0; i--) { if (!hidden.has(i)) { return i; @@ -462,15 +538,23 @@ export class CellModel implements ICellModel { public getBounds(range: ICellRange): IRectangle { const top: number = this.getRowOffset(range.startRow); const left: number = this.getColumnOffset(range.startColumn); - const bottom: number = this.getRowOffset(range.endRow) + (this.isRowHidden(range.endRow) ? 0.0 : this.getRowSize(range.endRow)); - const right: number = this.getColumnOffset(range.endColumn) + (this.isColumnHidden(range.endColumn) ? 0.0 : this.getColumnSize(range.endColumn)); + const bottom: number = + this.getRowOffset(range.endRow) + + (this.isRowHidden(range.endRow) + ? 0.0 + : this.getRowSize(range.endRow)); + const right: number = + this.getColumnOffset(range.endColumn) + + (this.isColumnHidden(range.endColumn) + ? 0.0 + : this.getColumnSize(range.endColumn)); return { top, left, height: bottom - top, - width: right - left - } + width: right - left, + }; } /** @@ -479,10 +563,30 @@ export class CellModel implements ICellModel { */ private _calculateCellRangeForRect(rect: IRectangle): ICellRange { return { - startRow: CellModel._calculateIndexForOffset(rect.top, this._rowOffsets, this._rowSizes, this._hiddenRows), - endRow: CellModel._calculateIndexForOffset(rect.top + rect.height, this._rowOffsets, this._rowSizes, this._hiddenRows), - startColumn: CellModel._calculateIndexForOffset(rect.left, this._columnOffsets, this._columnSizes, this._hiddenColumns), - endColumn: CellModel._calculateIndexForOffset(rect.left + rect.width, this._columnOffsets, this._columnSizes, this._hiddenColumns), + startRow: CellModel._calculateIndexForOffset( + rect.top, + this._rowOffsets, + this._rowSizes, + this._hiddenRows + ), + endRow: CellModel._calculateIndexForOffset( + rect.top + rect.height, + this._rowOffsets, + this._rowSizes, + this._hiddenRows + ), + startColumn: CellModel._calculateIndexForOffset( + rect.left, + this._columnOffsets, + this._columnSizes, + this._hiddenColumns + ), + endColumn: CellModel._calculateIndexForOffset( + rect.left + rect.width, + this._columnOffsets, + this._columnSizes, + this._hiddenColumns + ), }; } @@ -501,18 +605,25 @@ export class CellModel implements ICellModel { ): number { const indexCount: number = sizes.length; if (indexCount === 0) { - throw new Error(`There are no indices yet. Do you have any rows/columns yet in the cell model?`); + throw new Error( + `There are no indices yet. Do you have any rows/columns yet in the cell model?` + ); } const maxIndex: number = indexCount - 1; - const maxOffset: number = offsets[maxIndex] + (hidden.has(maxIndex) ? 0.0 : sizes[maxIndex]); + const maxOffset: number = + offsets[maxIndex] + (hidden.has(maxIndex) ? 0.0 : sizes[maxIndex]); // Guess a possible index - let currentIndex: number = Math.round(Math.min(maxOffset / indexCount, maxIndex)); + let currentIndex: number = Math.round( + Math.min(maxOffset / indexCount, maxIndex) + ); // Calculate the lower and upper offsets of the index let lowerOffsetBound: number = offsets[currentIndex]; - let upperOffsetBound: number = lowerOffsetBound + (hidden.has(currentIndex) ? 0.0 : sizes[currentIndex]); + let upperOffsetBound: number = + lowerOffsetBound + + (hidden.has(currentIndex) ? 0.0 : sizes[currentIndex]); // Determine the direction we need to walk (next or previous index?) const direction: number = offset < lowerOffsetBound ? -1 : 1; @@ -530,7 +641,9 @@ export class CellModel implements ICellModel { // Calculate new lower and upper offsets for the index lowerOffsetBound = offsets[currentIndex]; - upperOffsetBound = lowerOffsetBound + (hidden.has(currentIndex) ? 0.0 : sizes[currentIndex]); + upperOffsetBound = + lowerOffsetBound + + (hidden.has(currentIndex) ? 0.0 : sizes[currentIndex]); } return currentIndex; @@ -556,7 +669,12 @@ export class CellModel implements ICellModel { public getWidth(): number { const lastColumnIndex: number = this.getColumnCount() - 1; - return this._columnOffsets[lastColumnIndex] + (this.isColumnHidden(lastColumnIndex) ? 0.0 : this.getColumnSize(lastColumnIndex)); + return ( + this._columnOffsets[lastColumnIndex] + + (this.isColumnHidden(lastColumnIndex) + ? 0.0 + : this.getColumnSize(lastColumnIndex)) + ); } /** @@ -565,7 +683,12 @@ export class CellModel implements ICellModel { public getHeight(): number { const lastRowIndex: number = this.getRowCount() - 1; - return this._rowOffsets[lastRowIndex] + (this.isRowHidden(lastRowIndex) ? 0.0 : this.getRowSize(lastRowIndex)); + return ( + this._rowOffsets[lastRowIndex] + + (this.isRowHidden(lastRowIndex) + ? 0.0 + : this.getRowSize(lastRowIndex)) + ); } /** @@ -606,7 +729,13 @@ export class CellModel implements ICellModel { * @param size new size for the rows to resize */ public resizeRows(rowIndices: number[], size: number): void { - CellModel._resize(this._rowSizes, this._rowOffsets, this._hiddenRows, rowIndices, size); + CellModel._resize( + this._rowSizes, + this._rowOffsets, + this._hiddenRows, + rowIndices, + size + ); } /** @@ -615,7 +744,13 @@ export class CellModel implements ICellModel { * @param size new size for the columns to resize */ public resizeColumns(columnIndices: number[], size: number): void { - CellModel._resize(this._columnSizes, this._columnOffsets, this._hiddenColumns, columnIndices, size); + CellModel._resize( + this._columnSizes, + this._columnOffsets, + this._hiddenColumns, + columnIndices, + size + ); } /** @@ -709,9 +844,13 @@ export class CellModel implements ICellModel { cellInitializer?: (row: number, column: number) => ICell ): void { // First and foremost collect all merged cells ranging over the new area to insert - const intersectingMergedAreaRanges: ICellRange[] = insertBeforeIndex > 0 - ? this._collectMergedCellsRangingOverIndex(isRow, insertBeforeIndex - 1) - : []; + const intersectingMergedAreaRanges: ICellRange[] = + insertBeforeIndex > 0 + ? this._collectMergedCellsRangingOverIndex( + isRow, + insertBeforeIndex - 1 + ) + : []; // Split those merged cells before inserting for (const range of intersectingMergedAreaRanges) { @@ -719,13 +858,26 @@ export class CellModel implements ICellModel { } // Expand cell lookup and other collections - const totalAddedSize: number = this._expandModelForInsert(insertBeforeIndex, count, isRow, cellInitializer); + const totalAddedSize: number = this._expandModelForInsert( + insertBeforeIndex, + count, + isRow, + cellInitializer + ); // Shift hidden rows/columns - CellModel._shiftHidden(isRow ? this._hiddenRows : this._hiddenColumns, insertBeforeIndex + 1, count); + CellModel._shiftHidden( + isRow ? this._hiddenRows : this._hiddenColumns, + insertBeforeIndex + 1, + count + ); // Shift offsets for the old rows/columns below/after the inserted ones by the total added size - CellModel._shiftOffsets(isRow ? this._rowOffsets : this._columnOffsets, insertBeforeIndex + count, totalAddedSize); + CellModel._shiftOffsets( + isRow ? this._rowOffsets : this._columnOffsets, + insertBeforeIndex + count, + totalAddedSize + ); // Shift cell ranges for all cells below/after the inserted ones this._shiftCellRangesForInsert(isRow, insertBeforeIndex + count, count); @@ -750,37 +902,49 @@ export class CellModel implements ICellModel { * @param fromIndex from which index to start shifting (inclusive) * @param by what amount to shift */ - private _shiftCellRangesForInsert(isRowAxis: boolean, fromIndex: number, by: number): void { + private _shiftCellRangesForInsert( + isRowAxis: boolean, + fromIndex: number, + by: number + ): void { const forEachOptions: ForEachInRangeOptions = { unique: true, - includeHidden: true + includeHidden: true, }; if (isRowAxis) { - this._forEachCellInRange({ - startRow: fromIndex, - endRow: this.getRowCount() - 1, - startColumn: 0, - endColumn: this.getColumnCount() - 1 - }, (cell, row, column) => { - if (!!cell) { - // Change cell range for the cell - cell.range.startRow += by; - cell.range.endRow += by; - } - }, forEachOptions); + this._forEachCellInRange( + { + startRow: fromIndex, + endRow: this.getRowCount() - 1, + startColumn: 0, + endColumn: this.getColumnCount() - 1, + }, + (cell, row, column) => { + if (!!cell) { + // Change cell range for the cell + cell.range.startRow += by; + cell.range.endRow += by; + } + }, + forEachOptions + ); } else { - this._forEachCellInRange({ - startRow: 0, - endRow: this.getRowCount() - 1, - startColumn: fromIndex, - endColumn: this.getColumnCount() - 1 - }, (cell, row, column) => { - if (!!cell) { - // Change cell range for the cell - cell.range.startColumn += by; - cell.range.endColumn += by; - } - }, forEachOptions); + this._forEachCellInRange( + { + startRow: 0, + endRow: this.getRowCount() - 1, + startColumn: fromIndex, + endColumn: this.getColumnCount() - 1, + }, + (cell, row, column) => { + if (!!cell) { + // Change cell range for the cell + cell.range.startColumn += by; + cell.range.endColumn += by; + } + }, + forEachOptions + ); } } @@ -803,13 +967,21 @@ export class CellModel implements ICellModel { continue; } - for (let column = range.startColumn; column <= range.endColumn; column++) { + for ( + let column = range.startColumn; + column <= range.endColumn; + column++ + ) { if (!options.includeHidden && this.isColumnHidden(column)) { continue; } const cell: ICell = this.getCell(row, column); - if (options.unique && !!cell && !CellRangeUtil.isSingleRowColumnRange(cell.range)) { + if ( + options.unique && + !!cell && + !CellRangeUtil.isSingleRowColumnRange(cell.range) + ) { // Check if we already processed the cell with the given range if (alreadyEncounteredMergedCells.has(cell)) { continue; @@ -830,7 +1002,11 @@ export class CellModel implements ICellModel { * @param fromIndex from which index to start shifting * @param by the amount to shift by */ - private static _shiftOffsets(offsets: number[], fromIndex: number, by: number): void { + private static _shiftOffsets( + offsets: number[], + fromIndex: number, + by: number + ): void { for (let i = fromIndex; i < offsets.length; i++) { offsets[i] += by; } @@ -851,9 +1027,17 @@ export class CellModel implements ICellModel { cellInitializer?: (row: number, column: number) => ICell ): number { if (isRow) { - return this._expandModelForInsertForRows(insertBeforeIndex, count, cellInitializer); + return this._expandModelForInsertForRows( + insertBeforeIndex, + count, + cellInitializer + ); } else { - return this._expandModelForInsertForColumns(insertBeforeIndex, count, cellInitializer); + return this._expandModelForInsertForColumns( + insertBeforeIndex, + count, + cellInitializer + ); } } @@ -878,14 +1062,19 @@ export class CellModel implements ICellModel { rowsToInsert[row] = new Array(columnCount); for (let column = 0; column < columnCount; column++) { - rowsToInsert[row][column] = !!cellInitializer ? cellInitializer(row + insertBeforeIndex, column) : null; + rowsToInsert[row][column] = !!cellInitializer + ? cellInitializer(row + insertBeforeIndex, column) + : null; } } // Prepare new row sizes - const defaultRowSize = rowCount > 0 - ? (insertBeforeIndex < rowCount ? this.getRowSize(insertBeforeIndex) : this.getRowSize(rowCount - 1)) - : CellModel.DEFAULT_ROW_SIZE; + const defaultRowSize = + rowCount > 0 + ? insertBeforeIndex < rowCount + ? this.getRowSize(insertBeforeIndex) + : this.getRowSize(rowCount - 1) + : CellModel.DEFAULT_ROW_SIZE; const rowSizesToInsert: number[] = new Array(count); let totalAddedHeight: number = 0; for (let i = 0; i < count; i++) { @@ -895,7 +1084,10 @@ export class CellModel implements ICellModel { } // Prepare new offsets - let lastOffset: number = insertBeforeIndex < rowCount ? this.getRowOffset(insertBeforeIndex) : this.getHeight(); + let lastOffset: number = + insertBeforeIndex < rowCount + ? this.getRowOffset(insertBeforeIndex) + : this.getHeight(); const offsetsToInsert: number[] = new Array(count); for (let i = 0; i < count; i++) { offsetsToInsert[i] = lastOffset; @@ -926,9 +1118,12 @@ export class CellModel implements ICellModel { const columnCount = this.getColumnCount(); // Prepare columns sizes - const defaultColumnSize = columnCount > 0 - ? (insertBeforeIndex < columnCount ? this.getColumnSize(insertBeforeIndex) : this.getColumnSize(columnCount - 1)) - : CellModel.DEFAULT_COLUMN_SIZE; + const defaultColumnSize = + columnCount > 0 + ? insertBeforeIndex < columnCount + ? this.getColumnSize(insertBeforeIndex) + : this.getColumnSize(columnCount - 1) + : CellModel.DEFAULT_COLUMN_SIZE; const columnSizesToInsert: number[] = new Array(count); let totalAddedWidth: number = 0; for (let i = 0; i < count; i++) { @@ -938,7 +1133,10 @@ export class CellModel implements ICellModel { } // Prepare offsets - let lastOffset: number = insertBeforeIndex < columnCount ? this.getColumnOffset(insertBeforeIndex) : this.getWidth(); + let lastOffset: number = + insertBeforeIndex < columnCount + ? this.getColumnOffset(insertBeforeIndex) + : this.getWidth(); const offsetsToInsert: number[] = new Array(count); for (let i = 0; i < count; i++) { offsetsToInsert[i] = lastOffset; @@ -949,10 +1147,16 @@ export class CellModel implements ICellModel { for (let row = 0; row < rowCount; row++) { const cellsToInsert: Array = new Array(count); for (let i = 0; i < cellsToInsert.length; i++) { - cellsToInsert[i] = !!cellInitializer ? cellInitializer(row, i + insertBeforeIndex) : null; + cellsToInsert[i] = !!cellInitializer + ? cellInitializer(row, i + insertBeforeIndex) + : null; } - this._cellLookup[row].splice(insertBeforeIndex, 0, ...cellsToInsert); + this._cellLookup[row].splice( + insertBeforeIndex, + 0, + ...cellsToInsert + ); } // Expand other collections @@ -968,7 +1172,11 @@ export class CellModel implements ICellModel { * @param fromIndex index to start shifting from (inclusive) * @param offset to shift indices by */ - private static _shiftHidden(hidden: Set, fromIndex: number, offset: number): void { + private static _shiftHidden( + hidden: Set, + fromIndex: number, + offset: number + ): void { const tmp: number[] = Array.from(hidden.values()); hidden.clear(); @@ -1011,14 +1219,23 @@ export class CellModel implements ICellModel { this._events.next(new BeforeDeleteEvent(fromIndex, count, isRow)); // Find all merged cells ranging over the first row/column to delete - const intersectingMergedAreaRanges: ICellRange[] = fromIndex > 0 - ? this._collectMergedCellsRangingOverIndex(isRow, fromIndex - 1) - : []; + const intersectingMergedAreaRanges: ICellRange[] = + fromIndex > 0 + ? this._collectMergedCellsRangingOverIndex(isRow, fromIndex - 1) + : []; const lastIndexToRemove: number = fromIndex + count - 1; const totalSizeToRemove: number = isRow - ? (this.getRowOffset(lastIndexToRemove) + (this.isRowHidden(lastIndexToRemove) ? 0.0 : this.getRowSize(lastIndexToRemove))) - this.getRowOffset(fromIndex) - : (this.getColumnOffset(lastIndexToRemove) + (this.isColumnHidden(lastIndexToRemove) ? 0.0 : this.getColumnSize(lastIndexToRemove))) - this.getColumnOffset(fromIndex) + ? this.getRowOffset(lastIndexToRemove) + + (this.isRowHidden(lastIndexToRemove) + ? 0.0 + : this.getRowSize(lastIndexToRemove)) - + this.getRowOffset(fromIndex) + : this.getColumnOffset(lastIndexToRemove) + + (this.isColumnHidden(lastIndexToRemove) + ? 0.0 + : this.getColumnSize(lastIndexToRemove)) - + this.getColumnOffset(fromIndex); // Adjust cell ranges ranging over the first row/column to delete, // but not ranges that that span over the whole area to delete @@ -1026,13 +1243,19 @@ export class CellModel implements ICellModel { for (const range of intersectingMergedAreaRanges) { if (isRow) { if (range.endRow <= lastIndexToRemove) { - const cell: ICell = this.getCell(range.startRow, range.startColumn); + const cell: ICell = this.getCell( + range.startRow, + range.startColumn + ); cell.range.endRow = fromIndex - 1; } } else { if (range.endColumn <= lastIndexToRemove) { - const cell: ICell = this.getCell(range.startRow, range.startColumn); + const cell: ICell = this.getCell( + range.startRow, + range.startColumn + ); cell.range.endColumn = fromIndex - 1; } @@ -1043,16 +1266,24 @@ export class CellModel implements ICellModel { this._shiftCellRangesForDelete(isRow, fromIndex, count); // Remove hidden rows/columns from the lookup - const hidden: Set = isRow ? this._hiddenRows : this._hiddenColumns; + const hidden: Set = isRow + ? this._hiddenRows + : this._hiddenColumns; for (let i = fromIndex; i < fromIndex + count; i++) { hidden.delete(i); } // Shift hidden rows/columns to the left as there are now less rows/columns - CellModel._shiftHidden(isRow ? this._hiddenRows : this._hiddenColumns, fromIndex + count, -count); + CellModel._shiftHidden( + isRow ? this._hiddenRows : this._hiddenColumns, + fromIndex + count, + -count + ); // Delete from the offset lookup - const offsets: number[] = isRow ? this._rowOffsets : this._columnOffsets; + const offsets: number[] = isRow + ? this._rowOffsets + : this._columnOffsets; offsets.splice(fromIndex, count); // Remove offsets for rows/columns that are now deleted // Shift offsets by the removed size @@ -1080,66 +1311,84 @@ export class CellModel implements ICellModel { * @param fromIndex the index rows/columns are deleted from (inclusive) * @param count of rows/columns that are deleted */ - private _shiftCellRangesForDelete(isRowAxis: boolean, fromIndex: number, count: number): void { + private _shiftCellRangesForDelete( + isRowAxis: boolean, + fromIndex: number, + count: number + ): void { const forEachOptions: ForEachInRangeOptions = { unique: true, - includeHidden: true + includeHidden: true, }; if (isRowAxis) { - this._forEachCellInRange({ - startRow: fromIndex + count, - endRow: this.getRowCount() - 1, - startColumn: 0, - endColumn: this.getColumnCount() - 1 - }, (cell, row, column) => { - if (!!cell) { - if (CellRangeUtil.isSingleRowColumnRange(cell.range)) { - cell.range.startRow -= count; - cell.range.endRow -= count; - } else { - if (cell.range.startRow < fromIndex) { - // Case 1: Merged cell starts in row above the rows to delete - cell.range.endRow -= count; - } else if (cell.range.startRow < fromIndex + count) { - // Case 2: Merged cell starts in a row to delete - cell.range.startRow = fromIndex; - cell.range.endRow -= count; - } else { - // Case 3: Merged cell starts without concern to the deleted area + this._forEachCellInRange( + { + startRow: fromIndex + count, + endRow: this.getRowCount() - 1, + startColumn: 0, + endColumn: this.getColumnCount() - 1, + }, + (cell, row, column) => { + if (!!cell) { + if (CellRangeUtil.isSingleRowColumnRange(cell.range)) { cell.range.startRow -= count; cell.range.endRow -= count; + } else { + if (cell.range.startRow < fromIndex) { + // Case 1: Merged cell starts in row above the rows to delete + cell.range.endRow -= count; + } else if ( + cell.range.startRow < + fromIndex + count + ) { + // Case 2: Merged cell starts in a row to delete + cell.range.startRow = fromIndex; + cell.range.endRow -= count; + } else { + // Case 3: Merged cell starts without concern to the deleted area + cell.range.startRow -= count; + cell.range.endRow -= count; + } } } - } - }, forEachOptions); + }, + forEachOptions + ); } else { - this._forEachCellInRange({ - startRow: 0, - endRow: this.getRowCount() - 1, - startColumn: fromIndex + count, - endColumn: this.getColumnCount() - 1 - }, (cell, row, column) => { - if (!!cell) { - if (CellRangeUtil.isSingleRowColumnRange(cell.range)) { - cell.range.startColumn -= count; - cell.range.endColumn -= count; - } else { - if (cell.range.startColumn < fromIndex) { - // Case 1: Merged cell starts in column before the columns to delete - cell.range.endColumn -= count; - } else if (cell.range.startColumn < fromIndex + count) { - // Case 2: Merged cell starts in a column to delete - cell.range.startColumn = fromIndex; - cell.range.endColumn -= count; - } else { - // Case 3: Merged cell starts without concern to the deleted area + this._forEachCellInRange( + { + startRow: 0, + endRow: this.getRowCount() - 1, + startColumn: fromIndex + count, + endColumn: this.getColumnCount() - 1, + }, + (cell, row, column) => { + if (!!cell) { + if (CellRangeUtil.isSingleRowColumnRange(cell.range)) { cell.range.startColumn -= count; cell.range.endColumn -= count; + } else { + if (cell.range.startColumn < fromIndex) { + // Case 1: Merged cell starts in column before the columns to delete + cell.range.endColumn -= count; + } else if ( + cell.range.startColumn < + fromIndex + count + ) { + // Case 2: Merged cell starts in a column to delete + cell.range.startColumn = fromIndex; + cell.range.endColumn -= count; + } else { + // Case 3: Merged cell starts without concern to the deleted area + cell.range.startColumn -= count; + cell.range.endColumn -= count; + } } } - } - }, forEachOptions); + }, + forEachOptions + ); } } @@ -1148,11 +1397,18 @@ export class CellModel implements ICellModel { * @param overRow whether to collect merged cells over rows or over columns * @param index (row/column) index to detect merged cells for that span over it (row index when overRow is true) */ - private _collectMergedCellsRangingOverIndex(overRow: boolean, index: number): ICellRange[] { + private _collectMergedCellsRangingOverIndex( + overRow: boolean, + index: number + ): ICellRange[] { const result: ICellRange[] = []; - const maxIndex: number = overRow ? this.getRowCount() - 1 : this.getColumnCount() - 1; - const otherAxisMaxIndex: number = overRow ? this.getColumnCount() - 1 : this.getRowCount() - 1; + const maxIndex: number = overRow + ? this.getRowCount() - 1 + : this.getColumnCount() - 1; + const otherAxisMaxIndex: number = overRow + ? this.getColumnCount() - 1 + : this.getRowCount() - 1; if (index >= maxIndex) { return result; // Nothing to do here as there cannot be cells ranging over the max index @@ -1197,7 +1453,11 @@ export class CellModel implements ICellModel { public mergeCells(range: ICellRange): boolean { // Check whether we can merge the cell range for (let row = range.startRow; row <= range.endRow; row++) { - for (let column = range.startColumn; column <= range.endColumn; column++) { + for ( + let column = range.startColumn; + column <= range.endColumn; + column++ + ) { const cell = this.getCell(row, column); if (!!cell) { if (!CellRangeUtil.isSingleRowColumnRange(cell.range)) { @@ -1207,7 +1467,10 @@ export class CellModel implements ICellModel { } } - const cell: ICell = this._getCellOrFill(range.startRow, range.startColumn); + const cell: ICell = this._getCellOrFill( + range.startRow, + range.startColumn + ); // Update cell range cell.range.endRow = range.endRow; @@ -1215,7 +1478,11 @@ export class CellModel implements ICellModel { // Actually merge the cell by writing cell references in the cell lookup to the most upper left corner cell for (let row = range.startRow; row <= range.endRow; row++) { - for (let column = range.startColumn; column <= range.endColumn; column++) { + for ( + let column = range.startColumn; + column <= range.endColumn; + column++ + ) { if (row === range.startRow && column === range.startColumn) { continue; } @@ -1244,9 +1511,16 @@ export class CellModel implements ICellModel { // Reset cell lookup to empty cell for all cells but the most upper left one (if no borders are set to the side). for (let row = cell.range.startRow; row <= cell.range.endRow; row++) { - for (let column = cell.range.startColumn; column <= cell.range.endColumn; column++) { + for ( + let column = cell.range.startColumn; + column <= cell.range.endColumn; + column++ + ) { let toSet: ICell | null = null; - if (row === cell.range.startRow && column === cell.range.startColumn) { + if ( + row === cell.range.startRow && + column === cell.range.startColumn + ) { toSet = cell; toSet.border = {}; } @@ -1256,68 +1530,80 @@ export class CellModel implements ICellModel { // Keep upper border if (!toSet) { toSet = { - range: CellRange.fromSingleRowColumn(row, column), + range: CellRange.fromSingleRowColumn( + row, + column + ), value: null, rendererName: cell.rendererName, - border: {} + border: {}, }; } toSet.border.top = { size: border.top.size, color: border.top.color, style: border.top.style, - priority: border.top.priority + priority: border.top.priority, }; } if (row === cell.range.endRow && !!border.bottom) { // Keep lower border if (!toSet) { toSet = { - range: CellRange.fromSingleRowColumn(row, column), + range: CellRange.fromSingleRowColumn( + row, + column + ), value: null, rendererName: cell.rendererName, - border: {} + border: {}, }; } toSet.border.bottom = { size: border.bottom.size, color: border.bottom.color, style: border.bottom.style, - priority: border.bottom.priority + priority: border.bottom.priority, }; } if (column === cell.range.startColumn && !!border.left) { // Keep left border if (!toSet) { toSet = { - range: CellRange.fromSingleRowColumn(row, column), + range: CellRange.fromSingleRowColumn( + row, + column + ), value: null, rendererName: cell.rendererName, - border: {} + border: {}, }; } toSet.border.left = { size: border.left.size, color: border.left.color, style: border.left.style, - priority: border.left.priority + priority: border.left.priority, }; } if (column === cell.range.endColumn && !!border.right) { // Keep right border if (!toSet) { toSet = { - range: CellRange.fromSingleRowColumn(row, column), + range: CellRange.fromSingleRowColumn( + row, + column + ), value: null, rendererName: cell.rendererName, - border: {} + border: {}, }; } toSet.border.right = { size: border.right.size, color: border.right.color, style: border.right.style, - priority: border.right.priority + priority: border.right.priority, }; } } @@ -1352,7 +1638,12 @@ export class CellModel implements ICellModel { * @param rowIndices indices to hide rows for */ public hideRows(rowIndices: number[]): void { - const hiddenIndices: number[] = CellModel._hide(rowIndices, this._rowOffsets, this._hiddenRows, this._rowSizes); + const hiddenIndices: number[] = CellModel._hide( + rowIndices, + this._rowOffsets, + this._hiddenRows, + this._rowSizes + ); if (hiddenIndices.length > 0) { this._events.next(new HiddenEvent(hiddenIndices, true)); @@ -1364,7 +1655,12 @@ export class CellModel implements ICellModel { * @param columnIndices to hide columns for */ public hideColumns(columnIndices: number[]): void { - const hiddenIndices: number[] = CellModel._hide(columnIndices, this._columnOffsets, this._hiddenColumns, this._columnSizes); + const hiddenIndices: number[] = CellModel._hide( + columnIndices, + this._columnOffsets, + this._hiddenColumns, + this._columnSizes + ); if (hiddenIndices.length > 0) { this._events.next(new HiddenEvent(hiddenIndices, false)); @@ -1379,7 +1675,12 @@ export class CellModel implements ICellModel { * @param sizes lookup for the indices * @returns consecutive sorted indices that have been hidden */ - private static _hide(indices: number[], offsets: number[], hidden: Set, sizes: number[]): number[] { + private static _hide( + indices: number[], + offsets: number[], + hidden: Set, + sizes: number[] + ): number[] { // Add indices to the hidden collection first const adjustOffsetsIndices: number[] = []; for (const index of indices) { @@ -1399,7 +1700,10 @@ export class CellModel implements ICellModel { // Adjust offsets let toDecrease = sizes[adjustOffsetsIndices[0]]; let alreadyHiddenCount = 1; - let nextHideIndex = adjustOffsetsIndices.length > alreadyHiddenCount ? adjustOffsetsIndices[alreadyHiddenCount] : -1; + let nextHideIndex = + adjustOffsetsIndices.length > alreadyHiddenCount + ? adjustOffsetsIndices[alreadyHiddenCount] + : -1; for (let i = adjustOffsetsIndices[0] + 1; i < offsets.length; i++) { offsets[i] -= toDecrease; @@ -1408,7 +1712,8 @@ export class CellModel implements ICellModel { alreadyHiddenCount++; if (adjustOffsetsIndices.length > alreadyHiddenCount) { - nextHideIndex = adjustOffsetsIndices[alreadyHiddenCount]; + nextHideIndex = + adjustOffsetsIndices[alreadyHiddenCount]; } else { nextHideIndex = -1; } @@ -1426,7 +1731,12 @@ export class CellModel implements ICellModel { * @param rowIndices indices of rows to show */ public showRows(rowIndices: number[]): void { - CellModel._show(rowIndices, this._rowOffsets, this._hiddenRows, this._rowSizes); + CellModel._show( + rowIndices, + this._rowOffsets, + this._hiddenRows, + this._rowSizes + ); } /** @@ -1434,7 +1744,12 @@ export class CellModel implements ICellModel { * @param columnIndices indices of columns to show */ public showColumns(columnIndices: number[]): void { - CellModel._show(columnIndices, this._columnOffsets, this._hiddenColumns, this._columnSizes); + CellModel._show( + columnIndices, + this._columnOffsets, + this._hiddenColumns, + this._columnSizes + ); } /** @@ -1444,7 +1759,12 @@ export class CellModel implements ICellModel { * @param hidden lookup of hidden rows or columns * @param sizes lookup for the indices */ - private static _show(indices: number[], offsets: number[], hidden: Set, sizes: number[]): void { + private static _show( + indices: number[], + offsets: number[], + hidden: Set, + sizes: number[] + ): void { // Remove indices from the hidden collection first const adjustOffsetsIndices: number[] = []; for (const index of indices) { @@ -1465,7 +1785,10 @@ export class CellModel implements ICellModel { // Adjust offsets let toIncrease = sizes[adjustOffsetsIndices[0]]; let alreadyShownCount = 1; - let nextShowIndex = adjustOffsetsIndices.length > alreadyShownCount ? adjustOffsetsIndices[alreadyShownCount] : -1; + let nextShowIndex = + adjustOffsetsIndices.length > alreadyShownCount + ? adjustOffsetsIndices[alreadyShownCount] + : -1; for (let i = adjustOffsetsIndices[0] + 1; i < offsets.length; i++) { offsets[i] += toIncrease; @@ -1491,7 +1814,6 @@ export class CellModel implements ICellModel { this.showRows(Array.from(this._hiddenRows)); this.showColumns(Array.from(this._hiddenColumns)); } - } /** diff --git a/src/cell/model/event/before-delete-event.ts b/src/cell/model/event/before-delete-event.ts index 251c98e..c8c0ee0 100644 --- a/src/cell/model/event/before-delete-event.ts +++ b/src/cell/model/event/before-delete-event.ts @@ -1,12 +1,11 @@ -import {ICellModelEvent} from "./cell-model-change"; -import {CellModelEventType} from "./cell-model-event-type"; +import { ICellModelEvent } from './cell-model-change'; +import { CellModelEventType } from './cell-model-event-type'; /** * Deletion change in the cell model. * Rows or columns have been deleted. */ export class BeforeDeleteEvent implements ICellModelEvent { - public readonly type: CellModelEventType = CellModelEventType.BEFORE_DELETE; /** @@ -41,5 +40,4 @@ export class BeforeDeleteEvent implements ICellModelEvent { get isRow(): boolean { return this._isRow; } - } diff --git a/src/cell/model/event/cell-model-change.ts b/src/cell/model/event/cell-model-change.ts index bc68005..b0bb2ff 100644 --- a/src/cell/model/event/cell-model-change.ts +++ b/src/cell/model/event/cell-model-change.ts @@ -1,13 +1,11 @@ -import {CellModelEventType} from "./cell-model-event-type"; +import { CellModelEventType } from './cell-model-event-type'; /** * Representation of an event in the cell model. */ export interface ICellModelEvent { - /** * Type of the event. */ type: CellModelEventType; - } diff --git a/src/cell/model/event/cell-model-event-type.ts b/src/cell/model/event/cell-model-event-type.ts index eae23b6..2adf5ae 100644 --- a/src/cell/model/event/cell-model-event-type.ts +++ b/src/cell/model/event/cell-model-event-type.ts @@ -2,7 +2,6 @@ * Available cell model event types. */ export enum CellModelEventType { - /** * When rows/columns will be deleted. */ @@ -11,6 +10,5 @@ export enum CellModelEventType { /** * When rows/columns have been hidden. */ - HIDDEN - + HIDDEN, } diff --git a/src/cell/model/event/hidden-event.ts b/src/cell/model/event/hidden-event.ts index 2eedaa2..5ea2e1a 100644 --- a/src/cell/model/event/hidden-event.ts +++ b/src/cell/model/event/hidden-event.ts @@ -1,11 +1,10 @@ -import {ICellModelEvent} from "./cell-model-change"; -import {CellModelEventType} from "./cell-model-event-type"; +import { ICellModelEvent } from './cell-model-change'; +import { CellModelEventType } from './cell-model-event-type'; /** * Event when rows/columns have been hidden. */ export class HiddenEvent implements ICellModelEvent { - public readonly type: CellModelEventType = CellModelEventType.HIDDEN; /** @@ -30,5 +29,4 @@ export class HiddenEvent implements ICellModelEvent { get isRow(): boolean { return this._isRow; } - } diff --git a/src/cell/model/event/index.ts b/src/cell/model/event/index.ts new file mode 100644 index 0000000..e562228 --- /dev/null +++ b/src/cell/model/event/index.ts @@ -0,0 +1,4 @@ +export { ICellModelEvent } from './cell-model-change'; +export { BeforeDeleteEvent } from './before-delete-event'; +export { CellModelEventType } from './cell-model-event-type'; +export { HiddenEvent } from './hidden-event'; diff --git a/src/cell/model/index.ts b/src/cell/model/index.ts new file mode 100644 index 0000000..175e621 --- /dev/null +++ b/src/cell/model/index.ts @@ -0,0 +1,3 @@ +export { ICellModel } from './cell-model.interface'; +export { CellModel } from './cell-model'; +export * from './event'; diff --git a/src/cell/range/cell-range-util.spec.ts b/src/cell/range/cell-range-util.spec.ts index e5b1dfa..d15ea13 100644 --- a/src/cell/range/cell-range-util.spec.ts +++ b/src/cell/range/cell-range-util.spec.ts @@ -1,53 +1,68 @@ -import {CellRangeUtil} from "./cell-range-util"; +import { CellRangeUtil } from './cell-range-util'; -test("[CellRangeUtil.and] AND operation - 1", () => { - expect(CellRangeUtil.and({ - startRow: 4, - endRow: 10, - startColumn: 5, - endColumn: 7 - }, { - startRow: 8, - endRow: 12, - startColumn: 7, - endColumn: 9 - })).toEqual({ +test('[CellRangeUtil.and] AND operation - 1', () => { + expect( + CellRangeUtil.and( + { + startRow: 4, + endRow: 10, + startColumn: 5, + endColumn: 7, + }, + { + startRow: 8, + endRow: 12, + startColumn: 7, + endColumn: 9, + } + ) + ).toEqual({ startRow: 8, endRow: 10, startColumn: 7, - endColumn: 7 + endColumn: 7, }); }); -test("[CellRangeUtil.and] AND operation - 2", () => { - expect(CellRangeUtil.and({ - startRow: 4, - endRow: 10, - startColumn: 5, - endColumn: 7 - }, { +test('[CellRangeUtil.and] AND operation - 2', () => { + expect( + CellRangeUtil.and( + { + startRow: 4, + endRow: 10, + startColumn: 5, + endColumn: 7, + }, + { + startRow: 5, + endRow: 6, + startColumn: 5, + endColumn: 5, + } + ) + ).toEqual({ startRow: 5, endRow: 6, startColumn: 5, - endColumn: 5 - })).toEqual({ - startRow: 5, - endRow: 6, - startColumn: 5, - endColumn: 5 + endColumn: 5, }); }); -test("[CellRangeUtil.and] AND operation - 3", () => { - expect(CellRangeUtil.and({ - startRow: 4, - endRow: 10, - startColumn: 5, - endColumn: 7 - }, { - startRow: 20, - endRow: 20, - startColumn: 20, - endColumn: 20 - })).toBeNull(); +test('[CellRangeUtil.and] AND operation - 3', () => { + expect( + CellRangeUtil.and( + { + startRow: 4, + endRow: 10, + startColumn: 5, + endColumn: 7, + }, + { + startRow: 20, + endRow: 20, + startColumn: 20, + endColumn: 20, + } + ) + ).toBeNull(); }); diff --git a/src/cell/range/cell-range-util.ts b/src/cell/range/cell-range-util.ts index 510c99f..f423044 100644 --- a/src/cell/range/cell-range-util.ts +++ b/src/cell/range/cell-range-util.ts @@ -1,16 +1,18 @@ -import {ICellRange} from "./cell-range"; +import { ICellRange } from './cell-range'; /** * Utility methods that are cell range related. */ export class CellRangeUtil { - /** * Whether the range only spans over one row and column. * @param range to check */ public static isSingleRowColumnRange(range: ICellRange): boolean { - return range.startRow === range.endRow && range.startColumn === range.endColumn; + return ( + range.startRow === range.endRow && + range.startColumn === range.endColumn + ); } /** @@ -18,11 +20,16 @@ export class CellRangeUtil { * @param range to check whether it is contained in the second range * @param containedIn range that contains the first range */ - public static contains(range: ICellRange, containedIn: ICellRange): boolean { - return range.startRow >= containedIn.startRow - && range.startColumn >= containedIn.startColumn - && range.endRow <= containedIn.endRow - && range.endColumn <= containedIn.endColumn; + public static contains( + range: ICellRange, + containedIn: ICellRange + ): boolean { + return ( + range.startRow >= containedIn.startRow && + range.startColumn >= containedIn.startColumn && + range.endRow <= containedIn.endRow && + range.endColumn <= containedIn.endColumn + ); } /** @@ -31,12 +38,14 @@ export class CellRangeUtil { * @param b second cell range */ public static overlap(a: ICellRange, b: ICellRange): boolean { - const cantOverlapVertically: boolean = a.startRow > b.endRow || a.endRow < b.startRow; + const cantOverlapVertically: boolean = + a.startRow > b.endRow || a.endRow < b.startRow; if (cantOverlapVertically) { return false; } - const cantOverlapHorizontally: boolean = a.startColumn > b.endColumn || a.endColumn < b.startColumn; + const cantOverlapHorizontally: boolean = + a.startColumn > b.endColumn || a.endColumn < b.startColumn; return !cantOverlapHorizontally; } @@ -56,7 +65,7 @@ export class CellRangeUtil { startRow: Math.max(a.startRow, b.startRow), endRow: Math.min(a.endRow, b.endRow), startColumn: Math.max(a.startColumn, b.startColumn), - endColumn: Math.min(a.endColumn, b.endColumn) + endColumn: Math.min(a.endColumn, b.endColumn), }; } @@ -74,7 +83,7 @@ export class CellRangeUtil { startRow: Math.min(a.startRow, b.startRow), endRow: Math.max(a.startRow, b.startRow) - 1, // Exclusive! startColumn: Math.min(a.startColumn, b.startColumn), - endColumn: Math.max(a.endColumn, b.endColumn) + endColumn: Math.max(a.endColumn, b.endColumn), }); } @@ -84,7 +93,7 @@ export class CellRangeUtil { startRow: Math.min(a.endRow, b.endRow) + 1, // Exclusive! endRow: Math.max(a.endRow, b.endRow), startColumn: Math.min(a.startColumn, b.startColumn), - endColumn: Math.max(a.endColumn, b.endColumn) + endColumn: Math.max(a.endColumn, b.endColumn), }); } @@ -94,7 +103,7 @@ export class CellRangeUtil { startRow: Math.max(a.startRow, b.startRow), endRow: Math.min(a.endRow, b.endRow), startColumn: Math.min(a.startColumn, b.startColumn), - endColumn: Math.max(a.startColumn, b.startColumn) - 1 // Exclusive! + endColumn: Math.max(a.startColumn, b.startColumn) - 1, // Exclusive! }); } @@ -104,7 +113,7 @@ export class CellRangeUtil { startRow: Math.max(a.startRow, b.startRow), endRow: Math.min(a.endRow, b.endRow), startColumn: Math.min(a.endColumn, b.endColumn) + 1, // Exclusive! - endColumn: Math.max(a.endColumn, b.endColumn) + endColumn: Math.max(a.endColumn, b.endColumn), }); } @@ -117,10 +126,12 @@ export class CellRangeUtil { * @param b second range to check for equality */ public static equals(a: ICellRange, b: ICellRange): boolean { - return a.startRow === b.startRow - && a.startColumn === b.startColumn - && a.endRow === b.endRow - && a.endColumn === b.endColumn; + return ( + a.startRow === b.startRow && + a.startColumn === b.startColumn && + a.endRow === b.endRow && + a.endColumn === b.endColumn + ); } /** @@ -133,5 +144,4 @@ export class CellRangeUtil { return width * height; } - } diff --git a/src/cell/range/cell-range.ts b/src/cell/range/cell-range.ts index 7abb738..870a08d 100644 --- a/src/cell/range/cell-range.ts +++ b/src/cell/range/cell-range.ts @@ -65,7 +65,7 @@ export class CellRange implements ICellRange { startRow: row, endRow: row, startColumn: column, - endColumn: column + endColumn: column, }); } diff --git a/src/cell/range/index.ts b/src/cell/range/index.ts new file mode 100644 index 0000000..d86019b --- /dev/null +++ b/src/cell/range/index.ts @@ -0,0 +1,2 @@ +export { ICellRange, CellRange } from './cell-range'; +export { CellRangeUtil } from './cell-range-util'; diff --git a/src/event/event-type.ts b/src/event/event-type.ts index 326b0e7..89429e5 100644 --- a/src/event/event-type.ts +++ b/src/event/event-type.ts @@ -2,11 +2,9 @@ * Types of events that may occur in the table-engine. */ export enum TableEngineEventType { - /** * Emitted, when the table renderer is ready for rendering * after initializing the table engine. */ - RENDERER_READY - + RENDERER_READY, } diff --git a/src/event/event.ts b/src/event/event.ts index 88fa3f1..1830ec4 100644 --- a/src/event/event.ts +++ b/src/event/event.ts @@ -1,13 +1,11 @@ -import {TableEngineEventType} from "./event-type"; +import { TableEngineEventType } from './event-type'; /** * Event that may occur in the table engine. */ export interface ITableEngineEvent { - /** * Type of the event. */ type: TableEngineEventType; - } diff --git a/src/event/index.ts b/src/event/index.ts new file mode 100644 index 0000000..228863e --- /dev/null +++ b/src/event/index.ts @@ -0,0 +1,2 @@ +export { ITableEngineEvent } from './event'; +export { TableEngineEventType } from './event-type'; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2c3731c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,8 @@ +export { TableEngine } from './table-engine'; +export * from './border'; +export * from './cell'; +export * from './event'; +export * from './overlay'; +export * from './renderer'; +export * from './selection'; +export * from './util'; diff --git a/src/options.ts b/src/options.ts index c74ea6e..2c56a6b 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,12 +1,11 @@ -import {fillOptions as fillRendererOptions, IRendererOptions} from "./renderer/options"; -import {fillOptions as fillSelectionOptions, ISelectionOptions} from "./selection/options"; -import {fillOptions as fillBorderOptions, IBorderOptions} from "./border/options"; +import { fillRendererOptions, IRendererOptions } from './renderer'; +import { fillSelectionOptions, ISelectionOptions } from './selection'; +import { fillBorderOptions, IBorderOptions } from './border'; /** * Options used to modify the table engine behavior. */ export interface ITableEngineOptions { - /** * Options regarding the table-engine renderer. */ @@ -26,20 +25,17 @@ export interface ITableEngineOptions { * Other options. */ misc?: IMiscOptions; - } /** * Other options regarding the table-engine. */ export interface IMiscOptions { - /** * Whether debugging mode is enabled. * This will enable for example debugging logs. */ debug?: boolean; - } /** diff --git a/src/overlay/index.ts b/src/overlay/index.ts new file mode 100644 index 0000000..ee1e88f --- /dev/null +++ b/src/overlay/index.ts @@ -0,0 +1,2 @@ +export { IOverlay } from './overlay'; +export { IOverlayManager } from './overlay-manager'; diff --git a/src/overlay/overlay-manager.ts b/src/overlay/overlay-manager.ts index 1deeb99..1be1408 100644 --- a/src/overlay/overlay-manager.ts +++ b/src/overlay/overlay-manager.ts @@ -1,10 +1,9 @@ -import {IOverlay} from "./overlay"; +import { IOverlay } from './overlay'; /** * Manager for overlays. */ export interface IOverlayManager { - /** * Add an overlay. * @param overlay to add @@ -27,5 +26,4 @@ export interface IOverlayManager { * @param overlay to update */ updateOverlay(overlay: IOverlay): void; - } diff --git a/src/overlay/overlay.ts b/src/overlay/overlay.ts index 569ed89..6a1b7e7 100644 --- a/src/overlay/overlay.ts +++ b/src/overlay/overlay.ts @@ -1,4 +1,4 @@ -import {IRectangle} from "../util/rect"; +import { IRectangle } from '../util'; /** * Representation of something that is displayed over the table @@ -7,7 +7,6 @@ import {IRectangle} from "../util/rect"; * displayed over them. */ export interface IOverlay { - /** * HTML element of the overlay. */ @@ -17,5 +16,4 @@ export interface IOverlay { * Bounds of the overlay. */ bounds: IRectangle; - } diff --git a/src/renderer/canvas/canvas-renderer.ts b/src/renderer/canvas/canvas-renderer.ts index 2349bf7..c53ce93 100644 --- a/src/renderer/canvas/canvas-renderer.ts +++ b/src/renderer/canvas/canvas-renderer.ts @@ -1,52 +1,54 @@ -import {ITableEngineRenderer} from "../renderer"; -import {ICellModel} from "../../cell/model/cell-model.interface"; -import {asyncScheduler, Subject} from "rxjs"; -import {takeUntil, throttleTime} from "rxjs/operators"; -import {ScrollUtil} from "../util/scroll"; -import {CanvasUtil} from "../util/canvas"; -import {IRectangle} from "../../util/rect"; -import {IScrollBarOptions} from "../options/scrollbar"; -import {ICell} from "../../cell/cell"; -import {ICellRenderer} from "../cell/cell-renderer"; -import {ICanvasCellRenderer} from "./cell/canvas-cell-renderer"; -import {CellRange, ICellRange} from "../../cell/range/cell-range"; -import {IColor} from "../../util/color"; -import {ISelectionModel} from "../../selection/model/selection-model.interface"; -import {IInitialPosition, ISelection} from "../../selection/selection"; -import {ISelectionRenderingOptions} from "../options/selection"; -import {CellRangeUtil} from "../../cell/range/cell-range-util"; -import {TableEngine} from "../../table-engine"; -import {IBorderModel} from "../../border/model/border-model.interface"; -import {IBorder} from "../../border/border"; -import {IBorderSide} from "../../border/border-side"; -import {BorderStyle} from "../../border/border-style"; -import {ICellRendererEventListener} from "../cell/event/cell-renderer-event-listener"; -import {ICellRendererEvent} from "../cell/event/cell-renderer-event"; -import {IOverlay} from "../../overlay/overlay"; -import {ClipboardUtil} from "../../util/clipboard/clipboard-util"; -import {ISize} from "../../util/size"; -import {CopyPerformanceWarningNotification} from "../../util/notification/impl/copy-performance-warning"; -import {CopyNotification} from "../../util/notification/impl/copy"; -import {ITableEngineOptions} from "../../options"; -import {IRendererOptions} from "../options"; -import {Colors} from "../../util/colors"; -import {ICellRendererMouseEvent} from "../cell/event/cell-renderer-mouse-event"; -import {ICellRendererFocusEvent} from "../cell/event/cell-renderer-focus-event"; -import {ICellRendererKeyboardEvent} from "../cell/event/cell-renderer-keyboard-event"; -import {IPoint} from "../../util/point"; -import {ICellModelEvent} from "../../cell/model/event/cell-model-change"; -import {CellModelEventType} from "../../cell/model/event/cell-model-event-type"; -import {BeforeDeleteEvent} from "../../cell/model/event/before-delete-event"; -import {HiddenEvent} from "../../cell/model/event/hidden-event"; +import { ITableEngineRenderer } from '../renderer'; +import { + BeforeDeleteEvent, + CellModelEventType, + CellRange, + CellRangeUtil, + HiddenEvent, + ICell, + ICellModel, + ICellModelEvent, + ICellRange, +} from '../../cell'; +import { asyncScheduler, Subject } from 'rxjs'; +import { takeUntil, throttleTime } from 'rxjs/operators'; +import { CanvasUtil, ScrollUtil } from '../util'; +import { + ClipboardUtil, + Colors, + CopyNotification, + CopyPerformanceWarningNotification, + IColor, + IPoint, + IRectangle, + ISize, +} from '../../util'; +import { IScrollBarOptions, ISelectionRenderingOptions } from '../options'; +import { + ICellRenderer, + ICellRendererEvent, + ICellRendererEventListener, + ICellRendererFocusEvent, + ICellRendererKeyboardEvent, + ICellRendererMouseEvent, +} from '../cell'; +import { ICanvasCellRenderer } from './cell'; +import { IInitialPosition, ISelection, ISelectionModel } from '../../selection'; +import { TableEngine } from '../../table-engine'; +import { BorderStyle, IBorder, IBorderModel, IBorderSide } from '../../border'; +import { IOverlay } from '../../overlay'; +import { ITableEngineOptions } from '../../options'; +import { IRendererOptions } from '../renderer-options'; type CellRendererEventListenerFunction = (event: ICellRendererEvent) => void; -type CellRendererEventListenerFunctionSupplier = (listener: ICellRendererEventListener) => CellRendererEventListenerFunction | null | undefined; +type CellRendererEventListenerFunctionSupplier = ( + listener: ICellRendererEventListener +) => CellRendererEventListenerFunction | null | undefined; /** * Table-engine renderer using the HTML5 canvas. */ export class CanvasRenderer implements ITableEngineRenderer { - /** * Maximum zoom level. */ @@ -55,7 +57,8 @@ export class CanvasRenderer implements ITableEngineRenderer { /** * Lookup for cell renderers by their name. */ - private readonly _cellRendererLookup: Map = new Map(); + private readonly _cellRendererLookup: Map = + new Map(); /** * Container the renderer should operate on. @@ -178,7 +181,7 @@ export class CanvasRenderer implements ITableEngineRenderer { */ private _scrollOffset: IScrollOffset = { x: 0, - y: 0 + y: 0, }; /** @@ -336,7 +339,11 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param engine reference to the table-engine * @param options of the renderer */ - public async initialize(container: HTMLElement, engine: TableEngine, options: ITableEngineOptions): Promise { + public async initialize( + container: HTMLElement, + engine: TableEngine, + options: ITableEngineOptions + ): Promise { this._container = container; this._engine = engine; this._cellModel = engine.getCellModel(); @@ -344,7 +351,8 @@ export class CanvasRenderer implements ITableEngineRenderer { this._borderModel = engine.getBorderModel(); this._options = options; - this._cellModel.events() + this._cellModel + .events() .pipe(takeUntil(this._onCleanup)) .subscribe((event) => this._onCellModelEvent(event)); @@ -360,7 +368,11 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param count of rows/columns to be deleted * @param isRow whether rows or columns are to be deleted */ - private _onBeforeDeletingCells(fromIndex: number, count: number, isRow: boolean): void { + private _onBeforeDeletingCells( + fromIndex: number, + count: number, + isRow: boolean + ): void { this._cleanupViewportCacheBeforeDeletingCells(fromIndex, count, isRow); } @@ -371,7 +383,11 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param count of rows/columns to deleted * @param isRow whether to delete rows or columns */ - private _cleanupViewportCacheBeforeDeletingCells(fromIndex: number, count: number, isRow: boolean): void { + private _cleanupViewportCacheBeforeDeletingCells( + fromIndex: number, + count: number, + isRow: boolean + ): void { this._doForCellAreaRange((range) => { // Collect all cells to be deleted and clear the viewport cache let rangeToBeDeleted: ICellRange; @@ -380,26 +396,29 @@ export class CanvasRenderer implements ITableEngineRenderer { startRow: Math.max(fromIndex, range.startRow), endRow: Math.min(fromIndex + count - 1, range.endRow), startColumn: range.startColumn, - endColumn: range.endColumn + endColumn: range.endColumn, }; } else { rangeToBeDeleted = { startRow: range.startRow, endRow: range.endRow, startColumn: Math.max(fromIndex, range.startColumn), - endColumn: Math.min(fromIndex + count - 1, range.endColumn) + endColumn: Math.min(fromIndex + count - 1, range.endColumn), }; } - if (rangeToBeDeleted.startRow > rangeToBeDeleted.endRow - || rangeToBeDeleted.startColumn > rangeToBeDeleted.endColumn) { + if ( + rangeToBeDeleted.startRow > rangeToBeDeleted.endRow || + rangeToBeDeleted.startColumn > rangeToBeDeleted.endColumn + ) { return; } // Clear viewport cache this._cleanupCellViewportCachesForCellRange( rangeToBeDeleted, - (cellRange) => CellRangeUtil.contains(cellRange, rangeToBeDeleted) + (cellRange) => + CellRangeUtil.contains(cellRange, rangeToBeDeleted) ); }); } @@ -418,7 +437,10 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param indices of rows or columns to be hidden * @param isRow whether rows or columns are hidden */ - private _cleanupViewportCacheDueToHidingCells(indices: number[], isRow: boolean): void { + private _cleanupViewportCacheDueToHidingCells( + indices: number[], + isRow: boolean + ): void { // Collect all consecutive index ranges that have been hidden let firstIndex: number = -1; let lastIndex: number = -1; @@ -430,31 +452,35 @@ export class CanvasRenderer implements ITableEngineRenderer { } else if (index === lastIndex + 1) { lastIndex = index; } else { - indexRanges.push({from: firstIndex, to: lastIndex}); + indexRanges.push({ from: firstIndex, to: lastIndex }); firstIndex = index; lastIndex = index; } } - indexRanges.push({from: firstIndex, to: lastIndex}); + indexRanges.push({ from: firstIndex, to: lastIndex }); // Transform index ranges to cell ranges const maxRowIndex: number = this._cellModel.getRowCount() - 1; const maxColumnIndex: number = this._cellModel.getColumnCount() - 1; - const hiddenRanges: ICellRange[] = indexRanges.map(indexRange => ({ + const hiddenRanges: ICellRange[] = indexRanges.map((indexRange) => ({ startRow: isRow ? indexRange.from : 0, endRow: isRow ? indexRange.to : maxRowIndex, startColumn: isRow ? 0 : indexRange.from, - endColumn: isRow ? maxColumnIndex : indexRange.to + endColumn: isRow ? maxColumnIndex : indexRange.to, })); this._doForCellAreaRange((range) => { for (const hiddenRange of hiddenRanges) { - const rangeToClean: ICellRange | null = CellRangeUtil.and(hiddenRange, range); + const rangeToClean: ICellRange | null = CellRangeUtil.and( + hiddenRange, + range + ); if (!!rangeToClean) { this._cleanupCellViewportCachesForCellRange( rangeToClean, - (cellRange) => !this._cellModel.isRangeVisible(cellRange), + (cellRange) => + !this._cellModel.isRangeVisible(cellRange), true ); } @@ -505,64 +531,89 @@ export class CanvasRenderer implements ITableEngineRenderer { private _bindListeners(): void { // Listen for keyboard events this._keyDownListener = (event) => this._onKeyDown(event); - this._overlayContainer.addEventListener("keydown", this._keyDownListener); + this._overlayContainer.addEventListener( + 'keydown', + this._keyDownListener + ); this._keyUpListener = (event) => this._onKeyUp(event); - this._overlayContainer.addEventListener("keyup", this._keyUpListener); + this._overlayContainer.addEventListener('keyup', this._keyUpListener); // Listen for mouse events this._wheelListener = (event) => this._onWheel(event); - this._overlayContainer.addEventListener("wheel", this._wheelListener, { - passive: false + this._overlayContainer.addEventListener('wheel', this._wheelListener, { + passive: false, }); this._mouseDownListener = (event) => this._onMouseDown(event); - this._overlayContainer.addEventListener("mousedown", this._mouseDownListener); + this._overlayContainer.addEventListener( + 'mousedown', + this._mouseDownListener + ); this._mouseMoveListener = (event) => this._onMouseMove(event); - window.addEventListener("mousemove", this._mouseMoveListener); + window.addEventListener('mousemove', this._mouseMoveListener); this._mouseUpListener = (event) => this._onMouseUp(event); - window.addEventListener("mouseup", this._mouseUpListener); + window.addEventListener('mouseup', this._mouseUpListener); // Listen for touch events this._touchStartListener = (event) => this._onTouchStart(event); - this._overlayContainer.addEventListener("touchstart", this._touchStartListener); + this._overlayContainer.addEventListener( + 'touchstart', + this._touchStartListener + ); this._touchMoveListener = (event) => this._onTouchMove(event); - window.addEventListener("touchmove", this._touchMoveListener); + window.addEventListener('touchmove', this._touchMoveListener); this._touchEndListener = (event) => this._onTouchEnd(event); - window.addEventListener("touchend", this._touchEndListener); + window.addEventListener('touchend', this._touchEndListener); // Listen for size changes on the container HTML element - this._resizeObserver = new ResizeObserver((resizeEntries) => this._onResize(resizeEntries)); + this._resizeObserver = new ResizeObserver((resizeEntries) => + this._onResize(resizeEntries) + ); this._resizeObserver.observe(this._container); // Listen for focus and blur events this._focusListener = (event) => this._onFocus(event); - this._overlayContainer.addEventListener("focus", this._focusListener); + this._overlayContainer.addEventListener('focus', this._focusListener); this._blurListener = (event) => this._onBlur(event); - this._overlayContainer.addEventListener("blur", this._blurListener); + this._overlayContainer.addEventListener('blur', this._blurListener); // Throttle resize events - this._resizeThrottleSubject.asObservable().pipe( - takeUntil(this._onCleanup), - throttleTime(this.rendererOptions.canvas.lazyRenderingThrottleDuration, asyncScheduler, { - leading: false, - trailing: true - }) - ).subscribe(() => this._onContainerResized()); + this._resizeThrottleSubject + .asObservable() + .pipe( + takeUntil(this._onCleanup), + throttleTime( + this.rendererOptions.canvas.lazyRenderingThrottleDuration, + asyncScheduler, + { + leading: false, + trailing: true, + } + ) + ) + .subscribe(() => this._onContainerResized()); // Repaint when necessary (for example on scrolling) - this._repaintScheduler.asObservable().pipe( - takeUntil(this._onCleanup), - throttleTime(this.rendererOptions.canvas.lazyRenderingThrottleDuration, asyncScheduler, { - leading: false, - trailing: true - }) - ).subscribe(() => this._render()); + this._repaintScheduler + .asObservable() + .pipe( + takeUntil(this._onCleanup), + throttleTime( + this.rendererOptions.canvas.lazyRenderingThrottleDuration, + asyncScheduler, + { + leading: false, + trailing: true, + } + ) + ) + .subscribe(() => this._render()); } /** @@ -570,33 +621,48 @@ export class CanvasRenderer implements ITableEngineRenderer { */ private _unbindListeners(): void { if (!!this._keyDownListener) { - this._overlayContainer.removeEventListener("keydown", this._keyDownListener); + this._overlayContainer.removeEventListener( + 'keydown', + this._keyDownListener + ); } if (!!this._keyUpListener) { - this._overlayContainer.removeEventListener("keyup", this._keyUpListener); + this._overlayContainer.removeEventListener( + 'keyup', + this._keyUpListener + ); } if (!!this._wheelListener) { - this._overlayContainer.removeEventListener("wheel", this._wheelListener); + this._overlayContainer.removeEventListener( + 'wheel', + this._wheelListener + ); } if (!!this._mouseDownListener) { - this._overlayContainer.removeEventListener("mousedown", this._mouseDownListener); + this._overlayContainer.removeEventListener( + 'mousedown', + this._mouseDownListener + ); } if (!!this._mouseMoveListener) { - window.removeEventListener("mousemove", this._mouseMoveListener); + window.removeEventListener('mousemove', this._mouseMoveListener); } if (!!this._mouseUpListener) { - window.removeEventListener("mouseup", this._mouseUpListener); + window.removeEventListener('mouseup', this._mouseUpListener); } if (!!this._touchStartListener) { - this._overlayContainer.removeEventListener("touchstart", this._touchStartListener); + this._overlayContainer.removeEventListener( + 'touchstart', + this._touchStartListener + ); } if (!!this._touchMoveListener) { - window.removeEventListener("touchmove", this._touchMoveListener); + window.removeEventListener('touchmove', this._touchMoveListener); } if (!!this._touchEndListener) { - window.removeEventListener("touchend", this._touchEndListener); + window.removeEventListener('touchend', this._touchEndListener); } if (!!this._resizeObserver) { @@ -604,10 +670,16 @@ export class CanvasRenderer implements ITableEngineRenderer { } if (!!this._focusListener) { - this._overlayContainer.removeEventListener("focus", this._focusListener); + this._overlayContainer.removeEventListener( + 'focus', + this._focusListener + ); } if (!!this._blurListener) { - this._overlayContainer.removeEventListener("blur", this._blurListener); + this._overlayContainer.removeEventListener( + 'blur', + this._blurListener + ); } } @@ -649,9 +721,24 @@ export class CanvasRenderer implements ITableEngineRenderer { const [x, y] = this._getMouseOffset(event); // Check if mouse if over a scroll bar - const isOverVerticalScrollBar: boolean = CanvasRenderer._isMouseOverScrollBar(x, y, true, this._lastRenderingContext.scrollBar.vertical); - const isOverHorizontalScrollBar: boolean = CanvasRenderer._isMouseOverScrollBar(x, y, false, this._lastRenderingContext.scrollBar.horizontal); - const resizingInfo: IResizerInfo = this._isMouseOverResizingSpace(x, y); + const isOverVerticalScrollBar: boolean = + CanvasRenderer._isMouseOverScrollBar( + x, + y, + true, + this._lastRenderingContext.scrollBar.vertical + ); + const isOverHorizontalScrollBar: boolean = + CanvasRenderer._isMouseOverScrollBar( + x, + y, + false, + this._lastRenderingContext.scrollBar.horizontal + ); + const resizingInfo: IResizerInfo = this._isMouseOverResizingSpace( + x, + y + ); if (isOverVerticalScrollBar || isOverHorizontalScrollBar) { const scrollVertically: boolean = isOverVerticalScrollBar; @@ -660,34 +747,46 @@ export class CanvasRenderer implements ITableEngineRenderer { scrollVertically, startX: x, startY: y, - offsetFromScrollBarStart: scrollVertically ? this._lastRenderingContext.scrollBar.vertical.y - y : this._lastRenderingContext.scrollBar.horizontal.x - x, + offsetFromScrollBarStart: scrollVertically + ? this._lastRenderingContext.scrollBar.vertical.y - y + : this._lastRenderingContext.scrollBar.horizontal.x - x, startScrollOffset: { x: this._scrollOffset.x, - y: this._scrollOffset.y - } + y: this._scrollOffset.y, + }, }; - } else if (CanvasRenderer._isMouseOverCopyHandle(x, y, this._lastRenderingContext)) { + } else if ( + CanvasRenderer._isMouseOverCopyHandle( + x, + y, + this._lastRenderingContext + ) + ) { this._copyHandleDragStart = { startX: x, startY: y, startScrollOffset: { x: this._scrollOffset.x, - y: this._scrollOffset.y - } + y: this._scrollOffset.y, + }, }; - this._initialSelectionRange = this._selectionModel.getPrimary().range; + this._initialSelectionRange = + this._selectionModel.getPrimary().range; this._copyHandleInitial = { row: this._selectionModel.getPrimary().initial.row, column: this._selectionModel.getPrimary().initial.column, }; - } else if (this._options.renderer.canvas.rowColumnResizing.allowResizing && resizingInfo.isMouseOver) { + } else if ( + this._options.renderer.canvas.rowColumnResizing.allowResizing && + resizingInfo.isMouseOver + ) { this._resizingDragStart = { startX: x, startY: y, currentX: x, currentY: y, - info: resizingInfo + info: resizingInfo, }; } else if (this._isInMouseDragMode) { this._mouseDragStart = { @@ -695,8 +794,8 @@ export class CanvasRenderer implements ITableEngineRenderer { startY: y, startScrollOffset: { x: this._scrollOffset.x, - y: this._scrollOffset.y - } + y: this._scrollOffset.y, + }, }; } else { const range: ICellRange = this._getCellRangeAtPoint(x, y); @@ -707,8 +806,9 @@ export class CanvasRenderer implements ITableEngineRenderer { range.startColumn, (listener) => listener.onMouseDown, (e) => { - const mouseEvent: ICellRendererMouseEvent = e as ICellRendererMouseEvent; - mouseEvent.offset = {x, y}; + const mouseEvent: ICellRendererMouseEvent = + e as ICellRendererMouseEvent; + mouseEvent.offset = { x, y }; mouseEvent.originalEvent = event; return mouseEvent; } @@ -717,12 +817,23 @@ export class CanvasRenderer implements ITableEngineRenderer { this._initialSelectionRange = range; // Update selection (if changed) - const updateNotNecessary: boolean = this._selectionModel.getSelections().length === 1 && CellRangeUtil.equals(this._selectionModel.getPrimary().range, range); + const updateNotNecessary: boolean = + this._selectionModel.getSelections().length === 1 && + CellRangeUtil.equals( + this._selectionModel.getPrimary().range, + range + ); if (!updateNotNecessary) { - this._updateCurrentSelection(this._initialSelectionRange, { - row: this._initialSelectionRange.startRow, - column: this._initialSelectionRange.startColumn, - }, !event.ctrlKey, true, false); + this._updateCurrentSelection( + this._initialSelectionRange, + { + row: this._initialSelectionRange.startRow, + column: this._initialSelectionRange.startColumn, + }, + !event.ctrlKey, + true, + false + ); } } } @@ -744,19 +855,27 @@ export class CanvasRenderer implements ITableEngineRenderer { eventListenerSupplier: CellRendererEventListenerFunctionSupplier, eventPopulator: (event: ICellRendererEvent) => ICellRendererEvent ): boolean { - if (row >= this._cellModel.getRowCount() || column >= this._cellModel.getColumnCount()) { + if ( + row >= this._cellModel.getRowCount() || + column >= this._cellModel.getColumnCount() + ) { return false; } const cell: ICell = this._cellModel.getCell(row, column, true); - const listener: ICellRendererEventListener = this._cellRendererLookup.get(cell.rendererName).getEventListener(); + const listener: ICellRendererEventListener = this._cellRendererLookup + .get(cell.rendererName) + .getEventListener(); if (!!listener) { - const listenerFunction: CellRendererEventListenerFunction | null | undefined = eventListenerSupplier(listener); + const listenerFunction: + | CellRendererEventListenerFunction + | null + | undefined = eventListenerSupplier(listener); if (!!listenerFunction) { // Create the event const event: ICellRendererEvent = eventPopulator({ cell, - preventDefault: false + preventDefault: false, }); listenerFunction(event); @@ -774,17 +893,31 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param y offset * @param allowOverflow whether to allow the given x any y coordinate to overflow the current viewport (method will never return null) */ - private _getCellRangeAtPoint(x: number, y: number, allowOverflow: boolean = true): ICellRange | null { - const fixedRowsHeight: number = !!this._lastRenderingContext && !!this._lastRenderingContext.cells.fixedRowCells - ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds.height - : 0; - const fixedColumnWidth: number = !!this._lastRenderingContext && !!this._lastRenderingContext.cells.fixedColumnCells - ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds.width - : 0; + private _getCellRangeAtPoint( + x: number, + y: number, + allowOverflow: boolean = true + ): ICellRange | null { + const fixedRowsHeight: number = + !!this._lastRenderingContext && + !!this._lastRenderingContext.cells.fixedRowCells + ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds + .height + : 0; + const fixedColumnWidth: number = + !!this._lastRenderingContext && + !!this._lastRenderingContext.cells.fixedColumnCells + ? this._lastRenderingContext.cells.fixedColumnCells + .viewPortBounds.width + : 0; if (!allowOverflow) { - const viewPortWidth: number = !!this._lastRenderingContext ? this._lastRenderingContext.viewPort.width : 0; - const viewPortHeight: number = !!this._lastRenderingContext ? this._lastRenderingContext.viewPort.height : 0; + const viewPortWidth: number = !!this._lastRenderingContext + ? this._lastRenderingContext.viewPort.width + : 0; + const viewPortHeight: number = !!this._lastRenderingContext + ? this._lastRenderingContext.viewPort.height + : 0; // Check if we are in viewport bounds -> if not return null if (x < 0 || y < 0 || x > viewPortWidth || y > viewPortHeight) { @@ -805,10 +938,13 @@ export class CanvasRenderer implements ITableEngineRenderer { startRow: cell.range.startRow, endRow: cell.range.endRow, startColumn: cell.range.startColumn, - endColumn: cell.range.endColumn + endColumn: cell.range.endColumn, }; } else { - return CellRange.fromSingleRowColumn(this._cellModel.getRowAtOffset(y), this._cellModel.getColumnAtOffset(x)); + return CellRange.fromSingleRowColumn( + this._cellModel.getRowAtOffset(y), + this._cellModel.getColumnAtOffset(x) + ); } } @@ -820,7 +956,13 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param push whether to add or update the current primary selection * @param end whether the selection will no more change (for example on mouse move end) */ - private _updateCurrentSelection(range: ICellRange, initial: IInitialPosition, clear: boolean, push: boolean, end: boolean): void { + private _updateCurrentSelection( + range: ICellRange, + initial: IInitialPosition, + clear: boolean, + push: boolean, + end: boolean + ): void { let repaint: boolean = false; if (clear) { @@ -829,10 +971,14 @@ export class CanvasRenderer implements ITableEngineRenderer { } if (push) { - this._selectionModel.addSelection({ - range, - initial - }, true, end); + this._selectionModel.addSelection( + { + range, + initial, + }, + true, + end + ); repaint = true; } else { // Only change and validate range of current primary selection @@ -841,7 +987,15 @@ export class CanvasRenderer implements ITableEngineRenderer { return; } - if (this._selectionModel.modifySelection(primary, range, initial, true, end)) { + if ( + this._selectionModel.modifySelection( + primary, + range, + initial, + true, + end + ) + ) { repaint = true; } } @@ -865,7 +1019,7 @@ export class CanvasRenderer implements ITableEngineRenderer { return [ (event.clientX - rect.left) / this._zoom, - (event.clientY - rect.top) / this._zoom + (event.clientY - rect.top) / this._zoom, ]; } @@ -876,7 +1030,12 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param vertical whether to check for vertical or horizontal scroll bar * @param ctx of the scroll bar on a axis (horizontal, vertical) */ - private static _isMouseOverScrollBar(x: number, y: number, vertical: boolean, ctx: IScrollBarAxisRenderContext): boolean { + private static _isMouseOverScrollBar( + x: number, + y: number, + vertical: boolean, + ctx: IScrollBarAxisRenderContext + ): boolean { if (!ctx) { return false; } @@ -884,8 +1043,12 @@ export class CanvasRenderer implements ITableEngineRenderer { const width: number = vertical ? ctx.size : ctx.length; const height: number = vertical ? ctx.length : ctx.size; - return x >= ctx.x && x <= ctx.x + width - && y >= ctx.y && y <= ctx.y + height; + return ( + x >= ctx.x && + x <= ctx.x + width && + y >= ctx.y && + y <= ctx.y + height + ); } /** @@ -894,7 +1057,11 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param y position of the mouse * @param ctx the current rendering context */ - private static _isMouseOverCopyHandle(x: number, y: number, ctx: IRenderContext): boolean { + private static _isMouseOverCopyHandle( + x: number, + y: number, + ctx: IRenderContext + ): boolean { if (!ctx || !ctx.selection) { return false; } @@ -905,8 +1072,16 @@ export class CanvasRenderer implements ITableEngineRenderer { } // Check if mouse is over copy handle - return x >= ctx.selection.copyHandle.bounds.left && x <= ctx.selection.copyHandle.bounds.left + ctx.selection.copyHandle.bounds.width - && y >= ctx.selection.copyHandle.bounds.top && y <= ctx.selection.copyHandle.bounds.top + ctx.selection.copyHandle.bounds.height; + return ( + x >= ctx.selection.copyHandle.bounds.left && + x <= + ctx.selection.copyHandle.bounds.left + + ctx.selection.copyHandle.bounds.width && + y >= ctx.selection.copyHandle.bounds.top && + y <= + ctx.selection.copyHandle.bounds.top + + ctx.selection.copyHandle.bounds.height + ); } /** @@ -918,18 +1093,28 @@ export class CanvasRenderer implements ITableEngineRenderer { const targetRange: ICellRange = this._getCellRangeAtPoint(x, y); // Check whether we are allowed to row/column resize at this point - const isInRowCount: boolean = targetRange.startRow < this._options.renderer.canvas.rowColumnResizing.rowCount; - const isInColumnCount: boolean = targetRange.startColumn < this._options.renderer.canvas.rowColumnResizing.columnCount; + const isInRowCount: boolean = + targetRange.startRow < + this._options.renderer.canvas.rowColumnResizing.rowCount; + const isInColumnCount: boolean = + targetRange.startColumn < + this._options.renderer.canvas.rowColumnResizing.columnCount; if (!isInRowCount && !isInColumnCount) { - return {isMouseOver: false}; + return { isMouseOver: false }; } // Check bounds of the targetRange const bounds: IRectangle = this._cellModel.getBounds(targetRange); - const fixedRows: number = Math.min(this.rendererOptions.view.fixedRows, this._cellModel.getRowCount()); - const fixedColumns: number = Math.min(this.rendererOptions.view.fixedColumns, this._cellModel.getColumnCount()); + const fixedRows: number = Math.min( + this.rendererOptions.view.fixedRows, + this._cellModel.getRowCount() + ); + const fixedColumns: number = Math.min( + this.rendererOptions.view.fixedColumns, + this._cellModel.getColumnCount() + ); if (targetRange.startRow > fixedRows) { bounds.top -= this._scrollOffset.y; @@ -952,7 +1137,8 @@ export class CanvasRenderer implements ITableEngineRenderer { const bottomYDiff: number = bottomYOffset - y; // Determine whether offsets satisfy a certain threshold to count as space between rows/columns - const resizerSize: number = this._options.renderer.canvas.rowColumnResizing.resizerSize; + const resizerSize: number = + this._options.renderer.canvas.rowColumnResizing.resizerSize; let column: number = -1; if (isInRowCount) { @@ -975,7 +1161,7 @@ export class CanvasRenderer implements ITableEngineRenderer { return { isMouseOver: row !== -1 || column != -1, overRow: row !== -1, - index: row !== -1 ? row : column + index: row !== -1 ? row : column, }; } @@ -991,8 +1177,8 @@ export class CanvasRenderer implements ITableEngineRenderer { * Reset the cursor to show. */ private _resetCursor(): void { - if (this._container.style.cursor !== "auto") { - this._container.style.cursor = "auto"; + if (this._container.style.cursor !== 'auto') { + this._container.style.cursor = 'auto'; } } @@ -1022,10 +1208,17 @@ export class CanvasRenderer implements ITableEngineRenderer { isHandled = true; } else if (isCopyHandleDragging) { // Determine direction to extend in (based on the current x and y coordinates and their difference to the initial selection bounds) - const initialSelectionBounds: IRectangle = this._cellModel.getBounds(this._initialSelectionRange); + const initialSelectionBounds: IRectangle = + this._cellModel.getBounds(this._initialSelectionRange); - const fixedRows: number = Math.min(this.rendererOptions.view.fixedRows, this._cellModel.getRowCount()); - const fixedColumns: number = Math.min(this.rendererOptions.view.fixedColumns, this._cellModel.getColumnCount()); + const fixedRows: number = Math.min( + this.rendererOptions.view.fixedRows, + this._cellModel.getRowCount() + ); + const fixedColumns: number = Math.min( + this.rendererOptions.view.fixedColumns, + this._cellModel.getColumnCount() + ); if (this._initialSelectionRange.startRow > fixedRows) { initialSelectionBounds.top -= this._scrollOffset.y; @@ -1037,19 +1230,32 @@ export class CanvasRenderer implements ITableEngineRenderer { let xDiff: number = 0; if (x < initialSelectionBounds.left) { xDiff = x - initialSelectionBounds.left; - } else if (x > initialSelectionBounds.left + initialSelectionBounds.width) { - xDiff = x - (initialSelectionBounds.left + initialSelectionBounds.width); + } else if ( + x > + initialSelectionBounds.left + initialSelectionBounds.width + ) { + xDiff = + x - + (initialSelectionBounds.left + + initialSelectionBounds.width); } let yDiff: number = 0; if (y < initialSelectionBounds.top) { yDiff = y - initialSelectionBounds.top; - } else if (y > initialSelectionBounds.top + initialSelectionBounds.height) { - yDiff = y - (initialSelectionBounds.top + initialSelectionBounds.height); + } else if ( + y > + initialSelectionBounds.top + initialSelectionBounds.height + ) { + yDiff = + y - + (initialSelectionBounds.top + + initialSelectionBounds.height); } // Check whether to extend horizontally or vertically - const extendHorizontally: boolean = Math.abs(xDiff) > Math.abs(yDiff) + const extendHorizontally: boolean = + Math.abs(xDiff) > Math.abs(yDiff); // Extend selection, but only to the left, right, top and bottom and not diagonally! const targetRange: ICellRange = this._getCellRangeAtPoint(x, y); @@ -1061,42 +1267,76 @@ export class CanvasRenderer implements ITableEngineRenderer { if (xDiff > 0) { // Extend to right - targetRange.startColumn = this._initialSelectionRange.startColumn; + targetRange.startColumn = + this._initialSelectionRange.startColumn; } else { // Extend to left - targetRange.endColumn = this._initialSelectionRange.endColumn; + targetRange.endColumn = + this._initialSelectionRange.endColumn; } } else { - targetRange.startColumn = this._initialSelectionRange.startColumn; - targetRange.endColumn = this._initialSelectionRange.endColumn; + targetRange.startColumn = + this._initialSelectionRange.startColumn; + targetRange.endColumn = + this._initialSelectionRange.endColumn; if (yDiff > 0) { // Extend to bottom - targetRange.startRow = this._initialSelectionRange.startRow; + targetRange.startRow = + this._initialSelectionRange.startRow; } else { // Extend to top targetRange.endRow = this._initialSelectionRange.endRow; } } - this._updateCurrentSelection({ - startRow: Math.min(this._initialSelectionRange.startRow, targetRange.startRow), - endRow: Math.max(this._initialSelectionRange.endRow, targetRange.endRow), - startColumn: Math.min(this._initialSelectionRange.startColumn, targetRange.startColumn), - endColumn: Math.max(this._initialSelectionRange.endColumn, targetRange.endColumn), - }, { - row: this._copyHandleInitial.row, - column: this._copyHandleInitial.column - }, false, false, false); + this._updateCurrentSelection( + { + startRow: Math.min( + this._initialSelectionRange.startRow, + targetRange.startRow + ), + endRow: Math.max( + this._initialSelectionRange.endRow, + targetRange.endRow + ), + startColumn: Math.min( + this._initialSelectionRange.startColumn, + targetRange.startColumn + ), + endColumn: Math.max( + this._initialSelectionRange.endColumn, + targetRange.endColumn + ), + }, + { + row: this._copyHandleInitial.row, + column: this._copyHandleInitial.column, + }, + false, + false, + false + ); // Initialize automatic scrolling when out of viewport bounds with the mouse - const viewPortBounds: IRectangle = this._lastRenderingContext.viewPort; - const outOfViewPortBoundsX: boolean = x < 0 || x > viewPortBounds.width; - const outOfViewPortBoundsY: boolean = y < 0 || y > viewPortBounds.height; + const viewPortBounds: IRectangle = + this._lastRenderingContext.viewPort; + const outOfViewPortBoundsX: boolean = + x < 0 || x > viewPortBounds.width; + const outOfViewPortBoundsY: boolean = + y < 0 || y > viewPortBounds.height; if (outOfViewPortBoundsX || outOfViewPortBoundsY) { this._updateAutoScrolling( - outOfViewPortBoundsX ? (x < 0 ? x : x - viewPortBounds.width) : 0, - outOfViewPortBoundsY ? (y < 0 ? y : y - viewPortBounds.height) : 0, + outOfViewPortBoundsX + ? x < 0 + ? x + : x - viewPortBounds.width + : 0, + outOfViewPortBoundsY + ? y < 0 + ? y + : y - viewPortBounds.height + : 0, 0 ); } else { @@ -1119,24 +1359,53 @@ export class CanvasRenderer implements ITableEngineRenderer { // Extend selection const targetRange: ICellRange = this._getCellRangeAtPoint(x, y); - this._updateCurrentSelection({ - startRow: Math.min(this._initialSelectionRange.startRow, targetRange.startRow), - endRow: Math.max(this._initialSelectionRange.endRow, targetRange.endRow), - startColumn: Math.min(this._initialSelectionRange.startColumn, targetRange.startColumn), - endColumn: Math.max(this._initialSelectionRange.endColumn, targetRange.endColumn), - }, { - row: this._initialSelectionRange.startRow, - column: this._initialSelectionRange.startColumn, - }, false, false, false); + this._updateCurrentSelection( + { + startRow: Math.min( + this._initialSelectionRange.startRow, + targetRange.startRow + ), + endRow: Math.max( + this._initialSelectionRange.endRow, + targetRange.endRow + ), + startColumn: Math.min( + this._initialSelectionRange.startColumn, + targetRange.startColumn + ), + endColumn: Math.max( + this._initialSelectionRange.endColumn, + targetRange.endColumn + ), + }, + { + row: this._initialSelectionRange.startRow, + column: this._initialSelectionRange.startColumn, + }, + false, + false, + false + ); // Initialize automatic scrolling when out of viewport bounds with the mouse - const viewPortBounds: IRectangle = this._lastRenderingContext.viewPort; - const outOfViewPortBoundsX: boolean = x < 0 || x > viewPortBounds.width; - const outOfViewPortBoundsY: boolean = y < 0 || y > viewPortBounds.height; + const viewPortBounds: IRectangle = + this._lastRenderingContext.viewPort; + const outOfViewPortBoundsX: boolean = + x < 0 || x > viewPortBounds.width; + const outOfViewPortBoundsY: boolean = + y < 0 || y > viewPortBounds.height; if (outOfViewPortBoundsX || outOfViewPortBoundsY) { this._updateAutoScrolling( - outOfViewPortBoundsX ? (x < 0 ? x : x - viewPortBounds.width) : 0, - outOfViewPortBoundsY ? (y < 0 ? y : y - viewPortBounds.height) : 0, + outOfViewPortBoundsX + ? x < 0 + ? x + : x - viewPortBounds.width + : 0, + outOfViewPortBoundsY + ? y < 0 + ? y + : y - viewPortBounds.height + : 0, 0 ); } else { @@ -1146,13 +1415,24 @@ export class CanvasRenderer implements ITableEngineRenderer { isHandled = true; } } else { - if (CanvasRenderer._isMouseOverCopyHandle(x, y, this._lastRenderingContext)) { - this._setCursor("crosshair"); + if ( + CanvasRenderer._isMouseOverCopyHandle( + x, + y, + this._lastRenderingContext + ) + ) { + this._setCursor('crosshair'); resetCursor = false; - } else if (this._options.renderer.canvas.rowColumnResizing.allowResizing) { - const resizingInfo: IResizerInfo = this._isMouseOverResizingSpace(x, y); + } else if ( + this._options.renderer.canvas.rowColumnResizing.allowResizing + ) { + const resizingInfo: IResizerInfo = + this._isMouseOverResizingSpace(x, y); if (resizingInfo.isMouseOver) { - this._setCursor(resizingInfo.overRow ? "row-resize" : "col-resize"); + this._setCursor( + resizingInfo.overRow ? 'row-resize' : 'col-resize' + ); resetCursor = false; } } @@ -1164,16 +1444,24 @@ export class CanvasRenderer implements ITableEngineRenderer { if (!isHandled) { // Send event to cell renderer for the cell on the current position - const range: ICellRange | null = this._getCellRangeAtPoint(x, y, false); + const range: ICellRange | null = this._getCellRangeAtPoint( + x, + y, + false + ); if (!!range) { - if (!!this._lastHoveredCellRange && !CellRangeUtil.equals(range, this._lastHoveredCellRange)) { + if ( + !!this._lastHoveredCellRange && + !CellRangeUtil.equals(range, this._lastHoveredCellRange) + ) { // Send mouse out event this._sendEventForPosition( this._lastHoveredCellRange.startRow, this._lastHoveredCellRange.startColumn, (listener) => listener.onMouseOut, (e) => { - const mouseEvent: ICellRendererMouseEvent = e as ICellRendererMouseEvent; + const mouseEvent: ICellRendererMouseEvent = + e as ICellRendererMouseEvent; mouseEvent.originalEvent = event; return mouseEvent; } @@ -1186,8 +1474,9 @@ export class CanvasRenderer implements ITableEngineRenderer { range.startColumn, (listener) => listener.onMouseMove, (e) => { - const mouseEvent: ICellRendererMouseEvent = e as ICellRendererMouseEvent; - mouseEvent.offset = {x, y}; + const mouseEvent: ICellRendererMouseEvent = + e as ICellRendererMouseEvent; + mouseEvent.offset = { x, y }; mouseEvent.originalEvent = event; return mouseEvent; } @@ -1200,7 +1489,8 @@ export class CanvasRenderer implements ITableEngineRenderer { this._lastHoveredCellRange.startColumn, (listener) => listener.onMouseOut, (e) => { - const mouseEvent: ICellRendererMouseEvent = e as ICellRendererMouseEvent; + const mouseEvent: ICellRendererMouseEvent = + e as ICellRendererMouseEvent; mouseEvent.originalEvent = event; return mouseEvent; } @@ -1209,7 +1499,7 @@ export class CanvasRenderer implements ITableEngineRenderer { } } - this._lastMousePosition = {x, y}; + this._lastMousePosition = { x, y }; } /** @@ -1218,17 +1508,26 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param yDiff vertical offset from the viewport bounds * @param acceleration to apply */ - private _updateAutoScrolling(xDiff: number, yDiff: number, acceleration: number): void { + private _updateAutoScrolling( + xDiff: number, + yDiff: number, + acceleration: number + ): void { if (!!this._autoScrollContext) { this._autoScrollContext.xDiff = xDiff; this._autoScrollContext.yDiff = yDiff; } else { this._autoScrollContext = { - animationFrameID: window.requestAnimationFrame((timestamp) => this._autoScrollStep(timestamp, this._autoScrollContext.lastTimestamp)), + animationFrameID: window.requestAnimationFrame((timestamp) => + this._autoScrollStep( + timestamp, + this._autoScrollContext.lastTimestamp + ) + ), lastTimestamp: window.performance.now(), xDiff: xDiff, yDiff: yDiff, - acceleration + acceleration, }; } } @@ -1243,49 +1542,101 @@ export class CanvasRenderer implements ITableEngineRenderer { const diff: number = timestamp - oldTimestamp; this._autoScrollContext.lastTimestamp = timestamp; - const baseOffsetToScroll = this.rendererOptions.canvas.selection.autoScrollingSpeed * (diff / 1000); + const baseOffsetToScroll = + this.rendererOptions.canvas.selection.autoScrollingSpeed * + (diff / 1000); - const xScrollDiff: number = this._autoScrollContext.xDiff * baseOffsetToScroll; - const yScrollDiff: number = this._autoScrollContext.yDiff * baseOffsetToScroll; + const xScrollDiff: number = + this._autoScrollContext.xDiff * baseOffsetToScroll; + const yScrollDiff: number = + this._autoScrollContext.yDiff * baseOffsetToScroll; // Accelerate (or slow) if (this._autoScrollContext.xDiff > 0) { - this._autoScrollContext.xDiff = Math.max(this._autoScrollContext.xDiff + this._autoScrollContext.acceleration * diff / 1000, 0); + this._autoScrollContext.xDiff = Math.max( + this._autoScrollContext.xDiff + + (this._autoScrollContext.acceleration * diff) / 1000, + 0 + ); } else { - this._autoScrollContext.xDiff = Math.min(this._autoScrollContext.xDiff - this._autoScrollContext.acceleration * diff / 1000, 0); + this._autoScrollContext.xDiff = Math.min( + this._autoScrollContext.xDiff - + (this._autoScrollContext.acceleration * diff) / 1000, + 0 + ); } if (this._autoScrollContext.yDiff > 0) { - this._autoScrollContext.yDiff = Math.max(this._autoScrollContext.yDiff + this._autoScrollContext.acceleration * diff / 1000, 0); + this._autoScrollContext.yDiff = Math.max( + this._autoScrollContext.yDiff + + (this._autoScrollContext.acceleration * diff) / 1000, + 0 + ); } else { - this._autoScrollContext.yDiff = Math.min(this._autoScrollContext.yDiff - this._autoScrollContext.acceleration * diff / 1000, 0); + this._autoScrollContext.yDiff = Math.min( + this._autoScrollContext.yDiff - + (this._autoScrollContext.acceleration * diff) / 1000, + 0 + ); } - if (this._scrollTo( - this._scrollOffset.x + xScrollDiff, - this._scrollOffset.y + yScrollDiff - )) { + if ( + this._scrollTo( + this._scrollOffset.x + xScrollDiff, + this._scrollOffset.y + yScrollDiff + ) + ) { this._repaintScheduler.next(); } // Update selection extending when currently dragging selection const isSelectionDragging: boolean = !!this._initialSelectionRange; if (isSelectionDragging) { - const targetRange: ICellRange = this._getCellRangeAtPoint(this._lastMousePosition.x, this._lastMousePosition.y); + const targetRange: ICellRange = this._getCellRangeAtPoint( + this._lastMousePosition.x, + this._lastMousePosition.y + ); - this._updateCurrentSelection({ - startRow: Math.min(this._initialSelectionRange.startRow, targetRange.startRow), - endRow: Math.max(this._initialSelectionRange.endRow, targetRange.endRow), - startColumn: Math.min(this._initialSelectionRange.startColumn, targetRange.startColumn), - endColumn: Math.max(this._initialSelectionRange.endColumn, targetRange.endColumn), - }, { - row: this._initialSelectionRange.startRow, - column: this._initialSelectionRange.startColumn, - }, false, false, false); + this._updateCurrentSelection( + { + startRow: Math.min( + this._initialSelectionRange.startRow, + targetRange.startRow + ), + endRow: Math.max( + this._initialSelectionRange.endRow, + targetRange.endRow + ), + startColumn: Math.min( + this._initialSelectionRange.startColumn, + targetRange.startColumn + ), + endColumn: Math.max( + this._initialSelectionRange.endColumn, + targetRange.endColumn + ), + }, + { + row: this._initialSelectionRange.startRow, + column: this._initialSelectionRange.startColumn, + }, + false, + false, + false + ); } // Schedule next animation frame - if (this._autoScrollContext.xDiff !== 0 || this._autoScrollContext.yDiff !== 0) { - this._autoScrollContext.animationFrameID = window.requestAnimationFrame((timestamp) => this._autoScrollStep(timestamp, this._autoScrollContext.lastTimestamp)); + if ( + this._autoScrollContext.xDiff !== 0 || + this._autoScrollContext.yDiff !== 0 + ) { + this._autoScrollContext.animationFrameID = + window.requestAnimationFrame((timestamp) => + this._autoScrollStep( + timestamp, + this._autoScrollContext.lastTimestamp + ) + ); } else { this._stopAutoScrolling(); } @@ -1296,7 +1647,9 @@ export class CanvasRenderer implements ITableEngineRenderer { */ private _stopAutoScrolling(): void { if (!!this._autoScrollContext) { - window.cancelAnimationFrame(this._autoScrollContext.animationFrameID); + window.cancelAnimationFrame( + this._autoScrollContext.animationFrameID + ); this._autoScrollContext = null; } @@ -1308,11 +1661,17 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param y the new y position * @param start of the drag */ - private _onViewPortMove(x: number, y: number, start: IMouseDragContext): void { - if (this._scrollTo( - start.startScrollOffset.x + (start.startX - x), - start.startScrollOffset.y + (start.startY - y) - )) { + private _onViewPortMove( + x: number, + y: number, + start: IMouseDragContext + ): void { + if ( + this._scrollTo( + start.startScrollOffset.x + (start.startX - x), + start.startScrollOffset.y + (start.startY - y) + ) + ) { this._repaintScheduler.next(); } } @@ -1323,11 +1682,21 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param y the new y position * @param start of the scrollbar drag */ - private _onScrollBarMove(x: number, y: number, start: IScrollBarDragContext): void { + private _onScrollBarMove( + x: number, + y: number, + start: IScrollBarDragContext + ): void { // Update scroll offset accordingly if (start.scrollVertically) { - const fixedRowsHeight: number = !!this._lastRenderingContext.cells.fixedRowCells ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds.height : 0; - const viewPortHeight = this._lastRenderingContext.cells.nonFixedCells.viewPortBounds.height; + const fixedRowsHeight: number = !!this._lastRenderingContext.cells + .fixedRowCells + ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds + .height + : 0; + const viewPortHeight = + this._lastRenderingContext.cells.nonFixedCells.viewPortBounds + .height; const tableHeight = this._cellModel.getHeight() - fixedRowsHeight; // Normalize x and y coordinates for fixed rows/columns @@ -1335,15 +1704,25 @@ export class CanvasRenderer implements ITableEngineRenderer { const startY = start.startY - fixedRowsHeight; const curY = startY + (y - startY) + start.offsetFromScrollBarStart; - const maxY = viewPortHeight - this._lastRenderingContext.scrollBar.vertical.length; + const maxY = + viewPortHeight - + this._lastRenderingContext.scrollBar.vertical.length; - if (this._scrollToY(curY / maxY * (tableHeight - viewPortHeight))) { + if ( + this._scrollToY((curY / maxY) * (tableHeight - viewPortHeight)) + ) { this._repaintScheduler.next(); } } if (start.scrollHorizontally) { - const fixedColumnWidth: number = !!this._lastRenderingContext.cells.fixedColumnCells ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds.width : 0; - const viewPortWidth = this._lastRenderingContext.cells.nonFixedCells.viewPortBounds.width; + const fixedColumnWidth: number = !!this._lastRenderingContext.cells + .fixedColumnCells + ? this._lastRenderingContext.cells.fixedColumnCells + .viewPortBounds.width + : 0; + const viewPortWidth = + this._lastRenderingContext.cells.nonFixedCells.viewPortBounds + .width; const tableWidth = this._cellModel.getWidth() - fixedColumnWidth; // Normalize x and y coordinates for fixed rows/columns @@ -1351,9 +1730,11 @@ export class CanvasRenderer implements ITableEngineRenderer { const startX = start.startX - fixedColumnWidth; const curX = startX + (x - startX) + start.offsetFromScrollBarStart; - const maxX = viewPortWidth - this._lastRenderingContext.scrollBar.horizontal.length; + const maxX = + viewPortWidth - + this._lastRenderingContext.scrollBar.horizontal.length; - if (this._scrollToX(curX / maxX * (tableWidth - viewPortWidth))) { + if (this._scrollToX((curX / maxX) * (tableWidth - viewPortWidth))) { this._repaintScheduler.next(); } } @@ -1378,28 +1759,43 @@ export class CanvasRenderer implements ITableEngineRenderer { // Determine new size const oldSize: number = this._resizingDragStart.info.overRow ? this._cellModel.getRowSize(this._resizingDragStart.info.index) - : this._cellModel.getColumnSize(this._resizingDragStart.info.index); + : this._cellModel.getColumnSize( + this._resizingDragStart.info.index + ); let newSize: number = oldSize + sizeDiff; // Restrict new size by the min allowed row/column sizes from options if (this._resizingDragStart.info.overRow) { - if (newSize < this._options.renderer.canvas.rowColumnResizing.minRowSize) { - newSize = this._options.renderer.canvas.rowColumnResizing.minRowSize; + if ( + newSize < + this._options.renderer.canvas.rowColumnResizing.minRowSize + ) { + newSize = + this._options.renderer.canvas.rowColumnResizing + .minRowSize; } } else { - if (newSize < this._options.renderer.canvas.rowColumnResizing.minColumnSize) { - newSize = this._options.renderer.canvas.rowColumnResizing.minColumnSize; + if ( + newSize < + this._options.renderer.canvas.rowColumnResizing + .minColumnSize + ) { + newSize = + this._options.renderer.canvas.rowColumnResizing + .minColumnSize; } } // Call resizer handler to process the resizing - if (this._options.renderer.canvas.rowColumnResizing.resizingHandler( - newSize, - this._resizingDragStart.info.overRow, - this._resizingDragStart.info.index, - this._cellModel, - this._selectionModel - )) { + if ( + this._options.renderer.canvas.rowColumnResizing.resizingHandler( + newSize, + this._resizingDragStart.info.overRow, + this._resizingDragStart.info.index, + this._cellModel, + this._selectionModel + ) + ) { this._repaintScheduler.next(); // Schedule a repaint } @@ -1409,38 +1805,66 @@ export class CanvasRenderer implements ITableEngineRenderer { if (!this._copyHandleDragStart) { const targetRange: ICellRange = this._getCellRangeAtPoint(x, y); - const selectionChangedFromBeginning: boolean = !CellRangeUtil.equals(this._initialSelectionRange, targetRange); + const selectionChangedFromBeginning: boolean = + !CellRangeUtil.equals( + this._initialSelectionRange, + targetRange + ); const isMultiSelection: boolean = event.ctrlKey; if (selectionChangedFromBeginning || isMultiSelection) { - this._updateCurrentSelection({ - startRow: Math.min(this._initialSelectionRange.startRow, targetRange.startRow), - endRow: Math.max(this._initialSelectionRange.endRow, targetRange.endRow), - startColumn: Math.min(this._initialSelectionRange.startColumn, targetRange.startColumn), - endColumn: Math.max(this._initialSelectionRange.endColumn, targetRange.endColumn), - }, { - row: this._initialSelectionRange.startRow, - column: this._initialSelectionRange.startColumn, - }, false, false, true); + this._updateCurrentSelection( + { + startRow: Math.min( + this._initialSelectionRange.startRow, + targetRange.startRow + ), + endRow: Math.max( + this._initialSelectionRange.endRow, + targetRange.endRow + ), + startColumn: Math.min( + this._initialSelectionRange.startColumn, + targetRange.startColumn + ), + endColumn: Math.max( + this._initialSelectionRange.endColumn, + targetRange.endColumn + ), + }, + { + row: this._initialSelectionRange.startRow, + column: this._initialSelectionRange.startColumn, + }, + false, + false, + true + ); } else { /* * Make sure the current selection is focused. * May not be the case when only selecting a single cell. */ - const primary: ISelection | null = this._selectionModel.getPrimary(); + const primary: ISelection | null = + this._selectionModel.getPrimary(); if (!!primary) { this._updateFocusedCell(primary.initial); } // Send event to cell renderer for the cell on the current position - const range: ICellRange | null = this._getCellRangeAtPoint(x, y, false); + const range: ICellRange | null = this._getCellRangeAtPoint( + x, + y, + false + ); if (!!range) { this._sendEventForPosition( range.startRow, range.startColumn, (listener) => listener.onMouseUp, (e) => { - const mouseEvent: ICellRendererMouseEvent = e as ICellRendererMouseEvent; - mouseEvent.offset = {x, y}; + const mouseEvent: ICellRendererMouseEvent = + e as ICellRendererMouseEvent; + mouseEvent.offset = { x, y }; mouseEvent.originalEvent = event; return mouseEvent; } @@ -1453,17 +1877,28 @@ export class CanvasRenderer implements ITableEngineRenderer { // Send copy event if (!!this._options.selection.copyHandle.copyHandler) { - this._options.selection.copyHandle.copyHandler({ - startRow: this._initialSelectionRange.startRow, - endRow: this._initialSelectionRange.endRow, - startColumn: this._initialSelectionRange.startColumn, - endColumn: this._initialSelectionRange.endColumn - }, { - startRow: this._selectionModel.getPrimary().range.startRow, - endRow: this._selectionModel.getPrimary().range.endRow, - startColumn: this._selectionModel.getPrimary().range.startColumn, - endColumn: this._selectionModel.getPrimary().range.endColumn - }); + this._options.selection.copyHandle.copyHandler( + { + startRow: this._initialSelectionRange.startRow, + endRow: this._initialSelectionRange.endRow, + startColumn: + this._initialSelectionRange.startColumn, + endColumn: this._initialSelectionRange.endColumn, + }, + { + startRow: + this._selectionModel.getPrimary().range + .startRow, + endRow: this._selectionModel.getPrimary().range + .endRow, + startColumn: + this._selectionModel.getPrimary().range + .startColumn, + endColumn: + this._selectionModel.getPrimary().range + .endColumn, + } + ); } } @@ -1497,9 +1932,9 @@ export class CanvasRenderer implements ITableEngineRenderer { lastTimestamp: window.performance.now(), startScrollOffset: { x: this._scrollOffset.x, - y: this._scrollOffset.y + y: this._scrollOffset.y, }, - isTap: true + isTap: true, }; } else if (event.touches.length === 2 && this._secondTouchID === null) { this._secondTouchID = event.touches[1].identifier; @@ -1508,7 +1943,7 @@ export class CanvasRenderer implements ITableEngineRenderer { const [x1, y1] = this._getMouseOffset(event.touches[0]); this._touchZoomContext = { startTouchDistance: Math.hypot(x2 - x1, y2 - y1), - startZoom: this._zoom + startZoom: this._zoom, }; } } @@ -1525,7 +1960,10 @@ export class CanvasRenderer implements ITableEngineRenderer { const currentFingerDistance: number = Math.hypot(x2 - x1, y2 - y1); - this.setZoom(this._touchZoomContext.startZoom * currentFingerDistance / this._touchZoomContext.startTouchDistance); + this.setZoom( + (this._touchZoomContext.startZoom * currentFingerDistance) / + this._touchZoomContext.startTouchDistance + ); return; } @@ -1538,11 +1976,14 @@ export class CanvasRenderer implements ITableEngineRenderer { this._panningStart.isTap = false; // Finger moved, so this cannot be a tap event const currentTimestamp = window.performance.now(); - const diff = currentTimestamp - this._panningStart.lastTimestamp; + const diff = + currentTimestamp - this._panningStart.lastTimestamp; this._panningStart.lastTimestamp = currentTimestamp; - this._panningStart.speedX = (x - this._panningStart.lastX) / diff; - this._panningStart.speedY = (y - this._panningStart.lastY) / diff; + this._panningStart.speedX = + (x - this._panningStart.lastX) / diff; + this._panningStart.speedY = + (y - this._panningStart.lastY) / diff; this._panningStart.lastX = x; this._panningStart.lastY = y; @@ -1563,18 +2004,33 @@ export class CanvasRenderer implements ITableEngineRenderer { const [x, y] = this._getMouseOffset(touch); this._updateAutoScrolling( - -this._panningStart.speedX * this.rendererOptions.canvas.scrolling.touchScrollingSpeedFactor, - -this._panningStart.speedY * this.rendererOptions.canvas.scrolling.touchScrollingSpeedFactor, - this.rendererOptions.canvas.scrolling.touchScrollingAcceleration + -this._panningStart.speedX * + this.rendererOptions.canvas.scrolling + .touchScrollingSpeedFactor, + -this._panningStart.speedY * + this.rendererOptions.canvas.scrolling + .touchScrollingSpeedFactor, + this.rendererOptions.canvas.scrolling + .touchScrollingAcceleration ); - if (event.changedTouches.length === 1 && this._panningStart.isTap) { + if ( + event.changedTouches.length === 1 && + this._panningStart.isTap + ) { // Select cell at the position - const selectionRange: ICellRange = this._getCellRangeAtPoint(x, y); - this._updateCurrentSelection(selectionRange, { - row: selectionRange.startRow, - column: selectionRange.startColumn, - }, true, true, true); + const selectionRange: ICellRange = + this._getCellRangeAtPoint(x, y); + this._updateCurrentSelection( + selectionRange, + { + row: selectionRange.startRow, + column: selectionRange.startColumn, + }, + true, + true, + true + ); } // Stop panning @@ -1603,42 +2059,71 @@ export class CanvasRenderer implements ITableEngineRenderer { const newBounds: DOMRect = this._container.getBoundingClientRect(); // Re-size scroll bar offsets as well - const fixedRowsHeight: number = !!this._lastRenderingContext && !!this._lastRenderingContext.cells.fixedRowCells - ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds.height - : 0; - const fixedColumnsWidth: number = !!this._lastRenderingContext && !!this._lastRenderingContext.cells.fixedColumnCells - ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds.width - : 0; - - const tableHeight: number = this._cellModel.getHeight() - fixedRowsHeight; - const tableWidth: number = this._cellModel.getWidth() - fixedColumnsWidth; - - const oldViewPort: IRectangle = !!this._lastRenderingContext ? this._lastRenderingContext.cells.nonFixedCells.viewPortBounds : { - top: 0, - left: 0, - width: 0, - height: 0 - }; + const fixedRowsHeight: number = + !!this._lastRenderingContext && + !!this._lastRenderingContext.cells.fixedRowCells + ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds + .height + : 0; + const fixedColumnsWidth: number = + !!this._lastRenderingContext && + !!this._lastRenderingContext.cells.fixedColumnCells + ? this._lastRenderingContext.cells.fixedColumnCells + .viewPortBounds.width + : 0; + + const tableHeight: number = + this._cellModel.getHeight() - fixedRowsHeight; + const tableWidth: number = + this._cellModel.getWidth() - fixedColumnsWidth; + + const oldViewPort: IRectangle = !!this._lastRenderingContext + ? this._lastRenderingContext.cells.nonFixedCells.viewPortBounds + : { + top: 0, + left: 0, + width: 0, + height: 0, + }; if (tableWidth > newBounds.width) { - const oldMaxOffset = (tableWidth - oldViewPort.width); - const newMaxOffset = (tableWidth - (newBounds.width - fixedColumnsWidth)); - - this._scrollOffset.x = Math.max(Math.min(this._scrollOffset.x * newMaxOffset / oldMaxOffset, newMaxOffset), 0); + const oldMaxOffset = tableWidth - oldViewPort.width; + const newMaxOffset = + tableWidth - (newBounds.width - fixedColumnsWidth); + + this._scrollOffset.x = Math.max( + Math.min( + (this._scrollOffset.x * newMaxOffset) / oldMaxOffset, + newMaxOffset + ), + 0 + ); } else { this._scrollOffset.x = 0; } if (tableHeight > newBounds.height) { - const oldMaxOffset = (tableHeight - oldViewPort.height); - const newMaxOffset = (tableHeight - (newBounds.height - fixedRowsHeight)); - - this._scrollOffset.y = Math.max(Math.min(this._scrollOffset.y * newMaxOffset / oldMaxOffset, newMaxOffset), 0); + const oldMaxOffset = tableHeight - oldViewPort.height; + const newMaxOffset = + tableHeight - (newBounds.height - fixedRowsHeight); + + this._scrollOffset.y = Math.max( + Math.min( + (this._scrollOffset.y * newMaxOffset) / oldMaxOffset, + newMaxOffset + ), + 0 + ); } else { this._scrollOffset.y = 0; } // Set new size to canvas - CanvasUtil.setCanvasSize(this._canvasElement, newBounds.width, newBounds.height, this._devicePixelRatio); + CanvasUtil.setCanvasSize( + this._canvasElement, + newBounds.width, + newBounds.height, + this._devicePixelRatio + ); // Schedule a repaint this._repaintScheduler.next(); @@ -1649,16 +2134,19 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param event that occurred */ private _onKeyDown(event: KeyboardEvent): void { - const preventDefault: boolean = !!this._focusedCellPosition && this._sendEventForPosition( - this._focusedCellPosition.row, - this._focusedCellPosition.column, - (listener) => listener.onKeyDown, - (e) => { - const keyboardEvent: ICellRendererKeyboardEvent = e as ICellRendererKeyboardEvent; - keyboardEvent.originalEvent = event; - return keyboardEvent; - } - ); + const preventDefault: boolean = + !!this._focusedCellPosition && + this._sendEventForPosition( + this._focusedCellPosition.row, + this._focusedCellPosition.column, + (listener) => listener.onKeyDown, + (e) => { + const keyboardEvent: ICellRendererKeyboardEvent = + e as ICellRendererKeyboardEvent; + keyboardEvent.originalEvent = event; + return keyboardEvent; + } + ); if (preventDefault) { event.preventDefault(); @@ -1666,41 +2154,50 @@ export class CanvasRenderer implements ITableEngineRenderer { } switch (event.code) { - case "Space": + case 'Space': event.preventDefault(); this._isInMouseDragMode = true; break; - case "Tab": - case "Enter": + case 'Tab': + case 'Enter': event.preventDefault(); if (!event.ctrlKey) { - const axisVertical: boolean = event.code === "Enter"; + const axisVertical: boolean = event.code === 'Enter'; - const primary: ISelection | null = this._selectionModel.getPrimary(); + const primary: ISelection | null = + this._selectionModel.getPrimary(); if (!!primary) { // Check if selection is only a single cell - const cell: ICell | null = this._cellModel.getCell(primary.range.startRow, primary.range.startColumn); - const isSingleCell: boolean = !cell || CellRangeUtil.equals(primary.range, cell.range); - - if (isSingleCell && this._selectionModel.getSelections().length === 1) { + const cell: ICell | null = this._cellModel.getCell( + primary.range.startRow, + primary.range.startColumn + ); + const isSingleCell: boolean = + !cell || + CellRangeUtil.equals(primary.range, cell.range); + + if ( + isSingleCell && + this._selectionModel.getSelections().length === 1 + ) { // Single cell selected -> move selection this._moveSelection( - axisVertical ? 0 : (event.shiftKey ? -1 : 1), + axisVertical ? 0 : event.shiftKey ? -1 : 1, axisVertical ? (event.shiftKey ? -1 : 1) : 0, false ); } else { // Multiple cells selected -> move only initial in selection this._moveInitialSelection( - axisVertical ? 0 : (event.shiftKey ? -1 : 1), + axisVertical ? 0 : event.shiftKey ? -1 : 1, axisVertical ? (event.shiftKey ? -1 : 1) : 0 ); } } } break; - case "KeyA": + case 'KeyA': if (event.ctrlKey) { event.preventDefault(); @@ -1708,8 +2205,8 @@ export class CanvasRenderer implements ITableEngineRenderer { this._selectAll(); } break; - case "KeyC": - case "KeyX": + case 'KeyC': + case 'KeyX': if (event.ctrlKey) { // Copy or cut requested // Note that the table-engine will only handle copying and not cutting @@ -1719,21 +2216,21 @@ export class CanvasRenderer implements ITableEngineRenderer { this._copySelection(); } break; - case "ArrowDown": - case "ArrowLeft": - case "ArrowRight": - case "ArrowUp": + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': + case 'ArrowUp': const extend: boolean = event.shiftKey; const jump: boolean = event.ctrlKey; let xDiff: number = 0; - if (event.code === "ArrowLeft" || event.code === "ArrowRight") { - xDiff = event.code === "ArrowLeft" ? -1 : 1; + if (event.code === 'ArrowLeft' || event.code === 'ArrowRight') { + xDiff = event.code === 'ArrowLeft' ? -1 : 1; } let yDiff: number = 0; - if (event.code === "ArrowUp" || event.code === "ArrowDown") { - yDiff = event.code === "ArrowUp" ? -1 : 1; + if (event.code === 'ArrowUp' || event.code === 'ArrowDown') { + yDiff = event.code === 'ArrowUp' ? -1 : 1; } if (extend) { @@ -1753,25 +2250,35 @@ export class CanvasRenderer implements ITableEngineRenderer { const primary: ISelection | null = this._selectionModel.getPrimary(); if (!!primary) { // Check whether there is a limit to the copyable cell count for performance reasons - const copyableCellCountLimit: number = this.rendererOptions.view.maxCellCountToCopy; + const copyableCellCountLimit: number = + this.rendererOptions.view.maxCellCountToCopy; if (copyableCellCountLimit >= 0) { - const cellCountInRange: number = CellRangeUtil.size(primary.range); + const cellCountInRange: number = CellRangeUtil.size( + primary.range + ); if (cellCountInRange > copyableCellCountLimit) { // Notify user that the amount of cells is high and may take a while to copy (performance warning) - const notification: CopyPerformanceWarningNotification = new CopyPerformanceWarningNotification(copyableCellCountLimit, cellCountInRange); + const notification: CopyPerformanceWarningNotification = + new CopyPerformanceWarningNotification( + copyableCellCountLimit, + cellCountInRange + ); let copyAnyway: boolean = false; if (!!this.rendererOptions.notificationService) { copyAnyway = await Promise.race([ - new Promise(resolve => { - notification.callback = (copyAnyway) => resolve(copyAnyway); + new Promise((resolve) => { + notification.callback = (copyAnyway) => + resolve(copyAnyway); - this.rendererOptions.notificationService.notify(notification); + this.rendererOptions.notificationService.notify( + notification + ); }), - new Promise(resolve => { + new Promise((resolve) => { setTimeout(() => resolve(false), 60000); - }) + }), ]); } else { console.warn(`[table-engine] ${notification.message}`); @@ -1784,15 +2291,23 @@ export class CanvasRenderer implements ITableEngineRenderer { } // Build HTML table to copy - const htmlTable: string = ClipboardUtil.buildHTMLTableForCopy(primary.range, this._cellModel, (cell) => { - return this._cellRendererLookup.get(cell.rendererName).getCopyValue(cell); - }); + const htmlTable: string = ClipboardUtil.buildHTMLTableForCopy( + primary.range, + this._cellModel, + (cell) => { + return this._cellRendererLookup + .get(cell.rendererName) + .getCopyValue(cell); + } + ); // Actually copy the HTML table ClipboardUtil.setClipboardContent(htmlTable); if (!!this.rendererOptions.notificationService) { - this.rendererOptions.notificationService.notify(new CopyNotification()); + this.rendererOptions.notificationService.notify( + new CopyNotification() + ); } } } @@ -1804,22 +2319,32 @@ export class CanvasRenderer implements ITableEngineRenderer { this._selectionModel.clear(); const firstVisibleRow: number = this._cellModel.findNextVisibleRow(0); - const firstVisibleColumn: number = this._cellModel.findNextVisibleColumn(0); - const lastVisibleRow: number = this._cellModel.findPreviousVisibleRow(this._cellModel.getRowCount() - 1); - const lastVisibleColumn: number = this._cellModel.findPreviousVisibleColumn(this._cellModel.getColumnCount() - 1); - - this._selectionModel.addSelection({ - range: { - startRow: firstVisibleRow, - endRow: lastVisibleRow, - startColumn: firstVisibleColumn, - endColumn: lastVisibleColumn + const firstVisibleColumn: number = + this._cellModel.findNextVisibleColumn(0); + const lastVisibleRow: number = this._cellModel.findPreviousVisibleRow( + this._cellModel.getRowCount() - 1 + ); + const lastVisibleColumn: number = + this._cellModel.findPreviousVisibleColumn( + this._cellModel.getColumnCount() - 1 + ); + + this._selectionModel.addSelection( + { + range: { + startRow: firstVisibleRow, + endRow: lastVisibleRow, + startColumn: firstVisibleColumn, + endColumn: lastVisibleColumn, + }, + initial: { + row: firstVisibleRow, + column: firstVisibleColumn, + }, }, - initial: { - row: firstVisibleRow, - column: firstVisibleColumn - } - }, true, false); + true, + false + ); const primary: ISelection = this._selectionModel.getPrimary(); this._updateFocusedCell(primary.initial); @@ -1833,8 +2358,12 @@ export class CanvasRenderer implements ITableEngineRenderer { */ private _updateFocusedCell(position: IInitialPosition | null): void { // Check if position changed - const oldRow: number = !!this._focusedCellPosition ? this._focusedCellPosition.row : -1; - const oldColumn: number = !!this._focusedCellPosition ? this._focusedCellPosition.column : -1; + const oldRow: number = !!this._focusedCellPosition + ? this._focusedCellPosition.row + : -1; + const oldColumn: number = !!this._focusedCellPosition + ? this._focusedCellPosition.column + : -1; const newRow: number = !!position ? position.row : -1; const newColumn: number = !!position ? position.column : -1; @@ -1857,7 +2386,7 @@ export class CanvasRenderer implements ITableEngineRenderer { if (!!position) { this._focusedCellPosition = { row: position.row, - column: position.column + column: position.column, }; // Send focus event @@ -1910,7 +2439,11 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param yDiff to extend vertically * @param jump whether to jump to the end in the specified direction */ - private _extendSelection(xDiff: number, yDiff: number, jump: boolean): void { + private _extendSelection( + xDiff: number, + yDiff: number, + jump: boolean + ): void { const primary: ISelection = this._selectionModel.getPrimary(); if (!primary) { return; // Nothing to extend @@ -1919,12 +2452,16 @@ export class CanvasRenderer implements ITableEngineRenderer { if (this._selectionModel.extendSelection(primary, xDiff, yDiff, jump)) { let rowToScrollTo: number = primary.initial.row; if (yDiff !== 0) { - rowToScrollTo = yDiff < 0 ? primary.range.startRow : primary.range.endRow; + rowToScrollTo = + yDiff < 0 ? primary.range.startRow : primary.range.endRow; } let columnToScrollTo: number = primary.initial.column; if (xDiff !== 0) { - columnToScrollTo = xDiff < 0 ? primary.range.startColumn : primary.range.endColumn; + columnToScrollTo = + xDiff < 0 + ? primary.range.startColumn + : primary.range.endColumn; } this.scrollTo(rowToScrollTo, columnToScrollTo); @@ -1960,7 +2497,10 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param zoom level (1.0 = 100%) */ public setZoom(zoom: number): void { - const newZoom: number = Math.min(Math.max(zoom, 1.0), CanvasRenderer.MAX_ZOOM_LEVEL); + const newZoom: number = Math.min( + Math.max(zoom, 1.0), + CanvasRenderer.MAX_ZOOM_LEVEL + ); if (newZoom !== this._zoom) { this._zoom = newZoom; @@ -1980,7 +2520,7 @@ export class CanvasRenderer implements ITableEngineRenderer { public getScrollOffset(): IPoint { return { x: this._scrollOffset.x, - y: this._scrollOffset.y + y: this._scrollOffset.y, }; } @@ -1993,14 +2533,18 @@ export class CanvasRenderer implements ITableEngineRenderer { } public getFixedRowsHeight(): number { - return !!this._lastRenderingContext && !!this._lastRenderingContext.cells.fixedRowCells - ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds.height + return !!this._lastRenderingContext && + !!this._lastRenderingContext.cells.fixedRowCells + ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds + .height : 0; } public getFixedColumnsWidth(): number { - return !!this._lastRenderingContext && !!this._lastRenderingContext.cells.fixedColumnCells - ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds.width + return !!this._lastRenderingContext && + !!this._lastRenderingContext.cells.fixedColumnCells + ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds + .width : 0; } @@ -2011,20 +2555,39 @@ export class CanvasRenderer implements ITableEngineRenderer { */ public scrollTo(row: number, column: number): void { const cell: ICell = this._cellModel.getCell(row, column); - const range: ICellRange = !!cell ? cell.range : CellRange.fromSingleRowColumn(row, column); + const range: ICellRange = !!cell + ? cell.range + : CellRange.fromSingleRowColumn(row, column); const bounds: IRectangle = this._cellModel.getBounds(range); - const fixedRows: number = Math.min(this.rendererOptions.view.fixedRows, this._cellModel.getRowCount()); - const fixedColumns: number = Math.min(this.rendererOptions.view.fixedColumns, this._cellModel.getColumnCount()); + const fixedRows: number = Math.min( + this.rendererOptions.view.fixedRows, + this._cellModel.getRowCount() + ); + const fixedColumns: number = Math.min( + this.rendererOptions.view.fixedColumns, + this._cellModel.getColumnCount() + ); - const fixedRowsHeight: number = !!this._lastRenderingContext.cells.fixedRowCells ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds.height : 0; - const fixedColumnsWidth: number = !!this._lastRenderingContext.cells.fixedColumnCells ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds.width : 0; + const fixedRowsHeight: number = !!this._lastRenderingContext.cells + .fixedRowCells + ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds + .height + : 0; + const fixedColumnsWidth: number = !!this._lastRenderingContext.cells + .fixedColumnCells + ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds + .width + : 0; bounds.left -= fixedColumnsWidth; bounds.top -= fixedRowsHeight; - const viewPortWidth: number = this._lastRenderingContext.cells.nonFixedCells.viewPortBounds.width; - const viewPortHeight: number = this._lastRenderingContext.cells.nonFixedCells.viewPortBounds.height; + const viewPortWidth: number = + this._lastRenderingContext.cells.nonFixedCells.viewPortBounds.width; + const viewPortHeight: number = + this._lastRenderingContext.cells.nonFixedCells.viewPortBounds + .height; if (column >= fixedColumns) { const startX: number = this._scrollOffset.x; @@ -2037,7 +2600,9 @@ export class CanvasRenderer implements ITableEngineRenderer { } } else if (bounds.left + bounds.width > endX) { // Scroll to the right - if (this._scrollToX(bounds.left + bounds.width - viewPortWidth)) { + if ( + this._scrollToX(bounds.left + bounds.width - viewPortWidth) + ) { this._repaintScheduler.next(); } } @@ -2054,7 +2619,9 @@ export class CanvasRenderer implements ITableEngineRenderer { } } else if (bounds.top + bounds.height > endY) { // Scroll to the bottom - if (this._scrollToY(bounds.top + bounds.height - viewPortHeight)) { + if ( + this._scrollToY(bounds.top + bounds.height - viewPortHeight) + ) { this._repaintScheduler.next(); } } @@ -2066,23 +2633,26 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param event that occurred */ private _onKeyUp(event: KeyboardEvent): void { - const preventDefault: boolean = !!this._focusedCellPosition && this._sendEventForPosition( - this._focusedCellPosition.row, - this._focusedCellPosition.column, - (listener) => listener.onKeyUp, - (e) => { - const keyboardEvent: ICellRendererKeyboardEvent = e as ICellRendererKeyboardEvent; - keyboardEvent.originalEvent = event; - return keyboardEvent; - } - ); + const preventDefault: boolean = + !!this._focusedCellPosition && + this._sendEventForPosition( + this._focusedCellPosition.row, + this._focusedCellPosition.column, + (listener) => listener.onKeyUp, + (e) => { + const keyboardEvent: ICellRendererKeyboardEvent = + e as ICellRendererKeyboardEvent; + keyboardEvent.originalEvent = event; + return keyboardEvent; + } + ); if (preventDefault) { event.preventDefault(); return; } - if (event.code === "Space") { + if (event.code === 'Space') { this._isInMouseDragMode = false; } } @@ -2097,14 +2667,28 @@ export class CanvasRenderer implements ITableEngineRenderer { event.preventDefault(); this.setZoom(this._zoom + (event.deltaY > 0 ? -0.25 : 0.25)); } else { - const scrollDeltaY: number = ScrollUtil.determineScrollOffsetFromEvent(this._canvasElement, true, event); - const scrollDeltaX: number = ScrollUtil.determineScrollOffsetFromEvent(this._canvasElement, false, event); + const scrollDeltaY: number = + ScrollUtil.determineScrollOffsetFromEvent( + this._canvasElement, + true, + event + ); + const scrollDeltaX: number = + ScrollUtil.determineScrollOffsetFromEvent( + this._canvasElement, + false, + event + ); // When shift-key is pressed, deltaY means scrolling horizontally (same for deltaX). const switchScrollDirection: boolean = event.shiftKey; - const newScrollOffsetX: number = this._scrollOffset.x + (switchScrollDirection ? scrollDeltaY : scrollDeltaX); - const newScrollOffsetY: number = this._scrollOffset.y + (switchScrollDirection ? scrollDeltaX : scrollDeltaY); + const newScrollOffsetX: number = + this._scrollOffset.x + + (switchScrollDirection ? scrollDeltaY : scrollDeltaX); + const newScrollOffsetY: number = + this._scrollOffset.y + + (switchScrollDirection ? scrollDeltaX : scrollDeltaY); if (this._scrollTo(newScrollOffsetX, newScrollOffsetY)) { // Prevent the default action (for example scrolling in parent component) @@ -2153,10 +2737,16 @@ export class CanvasRenderer implements ITableEngineRenderer { *@returns whether the offset is out of bounds and thus corrected to the max/min value */ private _scrollToXInternal(offset: number): boolean { - const fixedColumnsWidth: number = !!this._lastRenderingContext.cells.fixedColumnCells ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds.width : 0; + const fixedColumnsWidth: number = !!this._lastRenderingContext.cells + .fixedColumnCells + ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds + .width + : 0; - const tableWidth: number = this._cellModel.getWidth() - fixedColumnsWidth; - const viewPortWidth: number = this._lastRenderingContext.cells.nonFixedCells.viewPortBounds.width; + const tableWidth: number = + this._cellModel.getWidth() - fixedColumnsWidth; + const viewPortWidth: number = + this._lastRenderingContext.cells.nonFixedCells.viewPortBounds.width; // Check if we're able to scroll if (tableWidth <= viewPortWidth) { @@ -2203,10 +2793,17 @@ export class CanvasRenderer implements ITableEngineRenderer { * @returns whether the offset changed */ private _scrollToYInternal(offset: number): boolean { - const fixedRowsHeight: number = !!this._lastRenderingContext.cells.fixedRowCells ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds.height : 0; + const fixedRowsHeight: number = !!this._lastRenderingContext.cells + .fixedRowCells + ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds + .height + : 0; - const tableHeight: number = this._cellModel.getHeight() - fixedRowsHeight; - const viewPortHeight: number = this._lastRenderingContext.cells.nonFixedCells.viewPortBounds.height; + const tableHeight: number = + this._cellModel.getHeight() - fixedRowsHeight; + const viewPortHeight: number = + this._lastRenderingContext.cells.nonFixedCells.viewPortBounds + .height; // Check if we're able to scroll if (tableHeight <= viewPortHeight) { @@ -2254,29 +2851,33 @@ export class CanvasRenderer implements ITableEngineRenderer { CanvasRenderer._clearContainerChildren(this._container); // Create HTML canvas element - this._canvasElement = document.createElement("canvas"); - this._canvasContext = this._canvasElement.getContext("2d"); + this._canvasElement = document.createElement('canvas'); + this._canvasContext = this._canvasElement.getContext('2d'); // Set position absolute to prevent resize events to occur due to canvas element resizing - this._canvasElement.style.position = "absolute"; + this._canvasElement.style.position = 'absolute'; // Set proper size based on the container HTML element size - CanvasUtil.setCanvasSize(this._canvasElement, bounds.width, bounds.height); + CanvasUtil.setCanvasSize( + this._canvasElement, + bounds.width, + bounds.height + ); // Append it to the container this._container.appendChild(this._canvasElement); // Create overlay container - this._overlayContainer = document.createElement("div"); - this._overlayContainer.style.position = "absolute"; - this._overlayContainer.style.zIndex = "999"; - this._overlayContainer.style.width = "100%"; - this._overlayContainer.style.height = "100%"; - this._overlayContainer.style.overflow = "hidden"; + this._overlayContainer = document.createElement('div'); + this._overlayContainer.style.position = 'absolute'; + this._overlayContainer.style.zIndex = '999'; + this._overlayContainer.style.width = '100%'; + this._overlayContainer.style.height = '100%'; + this._overlayContainer.style.overflow = 'hidden'; // Make it focusable (needed for key listeners for example). - this._overlayContainer.setAttribute("tabindex", "-1"); - this._overlayContainer.style.outline = "none"; // Remove focus outline when focused + this._overlayContainer.setAttribute('tabindex', '-1'); + this._overlayContainer.style.outline = 'none'; // Remove focus outline when focused this._container.appendChild(this._overlayContainer); } @@ -2288,8 +2889,12 @@ export class CanvasRenderer implements ITableEngineRenderer { return { top: this._scrollOffset.y, left: this._scrollOffset.x, - width: this._canvasElement.width / this._devicePixelRatio / this._zoom, - height: this._canvasElement.height / this._devicePixelRatio / this._zoom + width: + this._canvasElement.width / this._devicePixelRatio / this._zoom, + height: + this._canvasElement.height / + this._devicePixelRatio / + this._zoom, }; } @@ -2299,9 +2904,14 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param fixedRowsHeight height of the fixed rows * @param fixedColumnsWidth width of the fixed columns */ - private _calculateScrollBarContext(viewPort: IRectangle, fixedRowsHeight: number, fixedColumnsWidth: number): IScrollBarRenderContext { + private _calculateScrollBarContext( + viewPort: IRectangle, + fixedRowsHeight: number, + fixedColumnsWidth: number + ): IScrollBarRenderContext { // Derive scroll bar options - const scrollBarOptions: IScrollBarOptions = this.rendererOptions.canvas.scrollBar; + const scrollBarOptions: IScrollBarOptions = + this.rendererOptions.canvas.scrollBar; const scrollBarSize: number = scrollBarOptions.size; const minScrollBarLength: number = scrollBarOptions.minLength; const scrollBarOffset: number = scrollBarOptions.offset; @@ -2310,8 +2920,10 @@ export class CanvasRenderer implements ITableEngineRenderer { const viewPortWidth: number = viewPort.width - fixedColumnsWidth; const viewPortHeight: number = viewPort.height - fixedRowsHeight; - const tableHeight: number = this._cellModel.getHeight() - fixedRowsHeight; - const tableWidth: number = this._cellModel.getWidth() - fixedColumnsWidth; + const tableHeight: number = + this._cellModel.getHeight() - fixedRowsHeight; + const tableWidth: number = + this._cellModel.getWidth() - fixedColumnsWidth; const maxVerticalOffset: number = tableHeight - viewPortHeight; const maxHorizontalOffset: number = tableWidth - viewPortWidth; @@ -2319,28 +2931,42 @@ export class CanvasRenderer implements ITableEngineRenderer { // Calculate vertical scrollbar layout let vertical: IScrollBarAxisRenderContext = null; if (tableHeight > viewPortHeight) { - const length = Math.max(viewPortHeight / tableHeight * viewPortHeight, minScrollBarLength); + const length = Math.max( + (viewPortHeight / tableHeight) * viewPortHeight, + minScrollBarLength + ); const progress = this._scrollOffset.y / maxVerticalOffset; vertical = { size: scrollBarSize, length, - x: Math.min(viewPortWidth, tableWidth) - scrollBarSize - scrollBarOffset + fixedColumnsWidth, - y: (viewPortHeight - length) * progress + fixedRowsHeight + x: + Math.min(viewPortWidth, tableWidth) - + scrollBarSize - + scrollBarOffset + + fixedColumnsWidth, + y: (viewPortHeight - length) * progress + fixedRowsHeight, }; } // Calculate horizontal scrollbar layout let horizontal: IScrollBarAxisRenderContext = null; if (tableWidth > viewPortWidth) { - const length = Math.max(viewPortWidth / tableWidth * viewPortWidth, minScrollBarLength); + const length = Math.max( + (viewPortWidth / tableWidth) * viewPortWidth, + minScrollBarLength + ); const progress = this._scrollOffset.x / maxHorizontalOffset; horizontal = { size: scrollBarSize, length, x: (viewPortWidth - length) * progress + fixedColumnsWidth, - y: Math.min(viewPortHeight, tableHeight) - scrollBarSize - scrollBarOffset + fixedRowsHeight + y: + Math.min(viewPortHeight, tableHeight) - + scrollBarSize - + scrollBarOffset + + fixedRowsHeight, }; } @@ -2377,12 +3003,12 @@ export class CanvasRenderer implements ITableEngineRenderer { const result: ISelectionRenderContext = { options: this.rendererOptions.canvas.selection, copyHandle: { - isRendered: false + isRendered: false, }, inFixedCorner: [], inNonFixedArea: [], inFixedColumns: [], - inFixedRows: [] + inFixedRows: [], }; const isMultiSelection: boolean = selections.length > 1; @@ -2462,31 +3088,40 @@ export class CanvasRenderer implements ITableEngineRenderer { isPrimary: boolean, isMultiSelection: boolean ): void { - const bounds: IRectangle = this._calculateSelectionBoundsFromCellRangeBounds( - this._cellModel.getBounds(selection.range), - selection.range, - viewPort, - fixedRows, - fixedColumns, - fixedRowsHeight, - fixedColumnsWidth - ); + const bounds: IRectangle = + this._calculateSelectionBoundsFromCellRangeBounds( + this._cellModel.getBounds(selection.range), + selection.range, + viewPort, + fixedRows, + fixedColumns, + fixedRowsHeight, + fixedColumnsWidth + ); let initialBounds: IRectangle | null = null; if (isPrimary) { let initialRange: ICellRange; - const cellAtInitial = this._cellModel.getCell(selection.initial.row, selection.initial.column); + const cellAtInitial = this._cellModel.getCell( + selection.initial.row, + selection.initial.column + ); if (!!cellAtInitial) { initialRange = cellAtInitial.range; } else { - initialRange = CellRange.fromSingleRowColumn(selection.initial.row, selection.initial.column); + initialRange = CellRange.fromSingleRowColumn( + selection.initial.row, + selection.initial.column + ); } initialBounds = this._cellModel.getBounds(initialRange); } - const isStartInFixedRows: boolean = selection.range.startRow < fixedRows; - const isStartInFixedColumns: boolean = selection.range.startColumn < fixedColumns; + const isStartInFixedRows: boolean = + selection.range.startRow < fixedRows; + const isStartInFixedColumns: boolean = + selection.range.startColumn < fixedColumns; // Correct initial bounds (if any) for fixed rows/columns if (!!initialBounds) { @@ -2495,11 +3130,19 @@ export class CanvasRenderer implements ITableEngineRenderer { const distance: number = initialBounds.top - fixedRowsHeight; if (distance >= 0) { // Correct top offset or height based on distance to fixed rows - const initialUnderFixedRowsOffset: number = fixedRowsHeight - (initialBounds.top - this._scrollOffset.y); + const initialUnderFixedRowsOffset: number = + fixedRowsHeight - + (initialBounds.top - this._scrollOffset.y); - initialBounds.top = Math.max(initialBounds.top - this._scrollOffset.y, fixedRowsHeight); + initialBounds.top = Math.max( + initialBounds.top - this._scrollOffset.y, + fixedRowsHeight + ); if (initialUnderFixedRowsOffset > 0) { - initialBounds.height = Math.max(initialBounds.height - initialUnderFixedRowsOffset, 0); + initialBounds.height = Math.max( + initialBounds.height - initialUnderFixedRowsOffset, + 0 + ); } } } else { @@ -2511,11 +3154,20 @@ export class CanvasRenderer implements ITableEngineRenderer { const distance: number = initialBounds.left - fixedColumnsWidth; if (distance >= 0) { // Correct left offset or width based on distance to fixed columns - const initialUnderFixedColumnsOffset: number = fixedColumnsWidth - (initialBounds.left - this._scrollOffset.x); + const initialUnderFixedColumnsOffset: number = + fixedColumnsWidth - + (initialBounds.left - this._scrollOffset.x); - initialBounds.left = Math.max(initialBounds.left - this._scrollOffset.x, fixedColumnsWidth); + initialBounds.left = Math.max( + initialBounds.left - this._scrollOffset.x, + fixedColumnsWidth + ); if (initialUnderFixedColumnsOffset > 0) { - initialBounds.width = Math.max(initialBounds.width - initialUnderFixedColumnsOffset, 0); + initialBounds.width = Math.max( + initialBounds.width - + initialUnderFixedColumnsOffset, + 0 + ); } } } else { @@ -2547,10 +3199,16 @@ export class CanvasRenderer implements ITableEngineRenderer { initialBounds.left += offset; initialBounds.width -= offset; } - if (initialBounds.top + initialBounds.height === bounds.top + bounds.height) { + if ( + initialBounds.top + initialBounds.height === + bounds.top + bounds.height + ) { initialBounds.height -= offset; } - if (initialBounds.left + initialBounds.width === bounds.left + bounds.width) { + if ( + initialBounds.left + initialBounds.width === + bounds.left + bounds.width + ) { initialBounds.width -= offset; } } @@ -2566,7 +3224,10 @@ export class CanvasRenderer implements ITableEngineRenderer { bounds, initial: initialBounds, isPrimary, - renderCopyHandle: isPrimary && !isMultiSelection && this._options.selection.copyHandle.showCopyHandle + renderCopyHandle: + isPrimary && + !isMultiSelection && + this._options.selection.copyHandle.showCopyHandle, }); } @@ -2614,20 +3275,26 @@ export class CanvasRenderer implements ITableEngineRenderer { startRow: 0, endRow: 0, startColumn: range.startColumn, - endColumn: Math.min(fixedColumns - 1, range.endColumn) + endColumn: Math.min(fixedColumns - 1, range.endColumn), }).width; const heightInFixedRows: number = this._cellModel.getBounds({ startRow: range.startRow, endRow: Math.min(fixedRows - 1, range.endRow), startColumn: 0, - endColumn: 0 + endColumn: 0, }).height; return { left: startX, top: startY, - width: Math.max(endX - startX, isStartInFixedColumns ? widthInFixedColumns : 0), - height: Math.max(endY - startY, isStartInFixedRows ? heightInFixedRows : 0) + width: Math.max( + endX - startX, + isStartInFixedColumns ? widthInFixedColumns : 0 + ), + height: Math.max( + endY - startY, + isStartInFixedRows ? heightInFixedRows : 0 + ), }; } @@ -2639,8 +3306,10 @@ export class CanvasRenderer implements ITableEngineRenderer { if (!!this._lastRenderingContext) { const lastSize: ISize = this._lastRenderingContext.tableSize; - const widthChanged: boolean = lastSize.width !== this._cellModel.getWidth(); - const heightChanged: boolean = lastSize.height !== this._cellModel.getHeight(); + const widthChanged: boolean = + lastSize.width !== this._cellModel.getWidth(); + const heightChanged: boolean = + lastSize.height !== this._cellModel.getHeight(); let scrollChanged: boolean = false; if (widthChanged) { @@ -2657,49 +3326,118 @@ export class CanvasRenderer implements ITableEngineRenderer { const viewPort: IRectangle = this._getViewPort(); - const fixedRows: number = Math.min(this.rendererOptions.view.fixedRows, this._cellModel.getRowCount()); - const fixedColumns: number = Math.min(this.rendererOptions.view.fixedColumns, this._cellModel.getColumnCount()); + const fixedRows: number = Math.min( + this.rendererOptions.view.fixedRows, + this._cellModel.getRowCount() + ); + const fixedColumns: number = Math.min( + this.rendererOptions.view.fixedColumns, + this._cellModel.getColumnCount() + ); // Calculate width and height of the fixed rows and columns - const fixedRowsHeight: number = fixedRows > 0 - ? this._cellModel.getRowOffset(fixedRows - 1) + (this._cellModel.isRowHidden(fixedRows - 1) ? 0.0 : this._cellModel.getRowSize(fixedRows - 1)) - : 0; - const fixedColumnsWidth: number = fixedColumns > 0 - ? this._cellModel.getColumnOffset(fixedColumns - 1) + (this._cellModel.isColumnHidden(fixedColumns - 1) ? 0.0 : this._cellModel.getColumnSize(fixedColumns - 1)) - : 0; - - const cellsInfo: ICellRenderContextCollection = this._createCellRenderingInfo(viewPort, fixedRows, fixedColumns, fixedRowsHeight, fixedColumnsWidth); - const scrollBarContext: IScrollBarRenderContext = this._calculateScrollBarContext(viewPort, fixedRowsHeight, fixedColumnsWidth); - const selectionContext: ISelectionRenderContext = this._calculateSelectionContext(viewPort, fixedRows, fixedColumns, fixedRowsHeight, fixedColumnsWidth); - const borderContext: IBorderRenderContext = this._calculateBorderContext(cellsInfo); - const resizerContext: IResizerRenderContext = this._calculateResizerRenderContext(); + const fixedRowsHeight: number = + fixedRows > 0 + ? this._cellModel.getRowOffset(fixedRows - 1) + + (this._cellModel.isRowHidden(fixedRows - 1) + ? 0.0 + : this._cellModel.getRowSize(fixedRows - 1)) + : 0; + const fixedColumnsWidth: number = + fixedColumns > 0 + ? this._cellModel.getColumnOffset(fixedColumns - 1) + + (this._cellModel.isColumnHidden(fixedColumns - 1) + ? 0.0 + : this._cellModel.getColumnSize(fixedColumns - 1)) + : 0; + + const cellsInfo: ICellRenderContextCollection = + this._createCellRenderingInfo( + viewPort, + fixedRows, + fixedColumns, + fixedRowsHeight, + fixedColumnsWidth + ); + const scrollBarContext: IScrollBarRenderContext = + this._calculateScrollBarContext( + viewPort, + fixedRowsHeight, + fixedColumnsWidth + ); + const selectionContext: ISelectionRenderContext = + this._calculateSelectionContext( + viewPort, + fixedRows, + fixedColumns, + fixedRowsHeight, + fixedColumnsWidth + ); + const borderContext: IBorderRenderContext = + this._calculateBorderContext(cellsInfo); + const resizerContext: IResizerRenderContext = + this._calculateResizerRenderContext(); return { focused: this.isFocused(), viewPort, tableSize: { width: this._cellModel.getWidth(), - height: this._cellModel.getHeight() + height: this._cellModel.getHeight(), }, cells: cellsInfo, scrollBar: scrollBarContext, selection: selectionContext, borders: borderContext, resizer: resizerContext, - renderers: this._cellRendererLookup - } + renderers: this._cellRendererLookup, + }; } /** * Calculate the border rendering context. * @param cellsInfo to calculate borders for */ - private _calculateBorderContext(cellsInfo: ICellRenderContextCollection): IBorderRenderContext { + private _calculateBorderContext( + cellsInfo: ICellRenderContextCollection + ): IBorderRenderContext { return { - inFixedCorner: !!cellsInfo.fixedCornerCells ? this._calculateBorderInfo(this._borderModel.getBorders(cellsInfo.fixedCornerCells.cellRange), cellsInfo.fixedCornerCells.cellRange, false, false) : [], - inFixedColumns: !!cellsInfo.fixedColumnCells ? this._calculateBorderInfo(this._borderModel.getBorders(cellsInfo.fixedColumnCells.cellRange), cellsInfo.fixedColumnCells.cellRange, false, true) : [], - inFixedRows: !!cellsInfo.fixedRowCells ? this._calculateBorderInfo(this._borderModel.getBorders(cellsInfo.fixedRowCells.cellRange), cellsInfo.fixedRowCells.cellRange, true, false) : [], - inNonFixedArea: this._calculateBorderInfo(this._borderModel.getBorders(cellsInfo.nonFixedCells.cellRange), cellsInfo.nonFixedCells.cellRange, true, true) + inFixedCorner: !!cellsInfo.fixedCornerCells + ? this._calculateBorderInfo( + this._borderModel.getBorders( + cellsInfo.fixedCornerCells.cellRange + ), + cellsInfo.fixedCornerCells.cellRange, + false, + false + ) + : [], + inFixedColumns: !!cellsInfo.fixedColumnCells + ? this._calculateBorderInfo( + this._borderModel.getBorders( + cellsInfo.fixedColumnCells.cellRange + ), + cellsInfo.fixedColumnCells.cellRange, + false, + true + ) + : [], + inFixedRows: !!cellsInfo.fixedRowCells + ? this._calculateBorderInfo( + this._borderModel.getBorders( + cellsInfo.fixedRowCells.cellRange + ), + cellsInfo.fixedRowCells.cellRange, + true, + false + ) + : [], + inNonFixedArea: this._calculateBorderInfo( + this._borderModel.getBorders(cellsInfo.nonFixedCells.cellRange), + cellsInfo.nonFixedCells.cellRange, + true, + true + ), }; } @@ -2709,13 +3447,18 @@ export class CanvasRenderer implements ITableEngineRenderer { private _calculateResizerRenderContext(): IResizerRenderContext { const context: IResizerRenderContext = { showResizer: !!this._resizingDragStart, - color: this._options.renderer.canvas.rowColumnResizing.resizerLineColor, - thickness: this._options.renderer.canvas.rowColumnResizing.resizerLineThickness + color: this._options.renderer.canvas.rowColumnResizing + .resizerLineColor, + thickness: + this._options.renderer.canvas.rowColumnResizing + .resizerLineThickness, }; if (!!this._resizingDragStart) { context.isVertical = !this._resizingDragStart.info.overRow; - context.offset = this._resizingDragStart.info.overRow ? this._resizingDragStart.currentY : this._resizingDragStart.currentX; + context.offset = this._resizingDragStart.info.overRow + ? this._resizingDragStart.currentY + : this._resizingDragStart.currentX; } return context; @@ -2728,7 +3471,12 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param adjustBoundsX whether to adjust bounds due to scrolling * @param adjustBoundsY whether to adjust bounds due to scrolling */ - private _calculateBorderInfo(borders: IBorder[][], range: ICellRange, adjustBoundsX: boolean, adjustBoundsY: boolean): IBorderInfo[][] { + private _calculateBorderInfo( + borders: IBorder[][], + range: ICellRange, + adjustBoundsX: boolean, + adjustBoundsY: boolean + ): IBorderInfo[][] { const result: IBorderInfo[][] = []; for (let row = range.startRow; row <= range.endRow; row++) { @@ -2736,12 +3484,21 @@ export class CanvasRenderer implements ITableEngineRenderer { if (!isRowHidden) { const borderInfos: IBorderInfo[] = []; - for (let column = range.startColumn; column <= range.endColumn; column++) { - const isColumnHidden: boolean = this._cellModel.isColumnHidden(column); + for ( + let column = range.startColumn; + column <= range.endColumn; + column++ + ) { + const isColumnHidden: boolean = + this._cellModel.isColumnHidden(column); if (!isColumnHidden) { const info: IBorderInfo = { - border: borders[row - range.startRow][column - range.startColumn], - bounds: this._cellModel.getBounds(CellRange.fromSingleRowColumn(row, column)) + border: borders[row - range.startRow][ + column - range.startColumn + ], + bounds: this._cellModel.getBounds( + CellRange.fromSingleRowColumn(row, column) + ), }; // Correct bounds for current scroll offsets @@ -2789,72 +3546,99 @@ export class CanvasRenderer implements ITableEngineRenderer { left: viewPort.left + fixedColumnsWidth, top: viewPort.top + fixedRowsHeight, width: viewPort.width - fixedColumnsWidth, - height: viewPort.height - fixedRowsHeight + height: viewPort.height - fixedRowsHeight, }); const usedCellRendererNames: Set = new Set(); // Fill "normal" (non-fixed) cells first - const nonFixedCells = this._createCellRenderArea(nonFixedCellsRange, { - left: fixedColumnsWidth, - top: fixedRowsHeight, - width: viewPort.width - fixedColumnsWidth, - height: viewPort.height - fixedRowsHeight - }, true, true, usedCellRendererNames); + const nonFixedCells = this._createCellRenderArea( + nonFixedCellsRange, + { + left: fixedColumnsWidth, + top: fixedRowsHeight, + width: viewPort.width - fixedColumnsWidth, + height: viewPort.height - fixedRowsHeight, + }, + true, + true, + usedCellRendererNames + ); const result: ICellRenderContextCollection = { - nonFixedCells + nonFixedCells, }; // Fill fixed columns (if any) if (fixedColumns > 0) { - result.fixedColumnCells = this._createCellRenderArea({ - startRow: nonFixedCellsRange.startRow - fixedRows, - endRow: nonFixedCellsRange.endRow, - startColumn: 0, - endColumn: fixedColumns - 1 - }, { - left: 0, - top: fixedRowsHeight, - width: fixedColumnsWidth, - height: viewPort.height - fixedRowsHeight - }, false, true, usedCellRendererNames); + result.fixedColumnCells = this._createCellRenderArea( + { + startRow: nonFixedCellsRange.startRow - fixedRows, + endRow: nonFixedCellsRange.endRow, + startColumn: 0, + endColumn: fixedColumns - 1, + }, + { + left: 0, + top: fixedRowsHeight, + width: fixedColumnsWidth, + height: viewPort.height - fixedRowsHeight, + }, + false, + true, + usedCellRendererNames + ); } // Fill fixed rows (if any) if (fixedRows > 0) { - result.fixedRowCells = this._createCellRenderArea({ - startRow: 0, - endRow: fixedRows - 1, - startColumn: nonFixedCellsRange.startColumn - fixedColumns, - endColumn: nonFixedCellsRange.endColumn - }, { - left: fixedColumnsWidth, - top: 0, - width: viewPort.width - fixedColumnsWidth, - height: fixedRowsHeight - }, true, false, usedCellRendererNames); + result.fixedRowCells = this._createCellRenderArea( + { + startRow: 0, + endRow: fixedRows - 1, + startColumn: nonFixedCellsRange.startColumn - fixedColumns, + endColumn: nonFixedCellsRange.endColumn, + }, + { + left: fixedColumnsWidth, + top: 0, + width: viewPort.width - fixedColumnsWidth, + height: fixedRowsHeight, + }, + true, + false, + usedCellRendererNames + ); } // Fill fixed corner cells (if any) if (fixedColumns > 0 && fixedRows > 0) { - result.fixedCornerCells = this._createCellRenderArea({ - startRow: 0, - endRow: fixedRows - 1, - startColumn: 0, - endColumn: fixedColumns - 1 - }, { - left: 0, - top: 0, - width: fixedColumnsWidth, - height: fixedRowsHeight - }, false, false, usedCellRendererNames); + result.fixedCornerCells = this._createCellRenderArea( + { + startRow: 0, + endRow: fixedRows - 1, + startColumn: 0, + endColumn: fixedColumns - 1, + }, + { + left: 0, + top: 0, + width: fixedColumnsWidth, + height: fixedRowsHeight, + }, + false, + false, + usedCellRendererNames + ); } // Cleanup cell renderers that have been used in previous rendering cycles but now not anymore - for (const previouslyUsedCellRendererName of this._lastUsedCellRenderers) { + for (const previouslyUsedCellRendererName of this + ._lastUsedCellRenderers) { if (!usedCellRendererNames.has(previouslyUsedCellRendererName)) { - this._cellRendererLookup.get(previouslyUsedCellRendererName).cleanup(); + this._cellRendererLookup + .get(previouslyUsedCellRendererName) + .cleanup(); } } this._lastUsedCellRenderers = usedCellRendererNames; @@ -2871,7 +3655,13 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param usedCellRendererNames set of used cell renderer names * @returns mapping of renderer names to all cells that need to be rendered with the renderer */ - private _createCellRenderArea(range: ICellRange, viewPortBounds: IRectangle, adjustBoundsX: boolean, adjustBoundsY: boolean, usedCellRendererNames: Set): ICellAreaRenderContext { + private _createCellRenderArea( + range: ICellRange, + viewPortBounds: IRectangle, + adjustBoundsX: boolean, + adjustBoundsY: boolean, + usedCellRendererNames: Set + ): ICellAreaRenderContext { const cellsPerRenderer = new Map(); const cells = this._cellModel.getCells(range); @@ -2887,7 +3677,9 @@ export class CanvasRenderer implements ITableEngineRenderer { bounds.left -= this._scrollOffset.x; } - let cellsToRender: ICellRenderInfo[] = cellsPerRenderer.get(cell.rendererName); + let cellsToRender: ICellRenderInfo[] = cellsPerRenderer.get( + cell.rendererName + ); if (!cellsToRender) { cellsToRender = []; cellsPerRenderer.set(cell.rendererName, cellsToRender); @@ -2896,14 +3688,14 @@ export class CanvasRenderer implements ITableEngineRenderer { cellsToRender.push({ cell, - bounds + bounds, }); } return { cellRange: range, viewPortBounds: viewPortBounds, - cellsPerRenderer + cellsPerRenderer, }; } @@ -2914,10 +3706,15 @@ export class CanvasRenderer implements ITableEngineRenderer { */ public registerCellRenderer(renderer: ICellRenderer): void { if (this._cellRendererLookup.has(renderer.getName())) { - throw new Error(`Cell renderer with name '${renderer.getName()}' already registered`); + throw new Error( + `Cell renderer with name '${renderer.getName()}' already registered` + ); } - this._cellRendererLookup.set(renderer.getName(), renderer as ICanvasCellRenderer); + this._cellRendererLookup.set( + renderer.getName(), + renderer as ICanvasCellRenderer + ); } /** @@ -2925,9 +3722,12 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param rendererName name of the renderer to get */ private _getCellRendererForName(rendererName: string): ICanvasCellRenderer { - const cellRenderer: ICanvasCellRenderer = this._cellRendererLookup.get(rendererName); + const cellRenderer: ICanvasCellRenderer = + this._cellRendererLookup.get(rendererName); if (!cellRenderer) { - throw new Error(`Could not find cell renderer for name '${rendererName}'`); + throw new Error( + `Could not find cell renderer for name '${rendererName}'` + ); } return cellRenderer; @@ -2938,11 +3738,26 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param oldCells the former rendered cells * @param newCells the new cells to render */ - private _cleanupCellViewportCaches(oldCells: ICellRenderContextCollection, newCells: ICellRenderContextCollection): void { - this._cleanupCellViewportCachesForOverlappingCellRanges(oldCells.nonFixedCells.cellRange, newCells.nonFixedCells.cellRange); - this._cleanupCellViewportCachesForOverlappingCellRanges(oldCells.fixedRowCells.cellRange, newCells.fixedRowCells.cellRange); - this._cleanupCellViewportCachesForOverlappingCellRanges(oldCells.fixedColumnCells.cellRange, newCells.fixedColumnCells.cellRange); - this._cleanupCellViewportCachesForOverlappingCellRanges(oldCells.fixedCornerCells.cellRange, newCells.fixedCornerCells.cellRange); + private _cleanupCellViewportCaches( + oldCells: ICellRenderContextCollection, + newCells: ICellRenderContextCollection + ): void { + this._cleanupCellViewportCachesForOverlappingCellRanges( + oldCells.nonFixedCells.cellRange, + newCells.nonFixedCells.cellRange + ); + this._cleanupCellViewportCachesForOverlappingCellRanges( + oldCells.fixedRowCells.cellRange, + newCells.fixedRowCells.cellRange + ); + this._cleanupCellViewportCachesForOverlappingCellRanges( + oldCells.fixedColumnCells.cellRange, + newCells.fixedColumnCells.cellRange + ); + this._cleanupCellViewportCachesForOverlappingCellRanges( + oldCells.fixedCornerCells.cellRange, + newCells.fixedCornerCells.cellRange + ); } /** @@ -2966,11 +3781,20 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param oldRange the old cell range * @param newRange the new cell range */ - private _cleanupCellViewportCachesForOverlappingCellRanges(oldRange: ICellRange, newRange: ICellRange): void { - const candidateRanges: ICellRange[] = CellRangeUtil.xor(oldRange, newRange); + private _cleanupCellViewportCachesForOverlappingCellRanges( + oldRange: ICellRange, + newRange: ICellRange + ): void { + const candidateRanges: ICellRange[] = CellRangeUtil.xor( + oldRange, + newRange + ); for (const range of candidateRanges) { - this._cleanupCellViewportCachesForCellRange(range, (cellRange) => !CellRangeUtil.overlap(cellRange, newRange)); + this._cleanupCellViewportCachesForCellRange( + range, + (cellRange) => !CellRangeUtil.overlap(cellRange, newRange) + ); } } @@ -2981,23 +3805,33 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param mergedCellCleanupCheck check function whether to cleanup a merged cell as well * @param includeHidden whether to include hidden cells in the cleanup (default=false) */ - private _cleanupCellViewportCachesForCellRange(range: ICellRange, mergedCellCleanupCheck: (range) => boolean, includeHidden?: boolean): void { + private _cleanupCellViewportCachesForCellRange( + range: ICellRange, + mergedCellCleanupCheck: (range) => boolean, + includeHidden?: boolean + ): void { const cells: ICell[] = this._cellModel.getCells(range, { - includeHidden + includeHidden, }); for (const cell of cells) { // For merged cells make sure that the cell is completely disappeared from the viewport before clearing - const isMergedCell: boolean = !CellRangeUtil.isSingleRowColumnRange(cell.range); + const isMergedCell: boolean = !CellRangeUtil.isSingleRowColumnRange( + cell.range + ); if (isMergedCell) { - const cleanupMergedCell: boolean = mergedCellCleanupCheck(cell.range); + const cleanupMergedCell: boolean = mergedCellCleanupCheck( + cell.range + ); if (!cleanupMergedCell) { continue; } } // Clearing viewport cache property since the cell is no more visible - this._getCellRendererForName(cell.rendererName).onDisappearing(cell); + this._getCellRendererForName(cell.rendererName).onDisappearing( + cell + ); cell.viewportCache = undefined; } @@ -3009,7 +3843,8 @@ export class CanvasRenderer implements ITableEngineRenderer { private _render(): void { let creatingRenderingContextTime = window.performance.now(); const renderingContext: IRenderContext = this._createRenderingContext(); - creatingRenderingContextTime = window.performance.now() - creatingRenderingContextTime; + creatingRenderingContextTime = + window.performance.now() - creatingRenderingContextTime; if (!!this._requestAnimationFrameID) { // Already requested animation frame that has not been executed yet -> cancel it and reschedule one @@ -3020,7 +3855,10 @@ export class CanvasRenderer implements ITableEngineRenderer { this._requestAnimationFrameID = null; // Mark as executed if (!!this._lastRenderingContext) { - this._cleanupCellViewportCaches(this._lastRenderingContext.cells, renderingContext.cells); + this._cleanupCellViewportCaches( + this._lastRenderingContext.cells, + renderingContext.cells + ); } this._lastRenderingContext = renderingContext; @@ -3041,17 +3879,45 @@ export class CanvasRenderer implements ITableEngineRenderer { ctx.translate(0.5, 0.5); // Render "normal" (non-fixed) cells first - CanvasRenderer._renderArea(ctx, renderingContext, renderingContext.cells.nonFixedCells, renderingContext.borders.inNonFixedArea, renderingContext.selection?.inNonFixedArea, true); + CanvasRenderer._renderArea( + ctx, + renderingContext, + renderingContext.cells.nonFixedCells, + renderingContext.borders.inNonFixedArea, + renderingContext.selection?.inNonFixedArea, + true + ); // Then render fixed cells (if any). if (!!renderingContext.cells.fixedColumnCells) { - CanvasRenderer._renderArea(ctx, renderingContext, renderingContext.cells.fixedColumnCells, renderingContext.borders.inFixedColumns, renderingContext.selection?.inFixedColumns, true); + CanvasRenderer._renderArea( + ctx, + renderingContext, + renderingContext.cells.fixedColumnCells, + renderingContext.borders.inFixedColumns, + renderingContext.selection?.inFixedColumns, + true + ); } if (!!renderingContext.cells.fixedRowCells) { - CanvasRenderer._renderArea(ctx, renderingContext, renderingContext.cells.fixedRowCells, renderingContext.borders.inFixedRows, renderingContext.selection?.inFixedRows, true); + CanvasRenderer._renderArea( + ctx, + renderingContext, + renderingContext.cells.fixedRowCells, + renderingContext.borders.inFixedRows, + renderingContext.selection?.inFixedRows, + true + ); } if (!!renderingContext.cells.fixedCornerCells) { - CanvasRenderer._renderArea(ctx, renderingContext, renderingContext.cells.fixedCornerCells, renderingContext.borders.inFixedCorner, renderingContext.selection?.inFixedCorner, true); + CanvasRenderer._renderArea( + ctx, + renderingContext, + renderingContext.cells.fixedCornerCells, + renderingContext.borders.inFixedCorner, + renderingContext.selection?.inFixedCorner, + true + ); } // Render scrollbars @@ -3061,7 +3927,11 @@ export class CanvasRenderer implements ITableEngineRenderer { CanvasRenderer._renderResizerVisualization(ctx, renderingContext); if (this._options.misc.debug) { - console.log(`RENDERING: ${window.performance.now() - renderingTime}ms, CREATING RENDERING CONTEXT: ${creatingRenderingContextTime}ms`); + console.log( + `RENDERING: ${ + window.performance.now() - renderingTime + }ms, CREATING RENDERING CONTEXT: ${creatingRenderingContextTime}ms` + ); } if (this._updateOverlaysAfterRenderCycle) { @@ -3105,7 +3975,12 @@ export class CanvasRenderer implements ITableEngineRenderer { if (clipArea) { // Clip drawing to allowed area const clippingRegion = new Path2D(); - clippingRegion.rect(cellArea.viewPortBounds.left, cellArea.viewPortBounds.top, cellArea.viewPortBounds.width, cellArea.viewPortBounds.height); + clippingRegion.rect( + cellArea.viewPortBounds.left, + cellArea.viewPortBounds.top, + cellArea.viewPortBounds.width, + cellArea.viewPortBounds.height + ); ctx.save(); ctx.clip(clippingRegion); restoreContext = true; @@ -3130,14 +4005,29 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param context the rendering context * @param cellArea to render */ - private static _renderAreaCells(ctx: CanvasRenderingContext2D, context: IRenderContext, cellArea: ICellAreaRenderContext): void { + private static _renderAreaCells( + ctx: CanvasRenderingContext2D, + context: IRenderContext, + cellArea: ICellAreaRenderContext + ): void { // Clear area first - ctx.clearRect(cellArea.viewPortBounds.left, cellArea.viewPortBounds.top, cellArea.viewPortBounds.width, cellArea.viewPortBounds.height); + ctx.clearRect( + cellArea.viewPortBounds.left, + cellArea.viewPortBounds.top, + cellArea.viewPortBounds.width, + cellArea.viewPortBounds.height + ); - for (const [rendererName, cellsToRender] of cellArea.cellsPerRenderer.entries()) { - const cellRenderer: ICanvasCellRenderer = context.renderers.get(rendererName); + for (const [ + rendererName, + cellsToRender, + ] of cellArea.cellsPerRenderer.entries()) { + const cellRenderer: ICanvasCellRenderer = + context.renderers.get(rendererName); if (!cellRenderer) { - throw new Error(`Could not find cell renderer for name '${rendererName}'`); + throw new Error( + `Could not find cell renderer for name '${rendererName}'` + ); } if (cellsToRender.length === 0) { @@ -3147,7 +4037,11 @@ export class CanvasRenderer implements ITableEngineRenderer { cellRenderer.before(ctx, context); for (const cellToRender of cellsToRender) { - cellRenderer.render(ctx, cellToRender.cell, cellToRender.bounds); + cellRenderer.render( + ctx, + cellToRender.cell, + cellToRender.bounds + ); } // Notify cell renderer that we have rendered all cells for this rendering cycle. @@ -3162,7 +4056,11 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param context the rendering context * @param borders to render */ - private static _renderBorders(ctx: CanvasRenderingContext2D, context: IRenderContext, borders: IBorderInfo[][]): void { + private static _renderBorders( + ctx: CanvasRenderingContext2D, + context: IRenderContext, + borders: IBorderInfo[][] + ): void { for (let row = 0; row < borders.length; row++) { for (let column = 0; column < borders[row].length; column++) { const borderInfo: IBorderInfo = borders[row][column]; @@ -3196,45 +4094,133 @@ export class CanvasRenderer implements ITableEngineRenderer { // Draw right border if (!!border.right) { - const upperCrossingBorderEnvironment = CanvasRenderer._determineCrossingBorderEnvironment( - [border.right, (row > 0 ? borders[row - 1][column].border.right : null)], - [border.top, (column < borders[row].length - 1 ? borders[row][column + 1].border.top : null)] - ); - let topOffset: number = (upperCrossingBorderEnvironment.dominantBorderSide === border.right ? -1 : 1) * (!!upperCrossingBorderEnvironment.dominantHorizontalSide ? upperCrossingBorderEnvironment.dominantHorizontalSide.size / 2 : 0); - - const lowerCrossingBorderEnvironment = CanvasRenderer._determineCrossingBorderEnvironment( - [border.right, (row < borders.length - 1 ? borders[row + 1][column].border.right : null)], - [border.bottom, (column < borders[row].length - 1 ? borders[row][column + 1].border.bottom : null)] - ); - let bottomOffset: number = (lowerCrossingBorderEnvironment.dominantBorderSide === border.right ? -1 : 1) * (!!lowerCrossingBorderEnvironment.dominantHorizontalSide ? lowerCrossingBorderEnvironment.dominantHorizontalSide.size / 2 : 0); + const upperCrossingBorderEnvironment = + CanvasRenderer._determineCrossingBorderEnvironment( + [ + border.right, + row > 0 + ? borders[row - 1][column].border.right + : null, + ], + [ + border.top, + column < borders[row].length - 1 + ? borders[row][column + 1].border.top + : null, + ] + ); + let topOffset: number = + (upperCrossingBorderEnvironment.dominantBorderSide === + border.right + ? -1 + : 1) * + (!!upperCrossingBorderEnvironment.dominantHorizontalSide + ? upperCrossingBorderEnvironment + .dominantHorizontalSide.size / 2 + : 0); + + const lowerCrossingBorderEnvironment = + CanvasRenderer._determineCrossingBorderEnvironment( + [ + border.right, + row < borders.length - 1 + ? borders[row + 1][column].border.right + : null, + ], + [ + border.bottom, + column < borders[row].length - 1 + ? borders[row][column + 1].border.bottom + : null, + ] + ); + let bottomOffset: number = + (lowerCrossingBorderEnvironment.dominantBorderSide === + border.right + ? -1 + : 1) * + (!!lowerCrossingBorderEnvironment.dominantHorizontalSide + ? lowerCrossingBorderEnvironment + .dominantHorizontalSide.size / 2 + : 0); CanvasRenderer._applyBorderStyle(ctx, border.right); ctx.beginPath(); - ctx.moveTo(bounds.left + bounds.width, bounds.top + topOffset); - ctx.lineTo(bounds.left + bounds.width, bounds.top + bounds.height - bottomOffset); + ctx.moveTo( + bounds.left + bounds.width, + bounds.top + topOffset + ); + ctx.lineTo( + bounds.left + bounds.width, + bounds.top + bounds.height - bottomOffset + ); ctx.stroke(); } // Draw lower border if (!!border.bottom) { - const leftCrossingBorderEnvironment = CanvasRenderer._determineCrossingBorderEnvironment( - [border.left, (row < borders.length - 1 ? borders[row + 1][column].border.left : null)], - [border.bottom, (column > 0 ? borders[row][column - 1].border.bottom : null)] - ); - let leftOffset: number = (leftCrossingBorderEnvironment.dominantBorderSide === border.bottom ? -1 : 1) * (!!leftCrossingBorderEnvironment.dominantVerticalSide ? leftCrossingBorderEnvironment.dominantVerticalSide.size / 2 : 0); - - const rightCrossingBorderEnvironment = CanvasRenderer._determineCrossingBorderEnvironment( - [border.right, (row < borders.length - 1 ? borders[row + 1][column].border.right : null)], - [border.bottom, (column < borders[row].length - 1 ? borders[row][column + 1].border.bottom : null)] - ); - let rightOffset: number = (rightCrossingBorderEnvironment.dominantBorderSide === border.bottom ? -1 : 1) * (!!rightCrossingBorderEnvironment.dominantVerticalSide ? rightCrossingBorderEnvironment.dominantVerticalSide.size / 2 : 0); + const leftCrossingBorderEnvironment = + CanvasRenderer._determineCrossingBorderEnvironment( + [ + border.left, + row < borders.length - 1 + ? borders[row + 1][column].border.left + : null, + ], + [ + border.bottom, + column > 0 + ? borders[row][column - 1].border.bottom + : null, + ] + ); + let leftOffset: number = + (leftCrossingBorderEnvironment.dominantBorderSide === + border.bottom + ? -1 + : 1) * + (!!leftCrossingBorderEnvironment.dominantVerticalSide + ? leftCrossingBorderEnvironment.dominantVerticalSide + .size / 2 + : 0); + + const rightCrossingBorderEnvironment = + CanvasRenderer._determineCrossingBorderEnvironment( + [ + border.right, + row < borders.length - 1 + ? borders[row + 1][column].border.right + : null, + ], + [ + border.bottom, + column < borders[row].length - 1 + ? borders[row][column + 1].border.bottom + : null, + ] + ); + let rightOffset: number = + (rightCrossingBorderEnvironment.dominantBorderSide === + border.bottom + ? -1 + : 1) * + (!!rightCrossingBorderEnvironment.dominantVerticalSide + ? rightCrossingBorderEnvironment + .dominantVerticalSide.size / 2 + : 0); CanvasRenderer._applyBorderStyle(ctx, border.bottom); ctx.beginPath(); - ctx.moveTo(bounds.left + leftOffset, bounds.top + bounds.height); - ctx.lineTo(bounds.left + bounds.width - rightOffset, bounds.top + bounds.height); + ctx.moveTo( + bounds.left + leftOffset, + bounds.top + bounds.height + ); + ctx.lineTo( + bounds.left + bounds.width - rightOffset, + bounds.top + bounds.height + ); ctx.stroke(); } } @@ -3249,19 +4235,29 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param verticalSides border sides running vertically * @param horizontalSides border sides running horizontally */ - private static _determineCrossingBorderEnvironment(verticalSides: IBorderSide[], horizontalSides: IBorderSide[]): ICrossingBorderEnvironment { + private static _determineCrossingBorderEnvironment( + verticalSides: IBorderSide[], + horizontalSides: IBorderSide[] + ): ICrossingBorderEnvironment { return { - dominantBorderSide: CanvasRenderer._determineDominantBorderSide([...verticalSides, ...horizontalSides]), - dominantHorizontalSide: CanvasRenderer._determineDominantBorderSide(horizontalSides), - dominantVerticalSide: CanvasRenderer._determineDominantBorderSide(verticalSides) - } + dominantBorderSide: CanvasRenderer._determineDominantBorderSide([ + ...verticalSides, + ...horizontalSides, + ]), + dominantHorizontalSide: + CanvasRenderer._determineDominantBorderSide(horizontalSides), + dominantVerticalSide: + CanvasRenderer._determineDominantBorderSide(verticalSides), + }; } /** * Determine the dominant border side among the passed. * @param sides to determine dominant border side in */ - private static _determineDominantBorderSide(sides: IBorderSide[]): IBorderSide { + private static _determineDominantBorderSide( + sides: IBorderSide[] + ): IBorderSide { let dominant: IBorderSide = null; for (const side of sides) { if (!side) { @@ -3276,7 +4272,11 @@ export class CanvasRenderer implements ITableEngineRenderer { // Has same size -> let the color density decide (higher density wins) if (dominant.isDefault) { dominant = side; - } else if (!side.isDefault && CanvasRenderer._calculateColorDensity(side.color) < CanvasRenderer._calculateColorDensity(dominant.color)) { + } else if ( + !side.isDefault && + CanvasRenderer._calculateColorDensity(side.color) < + CanvasRenderer._calculateColorDensity(dominant.color) + ) { dominant = side; } } @@ -3290,7 +4290,9 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param color to calculate density for */ private static _calculateColorDensity(color: IColor): number { - return ((color.red << 16) + (color.green << 8) + color.blue) * color.alpha; + return ( + ((color.red << 16) + (color.green << 8) + color.blue) * color.alpha + ); } /** @@ -3298,10 +4300,13 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param ctx to apply style to * @param side to get style from */ - private static _applyBorderStyle(ctx: CanvasRenderingContext2D, side: IBorderSide): void { + private static _applyBorderStyle( + ctx: CanvasRenderingContext2D, + side: IBorderSide + ): void { ctx.strokeStyle = Colors.toStyleStr(side.color); ctx.lineWidth = side.size; - ctx.lineCap = "butt"; + ctx.lineCap = 'butt'; if (side.style === BorderStyle.SOLID) { ctx.setLineDash([]); @@ -3317,7 +4322,10 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param ctx to render with * @param context the rendering context */ - private static _renderResizerVisualization(ctx: CanvasRenderingContext2D, context: IRenderContext): void { + private static _renderResizerVisualization( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { if (!context.resizer.showResizer) { return; } @@ -3345,28 +4353,39 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param ctx to render with * @param context the rendering context */ - private static _renderScrollBars(ctx: CanvasRenderingContext2D, context: IRenderContext): void { + private static _renderScrollBars( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { ctx.fillStyle = Colors.toStyleStr(context.scrollBar.color); // Draw vertical scrollbar (if needed) if (!!context.scrollBar.vertical) { - CanvasUtil.makeRoundRectPath(ctx, { - left: context.scrollBar.vertical.x, - top: context.scrollBar.vertical.y, - width: context.scrollBar.vertical.size, - height: context.scrollBar.vertical.length - }, context.scrollBar.cornerRadius); + CanvasUtil.makeRoundRectPath( + ctx, + { + left: context.scrollBar.vertical.x, + top: context.scrollBar.vertical.y, + width: context.scrollBar.vertical.size, + height: context.scrollBar.vertical.length, + }, + context.scrollBar.cornerRadius + ); ctx.fill(); } // Draw horizontal scrollbar (if needed) if (!!context.scrollBar.horizontal) { - CanvasUtil.makeRoundRectPath(ctx, { - left: context.scrollBar.horizontal.x, - top: context.scrollBar.horizontal.y, - width: context.scrollBar.horizontal.length, - height: context.scrollBar.horizontal.size - }, context.scrollBar.cornerRadius); + CanvasUtil.makeRoundRectPath( + ctx, + { + left: context.scrollBar.horizontal.x, + top: context.scrollBar.horizontal.y, + width: context.scrollBar.horizontal.length, + height: context.scrollBar.horizontal.size, + }, + context.scrollBar.cornerRadius + ); ctx.fill(); } } @@ -3377,72 +4396,167 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param context the rendering context * @param infos rendering infos about selection rectangles to draw */ - private static _renderSelections(ctx: CanvasRenderingContext2D, context: IRenderContext, infos: ISelectionRenderInfo[]): void { - ctx.fillStyle = Colors.toStyleStr(context.focused ? context.selection.options.secondary.backgroundColor : context.selection.options.secondary.backgroundColorUnfocused); - ctx.strokeStyle = Colors.toStyleStr(context.focused ? context.selection.options.secondary.borderColor : context.selection.options.secondary.borderColorUnfocused); + private static _renderSelections( + ctx: CanvasRenderingContext2D, + context: IRenderContext, + infos: ISelectionRenderInfo[] + ): void { + ctx.fillStyle = Colors.toStyleStr( + context.focused + ? context.selection.options.secondary.backgroundColor + : context.selection.options.secondary.backgroundColorUnfocused + ); + ctx.strokeStyle = Colors.toStyleStr( + context.focused + ? context.selection.options.secondary.borderColor + : context.selection.options.secondary.borderColorUnfocused + ); ctx.lineWidth = context.selection.options.borderSize; for (const info of infos) { if (info.isPrimary) { - ctx.fillStyle = Colors.toStyleStr(context.focused ? context.selection.options.primary.backgroundColor : context.selection.options.primary.backgroundColorUnfocused); - ctx.strokeStyle = Colors.toStyleStr(context.focused ? context.selection.options.primary.borderColor : context.selection.options.primary.borderColorUnfocused); + ctx.fillStyle = Colors.toStyleStr( + context.focused + ? context.selection.options.primary.backgroundColor + : context.selection.options.primary + .backgroundColorUnfocused + ); + ctx.strokeStyle = Colors.toStyleStr( + context.focused + ? context.selection.options.primary.borderColor + : context.selection.options.primary.borderColorUnfocused + ); // Fill area over initial (if necessary) if (info.initial.top - info.bounds.top > 0) { - ctx.fillRect(info.bounds.left, info.bounds.top, info.bounds.width, info.initial.top - info.bounds.top); + ctx.fillRect( + info.bounds.left, + info.bounds.top, + info.bounds.width, + info.initial.top - info.bounds.top + ); } // Fill area left of initial (if necessary) if (info.initial.left - info.bounds.left > 0) { - ctx.fillRect(info.bounds.left, info.initial.top, info.initial.left - info.bounds.left, info.initial.height); + ctx.fillRect( + info.bounds.left, + info.initial.top, + info.initial.left - info.bounds.left, + info.initial.height + ); } // Fill area under initial (if necessary) - const underHeight: number = (info.bounds.top + info.bounds.height) - (info.initial.top + info.initial.height); + const underHeight: number = + info.bounds.top + + info.bounds.height - + (info.initial.top + info.initial.height); if (underHeight > 0) { - ctx.fillRect(info.bounds.left, info.initial.top + info.initial.height, info.bounds.width, underHeight); + ctx.fillRect( + info.bounds.left, + info.initial.top + info.initial.height, + info.bounds.width, + underHeight + ); } // Fill area right of initial (if necessary) - const rightWidth: number = (info.bounds.left + info.bounds.width) - (info.initial.left + info.initial.width); + const rightWidth: number = + info.bounds.left + + info.bounds.width - + (info.initial.left + info.initial.width); if (rightWidth > 0) { - ctx.fillRect(info.initial.left + info.initial.width, info.initial.top, rightWidth, info.initial.height); + ctx.fillRect( + info.initial.left + info.initial.width, + info.initial.top, + rightWidth, + info.initial.height + ); } // Stroke - ctx.strokeRect(info.bounds.left, info.bounds.top, info.bounds.width, info.bounds.height); + ctx.strokeRect( + info.bounds.left, + info.bounds.top, + info.bounds.width, + info.bounds.height + ); // Render copy handle (if enabled) if (info.renderCopyHandle) { - const copyHandleSize: number = context.selection.options.copyHandle.size; - const copyHandlePadding: number = context.selection.options.copyHandle.padding; - - const copyHandleX: number = info.bounds.left + info.bounds.width - copyHandleSize / 2; - const copyHandleY: number = info.bounds.top + info.bounds.height - copyHandleSize / 2; + const copyHandleSize: number = + context.selection.options.copyHandle.size; + const copyHandlePadding: number = + context.selection.options.copyHandle.padding; + + const copyHandleX: number = + info.bounds.left + + info.bounds.width - + copyHandleSize / 2; + const copyHandleY: number = + info.bounds.top + + info.bounds.height - + copyHandleSize / 2; // Fill padding rectangle first ctx.fillStyle = Colors.toStyleStr(Colors.WHITE); - ctx.fillRect(copyHandleX - copyHandlePadding, copyHandleY - copyHandlePadding, copyHandleSize + copyHandlePadding, copyHandleSize + copyHandlePadding) + ctx.fillRect( + copyHandleX - copyHandlePadding, + copyHandleY - copyHandlePadding, + copyHandleSize + copyHandlePadding, + copyHandleSize + copyHandlePadding + ); // Fill copy-handle rectangle next - ctx.fillStyle = Colors.toStyleStr(context.focused ? context.selection.options.primary.borderColor : context.selection.options.primary.borderColorUnfocused); - ctx.fillRect(copyHandleX, copyHandleY, copyHandleSize, copyHandleSize); + ctx.fillStyle = Colors.toStyleStr( + context.focused + ? context.selection.options.primary.borderColor + : context.selection.options.primary + .borderColorUnfocused + ); + ctx.fillRect( + copyHandleX, + copyHandleY, + copyHandleSize, + copyHandleSize + ); context.selection.copyHandle.isRendered = true; context.selection.copyHandle.bounds = { top: copyHandleY, left: copyHandleX, width: copyHandleSize, - height: copyHandleSize + height: copyHandleSize, }; } // Reset colors - ctx.fillStyle = Colors.toStyleStr(context.focused ? context.selection.options.secondary.backgroundColor : context.selection.options.secondary.backgroundColorUnfocused); - ctx.strokeStyle = Colors.toStyleStr(context.focused ? context.selection.options.secondary.borderColor : context.selection.options.secondary.borderColorUnfocused); + ctx.fillStyle = Colors.toStyleStr( + context.focused + ? context.selection.options.secondary.backgroundColor + : context.selection.options.secondary + .backgroundColorUnfocused + ); + ctx.strokeStyle = Colors.toStyleStr( + context.focused + ? context.selection.options.secondary.borderColor + : context.selection.options.secondary + .borderColorUnfocused + ); } else { - ctx.fillRect(info.bounds.left, info.bounds.top, info.bounds.width, info.bounds.height); - ctx.strokeRect(info.bounds.left, info.bounds.top, info.bounds.width, info.bounds.height); + ctx.fillRect( + info.bounds.left, + info.bounds.top, + info.bounds.width, + info.bounds.height + ); + ctx.strokeRect( + info.bounds.left, + info.bounds.top, + info.bounds.width, + info.bounds.height + ); } } } @@ -3458,7 +4572,7 @@ export class CanvasRenderer implements ITableEngineRenderer { this._overlayContainer.appendChild(overlay.element); // Set initial styles - overlay.element.style.position = "absolute"; + overlay.element.style.position = 'absolute'; // Initially layout the overlay element this._layoutOverlay(overlay); @@ -3478,9 +4592,10 @@ export class CanvasRenderer implements ITableEngineRenderer { * Update all overlays. */ private _updateOverlays(): void { - const toUpdate: IOverlay[] = this._overlaysToUpdateAfterRenderCycle.length > 0 - ? this._overlaysToUpdateAfterRenderCycle - : this.getOverlays(); + const toUpdate: IOverlay[] = + this._overlaysToUpdateAfterRenderCycle.length > 0 + ? this._overlaysToUpdateAfterRenderCycle + : this.getOverlays(); for (const overlay of toUpdate) { this._layoutOverlay(overlay); @@ -3496,11 +4611,25 @@ export class CanvasRenderer implements ITableEngineRenderer { * @param overlay to layout */ private _layoutOverlay(overlay: IOverlay): void { - const viewPortHeight: number = !!this._lastRenderingContext ? this._lastRenderingContext.viewPort.height : 0; - const viewPortWidth: number = !!this._lastRenderingContext ? this._lastRenderingContext.viewPort.width : 0; + const viewPortHeight: number = !!this._lastRenderingContext + ? this._lastRenderingContext.viewPort.height + : 0; + const viewPortWidth: number = !!this._lastRenderingContext + ? this._lastRenderingContext.viewPort.width + : 0; - const fixedRowsHeight: number = !!this._lastRenderingContext && !!this._lastRenderingContext.cells.fixedRowCells ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds.height : 0; - const fixedColumnsWidth: number = !!this._lastRenderingContext && !!this._lastRenderingContext.cells.fixedColumnCells ? this._lastRenderingContext.cells.fixedColumnCells.viewPortBounds.width : 0; + const fixedRowsHeight: number = + !!this._lastRenderingContext && + !!this._lastRenderingContext.cells.fixedRowCells + ? this._lastRenderingContext.cells.fixedRowCells.viewPortBounds + .height + : 0; + const fixedColumnsWidth: number = + !!this._lastRenderingContext && + !!this._lastRenderingContext.cells.fixedColumnCells + ? this._lastRenderingContext.cells.fixedColumnCells + .viewPortBounds.width + : 0; let top: number = overlay.bounds.top; let height: number = overlay.bounds.height; @@ -3523,7 +4652,8 @@ export class CanvasRenderer implements ITableEngineRenderer { let left: number = overlay.bounds.left; let width: number = overlay.bounds.width; - const isInFixedColumns: boolean = overlay.bounds.left < fixedColumnsWidth; + const isInFixedColumns: boolean = + overlay.bounds.left < fixedColumnsWidth; if (isInFixedColumns) { // Overlaps with fixed columns const remaining: number = fixedColumnsWidth - overlay.bounds.left; @@ -3540,36 +4670,42 @@ export class CanvasRenderer implements ITableEngineRenderer { left = left - this._scrollOffset.x; } - const isVisible: boolean = height > 0 && top < viewPortHeight - && width > 0 && left < viewPortWidth; + const isVisible: boolean = + height > 0 && + top < viewPortHeight && + width > 0 && + left < viewPortWidth; if (isVisible) { - overlay.element.style.display = "block"; - overlay.element.style.overflow = "hidden"; + overlay.element.style.display = 'block'; + overlay.element.style.overflow = 'hidden'; overlay.element.style.left = `${left * this.getZoom()}px`; overlay.element.style.top = `${top * this.getZoom()}px`; overlay.element.style.width = `${overlay.bounds.width}px`; overlay.element.style.height = `${overlay.bounds.height}px`; - if (width < overlay.bounds.width || height < overlay.bounds.height) { + if ( + width < overlay.bounds.width || + height < overlay.bounds.height + ) { // Add clipping const leftClipping: number = overlay.bounds.width - width; const topClipping: number = overlay.bounds.height - height; overlay.element.style.clipPath = `inset(${topClipping}px 0 0 ${leftClipping}px)`; } else if (overlay.element.style.clipPath.length > 0) { - overlay.element.style.clipPath = ""; + overlay.element.style.clipPath = ''; } if (this.getZoom() > 1) { overlay.element.style.transform = `scale(${this.getZoom()})`; - overlay.element.style.transformOrigin = "left top"; + overlay.element.style.transformOrigin = 'left top'; } else if (overlay.element.style.transform.length > 0) { - overlay.element.style.transform = ""; - overlay.element.style.transformOrigin = ""; + overlay.element.style.transform = ''; + overlay.element.style.transformOrigin = ''; } } else { - overlay.element.style.display = "none"; + overlay.element.style.display = 'none'; } } @@ -3591,14 +4727,12 @@ export class CanvasRenderer implements ITableEngineRenderer { public getOverlays(): IOverlay[] { return this._overlays; } - } /** * Context filled with data used to render the table. */ export interface IRenderContext { - /** * Whether the table is focused. */ @@ -3643,14 +4777,12 @@ export interface IRenderContext { * Lookup for cell renderers. */ renderers: Map; - } /** * Rendering context of borders. */ interface IBorderRenderContext { - /** * Borders completely contained in the non-fixed area. */ @@ -3670,14 +4802,12 @@ interface IBorderRenderContext { * Borders to be displayed above all areas. */ inFixedCorner: IBorderInfo[][]; - } /** * Infos used to draw borders. */ interface IBorderInfo { - /** * Bounds of the cell to paint borders for. */ @@ -3687,14 +4817,12 @@ interface IBorderInfo { * Border to paint for the cell. */ border: IBorder; - } /** * Rendering context of selections. */ interface ISelectionRenderContext { - /** * Selection options. */ @@ -3724,14 +4852,12 @@ interface ISelectionRenderContext { * Selection rectangles to be displayed above all areas. */ inFixedCorner: ISelectionRenderInfo[]; - } /** * Rendering information about the copy handle. */ interface ICopyHandleRenderingInfo { - /** * Whether the copy-handle is rendered (visible). */ @@ -3741,14 +4867,12 @@ interface ICopyHandleRenderingInfo { * Bounds of the copy-handle in the viewport. */ bounds?: IRectangle; - } /** * Rendering info about one selection. */ interface ISelectionRenderInfo { - /** * Whether the selection is the primary selection. */ @@ -3769,14 +4893,12 @@ interface ISelectionRenderInfo { * Whether the copy-handle should be rendered. */ renderCopyHandle: boolean; - } /** * Collections of all cells to render. */ interface ICellRenderContextCollection { - /** * "Normal" (non-fixed) cells to render. */ @@ -3796,7 +4918,6 @@ interface ICellRenderContextCollection { * Fixed column cells to render. */ fixedColumnCells?: ICellAreaRenderContext; - } /** @@ -3804,7 +4925,6 @@ interface ICellRenderContextCollection { * fixed row cells, "normal" (non-fixed) cells. */ interface ICellAreaRenderContext { - /** * Total bounds of the cell area in the viewport. */ @@ -3819,14 +4939,12 @@ interface ICellAreaRenderContext { * Cells to render per renderer name. */ cellsPerRenderer: Map; - } /** * Rendering info for a single cell. */ interface ICellRenderInfo { - /** * Cell to render. */ @@ -3836,14 +4954,12 @@ interface ICellRenderInfo { * Bounds of the cell. */ bounds: IRectangle; - } /** * Scroll bar rendering context. */ interface IScrollBarRenderContext { - /** * Color of the scroll bars. * Format is four floats (RGBA). @@ -3866,14 +4982,12 @@ interface IScrollBarRenderContext { * Only set if a scrollbar is needed. */ horizontal?: IScrollBarAxisRenderContext; - } /** * Rendering context for a scrollbar axis (horizontal or vertical). */ interface IScrollBarAxisRenderContext { - /** * Length of the scroll bar. */ @@ -3895,14 +5009,12 @@ interface IScrollBarAxisRenderContext { * Offset from top of the scrollbar. */ y: number; - } /** * Description of a scrollbar scroll offset. */ interface IScrollOffset { - /** * Current offset from left. */ @@ -3912,14 +5024,12 @@ interface IScrollOffset { * Current offset from top. */ y: number; - } /** * Context of a mouse/touch scroll dragging. */ interface IScrollBarDragContext { - /** * Whether scrolling vertical. */ @@ -3949,14 +5059,12 @@ interface IScrollBarDragContext { * Scroll offset to the start of the dragging. */ startScrollOffset: IScrollOffset; - } /** * Context holding info about a workspace dragging via mouse/touch. */ interface IMouseDragContext { - /** * Start x-offset. */ @@ -3971,14 +5079,12 @@ interface IMouseDragContext { * Scroll offset to the start of the dragging. */ startScrollOffset: IScrollOffset; - } /** * Context holding info about a current touch zooming via pinch gesture. */ interface ITouchZoomContext { - /** * Initial distance between the two fingers. */ @@ -3988,14 +5094,12 @@ interface ITouchZoomContext { * Zoom level when zooming has started. */ startZoom: number; - } /** * Context holding info about a workspace dragging via touch. */ interface ITouchPanContext extends IMouseDragContext { - /** * Whether the panning did not move and is thus a tap event. */ @@ -4025,14 +5129,12 @@ interface ITouchPanContext extends IMouseDragContext { * Last movement timestamp. */ lastTimestamp: number; - } /** * Context describing the current auto-scrolling state. */ interface IAutoScrollContext { - /** * ID of the current animation frame requested. */ @@ -4057,14 +5159,12 @@ interface IAutoScrollContext { * Acceleration per second (may be negative (slowing) or positive (accelerating)) */ acceleration: number; - } /** * Environment of crossing borders. */ interface ICrossingBorderEnvironment { - /** * Reference to the dominant border side. */ @@ -4079,11 +5179,9 @@ interface ICrossingBorderEnvironment { * Reference to the dominant horizontal border side. */ dominantVerticalSide: IBorderSide; - } interface IResizingDragStart { - /** * The start x coordinate. */ @@ -4108,14 +5206,12 @@ interface IResizingDragStart { * Initial info of the resizer. */ info: IResizerInfo; - } /** * Info about a row/column resizer. */ interface IResizerInfo { - /** * Whether the mouse is over a resizer. */ @@ -4130,14 +5226,12 @@ interface IResizerInfo { * Index of the row/column resizing. */ index?: number; - } /** * Render context for the resizer (if any). */ interface IResizerRenderContext { - /** * Whether to render a resizer visualization. */ @@ -4162,14 +5256,12 @@ interface IResizerRenderContext { * Thickness of the resizer line. */ thickness: number; - } /** * Range of indices. */ interface IndexRange { - /** * First index in the range. */ @@ -4179,5 +5271,4 @@ interface IndexRange { * Second index in the range. */ to: number; - } diff --git a/src/renderer/canvas/cell/canvas-cell-renderer.ts b/src/renderer/canvas/cell/canvas-cell-renderer.ts index 7df55b5..e32b6ee 100644 --- a/src/renderer/canvas/cell/canvas-cell-renderer.ts +++ b/src/renderer/canvas/cell/canvas-cell-renderer.ts @@ -1,11 +1,11 @@ -import {ICellRenderer} from "../../cell/cell-renderer"; -import {IRenderContext} from "../canvas-renderer"; +import { ICellRenderer } from '../../cell'; +import { IRenderContext } from '../canvas-renderer'; /** * Cell renderer for the HTML5 canvas renderer. */ -export interface ICanvasCellRenderer extends ICellRenderer { - +export interface ICanvasCellRenderer + extends ICellRenderer { /** * Called before rendering ALL cells to render for this renderer * in the current rendering cycle. @@ -26,5 +26,4 @@ export interface ICanvasCellRenderer extends ICellRenderer { } if (!options.checkedHoverBackgroundColor) { - options.checkedHoverBackgroundColor = DEFAULT_CHECKED_HOVER_BACKGROUND_COLOR; + options.checkedHoverBackgroundColor = + DEFAULT_CHECKED_HOVER_BACKGROUND_COLOR; } - if (options.disabledOpacity === undefined || options.disabledOpacity === null) { + if ( + options.disabledOpacity === undefined || + options.disabledOpacity === null + ) { options.disabledOpacity = DEFAULT_DISABLED_OPACITY; } @@ -186,7 +187,10 @@ export const fillOptions = (options?: ICheckboxCellRendererOptions) => { options.tickThickness = DEFAULT_TICK_THICKNESS; } - if (options.labelCheckboxSpacing === undefined || options.labelCheckboxSpacing === null) { + if ( + options.labelCheckboxSpacing === undefined || + options.labelCheckboxSpacing === null + ) { options.labelCheckboxSpacing = DEFAULT_LABEL_CHECKBOX_SPACING; } @@ -195,4 +199,4 @@ export const fillOptions = (options?: ICheckboxCellRendererOptions) => { } return options; -} +}; diff --git a/src/renderer/canvas/cell/checkbox/checkbox-cell-renderer-value.ts b/src/renderer/canvas/cell/checkbox/checkbox-cell-renderer-value.ts index c70c123..f1c15b8 100644 --- a/src/renderer/canvas/cell/checkbox/checkbox-cell-renderer-value.ts +++ b/src/renderer/canvas/cell/checkbox/checkbox-cell-renderer-value.ts @@ -1,4 +1,4 @@ -import {ICheckboxCellRendererOptions} from "./checkbox-cell-renderer-options"; +import { ICheckboxCellRendererOptions } from './checkbox-cell-renderer-options'; /** * Value of the checkbox cell renderer value. @@ -7,7 +7,6 @@ import {ICheckboxCellRendererOptions} from "./checkbox-cell-renderer-options"; * will allow more control and unlock more options. */ export interface ICheckboxCellRendererValue { - /** * Whether the checkbox is checked. */ @@ -23,5 +22,4 @@ export interface ICheckboxCellRendererValue { * If not set the default options of the checkbox cell renderer are applied. */ options?: ICheckboxCellRendererOptions; - } diff --git a/src/renderer/canvas/cell/checkbox/checkbox-cell-renderer.ts b/src/renderer/canvas/cell/checkbox/checkbox-cell-renderer.ts index 4a81791..8aed2ed 100644 --- a/src/renderer/canvas/cell/checkbox/checkbox-cell-renderer.ts +++ b/src/renderer/canvas/cell/checkbox/checkbox-cell-renderer.ts @@ -1,31 +1,32 @@ -import {ICanvasCellRenderer} from "../canvas-cell-renderer"; -import {IRenderContext} from "../../canvas-renderer"; -import {ICell} from "../../../../cell/cell"; -import {ICellRendererEventListener} from "../../../cell/event/cell-renderer-event-listener"; -import {TableEngine} from "../../../../table-engine"; -import {IRectangle} from "../../../../util/rect"; +import { ICanvasCellRenderer } from '../canvas-cell-renderer'; +import { IRenderContext } from '../../canvas-renderer'; +import { ICell } from '../../../../cell'; +import { ICellRendererEventListener } from '../../../cell'; +import { TableEngine } from '../../../../table-engine'; import { fillOptions as fillCheckboxCellRendererOptions, - ICheckboxCellRendererOptions -} from "./checkbox-cell-renderer-options"; -import {HorizontalAlignment} from "../../../../util/alignment/horizontal-alignment"; -import {AlignmentUtil} from "../../../../util/alignment/alignment-util"; -import {VerticalAlignment} from "../../../../util/alignment/vertical-alignment"; -import {ICheckboxCellRendererValue} from "./checkbox-cell-renderer-value"; -import {IPoint} from "../../../../util/point"; -import {IColor} from "../../../../util/color"; -import {Colors} from "../../../../util/colors"; -import {CanvasUtil} from "../../../util/canvas"; + ICheckboxCellRendererOptions, +} from './checkbox-cell-renderer-options'; +import { + AlignmentUtil, + Colors, + HorizontalAlignment, + IColor, + IPoint, + IRectangle, + VerticalAlignment, +} from '../../../../util'; +import { ICheckboxCellRendererValue } from './checkbox-cell-renderer-value'; +import { CanvasUtil } from '../../../util'; /** * Cell renderer rendering a checkbox. */ export class CheckboxCellRenderer implements ICanvasCellRenderer { - /** * Name of the renderer. */ - public static readonly NAME: string = "checkbox"; + public static readonly NAME: string = 'checkbox'; /** * Default checkbox cell renderer options. @@ -37,13 +38,19 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { */ private readonly _eventListener: ICellRendererEventListener = { onMouseMove: (event) => { - const cache: ICheckboxViewportCache = CheckboxCellRenderer._cache(event.cell); + const cache: ICheckboxViewportCache = CheckboxCellRenderer._cache( + event.cell + ); if (!!cache.checkboxBounds) { // Check if mouse is inside checkbox bounds - const isHovered: boolean = event.offset.x >= cache.checkboxBounds.left - && event.offset.x <= cache.checkboxBounds.left + cache.checkboxBounds.width - && event.offset.y >= cache.checkboxBounds.top - && event.offset.y <= cache.checkboxBounds.top + cache.checkboxBounds.height; + const isHovered: boolean = + event.offset.x >= cache.checkboxBounds.left && + event.offset.x <= + cache.checkboxBounds.left + + cache.checkboxBounds.width && + event.offset.y >= cache.checkboxBounds.top && + event.offset.y <= + cache.checkboxBounds.top + cache.checkboxBounds.height; if (isHovered !== cache.isHovered) { cache.isHovered = isHovered; @@ -52,14 +59,17 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { } }, onMouseOut: (event) => { - const cache: ICheckboxViewportCache = CheckboxCellRenderer._cache(event.cell); + const cache: ICheckboxViewportCache = CheckboxCellRenderer._cache( + event.cell + ); if (cache.isHovered) { cache.isHovered = false; this._engine.repaint(); } }, onMouseUp: (event) => { - const value: ICheckboxCellRendererValue = CheckboxCellRenderer._value(event.cell); + const value: ICheckboxCellRendererValue = + CheckboxCellRenderer._value(event.cell); value.checked = !value.checked; @@ -70,7 +80,7 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { } this._engine.repaint(); - } + }, }; /** @@ -78,9 +88,7 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { */ private _engine: TableEngine; - constructor( - defaultOptions?: ICheckboxCellRendererOptions - ) { + constructor(defaultOptions?: ICheckboxCellRendererOptions) { this._defaultOptions = fillCheckboxCellRendererOptions(defaultOptions); } @@ -89,13 +97,16 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { * @param cell to get value from */ private static _value(cell: ICell): ICheckboxCellRendererValue { - const isSpecialValue: boolean = !!cell.value && typeof cell.value === "object" && "checked" in cell.value; + const isSpecialValue: boolean = + !!cell.value && + typeof cell.value === 'object' && + 'checked' in cell.value; if (isSpecialValue) { return cell.value as ICheckboxCellRendererValue; } else { return { - checked: !!cell.value + checked: !!cell.value, }; } } @@ -109,7 +120,7 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { return cell.viewportCache as ICheckboxViewportCache; } else { const cache: ICheckboxViewportCache = { - isHovered: false + isHovered: false, }; cell.viewportCache = cache; @@ -122,9 +133,16 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { // Nothing to do after rendering all cells } - public before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { - ctx.textAlign = AlignmentUtil.horizontalAlignmentToStyleStr(HorizontalAlignment.LEFT) as CanvasTextAlign; - ctx.textBaseline = AlignmentUtil.verticalAlignmentToStyleStr(VerticalAlignment.MIDDLE) as CanvasTextBaseline; + public before( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { + ctx.textAlign = AlignmentUtil.horizontalAlignmentToStyleStr( + HorizontalAlignment.LEFT + ) as CanvasTextAlign; + ctx.textBaseline = AlignmentUtil.verticalAlignmentToStyleStr( + VerticalAlignment.MIDDLE + ) as CanvasTextBaseline; ctx.font = `${this._defaultOptions.labelFontSize}px ${this._defaultOptions.labelFontFamily}`; } @@ -133,9 +151,10 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { } public getCopyValue(cell: ICell): string { - const value: ICheckboxCellRendererValue = CheckboxCellRenderer._value(cell); + const value: ICheckboxCellRendererValue = + CheckboxCellRenderer._value(cell); - return value.checked ? "1" : "0"; + return value.checked ? '1' : '0'; } public getEventListener(): ICellRendererEventListener | null { @@ -158,24 +177,39 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { // Do nothing } - public render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { + public render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { let isContextSaved: boolean = false; - const value: ICheckboxCellRendererValue = CheckboxCellRenderer._value(cell); + const value: ICheckboxCellRendererValue = + CheckboxCellRenderer._value(cell); // Derive options let size: number = this._defaultOptions.size; - let labelCheckboxSpacing: number = this._defaultOptions.labelCheckboxSpacing; + let labelCheckboxSpacing: number = + this._defaultOptions.labelCheckboxSpacing; let cellSpacing: number = this._defaultOptions.cellSpacing; let labelColor: IColor = this._defaultOptions.labelTextColor; if (!!value.options) { - if (value.options.size !== undefined && value.options.size !== null) { + if ( + value.options.size !== undefined && + value.options.size !== null + ) { size = value.options.size; } - if (value.options.labelCheckboxSpacing !== undefined && value.options.labelCheckboxSpacing !== null) { + if ( + value.options.labelCheckboxSpacing !== undefined && + value.options.labelCheckboxSpacing !== null + ) { labelCheckboxSpacing = value.options.labelCheckboxSpacing; } - if (value.options.cellSpacing !== undefined && value.options.cellSpacing !== null) { + if ( + value.options.cellSpacing !== undefined && + value.options.cellSpacing !== null + ) { cellSpacing = value.options.cellSpacing; } @@ -183,9 +217,19 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { labelColor = value.options.labelTextColor; } - if ((value.options.labelFontSize !== undefined && value.options.labelFontSize !== null) || !!value.options.labelFontFamily) { - const fontSize: number = value.options.labelFontSize !== undefined && value.options.labelFontSize !== null ? value.options.labelFontSize : this._defaultOptions.labelFontSize; - const fontFamily: string = !!value.options.labelFontFamily ? value.options.labelFontFamily : this._defaultOptions.labelFontFamily; + if ( + (value.options.labelFontSize !== undefined && + value.options.labelFontSize !== null) || + !!value.options.labelFontFamily + ) { + const fontSize: number = + value.options.labelFontSize !== undefined && + value.options.labelFontSize !== null + ? value.options.labelFontSize + : this._defaultOptions.labelFontSize; + const fontFamily: string = !!value.options.labelFontFamily + ? value.options.labelFontFamily + : this._defaultOptions.labelFontFamily; if (!isContextSaved) { isContextSaved = true; @@ -200,7 +244,8 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { // Render label and checkbox const labelWidth: number = ctx.measureText(value.label).width; - const totalWidth: number = cellSpacing + labelWidth + size + labelCheckboxSpacing; + const totalWidth: number = + cellSpacing + labelWidth + size + labelCheckboxSpacing; let checkboxOffset: number; if (totalWidth > bounds.width) { @@ -208,14 +253,21 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { checkboxOffset = Math.round(cellSpacing + size / 2); const clippingRegion = new Path2D(); - clippingRegion.rect(bounds.left, bounds.top, bounds.width, bounds.height); + clippingRegion.rect( + bounds.left, + bounds.top, + bounds.width, + bounds.height + ); ctx.save(); isContextSaved = true; ctx.clip(clippingRegion); } else { - checkboxOffset = Math.round((bounds.width - totalWidth) / 2 + cellSpacing + size / 2); + checkboxOffset = Math.round( + (bounds.width - totalWidth) / 2 + cellSpacing + size / 2 + ); } CheckboxCellRenderer._renderCheckbox( @@ -232,7 +284,10 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { ctx.fillStyle = Colors.toStyleStr(labelColor); ctx.fillText( value.label, - bounds.left + Math.round(checkboxOffset + size / 2 + labelCheckboxSpacing), + bounds.left + + Math.round( + checkboxOffset + size / 2 + labelCheckboxSpacing + ), bounds.top + Math.round(bounds.height / 2) ); } else { @@ -274,41 +329,52 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { ): void { const center: IPoint = { x: bounds.left + offset, - y: bounds.top + Math.round(bounds.height / 2) + y: bounds.top + Math.round(bounds.height / 2), }; const halfSize: number = size / 2; const rect: IRectangle = { left: center.x - halfSize + 0.5, top: center.y - halfSize + 0.5, width: size, - height: size + height: size, }; // Derive options let borderRadius: number = defaultOptions.borderRadius; let borderSize: number = defaultOptions.borderSize; - let uncheckedBackgroundColor: IColor = defaultOptions.uncheckedBackgroundColor; - let checkedBackgroundColor: IColor = defaultOptions.checkedBackgroundColor; - let checkedHoverBackgroundColor: IColor = defaultOptions.checkedHoverBackgroundColor; + let uncheckedBackgroundColor: IColor = + defaultOptions.uncheckedBackgroundColor; + let checkedBackgroundColor: IColor = + defaultOptions.checkedBackgroundColor; + let checkedHoverBackgroundColor: IColor = + defaultOptions.checkedHoverBackgroundColor; let borderColor: IColor = defaultOptions.borderColor; let hoverBorderColor: IColor = defaultOptions.hoverBorderColor; let tickColor: IColor = defaultOptions.tickColor; let tickThickness: number = defaultOptions.tickThickness; if (!!value.options) { - if (value.options.borderRadius !== undefined && value.options.borderRadius !== null) { + if ( + value.options.borderRadius !== undefined && + value.options.borderRadius !== null + ) { borderRadius = value.options.borderRadius; } - if (value.options.borderSize !== undefined && value.options.borderSize !== null) { + if ( + value.options.borderSize !== undefined && + value.options.borderSize !== null + ) { borderSize = value.options.borderSize; } if (!!value.options.uncheckedBackgroundColor) { - uncheckedBackgroundColor = value.options.uncheckedBackgroundColor; + uncheckedBackgroundColor = + value.options.uncheckedBackgroundColor; } if (!!value.options.checkedBackgroundColor) { checkedBackgroundColor = value.options.checkedBackgroundColor; } if (!!value.options.checkedHoverBackgroundColor) { - checkedHoverBackgroundColor = value.options.checkedHoverBackgroundColor; + checkedHoverBackgroundColor = + value.options.checkedHoverBackgroundColor; } if (!!value.options.borderColor) { borderColor = value.options.borderColor; @@ -319,7 +385,10 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { if (!!value.options.tickColor) { tickColor = value.options.tickColor; } - if (value.options.tickThickness !== undefined && value.options.tickThickness !== null) { + if ( + value.options.tickThickness !== undefined && + value.options.tickThickness !== null + ) { tickThickness = value.options.tickThickness; } } @@ -328,7 +397,13 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { const isHovered: boolean = cache.isHovered; // Render background - ctx.fillStyle = Colors.toStyleStr(value.checked ? (isHovered ? checkedHoverBackgroundColor : checkedBackgroundColor) : uncheckedBackgroundColor); + ctx.fillStyle = Colors.toStyleStr( + value.checked + ? isHovered + ? checkedHoverBackgroundColor + : checkedBackgroundColor + : uncheckedBackgroundColor + ); if (borderRadius > 0) { CanvasUtil.makeRoundRectPath(ctx, rect, borderRadius); ctx.fill(); @@ -338,7 +413,9 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { // Render border (if unchecked) if (!value.checked) { - ctx.strokeStyle = Colors.toStyleStr(isHovered ? hoverBorderColor : borderColor); + ctx.strokeStyle = Colors.toStyleStr( + isHovered ? hoverBorderColor : borderColor + ); ctx.lineWidth = borderSize; if (borderRadius > 0) { @@ -352,15 +429,24 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { // Render tick (if checked) if (value.checked) { ctx.strokeStyle = Colors.toStyleStr(tickColor); - ctx.lineCap = "round"; - ctx.lineJoin = "round"; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; ctx.lineWidth = tickThickness; ctx.beginPath(); - ctx.moveTo(rect.left + rect.width * 0.2, rect.top + rect.height * 0.6); - ctx.lineTo(rect.left + rect.width * 0.4, rect.top + rect.height * 0.8); - ctx.lineTo(rect.left + rect.width * 0.8, rect.top + rect.height * 0.2); + ctx.moveTo( + rect.left + rect.width * 0.2, + rect.top + rect.height * 0.6 + ); + ctx.lineTo( + rect.left + rect.width * 0.4, + rect.top + rect.height * 0.8 + ); + ctx.lineTo( + rect.left + rect.width * 0.8, + rect.top + rect.height * 0.2 + ); ctx.stroke(); } @@ -370,7 +456,6 @@ export class CheckboxCellRenderer implements ICanvasCellRenderer { } interface ICheckboxViewportCache { - /** * Whether the checkbox is currently hovered. */ @@ -380,5 +465,4 @@ interface ICheckboxViewportCache { * Bounds of the checkbox. */ checkboxBounds?: IRectangle; - } diff --git a/src/renderer/canvas/cell/checkbox/index.ts b/src/renderer/canvas/cell/checkbox/index.ts new file mode 100644 index 0000000..ae14bac --- /dev/null +++ b/src/renderer/canvas/cell/checkbox/index.ts @@ -0,0 +1,6 @@ +export { CheckboxCellRenderer } from './checkbox-cell-renderer'; +export { ICheckboxCellRendererValue } from './checkbox-cell-renderer-value'; +export { + ICheckboxCellRendererOptions, + fillOptions as fillCheckboxCellRendererOptions, +} from './checkbox-cell-renderer-options'; diff --git a/src/renderer/canvas/cell/combobox/combobox-cell-renderer-options.ts b/src/renderer/canvas/cell/combobox/combobox-cell-renderer-options.ts index 9cfea6d..1abfcc4 100644 --- a/src/renderer/canvas/cell/combobox/combobox-cell-renderer-options.ts +++ b/src/renderer/canvas/cell/combobox/combobox-cell-renderer-options.ts @@ -1,33 +1,33 @@ -import {ICell} from "../../../../cell/cell"; -import {IColor} from "../../../../util/color"; -import {Colors} from "../../../../util/colors"; - -export const DEFAULT_PLACEHOLDER_TEXT: string = "Select..."; -export const DEFAULT_LABEL_COLOR: IColor = Colors.BLACK; -export const DEFAULT_LABEL_COLOR_HOVERED: IColor = Colors.CORAL; -export const DEFAULT_LABEL_COLOR_DISABLED: IColor = Colors.GRAY; -export const DEFAULT_LABEL_FONT_SIZE: number = 12; -export const DEFAULT_LABEL_FONT_FAMILY: string = "sans-serif"; -export const DEFAULT_PLACEHOLDER_COLOR: IColor = Colors.GRAY; -export const DEFAULT_PADDING: number = 8; -export const DEFAULT_SELECT_ARROW_COLOR: IColor = Colors.GRAY; -export const DEFAULT_SELECT_ARROW_COLOR_DISABLED: IColor = Colors.LIGHTGRAY; -export const DEFAULT_SELECT_ARROW_HOVER_COLOR: IColor = Colors.CORAL; -export const DEFAULT_SELECT_ARROW_SIZE: number = 10; -export const DEFAULT_SELECT_ARROW_THICKNESS: number = 2; -export const DEFAULT_SELECT_ARROW_LINE_JOIN: "bevel" | "miter" | "round" = "round"; -export const DEFAULT_SELECT_ARROW_LINE_CAP: "butt" | "round" | "square" = "round"; -export const DEFAULT_DROPDOWN_OVERLAY_CLASS_NAME = "table-engine-combobox-dropdown-list"; -export const DEFAULT_DROPDOWN_MAX_HEIGHT = 200; +import { ICell } from '../../../../cell'; +import { Colors, IColor } from '../../../../util'; + +const DEFAULT_PLACEHOLDER_TEXT: string = 'Select...'; +const DEFAULT_LABEL_COLOR: IColor = Colors.BLACK; +const DEFAULT_LABEL_COLOR_HOVERED: IColor = Colors.CORAL; +const DEFAULT_LABEL_COLOR_DISABLED: IColor = Colors.GRAY; +const DEFAULT_LABEL_FONT_SIZE: number = 12; +const DEFAULT_LABEL_FONT_FAMILY: string = 'sans-serif'; +const DEFAULT_PLACEHOLDER_COLOR: IColor = Colors.GRAY; +const DEFAULT_PADDING: number = 8; +const DEFAULT_SELECT_ARROW_COLOR: IColor = Colors.GRAY; +const DEFAULT_SELECT_ARROW_COLOR_DISABLED: IColor = Colors.LIGHTGRAY; +const DEFAULT_SELECT_ARROW_HOVER_COLOR: IColor = Colors.CORAL; +const DEFAULT_SELECT_ARROW_SIZE: number = 10; +const DEFAULT_SELECT_ARROW_THICKNESS: number = 2; +const DEFAULT_SELECT_ARROW_LINE_JOIN: 'bevel' | 'miter' | 'round' = 'round'; +const DEFAULT_SELECT_ARROW_LINE_CAP: 'butt' | 'round' | 'square' = 'round'; +const DEFAULT_DROPDOWN_OVERLAY_CLASS_NAME = + 'table-engine-combobox-dropdown-list'; +const DEFAULT_DROPDOWN_MAX_HEIGHT = 200; export interface IComboBoxCellRendererOptions { onChanged?: (cell: ICell) => void; editable?: boolean; padding?: number; label?: ILabelOptions; - placeholder?: IPlaceholderOptions, - selectArrow?: ISelectArrowOptions, - dropdown?: IDropdownOptions + placeholder?: IPlaceholderOptions; + selectArrow?: ISelectArrowOptions; + dropdown?: IDropdownOptions; } export interface ILabelOptions { @@ -49,8 +49,8 @@ export interface ISelectArrowOptions { disabledColor?: IColor; size?: number; thickness?: number; - lineJoin?: "bevel" | "miter" | "round"; - lineCap?: "butt" | "round" | "square"; + lineJoin?: 'bevel' | 'miter' | 'round'; + lineCap?: 'butt' | 'round' | 'square'; } export interface IDropdownOptions { @@ -77,7 +77,7 @@ export const fillOptions = (options?: IComboBoxCellRendererOptions) => { options.dropdown = fillDropdownOptions(options.dropdown); return options; -} +}; const fillLabelOptions = (options?: ILabelOptions) => { if (!options) { @@ -105,7 +105,7 @@ const fillLabelOptions = (options?: ILabelOptions) => { } return options; -} +}; const fillPlaceholderOptions = (options?: IPlaceholderOptions) => { if (!options) { @@ -121,7 +121,7 @@ const fillPlaceholderOptions = (options?: IPlaceholderOptions) => { } return options; -} +}; const fillSelectArrowOptions = (options?: ISelectArrowOptions) => { if (!options) { @@ -157,7 +157,7 @@ const fillSelectArrowOptions = (options?: ISelectArrowOptions) => { } return options; -} +}; const fillDropdownOptions = (options?: IDropdownOptions) => { if (!options) { @@ -173,4 +173,4 @@ const fillDropdownOptions = (options?: IDropdownOptions) => { } return options; -} +}; diff --git a/src/renderer/canvas/cell/combobox/combobox-cell-renderer-value.ts b/src/renderer/canvas/cell/combobox/combobox-cell-renderer-value.ts index 89bdc2e..9ef064e 100644 --- a/src/renderer/canvas/cell/combobox/combobox-cell-renderer-value.ts +++ b/src/renderer/canvas/cell/combobox/combobox-cell-renderer-value.ts @@ -1,4 +1,4 @@ -import {IComboBoxCellRendererOptions} from "./combobox-cell-renderer-options"; +import { IComboBoxCellRendererOptions } from './combobox-cell-renderer-options'; export interface IComboBoxCellRendererValue { selected_option_id?: string; diff --git a/src/renderer/canvas/cell/combobox/combobox-cell-renderer.ts b/src/renderer/canvas/cell/combobox/combobox-cell-renderer.ts index d2929b1..d6aa816 100644 --- a/src/renderer/canvas/cell/combobox/combobox-cell-renderer.ts +++ b/src/renderer/canvas/cell/combobox/combobox-cell-renderer.ts @@ -1,24 +1,27 @@ -import {ICanvasCellRenderer} from "../canvas-cell-renderer"; +import { ICanvasCellRenderer } from '../canvas-cell-renderer'; import { fillOptions, - IComboBoxCellRendererOptions, ILabelOptions, + IComboBoxCellRendererOptions, + ILabelOptions, IPlaceholderOptions, - ISelectArrowOptions -} from "./combobox-cell-renderer-options"; -import {TableEngine} from "../../../../table-engine"; -import {IRenderContext} from "../../canvas-renderer"; -import {ICell} from "../../../../cell/cell"; -import {ICellRendererEventListener} from "../../../cell/event/cell-renderer-event-listener"; -import {IRectangle} from "../../../../util/rect"; -import {ICellRendererMouseEvent} from "../../../cell/event/cell-renderer-mouse-event"; -import {IComboBoxCellRendererValue, IComboBoxOption} from "./combobox-cell-renderer-value"; -import {Colors} from "../../../../util/colors"; -import {IOverlay} from "../../../../overlay/overlay"; -import {ICellRange} from "../../../../cell/range/cell-range"; + ISelectArrowOptions, +} from './combobox-cell-renderer-options'; +import { TableEngine } from '../../../../table-engine'; +import { IRenderContext } from '../../canvas-renderer'; +import { ICell } from '../../../../cell'; +import { + ICellRendererEventListener, + ICellRendererMouseEvent, +} from '../../../cell'; +import { Colors, IRectangle } from '../../../../util'; +import { + IComboBoxCellRendererValue, + IComboBoxOption, +} from './combobox-cell-renderer-value'; +import { IOverlay } from '../../../../overlay'; export class ComboBoxCellRenderer implements ICanvasCellRenderer { - - static readonly NAME: string = "combobox"; + static readonly NAME: string = 'combobox'; private readonly _options: IComboBoxCellRendererOptions; @@ -27,7 +30,7 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { private _eventListener: ICellRendererEventListener = { onMouseUp: (event) => this._onClick(event), onMouseMove: (event) => this._onMouseMove(event), - onMouseOut: (event) => this._onMouseOut(event) + onMouseOut: (event) => this._onMouseOut(event), }; constructor(options?: IComboBoxCellRendererOptions) { @@ -37,7 +40,7 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { private static _value(cell: ICell): IComboBoxCellRendererValue { if (cell.value === undefined || cell.value === null) { return { - select_options: {} + select_options: {}, } as IComboBoxCellRendererValue; } @@ -49,7 +52,7 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { return cell.viewportCache as IViewportCache; } else { const cache: IViewportCache = { - hovered: false + hovered: false, }; cell.viewportCache = cache; return cache; @@ -61,7 +64,7 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { } before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { - ctx.textAlign = "left"; + ctx.textAlign = 'left'; } cleanup(): void { @@ -74,7 +77,7 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { return value.select_options[value.selected_option_id].label; } - return ""; + return ''; } getEventListener(): ICellRendererEventListener | null { @@ -93,17 +96,35 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { // Nothing to do when a cell disappears from the viewport } - render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { - const value: IComboBoxCellRendererValue = ComboBoxCellRenderer._value(cell); + render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { + const value: IComboBoxCellRendererValue = + ComboBoxCellRenderer._value(cell); const cache: IViewportCache = ComboBoxCellRenderer._cache(cell); - const style: IRenderingStyle = this._determineRenderingStyle(value, cache); + const style: IRenderingStyle = this._determineRenderingStyle( + value, + cache + ); - const selectArrowWidth: number = ComboBoxCellRenderer._renderSelectArrow(ctx, bounds, style); - ComboBoxCellRenderer._renderLabel(ctx, bounds, value, style, selectArrowWidth); + const selectArrowWidth: number = + ComboBoxCellRenderer._renderSelectArrow(ctx, bounds, style); + ComboBoxCellRenderer._renderLabel( + ctx, + bounds, + value, + style, + selectArrowWidth + ); } - private _determineRenderingStyle(value: IComboBoxCellRendererValue, cache: IViewportCache): IRenderingStyle { + private _determineRenderingStyle( + value: IComboBoxCellRendererValue, + cache: IViewportCache + ): IRenderingStyle { let editable: boolean = this._options.editable; let hovered = cache.hovered; let padding: number = this._options.padding; @@ -112,10 +133,16 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { let selectArrowOptions: ISelectArrowOptions = this._options.selectArrow; if (!!value.options) { - if (value.options.editable !== undefined && value.options.editable !== null) { + if ( + value.options.editable !== undefined && + value.options.editable !== null + ) { editable = value.options.editable; } - if (value.options.padding !== undefined && value.options.padding !== null) { + if ( + value.options.padding !== undefined && + value.options.padding !== null + ) { padding = value.options.padding; } if (!!value.options.label) { @@ -135,8 +162,8 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { padding, labelOptions, placeholderOptions, - selectArrowOptions - } + selectArrowOptions, + }; } private static _renderSelectArrow( @@ -144,23 +171,37 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { bounds: IRectangle, style: IRenderingStyle ): number { - const selectArrowPath: ISelectArrowPath = ComboBoxCellRenderer._makeSelectArrowPath(); + const selectArrowPath: ISelectArrowPath = + ComboBoxCellRenderer._makeSelectArrowPath(); - const selectArrowWidth: number = selectArrowPath.width * style.selectArrowOptions.size; - const selectArrowHeight: number = selectArrowPath.height * style.selectArrowOptions.size; + const selectArrowWidth: number = + selectArrowPath.width * style.selectArrowOptions.size; + const selectArrowHeight: number = + selectArrowPath.height * style.selectArrowOptions.size; const transformedSelectArrowPath: Path2D = new Path2D(); transformedSelectArrowPath.addPath( selectArrowPath.path, new DOMMatrix() .translateSelf( - bounds.left + bounds.width - selectArrowWidth - style.padding, - bounds.top + Math.round((bounds.height - selectArrowHeight) / 2) + bounds.left + + bounds.width - + selectArrowWidth - + style.padding, + bounds.top + + Math.round((bounds.height - selectArrowHeight) / 2) + ) + .scaleSelf( + style.selectArrowOptions.size, + style.selectArrowOptions.size ) - .scaleSelf(style.selectArrowOptions.size, style.selectArrowOptions.size) ); - ctx.strokeStyle = Colors.toStyleStr(style.hovered ? style.selectArrowOptions.hoverColor : style.selectArrowOptions.color); + ctx.strokeStyle = Colors.toStyleStr( + style.hovered + ? style.selectArrowOptions.hoverColor + : style.selectArrowOptions.color + ); ctx.lineWidth = style.selectArrowOptions.thickness; ctx.lineCap = style.selectArrowOptions.lineCap; ctx.lineJoin = style.selectArrowOptions.lineJoin; @@ -185,7 +226,8 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { const hasLabelSelected = !!value.selected_option_id; if (hasLabelSelected) { - const selectedOption: IComboBoxOption = value.select_options[value.selected_option_id]; + const selectedOption: IComboBoxOption = + value.select_options[value.selected_option_id]; labelText = selectedOption.label; color = style.labelOptions.color; @@ -199,12 +241,18 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { ctx.fillStyle = Colors.toStyleStr(color); // Check if label fits into box - const maxLabelWidth: number = bounds.width - style.padding - selectArrowWidth; + const maxLabelWidth: number = + bounds.width - style.padding - selectArrowWidth; const measuredWidth: number = ctx.measureText(labelText).width; const overflow: boolean = measuredWidth > maxLabelWidth; if (overflow) { const clippingRegion = new Path2D(); - clippingRegion.rect(bounds.left, bounds.top, bounds.width - selectArrowWidth, bounds.height); + clippingRegion.rect( + bounds.left, + bounds.top, + bounds.width - selectArrowWidth, + bounds.height + ); ctx.save(); ctx.clip(clippingRegion); @@ -227,17 +275,22 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { return { path, width: 1.0, - height: 0.4 + height: 0.4, }; } private _onClick(event: ICellRendererMouseEvent): void { - const value: IComboBoxCellRendererValue = ComboBoxCellRenderer._value(event.cell); + const value: IComboBoxCellRendererValue = ComboBoxCellRenderer._value( + event.cell + ); let editable: boolean = this._options.editable; let onChanged: (cell: ICell) => void = this._options.onChanged; if (!!value.options) { - if (value.options.editable !== undefined && value.options.editable !== null) { + if ( + value.options.editable !== undefined && + value.options.editable !== null + ) { editable = value.options.editable; } if (!!value.options.onChanged) { @@ -250,32 +303,42 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { } } - private _openDropdownOverlay(value: IComboBoxCellRendererValue, cell: ICell, onChanged: (cell: ICell) => void): void { - const cellBounds: IRectangle = this._engine.getCellModel().getBounds(cell.range); + private _openDropdownOverlay( + value: IComboBoxCellRendererValue, + cell: ICell, + onChanged: (cell: ICell) => void + ): void { + const cellBounds: IRectangle = this._engine + .getCellModel() + .getBounds(cell.range); // Determine height available to the top and bottom of the given cell range const viewport = this._engine.getViewport(); const fixedRowsHeight: number = this._engine.getFixedRowsHeight(); - const availableHeightToTheTop = cellBounds.top - (viewport.top + fixedRowsHeight); - const availableHeightToTheBottom = (viewport.top + viewport.height - cellBounds.height) - cellBounds.top; + const availableHeightToTheTop = + cellBounds.top - (viewport.top + fixedRowsHeight); + const availableHeightToTheBottom = + viewport.top + viewport.height - cellBounds.height - cellBounds.top; - const overlayElement: HTMLElement = document.createElement("div"); + const overlayElement: HTMLElement = document.createElement('div'); overlayElement.className = this._options.dropdown.overlayClassName; overlayElement.tabIndex = -1; // Div element must be focusable to enable the blur event - const listContainerElement: HTMLElement = document.createElement("div"); - listContainerElement.style.height = "100%"; - listContainerElement.style.overflow = "auto"; + const listContainerElement: HTMLElement = document.createElement('div'); + listContainerElement.style.height = '100%'; + listContainerElement.style.overflow = 'auto'; - const list: HTMLElement = document.createElement("ul"); + const list: HTMLElement = document.createElement('ul'); for (const optionId in value.select_options) { const label = value.select_options[optionId].label; - const listItem: HTMLElement = document.createElement("li"); + const listItem: HTMLElement = document.createElement('li'); listItem.textContent = label; - const mouseDownListener: (MouseEvent) => void = (event: MouseEvent) => { + const mouseDownListener: (MouseEvent) => void = ( + event: MouseEvent + ) => { event.stopPropagation(); // Prevent table selection }; const clickListener: (MouseEvent) => void = (event: MouseEvent) => { @@ -288,8 +351,8 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { blurListener(); }; - listItem.addEventListener("mousedown", mouseDownListener); - listItem.addEventListener("click", clickListener); + listItem.addEventListener('mousedown', mouseDownListener); + listItem.addEventListener('click', clickListener); list.appendChild(listItem); } @@ -298,14 +361,15 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { overlayElement.appendChild(listContainerElement); // Create render container to render overlay to measure the needed height - const renderContainer: HTMLElement = document.createElement("div"); - renderContainer.style.position = "absolute"; - renderContainer.style.left = "-9999px"; - renderContainer.style.top = "-9999px"; - renderContainer.style.visibility = "hidden"; + const renderContainer: HTMLElement = document.createElement('div'); + renderContainer.style.position = 'absolute'; + renderContainer.style.left = '-9999px'; + renderContainer.style.top = '-9999px'; + renderContainer.style.visibility = 'hidden'; renderContainer.appendChild(overlayElement); document.body.appendChild(renderContainer); - const neededOverlayHeight: number = renderContainer.getBoundingClientRect().height; + const neededOverlayHeight: number = + renderContainer.getBoundingClientRect().height; document.body.removeChild(renderContainer); const maxDropdownHeight: number = this._options.dropdown.maxHeight; @@ -318,7 +382,8 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { let openToBottom: boolean = true; if (availableHeightToTheBottom < dropdownHeight) { if (availableHeightToTheTop < dropdownHeight) { - openToBottom = availableHeightToTheBottom > availableHeightToTheTop; + openToBottom = + availableHeightToTheBottom > availableHeightToTheTop; // Cut dropdown height by the available space if (openToBottom) { @@ -335,7 +400,7 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { left: cellBounds.left, top: cellBounds.top + cellBounds.height, width: cellBounds.width, - height: dropdownHeight + height: dropdownHeight, }; if (!openToBottom) { dropdownBounds.top = cellBounds.top - dropdownHeight; @@ -343,7 +408,7 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { const overlay: IOverlay = { element: overlayElement, - bounds: dropdownBounds + bounds: dropdownBounds, }; this._engine.getOverlayManager().addOverlay(overlay); @@ -352,14 +417,14 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { }; const blurListener: () => void = () => { // Remove all event listeners again - overlayElement.removeEventListener("wheel", scrollListener); - overlayElement.removeEventListener("blur", blurListener); + overlayElement.removeEventListener('wheel', scrollListener); + overlayElement.removeEventListener('blur', blurListener); this._engine.getOverlayManager().removeOverlay(overlay); // Remove overlay this._engine.requestFocus(); // Re-focus table }; - overlayElement.addEventListener("wheel", scrollListener); - overlayElement.addEventListener("blur", blurListener); + overlayElement.addEventListener('wheel', scrollListener); + overlayElement.addEventListener('blur', blurListener); setTimeout(() => { overlayElement.focus(); @@ -368,10 +433,16 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { private _onMouseMove(event: ICellRendererMouseEvent): void { const cache: IViewportCache = ComboBoxCellRenderer._cache(event.cell); - const value: IComboBoxCellRendererValue = ComboBoxCellRenderer._value(event.cell); + const value: IComboBoxCellRendererValue = ComboBoxCellRenderer._value( + event.cell + ); let editable: boolean = this._options.editable; - if (!!value.options && value.options.editable !== undefined && value.options.editable !== null) { + if ( + !!value.options && + value.options.editable !== undefined && + value.options.editable !== null + ) { editable = value.options.editable; } @@ -388,13 +459,12 @@ export class ComboBoxCellRenderer implements ICanvasCellRenderer { this._engine.repaint(); } } - } interface ISelectArrowPath { - path: Path2D, - width: number, - height: number + path: Path2D; + width: number; + height: number; } interface IRenderingStyle { diff --git a/src/renderer/canvas/cell/combobox/index.ts b/src/renderer/canvas/cell/combobox/index.ts new file mode 100644 index 0000000..93ebbe5 --- /dev/null +++ b/src/renderer/canvas/cell/combobox/index.ts @@ -0,0 +1,14 @@ +export { ComboBoxCellRenderer } from './combobox-cell-renderer'; +export { + IComboBoxCellRendererValue, + IComboBoxOption, + IComboBoxSelectOptions, +} from './combobox-cell-renderer-value'; +export { + ILabelOptions, + IDropdownOptions, + ISelectArrowOptions, + IPlaceholderOptions, + IComboBoxCellRendererOptions, + fillOptions as fillComboboxCellRendererOptions, +} from './combobox-cell-renderer-options'; diff --git a/src/renderer/canvas/cell/dom/dom-cell-renderer.ts b/src/renderer/canvas/cell/dom/dom-cell-renderer.ts index b142081..eff2360 100644 --- a/src/renderer/canvas/cell/dom/dom-cell-renderer.ts +++ b/src/renderer/canvas/cell/dom/dom-cell-renderer.ts @@ -1,20 +1,19 @@ -import {ICanvasCellRenderer} from "../canvas-cell-renderer"; -import {IRectangle} from "../../../../util/rect"; -import {ICell} from "../../../../cell/cell"; -import {TableEngine} from "../../../../table-engine"; -import {ICellRendererEventListener} from "../../../cell/event/cell-renderer-event-listener"; -import {IRenderContext} from "../../canvas-renderer"; -import {IOverlay} from "../../../../overlay/overlay"; +import { ICanvasCellRenderer } from '../canvas-cell-renderer'; +import { IRectangle } from '../../../../util'; +import { ICell } from '../../../../cell'; +import { TableEngine } from '../../../../table-engine'; +import { ICellRendererEventListener } from '../../../cell'; +import { IRenderContext } from '../../canvas-renderer'; +import { IOverlay } from '../../../../overlay'; /** * Cell renderer for rendering HTML/DOM inside a cell. */ export class DOMCellRenderer implements ICanvasCellRenderer { - /** * Name of the cell renderer. */ - public static readonly NAME: string = "dom"; + public static readonly NAME: string = 'dom'; /** * Reference to the table engine. @@ -25,7 +24,10 @@ export class DOMCellRenderer implements ICanvasCellRenderer { // Nothing to do after rendering cells with this renderer } - public before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { + public before( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { // Nothing to do before rendering cells with this renderer } @@ -34,9 +36,10 @@ export class DOMCellRenderer implements ICanvasCellRenderer { } public getCopyValue(cell: ICell): string { - const cache: IDOMCellRendererViewportCache = DOMCellRenderer._cache(cell); + const cache: IDOMCellRendererViewportCache = + DOMCellRenderer._cache(cell); - return !!cache.overlay ? cache.overlay.element.innerHTML : ""; + return !!cache.overlay ? cache.overlay.element.innerHTML : ''; } public getEventListener(): ICellRendererEventListener | null { @@ -53,28 +56,34 @@ export class DOMCellRenderer implements ICanvasCellRenderer { public onDisappearing(cell: ICell): void { // Remove DOM element (overlay) again - const cache: IDOMCellRendererViewportCache = DOMCellRenderer._cache(cell); + const cache: IDOMCellRendererViewportCache = + DOMCellRenderer._cache(cell); if (!!cache.overlay) { this._engine.getOverlayManager().removeOverlay(cache.overlay); } } - public render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { + public render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { // Render nothing on the canvas, instead use an overlay to display the DOM element - const cache: IDOMCellRendererViewportCache = DOMCellRenderer._cache(cell); + const cache: IDOMCellRendererViewportCache = + DOMCellRenderer._cache(cell); if (!cache.overlay) { // Create overlay let domElement: HTMLElement; if (cell.value instanceof HTMLElement) { domElement = cell.value as HTMLElement; } else { - domElement = document.createElement("div"); + domElement = document.createElement('div'); domElement.innerHTML = `${cell.value}`; } const overlay: IOverlay = { element: domElement, - bounds: this._engine.getCellModel().getBounds(cell.range) + bounds: this._engine.getCellModel().getBounds(cell.range), }; this._engine.getOverlayManager().addOverlay(overlay); @@ -83,12 +92,15 @@ export class DOMCellRenderer implements ICanvasCellRenderer { } else { // Check whether cell dimensions changed -> Overlay update is needed const oldBounds: IRectangle = cache.overlay.bounds; - const newBounds: IRectangle = this._engine.getCellModel().getBounds(cell.range); - - const boundsChanged: boolean = newBounds.left !== oldBounds.left - || newBounds.top !== oldBounds.top - || newBounds.width !== oldBounds.width - || newBounds.height !== oldBounds.height; + const newBounds: IRectangle = this._engine + .getCellModel() + .getBounds(cell.range); + + const boundsChanged: boolean = + newBounds.left !== oldBounds.left || + newBounds.top !== oldBounds.top || + newBounds.width !== oldBounds.width || + newBounds.height !== oldBounds.height; if (boundsChanged) { cache.overlay.bounds = newBounds; this._engine.getOverlayManager().updateOverlay(cache.overlay); @@ -110,17 +122,14 @@ export class DOMCellRenderer implements ICanvasCellRenderer { return cache; } } - } /** * Viewport cache of the DOM cell renderer. */ interface IDOMCellRendererViewportCache { - /** * The overlay showing the DOM element for a cell. */ overlay?: IOverlay; - } diff --git a/src/renderer/canvas/cell/dom/index.ts b/src/renderer/canvas/cell/dom/index.ts new file mode 100644 index 0000000..541204a --- /dev/null +++ b/src/renderer/canvas/cell/dom/index.ts @@ -0,0 +1 @@ +export { DOMCellRenderer } from './dom-cell-renderer'; diff --git a/src/renderer/canvas/cell/header/index.ts b/src/renderer/canvas/cell/header/index.ts new file mode 100644 index 0000000..c0f5150 --- /dev/null +++ b/src/renderer/canvas/cell/header/index.ts @@ -0,0 +1 @@ +export { RowColumnHeaderRenderer } from './row-column-header-renderer'; diff --git a/src/renderer/canvas/cell/header/row-column-header-renderer.ts b/src/renderer/canvas/cell/header/row-column-header-renderer.ts index 3941540..b75fb4b 100644 --- a/src/renderer/canvas/cell/header/row-column-header-renderer.ts +++ b/src/renderer/canvas/cell/header/row-column-header-renderer.ts @@ -1,25 +1,24 @@ -import {ICanvasCellRenderer} from "../canvas-cell-renderer"; -import {ICell} from "../../../../cell/cell"; -import {IRectangle} from "../../../../util/rect"; -import {ISelectionModel} from "../../../../selection/model/selection-model.interface"; -import {TableEngine} from "../../../../table-engine"; -import {IRenderContext} from "../../canvas-renderer"; -import {ICellRendererEventListener} from "../../../cell/event/cell-renderer-event-listener"; +import { ICanvasCellRenderer } from '../canvas-cell-renderer'; +import { ICell } from '../../../../cell'; +import { IRectangle } from '../../../../util'; +import { ISelectionModel } from '../../../../selection'; +import { TableEngine } from '../../../../table-engine'; +import { IRenderContext } from '../../canvas-renderer'; +import { ICellRendererEventListener } from '../../../cell'; /** * Spreadsheet like row/column headers. */ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { - /** * Name of the cell renderer. */ - public static readonly NAME: string = "row-column-header"; + public static readonly NAME: string = 'row-column-header'; /** * Available letters in the alphabet to use for generating column names. */ - private static readonly ALPHABET: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static readonly ALPHABET: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; /** * Size of the highlight rect that is displayed when the row or column of the row/column header @@ -31,17 +30,17 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { * Color of the highlight rect that is displayed when the row or column of the row/column header * is currently selected. */ - private static readonly HIGHLIGHT_RECT_COLOR: string = "#FF3366"; + private static readonly HIGHLIGHT_RECT_COLOR: string = '#FF3366'; /** * Background color of a row/column header cell. */ - private static readonly BACKGROUND_COLOR: string = "#F9F9F9"; + private static readonly BACKGROUND_COLOR: string = '#F9F9F9'; /** * Background color of a hovered row/column header cell. */ - private static readonly HOVER_BACKGROUND_COLOR: string = "#EAEAEA"; + private static readonly HOVER_BACKGROUND_COLOR: string = '#EAEAEA'; /** * The table-engines selection model. @@ -73,7 +72,7 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { this._hoveredCell = null; this._engine.repaint(); } - } + }, }; /** @@ -100,9 +99,12 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { * @param ctx to render with * @param context of the current rendering cycle */ - public before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { - ctx.textBaseline = "middle"; - ctx.textAlign = "center"; + public before( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; } /** @@ -142,15 +144,19 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { remaining -= 1; } - const rest: number = remaining % RowColumnHeaderRenderer.ALPHABET.length; + const rest: number = + remaining % RowColumnHeaderRenderer.ALPHABET.length; result.push(rest); - remaining = Math.floor(remaining / RowColumnHeaderRenderer.ALPHABET.length); + remaining = Math.floor( + remaining / RowColumnHeaderRenderer.ALPHABET.length + ); } while (remaining > 0); - return result.reverse() + return result + .reverse() .map((v) => RowColumnHeaderRenderer.ALPHABET[v]) - .join(""); + .join(''); } else { // Generate row header return `${row}`; @@ -171,11 +177,21 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { * @param cell to render * @param bounds to render cell in */ - public render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { - ctx.fillStyle = cell === this._hoveredCell ? RowColumnHeaderRenderer.HOVER_BACKGROUND_COLOR : RowColumnHeaderRenderer.BACKGROUND_COLOR; + public render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { + ctx.fillStyle = + cell === this._hoveredCell + ? RowColumnHeaderRenderer.HOVER_BACKGROUND_COLOR + : RowColumnHeaderRenderer.BACKGROUND_COLOR; ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height); - const value: string | null = RowColumnHeaderRenderer._getCellValue(cell.range.startRow, cell.range.startColumn); + const value: string | null = RowColumnHeaderRenderer._getCellValue( + cell.range.startRow, + cell.range.startColumn + ); if (!!value) { const isRowHeader: boolean = cell.range.startColumn === 0; let selected: boolean = false; @@ -184,25 +200,45 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { selected = this._isRowSelected(cell.range.startRow); if (selected) { selected = true; - ctx.fillStyle = RowColumnHeaderRenderer.HIGHLIGHT_RECT_COLOR; - ctx.fillRect(bounds.left + bounds.width - RowColumnHeaderRenderer.HIGHLIGHT_RECT_SIZE, bounds.top, RowColumnHeaderRenderer.HIGHLIGHT_RECT_SIZE, bounds.height); + ctx.fillStyle = + RowColumnHeaderRenderer.HIGHLIGHT_RECT_COLOR; + ctx.fillRect( + bounds.left + + bounds.width - + RowColumnHeaderRenderer.HIGHLIGHT_RECT_SIZE, + bounds.top, + RowColumnHeaderRenderer.HIGHLIGHT_RECT_SIZE, + bounds.height + ); } } else { // Is column header selected = this._isColumnSelected(cell.range.startColumn); if (selected) { - ctx.fillStyle = RowColumnHeaderRenderer.HIGHLIGHT_RECT_COLOR; - ctx.fillRect(bounds.left, bounds.top + bounds.height - RowColumnHeaderRenderer.HIGHLIGHT_RECT_SIZE, bounds.width, RowColumnHeaderRenderer.HIGHLIGHT_RECT_SIZE); + ctx.fillStyle = + RowColumnHeaderRenderer.HIGHLIGHT_RECT_COLOR; + ctx.fillRect( + bounds.left, + bounds.top + + bounds.height - + RowColumnHeaderRenderer.HIGHLIGHT_RECT_SIZE, + bounds.width, + RowColumnHeaderRenderer.HIGHLIGHT_RECT_SIZE + ); } } - ctx.fillStyle = "#333333"; // Foreground color + ctx.fillStyle = '#333333'; // Foreground color if (selected) { - ctx.font = "bold 12px sans-serif"; + ctx.font = 'bold 12px sans-serif'; } else { - ctx.font = "12px sans-serif"; + ctx.font = '12px sans-serif'; } - ctx.fillText(value, Math.round(bounds.left + bounds.width / 2), Math.round(bounds.top + bounds.height / 2)); + ctx.fillText( + value, + Math.round(bounds.left + bounds.width / 2), + Math.round(bounds.top + bounds.height / 2) + ); } } @@ -226,7 +262,10 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { */ private _isColumnSelected(columnIndex: number): boolean { for (const s of this._selectionModel.getSelections()) { - if (s.range.startColumn <= columnIndex && s.range.endColumn >= columnIndex) { + if ( + s.range.startColumn <= columnIndex && + s.range.endColumn >= columnIndex + ) { return true; } } @@ -239,7 +278,7 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { * This may be a HTML representation of the value (for example for copying formatting, lists, ...). */ public getCopyValue(cell: ICell): string { - return ""; + return ''; } /** @@ -249,5 +288,4 @@ export class RowColumnHeaderRenderer implements ICanvasCellRenderer { public onDisappearing(cell: ICell): void { // Do nothing } - } diff --git a/src/renderer/canvas/cell/image/image-cell-renderer.ts b/src/renderer/canvas/cell/image/image-cell-renderer.ts index 95f0176..5c00278 100644 --- a/src/renderer/canvas/cell/image/image-cell-renderer.ts +++ b/src/renderer/canvas/cell/image/image-cell-renderer.ts @@ -1,24 +1,26 @@ -import {ICanvasCellRenderer} from "../canvas-cell-renderer"; -import {ICell} from "../../../../cell/cell"; -import {IRectangle} from "../../../../util/rect"; -import {TableEngine} from "../../../../table-engine"; -import {IRenderContext} from "../../canvas-renderer"; -import {ICellRendererEventListener} from "../../../cell/event/cell-renderer-event-listener"; +import { ICanvasCellRenderer } from '../canvas-cell-renderer'; +import { ICell } from '../../../../cell'; +import { IRectangle } from '../../../../util'; +import { TableEngine } from '../../../../table-engine'; +import { IRenderContext } from '../../canvas-renderer'; +import { ICellRendererEventListener } from '../../../cell'; /** * Cell renderer for rendering images. */ export class ImageCellRenderer implements ICanvasCellRenderer { - /** * Name of the cell renderer. */ - public static readonly NAME: string = "image"; + public static readonly NAME: string = 'image'; /** * Cache for already loaded images. */ - private readonly _imageCache: Map = new Map(); + private readonly _imageCache: Map = new Map< + string, + CanvasImageSource + >(); /** * Reference to the table-engine. @@ -48,11 +50,14 @@ export class ImageCellRenderer implements ICanvasCellRenderer { * @param ctx to render with * @param context of the current rendering cycle */ - public before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { - ctx.font = "12px sans-serif"; - ctx.fillStyle = "#333333"; - ctx.textBaseline = "middle"; - ctx.textAlign = "center"; + public before( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { + ctx.font = '12px sans-serif'; + ctx.fillStyle = '#333333'; + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; } /** @@ -85,15 +90,24 @@ export class ImageCellRenderer implements ICanvasCellRenderer { * @param cell to render * @param bounds to render cell in */ - public render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { + public render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { if (cell.value !== null) { - const value: IImageCellRendererValue = cell.value as IImageCellRendererValue; + const value: IImageCellRendererValue = + cell.value as IImageCellRendererValue; - const image: CanvasImageSource | null = this._getImageIfLoaded(value.src); + const image: CanvasImageSource | null = this._getImageIfLoaded( + value.src + ); if (!!image) { // Scale image to bounds - const hasWidth: boolean = value.width !== null && value.width !== undefined; - const hasHeight: boolean = value.height !== null && value.height !== undefined; + const hasWidth: boolean = + value.width !== null && value.width !== undefined; + const hasHeight: boolean = + value.height !== null && value.height !== undefined; const originalWidth: number = image.width as number; const originalHeight: number = image.height as number; @@ -110,15 +124,20 @@ export class ImageCellRenderer implements ICanvasCellRenderer { width = value.height * aspectRatio; } else if (!hasWidth && !hasHeight) { // Scale image to cell size - const scaleFactor: number = Math.min(bounds.width / originalWidth, bounds.height / originalHeight); + const scaleFactor: number = Math.min( + bounds.width / originalWidth, + bounds.height / originalHeight + ); width = originalWidth * scaleFactor; height = originalHeight * scaleFactor; } if (width > bounds.width || height > bounds.height) { // Image dimensions do not really fit into cell -> crop image - const overlapX: number = width > bounds.width ? width - bounds.width : 0; - const overlapY: number = height > bounds.height ? height - bounds.height : 0; + const overlapX: number = + width > bounds.width ? width - bounds.width : 0; + const overlapY: number = + height > bounds.height ? height - bounds.height : 0; const actualWidth: number = width - overlapX; const actualHeight: number = height - overlapY; @@ -144,7 +163,11 @@ export class ImageCellRenderer implements ICanvasCellRenderer { ); } } else { - ctx.fillText("Loading...", Math.round(bounds.left + bounds.width / 2), Math.round(bounds.top + bounds.height / 2)); + ctx.fillText( + 'Loading...', + Math.round(bounds.left + bounds.width / 2), + Math.round(bounds.top + bounds.height / 2) + ); } } } @@ -178,10 +201,14 @@ export class ImageCellRenderer implements ICanvasCellRenderer { private async _loadImage(src: string): Promise { const image = new Image(); const promise = new Promise((resolve) => { - image.addEventListener("load", () => { - resolve(); - }, false); - }) + image.addEventListener( + 'load', + () => { + resolve(); + }, + false + ); + }); image.src = src; await promise; @@ -194,26 +221,31 @@ export class ImageCellRenderer implements ICanvasCellRenderer { * This may be a HTML representation of the value (for example for copying formatting, lists, ...). */ public getCopyValue(cell: ICell): string { - const image: CanvasImageSource | null = this._getImageIfLoaded(cell.value.src); + const image: CanvasImageSource | null = this._getImageIfLoaded( + cell.value.src + ); if (!!image) { - const bounds: IRectangle = this._engine.getCellModel().getBounds(cell.range); + const bounds: IRectangle = this._engine + .getCellModel() + .getBounds(cell.range); - const copyCanvas: HTMLCanvasElement = document.createElement("canvas"); + const copyCanvas: HTMLCanvasElement = + document.createElement('canvas'); copyCanvas.width = bounds.width; copyCanvas.height = bounds.height; - const ctx: CanvasRenderingContext2D = copyCanvas.getContext("2d"); + const ctx: CanvasRenderingContext2D = copyCanvas.getContext('2d'); this.render(ctx, cell, { left: 0, top: 0, width: bounds.width, - height: bounds.height + height: bounds.height, }); - return ``; + return ``; } else { - return ""; + return ''; } } @@ -224,14 +256,12 @@ export class ImageCellRenderer implements ICanvasCellRenderer { public onDisappearing(cell: ICell): void { // Do nothing } - } /** * Value for the image cell renderer. */ export interface IImageCellRendererValue { - /** * Src to load image from. */ @@ -246,5 +276,4 @@ export interface IImageCellRendererValue { * Height of the image (or null). */ height?: number; - } diff --git a/src/renderer/canvas/cell/image/index.ts b/src/renderer/canvas/cell/image/index.ts new file mode 100644 index 0000000..09c8c01 --- /dev/null +++ b/src/renderer/canvas/cell/image/index.ts @@ -0,0 +1,4 @@ +export { + IImageCellRendererValue, + ImageCellRenderer, +} from './image-cell-renderer'; diff --git a/src/renderer/canvas/cell/index.ts b/src/renderer/canvas/cell/index.ts new file mode 100644 index 0000000..be06315 --- /dev/null +++ b/src/renderer/canvas/cell/index.ts @@ -0,0 +1,9 @@ +export { ICanvasCellRenderer } from './canvas-cell-renderer'; +export * from './checkbox'; +export * from './combobox'; +export * from './dom'; +export * from './header'; +export * from './image'; +export * from './loading'; +export * from './rating'; +export * from './text'; diff --git a/src/renderer/canvas/cell/loading/index.ts b/src/renderer/canvas/cell/loading/index.ts new file mode 100644 index 0000000..8dfd732 --- /dev/null +++ b/src/renderer/canvas/cell/loading/index.ts @@ -0,0 +1,4 @@ +export { + ILoadingCellRendererValue, + LoadingCellRenderer, +} from './loading-cell-renderer'; diff --git a/src/renderer/canvas/cell/loading/loading-cell-renderer.ts b/src/renderer/canvas/cell/loading/loading-cell-renderer.ts index 647606e..d9766f4 100644 --- a/src/renderer/canvas/cell/loading/loading-cell-renderer.ts +++ b/src/renderer/canvas/cell/loading/loading-cell-renderer.ts @@ -1,19 +1,18 @@ -import {ICanvasCellRenderer} from "../canvas-cell-renderer"; -import {ICell} from "../../../../cell/cell"; -import {IRectangle} from "../../../../util/rect"; -import {TableEngine} from "../../../../table-engine"; -import {IRenderContext} from "../../canvas-renderer"; -import {ICellRendererEventListener} from "../../../cell/event/cell-renderer-event-listener"; +import { ICanvasCellRenderer } from '../canvas-cell-renderer'; +import { ICell } from '../../../../cell'; +import { IRectangle } from '../../../../util'; +import { TableEngine } from '../../../../table-engine'; +import { IRenderContext } from '../../canvas-renderer'; +import { ICellRendererEventListener } from '../../../cell'; /** * Cell renderer for displaying a loading animation. */ export class LoadingCellRenderer implements ICanvasCellRenderer { - /** * Name of the cell renderer. */ - public static readonly NAME: string = "loading"; + public static readonly NAME: string = 'loading'; /** * Duration of one full animation in milliseconds. @@ -93,7 +92,10 @@ export class LoadingCellRenderer implements ICanvasCellRenderer { * @param ctx to render with * @param context of the current rendering cycle */ - public before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { + public before( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { this._ctx = ctx; this._context = context; @@ -106,7 +108,9 @@ export class LoadingCellRenderer implements ICanvasCellRenderer { this._progress = 0.0; } - this._transformedProgress = LoadingCellRenderer._easeInOut(this._progress); + this._transformedProgress = LoadingCellRenderer._easeInOut( + this._progress + ); } this._lastTimestamp = timestamp; @@ -137,8 +141,8 @@ export class LoadingCellRenderer implements ICanvasCellRenderer { */ private static _easeInOut(progress: number): number { return progress < 0.5 - ? (4 * progress * progress * progress) - : ((progress - 1) * (2 * progress - 2) * (2 * progress - 2) + 1); + ? 4 * progress * progress * progress + : (progress - 1) * (2 * progress - 2) * (2 * progress - 2) + 1; } /** @@ -154,7 +158,10 @@ export class LoadingCellRenderer implements ICanvasCellRenderer { * @param ctx to apply style to * @param progress to use */ - private static _prepareFillStyle(ctx: CanvasRenderingContext2D, progress: number): void { + private static _prepareFillStyle( + ctx: CanvasRenderingContext2D, + progress: number + ): void { const p = Math.abs(progress - 0.5); const colorVal: number = 170 + 60 * (1.0 - p); ctx.fillStyle = `rgb(${colorVal}, ${colorVal}, ${colorVal})`; @@ -166,9 +173,25 @@ export class LoadingCellRenderer implements ICanvasCellRenderer { * @param cell to render * @param bounds to render cell in */ - public render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { - const width: number = Math.max(Math.min(bounds.width - LoadingCellRenderer.HORIZONTAL_PADDING * 2, LoadingCellRenderer.MAX_WIDTH), 2); - const height: number = Math.max(Math.min(bounds.height - LoadingCellRenderer.VERTICAL_PADDING * 2, LoadingCellRenderer.MAX_HEIGHT), 2); + public render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { + const width: number = Math.max( + Math.min( + bounds.width - LoadingCellRenderer.HORIZONTAL_PADDING * 2, + LoadingCellRenderer.MAX_WIDTH + ), + 2 + ); + const height: number = Math.max( + Math.min( + bounds.height - LoadingCellRenderer.VERTICAL_PADDING * 2, + LoadingCellRenderer.MAX_HEIGHT + ), + 2 + ); let rect: IRectangle = { top: bounds.top + (bounds.height - height) / 2, @@ -177,7 +200,8 @@ export class LoadingCellRenderer implements ICanvasCellRenderer { height, }; - const value: ILoadingCellRendererValue = cell.value as ILoadingCellRendererValue; + const value: ILoadingCellRendererValue = + cell.value as ILoadingCellRendererValue; if (value.promiseSupplier !== undefined && !value.isLoading) { value.isLoading = true; @@ -200,7 +224,7 @@ export class LoadingCellRenderer implements ICanvasCellRenderer { * This may be a HTML representation of the value (for example for copying formatting, lists, ...). */ public getCopyValue(cell: ICell): string { - return ""; + return ''; } /** @@ -210,14 +234,12 @@ export class LoadingCellRenderer implements ICanvasCellRenderer { public onDisappearing(cell: ICell): void { // Do nothing } - } /** * Value for the loading cell renderer. */ export interface ILoadingCellRendererValue { - /** * Supplier for the loading promise. */ @@ -237,5 +259,4 @@ export interface ILoadingCellRendererValue { * Name of the cell renderer to display when loading finished. */ cellRenderer: string; - } diff --git a/src/renderer/canvas/cell/rating/index.ts b/src/renderer/canvas/cell/rating/index.ts new file mode 100644 index 0000000..f274834 --- /dev/null +++ b/src/renderer/canvas/cell/rating/index.ts @@ -0,0 +1,6 @@ +export { RatingCellRenderer } from './rating-cell-renderer'; +export { IRatingCellRendererValue } from './rating-cell-renderer-value'; +export { + IRatingCellRendererOptions, + fillOptions as fillRatingCellRendererOptions, +} from './rating-cell-renderer-options'; diff --git a/src/renderer/canvas/cell/rating/rating-cell-renderer-options.ts b/src/renderer/canvas/cell/rating/rating-cell-renderer-options.ts index 96cfe49..7d0232a 100644 --- a/src/renderer/canvas/cell/rating/rating-cell-renderer-options.ts +++ b/src/renderer/canvas/cell/rating/rating-cell-renderer-options.ts @@ -1,21 +1,19 @@ -import {IColor} from "../../../../util/color"; -import {Colors} from "../../../../util/colors"; -import {ICell} from "../../../../cell/cell"; - -export const DEFAULT_STAR_COUNT: number = 5; -export const DEFAULT_STAR_SPIKE_COUNT: number = 5; -export const DEFAULT_COLOR: IColor = Colors.ORANGE; -export const DEFAULT_INACTIVE_COLOR: IColor = Colors.LIGHTGRAY; -export const DEFAULT_HOVER_BORDER_COLOR: IColor = Colors.ORANGE; -export const DEFAULT_HOVER_BORDER_THICKNESS: number = 1; -export const DEFAULT_SPACING: number = 2; -export const DEFAULT_PADDING: number = 2; +import { Colors, IColor } from '../../../../util'; +import { ICell } from '../../../../cell'; + +const DEFAULT_STAR_COUNT: number = 5; +const DEFAULT_STAR_SPIKE_COUNT: number = 5; +const DEFAULT_COLOR: IColor = Colors.ORANGE; +const DEFAULT_INACTIVE_COLOR: IColor = Colors.LIGHTGRAY; +const DEFAULT_HOVER_BORDER_COLOR: IColor = Colors.ORANGE; +const DEFAULT_HOVER_BORDER_THICKNESS: number = 1; +const DEFAULT_SPACING: number = 2; +const DEFAULT_PADDING: number = 2; /** * Options for the rating cell renderer. */ export interface IRatingCellRendererOptions { - /** * Callback to inform about a change of the rating value. * @param cell that contains the changed value @@ -74,7 +72,6 @@ export interface IRatingCellRendererOptions { * Padding from the cells borders. */ padding?: number; - } /** @@ -114,7 +111,10 @@ export const fillOptions = (options?: IRatingCellRendererOptions) => { options.hoverBorderColor = DEFAULT_HOVER_BORDER_COLOR; } - if (options.hoverBorderThickness === undefined || options.hoverBorderThickness === null) { + if ( + options.hoverBorderThickness === undefined || + options.hoverBorderThickness === null + ) { options.hoverBorderThickness = DEFAULT_HOVER_BORDER_THICKNESS; } @@ -127,4 +127,4 @@ export const fillOptions = (options?: IRatingCellRendererOptions) => { } return options; -} +}; diff --git a/src/renderer/canvas/cell/rating/rating-cell-renderer-value.ts b/src/renderer/canvas/cell/rating/rating-cell-renderer-value.ts index 0edbb01..3c01f6a 100644 --- a/src/renderer/canvas/cell/rating/rating-cell-renderer-value.ts +++ b/src/renderer/canvas/cell/rating/rating-cell-renderer-value.ts @@ -1,4 +1,4 @@ -import {IRatingCellRendererOptions} from "./rating-cell-renderer-options"; +import { IRatingCellRendererOptions } from './rating-cell-renderer-options'; /** * Value of the checkbox cell renderer value. @@ -7,7 +7,6 @@ import {IRatingCellRendererOptions} from "./rating-cell-renderer-options"; * will allow more control and unlock more options. */ export interface IRatingCellRendererValue { - /** * The rating to render. */ @@ -18,5 +17,4 @@ export interface IRatingCellRendererValue { * If not set the default options of the rating cell renderer are applied. */ options?: IRatingCellRendererOptions; - } diff --git a/src/renderer/canvas/cell/rating/rating-cell-renderer.ts b/src/renderer/canvas/cell/rating/rating-cell-renderer.ts index 6b5fd60..2087176 100644 --- a/src/renderer/canvas/cell/rating/rating-cell-renderer.ts +++ b/src/renderer/canvas/cell/rating/rating-cell-renderer.ts @@ -1,24 +1,23 @@ -import {ICanvasCellRenderer} from "../canvas-cell-renderer"; -import {IRenderContext} from "../../canvas-renderer"; -import {ICell} from "../../../../cell/cell"; -import {ICellRendererEventListener} from "../../../cell/event/cell-renderer-event-listener"; -import {TableEngine} from "../../../../table-engine"; -import {IRectangle} from "../../../../util/rect"; -import {fillOptions, IRatingCellRendererOptions} from "./rating-cell-renderer-options"; -import {IRatingCellRendererValue} from "./rating-cell-renderer-value"; -import {IPoint} from "../../../../util/point"; -import {Colors} from "../../../../util/colors"; -import {IColor} from "../../../../util/color"; +import { ICanvasCellRenderer } from '../canvas-cell-renderer'; +import { IRenderContext } from '../../canvas-renderer'; +import { ICell } from '../../../../cell'; +import { ICellRendererEventListener } from '../../../cell'; +import { TableEngine } from '../../../../table-engine'; +import { Colors, IColor, IPoint, IRectangle } from '../../../../util'; +import { + fillOptions, + IRatingCellRendererOptions, +} from './rating-cell-renderer-options'; +import { IRatingCellRendererValue } from './rating-cell-renderer-value'; /** * Cell renderer rendering a rating visualization using a row of stars. */ export class RatingCellRenderer implements ICanvasCellRenderer { - /** * Name of the cell renderer. */ - public static readonly NAME: string = "rating"; + public static readonly NAME: string = 'rating'; /** * Options of the cell renderer. @@ -30,17 +29,27 @@ export class RatingCellRenderer implements ICanvasCellRenderer { */ private readonly _eventListener: ICellRendererEventListener = { onMouseMove: (event) => { - const cache: IRatingCellRendererViewportCache = RatingCellRenderer._cache(event.cell); - const value: IRatingCellRendererValue = RatingCellRenderer._value(event.cell); + const cache: IRatingCellRendererViewportCache = + RatingCellRenderer._cache(event.cell); + const value: IRatingCellRendererValue = RatingCellRenderer._value( + event.cell + ); let editable: boolean = this._options.editable; - if (!!value.options && value.options.editable !== undefined && value.options.editable !== null) { + if ( + !!value.options && + value.options.editable !== undefined && + value.options.editable !== null + ) { editable = value.options.editable; } if (editable) { // Check whether a star is hovered - let hoveredStar: number = this._getHoveredStar(event.offset, event.cell); + let hoveredStar: number = this._getHoveredStar( + event.offset, + event.cell + ); if (hoveredStar !== cache.hoveredStar) { cache.hoveredStar = hoveredStar; this._engine.repaint(); @@ -48,26 +57,38 @@ export class RatingCellRenderer implements ICanvasCellRenderer { } }, onMouseOut: (event) => { - const cache: IRatingCellRendererViewportCache = RatingCellRenderer._cache(event.cell); + const cache: IRatingCellRendererViewportCache = + RatingCellRenderer._cache(event.cell); if (cache.hoveredStar !== undefined && cache.hoveredStar > -1) { this._engine.repaint(); } }, onMouseUp: (event) => { - const value: IRatingCellRendererValue = RatingCellRenderer._value(event.cell); + const value: IRatingCellRendererValue = RatingCellRenderer._value( + event.cell + ); let editable: boolean = this._options.editable; let maxValue: number = this._options.maxValue; let starCount: number = this._options.starCount; let onChanged: (cell: ICell) => void = this._options.onChanged; if (!!value.options) { - if (value.options.editable !== undefined && value.options.editable !== null) { + if ( + value.options.editable !== undefined && + value.options.editable !== null + ) { editable = value.options.editable; } - if (value.options.maxValue !== undefined && value.options.maxValue !== null) { + if ( + value.options.maxValue !== undefined && + value.options.maxValue !== null + ) { maxValue = value.options.maxValue; } - if (value.options.starCount !== undefined && value.options.starCount !== null) { + if ( + value.options.starCount !== undefined && + value.options.starCount !== null + ) { starCount = value.options.starCount; } if (!!value.options.onChanged) { @@ -76,9 +97,12 @@ export class RatingCellRenderer implements ICanvasCellRenderer { } if (editable) { - let hoveredStar: number = this._getHoveredStar(event.offset, event.cell); + let hoveredStar: number = this._getHoveredStar( + event.offset, + event.cell + ); - value.rating = (hoveredStar + 1) / starCount * maxValue; + value.rating = ((hoveredStar + 1) / starCount) * maxValue; if (!!onChanged) { onChanged(event.cell); @@ -86,7 +110,7 @@ export class RatingCellRenderer implements ICanvasCellRenderer { this._engine.repaint(); } - } + }, }; /** @@ -109,7 +133,8 @@ export class RatingCellRenderer implements ICanvasCellRenderer { * @param cell to check for */ private _getHoveredStar(offset: IPoint, cell: ICell): number { - const cache: IRatingCellRendererViewportCache = RatingCellRenderer._cache(cell); + const cache: IRatingCellRendererViewportCache = + RatingCellRenderer._cache(cell); if (!cache.starPaths) { return -1; @@ -134,14 +159,20 @@ export class RatingCellRenderer implements ICanvasCellRenderer { * @param cell to get cell renderer value for */ private static _value(cell: ICell): IRatingCellRendererValue { - const isSpecialValue: boolean = !!cell.value && typeof cell.value === "object" && "rating" in cell.value; + const isSpecialValue: boolean = + !!cell.value && + typeof cell.value === 'object' && + 'rating' in cell.value; if (isSpecialValue) { return cell.value as IRatingCellRendererValue; } else { - const rating: number = cell.value !== undefined && cell.value !== null ? cell.value : 0; + const rating: number = + cell.value !== undefined && cell.value !== null + ? cell.value + : 0; - const value: IRatingCellRendererValue = {rating}; + const value: IRatingCellRendererValue = { rating }; cell.value = value; return value; } @@ -165,9 +196,12 @@ export class RatingCellRenderer implements ICanvasCellRenderer { // Nothing to do after rendering all cells with this renderer } - public before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { + public before( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { ctx.lineDashOffset = 0.0; - ctx.lineJoin = "round"; + ctx.lineJoin = 'round'; } public cleanup(): void { @@ -194,7 +228,11 @@ export class RatingCellRenderer implements ICanvasCellRenderer { // Nothing to do when a cell disappears from the viewport } - public render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { + public render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { this._ctx = ctx; const value: IRatingCellRendererValue = RatingCellRenderer._value(cell); @@ -203,7 +241,8 @@ export class RatingCellRenderer implements ICanvasCellRenderer { return; } - const cache: IRatingCellRendererViewportCache = RatingCellRenderer._cache(cell); + const cache: IRatingCellRendererViewportCache = + RatingCellRenderer._cache(cell); let starCount: number = this._options.starCount; let spikeCount: number = this._options.spikeCount; @@ -215,19 +254,34 @@ export class RatingCellRenderer implements ICanvasCellRenderer { let hoverBorderColor: IColor = this._options.hoverBorderColor; let hoverBorderThickness: number = this._options.hoverBorderThickness; if (!!value.options) { - if (value.options.starCount !== undefined && value.options.starCount !== null) { + if ( + value.options.starCount !== undefined && + value.options.starCount !== null + ) { starCount = value.options.starCount; } - if (value.options.spikeCount !== undefined && value.options.spikeCount !== null) { + if ( + value.options.spikeCount !== undefined && + value.options.spikeCount !== null + ) { spikeCount = value.options.spikeCount; } - if (value.options.maxValue !== undefined && value.options.maxValue !== null) { + if ( + value.options.maxValue !== undefined && + value.options.maxValue !== null + ) { maxValue = value.options.maxValue; } - if (value.options.spacing !== undefined && value.options.spacing !== null) { + if ( + value.options.spacing !== undefined && + value.options.spacing !== null + ) { spacing = value.options.spacing; } - if (value.options.padding !== undefined && value.options.padding !== null) { + if ( + value.options.padding !== undefined && + value.options.padding !== null + ) { padding = value.options.padding; } if (!!value.options.color) { @@ -239,29 +293,38 @@ export class RatingCellRenderer implements ICanvasCellRenderer { if (!!value.options.hoverBorderColor) { hoverBorderColor = value.options.hoverBorderColor; } - if (value.options.hoverBorderThickness !== undefined && value.options.hoverBorderThickness !== null) { + if ( + value.options.hoverBorderThickness !== undefined && + value.options.hoverBorderThickness !== null + ) { hoverBorderThickness = value.options.hoverBorderThickness; } } - const boundsChanged: boolean = !cache.oldBounds - || (cache.oldBounds.top !== bounds.top - || cache.oldBounds.left !== bounds.left - || cache.oldBounds.width !== bounds.width - || cache.oldBounds.height !== bounds.height); - - const sizePerStar: number = Math.min((bounds.width - padding * 2) / starCount, bounds.height - padding * 2); + const boundsChanged: boolean = + !cache.oldBounds || + cache.oldBounds.top !== bounds.top || + cache.oldBounds.left !== bounds.left || + cache.oldBounds.width !== bounds.width || + cache.oldBounds.height !== bounds.height; + + const sizePerStar: number = Math.min( + (bounds.width - padding * 2) / starCount, + bounds.height - padding * 2 + ); const totalWidth: number = sizePerStar * starCount; - const xOffset: number = bounds.left + Math.round((bounds.width - totalWidth) / 2); - const yOffset: number = bounds.top + Math.round((bounds.height - sizePerStar) / 2); + const xOffset: number = + bounds.left + Math.round((bounds.width - totalWidth) / 2); + const yOffset: number = + bounds.top + Math.round((bounds.height - sizePerStar) / 2); // Create rating path if (boundsChanged) { const outerRadius: number = (sizePerStar - spacing) / 2; const innerRadius: number = outerRadius / 2; const starPath: Path2D = RatingCellRenderer._makeStarPath( - {x: 0, y: 0}, + { x: 0, y: 0 }, outerRadius, innerRadius, spikeCount @@ -272,7 +335,10 @@ export class RatingCellRenderer implements ICanvasCellRenderer { const translatedStarPath: Path2D = new Path2D(); translatedStarPath.addPath( starPath, - new DOMMatrix().translateSelf(xOffset + sizePerStar * i, yOffset) + new DOMMatrix().translateSelf( + xOffset + sizePerStar * i, + yOffset + ) ); paths.push(translatedStarPath); @@ -291,7 +357,8 @@ export class RatingCellRenderer implements ICanvasCellRenderer { let normalizedValue: number = value.rating / maxValue; - const isHovered: boolean = cache.hoveredStar !== undefined && cache.hoveredStar > -1; + const isHovered: boolean = + cache.hoveredStar !== undefined && cache.hoveredStar > -1; if (isHovered) { // A star is hovered -> visualize how clicking on the star would look like normalizedValue = (cache.hoveredStar + 1) / maxValue; @@ -307,8 +374,10 @@ export class RatingCellRenderer implements ICanvasCellRenderer { const starCountValue: number = normalizedValue * starCount; const filledStarCount: number = Math.floor(starCountValue); - const partlyFilledStarFraction: number = starCountValue - filledStarCount; - const partlyFilledStarIndex: number = (partlyFilledStarFraction > 0) ? filledStarCount : -1; + const partlyFilledStarFraction: number = + starCountValue - filledStarCount; + const partlyFilledStarIndex: number = + partlyFilledStarFraction > 0 ? filledStarCount : -1; for (let i = 0; i < starCount; i++) { const starPath: Path2D = cache.starPaths[i]; @@ -318,11 +387,19 @@ export class RatingCellRenderer implements ICanvasCellRenderer { } else if (i === partlyFilledStarIndex) { // Draw star partly filled const starXOffset: number = xOffset + sizePerStar * i; - const gradient: CanvasGradient = ctx.createLinearGradient(starXOffset, 0, starXOffset + (sizePerStar - spacing), 0); + const gradient: CanvasGradient = ctx.createLinearGradient( + starXOffset, + 0, + starXOffset + (sizePerStar - spacing), + 0 + ); gradient.addColorStop(0, colorStr); gradient.addColorStop(partlyFilledStarFraction, colorStr); - gradient.addColorStop(partlyFilledStarFraction, inactiveColorStr); + gradient.addColorStop( + partlyFilledStarFraction, + inactiveColorStr + ); gradient.addColorStop(1, inactiveColorStr); ctx.fillStyle = gradient; @@ -363,7 +440,7 @@ export class RatingCellRenderer implements ICanvasCellRenderer { ): Path2D { const center: IPoint = { x: offset.x + outerRadius, - y: offset.y + outerRadius + y: offset.y + outerRadius, }; const path: Path2D = new Path2D(); @@ -371,7 +448,7 @@ export class RatingCellRenderer implements ICanvasCellRenderer { // Move beginning of path to first spike on top path.moveTo(center.x, center.y - outerRadius); - const startRotation: number = Math.PI / 2 * 3; + const startRotation: number = (Math.PI / 2) * 3; const anglePerSpike: number = Math.PI / spikeCount; for (let i = 0; i < spikeCount; i++) { const nextAngle: number = startRotation + 2 * i * anglePerSpike; @@ -393,14 +470,12 @@ export class RatingCellRenderer implements ICanvasCellRenderer { return path; } - } /** * Viewport cache for the rating cell renderer. */ interface IRatingCellRendererViewportCache { - /** * Paths of the rendered stars. */ @@ -420,5 +495,4 @@ interface IRatingCellRendererViewportCache { * The currently hovered star (if any). */ hoveredStar?: number; - } diff --git a/src/renderer/canvas/cell/text/index.ts b/src/renderer/canvas/cell/text/index.ts new file mode 100644 index 0000000..d1afe63 --- /dev/null +++ b/src/renderer/canvas/cell/text/index.ts @@ -0,0 +1,6 @@ +export { TextCellRenderer } from './text-cell-renderer'; +export { ITextCellRendererValue } from './text-cell-renderer-value'; +export { + ITextCellRendererOptions, + fillOptions as fillTextRendererOptions, +} from './text-cell-renderer-options'; diff --git a/src/renderer/canvas/cell/text/line-wrap/index.ts b/src/renderer/canvas/cell/text/line-wrap/index.ts new file mode 100644 index 0000000..8da558d --- /dev/null +++ b/src/renderer/canvas/cell/text/line-wrap/index.ts @@ -0,0 +1,4 @@ +export { ILine } from './line'; +export { ILineWrapper } from './line-wrapper'; +export { TrivialLineWrapper } from './trivial-line-wrapper'; +export { IParagraph } from './paragraph'; diff --git a/src/renderer/canvas/cell/text/line-wrap/line-wrapper.ts b/src/renderer/canvas/cell/text/line-wrap/line-wrapper.ts index 8c96bb3..82cd984 100644 --- a/src/renderer/canvas/cell/text/line-wrap/line-wrapper.ts +++ b/src/renderer/canvas/cell/text/line-wrap/line-wrapper.ts @@ -1,10 +1,9 @@ -import {IParagraph} from "./paragraph"; +import { IParagraph } from './paragraph'; /** * Algorithm that will break a paragraph into lines. */ export interface ILineWrapper { - /** * Wrap the given text to lines. * @param text to apply line wrapping to @@ -18,5 +17,4 @@ export interface ILineWrapper { lineHeight: number, widthLookup: (str: string) => number ): IParagraph; - } diff --git a/src/renderer/canvas/cell/text/line-wrap/line.ts b/src/renderer/canvas/cell/text/line-wrap/line.ts index 94df22d..d6940a7 100644 --- a/src/renderer/canvas/cell/text/line-wrap/line.ts +++ b/src/renderer/canvas/cell/text/line-wrap/line.ts @@ -2,7 +2,6 @@ * Representation of a line in a paragraph. */ export interface ILine { - /** * Text of the line. */ @@ -12,5 +11,4 @@ export interface ILine { * Width of the line. */ width: number; - } diff --git a/src/renderer/canvas/cell/text/line-wrap/paragraph.ts b/src/renderer/canvas/cell/text/line-wrap/paragraph.ts index 00023bd..93a5512 100644 --- a/src/renderer/canvas/cell/text/line-wrap/paragraph.ts +++ b/src/renderer/canvas/cell/text/line-wrap/paragraph.ts @@ -1,10 +1,9 @@ -import {ILine} from "./line"; +import { ILine } from './line'; /** * Representation of a paragraph. */ export interface IParagraph { - /** * Lines in the paragraph. */ @@ -19,5 +18,4 @@ export interface IParagraph { * Width of the paragraph. */ width: number; - } diff --git a/src/renderer/canvas/cell/text/line-wrap/trivial-line-wrapper.ts b/src/renderer/canvas/cell/text/line-wrap/trivial-line-wrapper.ts index d8a10c0..7491535 100644 --- a/src/renderer/canvas/cell/text/line-wrap/trivial-line-wrapper.ts +++ b/src/renderer/canvas/cell/text/line-wrap/trivial-line-wrapper.ts @@ -1,12 +1,11 @@ -import {ILineWrapper} from "./line-wrapper"; -import {IParagraph} from "./paragraph"; -import {ILine} from "./line"; +import { ILineWrapper } from './line-wrapper'; +import { IParagraph } from './paragraph'; +import { ILine } from './line'; /** * A trivial line wrapping algorithm. */ export class TrivialLineWrapper implements ILineWrapper { - public wrap( text: string, maxWidth: number, @@ -19,31 +18,35 @@ export class TrivialLineWrapper implements ILineWrapper { const totalWidth: number = widthLookup(text); if (totalWidth <= maxWidth) { return { - lines: [{ - text, - width: totalWidth - }], + lines: [ + { + text, + width: totalWidth, + }, + ], lineHeight, - width: totalWidth + width: totalWidth, }; } // Split text into words - const words: string[] = text.split(" "); + const words: string[] = text.split(' '); if (words.length === 0) { // Only having one word -> cannot break into lines return { - lines: [{ - text, - width: totalWidth - }], + lines: [ + { + text, + width: totalWidth, + }, + ], lineHeight, - width: totalWidth + width: totalWidth, }; } const wordWidths: number[] = words.map((word) => widthLookup(word)); - const whiteSpaceWidth: number = widthLookup(" "); + const whiteSpaceWidth: number = widthLookup(' '); let curLine: string[] = []; let curWidth: number = 0; @@ -52,12 +55,13 @@ export class TrivialLineWrapper implements ILineWrapper { const word: string = words[i]; const wordWidth: number = wordWidths[i]; - const newWidth: number = curWidth + (curWidth > 0 ? whiteSpaceWidth : 0) + wordWidth; + const newWidth: number = + curWidth + (curWidth > 0 ? whiteSpaceWidth : 0) + wordWidth; if (newWidth > maxWidth) { // Do a line break before appending the word lines.push({ - text: curLine.join(" "), - width: curWidth + text: curLine.join(' '), + width: curWidth, }); curWidth = wordWidth; @@ -70,16 +74,15 @@ export class TrivialLineWrapper implements ILineWrapper { if (curLine.length > 0) { lines.push({ - text: curLine.join(" "), - width: curWidth + text: curLine.join(' '), + width: curWidth, }); } return { lines, lineHeight, - width: maxWidth + width: maxWidth, }; } - } diff --git a/src/renderer/canvas/cell/text/text-cell-renderer-options.ts b/src/renderer/canvas/cell/text/text-cell-renderer-options.ts index 05504ea..6548b47 100644 --- a/src/renderer/canvas/cell/text/text-cell-renderer-options.ts +++ b/src/renderer/canvas/cell/text/text-cell-renderer-options.ts @@ -1,13 +1,15 @@ -import {IColor} from "../../../../util/color"; -import {HorizontalAlignment} from "../../../../util/alignment/horizontal-alignment"; -import {VerticalAlignment} from "../../../../util/alignment/vertical-alignment"; -import {Colors} from "../../../../util/colors"; -import {ICell} from "../../../../cell/cell"; +import { + Colors, + HorizontalAlignment, + IColor, + VerticalAlignment, +} from '../../../../util'; +import { ICell } from '../../../../cell'; /** * The default font family in use. */ -export const DEFAULT_FONT_FAMILY: string = "sans-serif"; +export const DEFAULT_FONT_FAMILY: string = 'sans-serif'; /** * The default font size in use (in pixel). @@ -22,12 +24,14 @@ export const DEFAULT_TEXT_COLOR: IColor = Colors.BLACK; /** * Default horizontal alignment in use. */ -export const DEFAULT_HORIZONTAL_ALIGNMENT: HorizontalAlignment = HorizontalAlignment.LEFT; +export const DEFAULT_HORIZONTAL_ALIGNMENT: HorizontalAlignment = + HorizontalAlignment.LEFT; /** * Default vertical alignment in use. */ -export const DEFAULT_VERTICAL_ALIGNMENT: VerticalAlignment = VerticalAlignment.MIDDLE; +export const DEFAULT_VERTICAL_ALIGNMENT: VerticalAlignment = + VerticalAlignment.MIDDLE; /** * Default line height in use. @@ -38,7 +42,6 @@ export const DEFAULT_LINE_HEIGHT: number = 16; * Options of the text cell renderer. */ export interface ITextCellRendererOptions { - /** * Callback called when the value of the text cell renderer has been edited. * @param cell that contains the changed value @@ -86,7 +89,6 @@ export interface ITextCellRendererOptions { * Whether the cell is editable. */ editable?: boolean; - } /** @@ -110,15 +112,24 @@ export const fillOptions = (options?: ITextCellRendererOptions) => { options.color = DEFAULT_TEXT_COLOR; } - if (options.horizontalAlignment === null || options.horizontalAlignment === undefined) { + if ( + options.horizontalAlignment === null || + options.horizontalAlignment === undefined + ) { options.horizontalAlignment = DEFAULT_HORIZONTAL_ALIGNMENT; } - if (options.verticalAlignment === null || options.verticalAlignment === undefined) { + if ( + options.verticalAlignment === null || + options.verticalAlignment === undefined + ) { options.verticalAlignment = DEFAULT_VERTICAL_ALIGNMENT; } - if (options.useLineWrapping === null || options.useLineWrapping === undefined) { + if ( + options.useLineWrapping === null || + options.useLineWrapping === undefined + ) { options.useLineWrapping = false; } @@ -131,4 +142,4 @@ export const fillOptions = (options?: ITextCellRendererOptions) => { } return options; -} +}; diff --git a/src/renderer/canvas/cell/text/text-cell-renderer-value.ts b/src/renderer/canvas/cell/text/text-cell-renderer-value.ts index 09ffaa2..5f16be9 100644 --- a/src/renderer/canvas/cell/text/text-cell-renderer-value.ts +++ b/src/renderer/canvas/cell/text/text-cell-renderer-value.ts @@ -1,4 +1,4 @@ -import {ITextCellRendererOptions} from "./text-cell-renderer-options"; +import { ITextCellRendererOptions } from './text-cell-renderer-options'; /** * Value of a cell to be rendered by the text cell renderer. @@ -8,7 +8,6 @@ import {ITextCellRendererOptions} from "./text-cell-renderer-options"; * you can just pass a string as value for the cell and do not really need to use this interface. */ export interface ITextCellRendererValue { - /** * Text to render. */ @@ -19,5 +18,4 @@ export interface ITextCellRendererValue { * If not set the default options of the text cell renderer are applied. */ options?: ITextCellRendererOptions; - } diff --git a/src/renderer/canvas/cell/text/text-cell-renderer.ts b/src/renderer/canvas/cell/text/text-cell-renderer.ts index 88d08b7..f74e346 100644 --- a/src/renderer/canvas/cell/text/text-cell-renderer.ts +++ b/src/renderer/canvas/cell/text/text-cell-renderer.ts @@ -1,30 +1,31 @@ -import {ICanvasCellRenderer} from "../canvas-cell-renderer"; -import {ICell} from "../../../../cell/cell"; -import {TableEngine} from "../../../../table-engine"; -import {ICellRendererEventListener} from "../../../cell/event/cell-renderer-event-listener"; -import {ICellRendererEvent} from "../../../cell/event/cell-renderer-event"; -import {IOverlay} from "../../../../overlay/overlay"; -import {IRenderContext} from "../../canvas-renderer"; -import {IRectangle} from "../../../../util/rect"; -import {ILineWrapper} from "./line-wrap/line-wrapper"; -import {TrivialLineWrapper} from "./line-wrap/trivial-line-wrapper"; -import {IParagraph} from "./line-wrap/paragraph"; -import {fillOptions as fillTextCellRendererOptions, ITextCellRendererOptions} from "./text-cell-renderer-options"; -import {Colors} from "../../../../util/colors"; -import {AlignmentUtil} from "../../../../util/alignment/alignment-util"; -import {ITextCellRendererValue} from "./text-cell-renderer-value"; -import {VerticalAlignment} from "../../../../util/alignment/vertical-alignment"; -import {HorizontalAlignment} from "../../../../util/alignment/horizontal-alignment"; +import { ICanvasCellRenderer } from '../canvas-cell-renderer'; +import { ICell } from '../../../../cell'; +import { TableEngine } from '../../../../table-engine'; +import { ICellRendererEvent, ICellRendererEventListener } from '../../../cell'; +import { IOverlay } from '../../../../overlay'; +import { IRenderContext } from '../../canvas-renderer'; +import { + AlignmentUtil, + Colors, + HorizontalAlignment, + IRectangle, + VerticalAlignment, +} from '../../../../util'; +import { ILineWrapper, IParagraph, TrivialLineWrapper } from './line-wrap'; +import { + fillOptions as fillTextCellRendererOptions, + ITextCellRendererOptions, +} from './text-cell-renderer-options'; +import { ITextCellRendererValue } from './text-cell-renderer-value'; /** * Cell renderer rendering every value as string. */ export class TextCellRenderer implements ICanvasCellRenderer { - /** * Name of the cell renderer. */ - public static readonly NAME: string = "text"; + public static readonly NAME: string = 'text'; /** * Max duration of two mouse up events to be detected as double click (in milliseconds). @@ -34,7 +35,8 @@ export class TextCellRenderer implements ICanvasCellRenderer { /** * Line wrapping algorithm to use. */ - private static readonly LINE_WRAPPER: ILineWrapper = new TrivialLineWrapper(); + private static readonly LINE_WRAPPER: ILineWrapper = + new TrivialLineWrapper(); /** * Cell of the last mouse up. @@ -62,9 +64,13 @@ export class TextCellRenderer implements ICanvasCellRenderer { private _eventListener: ICellRendererEventListener = { onMouseUp: (event) => { const currentTimestamp: number = window.performance.now(); - if (!!this._lastCellMouseUp && this._lastCellMouseUp === event.cell) { + if ( + !!this._lastCellMouseUp && + this._lastCellMouseUp === event.cell + ) { // Check if is double click - const diff: number = currentTimestamp - this._lastMouseUpTimestamp; + const diff: number = + currentTimestamp - this._lastMouseUpTimestamp; if (diff <= TextCellRenderer.MAX_DOUBLE_CLICK_DURATION) { this._onDoubleClick(event); } @@ -72,12 +78,10 @@ export class TextCellRenderer implements ICanvasCellRenderer { this._lastCellMouseUp = event.cell; this._lastMouseUpTimestamp = currentTimestamp; - } + }, }; - constructor( - defaultOptions?: ITextCellRendererOptions - ) { + constructor(defaultOptions?: ITextCellRendererOptions) { this._defaultOptions = fillTextCellRendererOptions(defaultOptions); } @@ -89,14 +93,23 @@ export class TextCellRenderer implements ICanvasCellRenderer { // Check if cell is editable let isEditable: boolean = this._defaultOptions.editable; let editValue: string; - const specialValue: ITextCellRendererValue | null = !!event.cell.value && typeof event.cell.value === "object" && "text" in event.cell.value ? event.cell.value as ITextCellRendererValue : null; + const specialValue: ITextCellRendererValue | null = + !!event.cell.value && + typeof event.cell.value === 'object' && + 'text' in event.cell.value + ? (event.cell.value as ITextCellRendererValue) + : null; if (!!specialValue) { - if (!!specialValue.options && specialValue.options.editable !== undefined && specialValue.options.editable !== null) { + if ( + !!specialValue.options && + specialValue.options.editable !== undefined && + specialValue.options.editable !== null + ) { isEditable = specialValue.options.editable; } editValue = specialValue.text; } else { - editValue = !!event.cell.value ? `${event.cell.value}` : ""; + editValue = !!event.cell.value ? `${event.cell.value}` : ''; } if (!isEditable) { @@ -104,18 +117,18 @@ export class TextCellRenderer implements ICanvasCellRenderer { } // Create editor overlay - const editorOverlayElement: HTMLElement = document.createElement("div"); - const editor: HTMLInputElement = document.createElement("input"); - editor.style.width = "100%"; - editor.style.height = "100%"; - editor.style.boxSizing = "border-box"; + const editorOverlayElement: HTMLElement = document.createElement('div'); + const editor: HTMLInputElement = document.createElement('input'); + editor.style.width = '100%'; + editor.style.height = '100%'; + editor.style.boxSizing = 'border-box'; editor.value = editValue; editorOverlayElement.appendChild(editor); const overlay: IOverlay = { element: editorOverlayElement, - bounds: this._engine.getCellModel().getBounds(event.cell.range) + bounds: this._engine.getCellModel().getBounds(event.cell.range), }; this._engine.getOverlayManager().addOverlay(overlay); @@ -123,20 +136,25 @@ export class TextCellRenderer implements ICanvasCellRenderer { const keyDownListener: (KeyboardEvent) => void = (e: KeyboardEvent) => { e.stopPropagation(); // Do not give control to the table - if (e.code === "Enter") { + if (e.code === 'Enter') { blurListener(); } }; - editor.addEventListener("keydown", keyDownListener); + editor.addEventListener('keydown', keyDownListener); const blurListener: () => void = () => { // Remove all event listeners again - editor.removeEventListener("blur", blurListener); - editor.removeEventListener("keydown", keyDownListener); + editor.removeEventListener('blur', blurListener); + editor.removeEventListener('keydown', keyDownListener); // Save changes - const callback = !!specialValue && !!specialValue.options.onChange ? specialValue.options.onChange : this._defaultOptions.onChange; - const acceptChange: boolean = !!callback ? callback(event.cell, editValue, editor.value) : true; + const callback = + !!specialValue && !!specialValue.options.onChange + ? specialValue.options.onChange + : this._defaultOptions.onChange; + const acceptChange: boolean = !!callback + ? callback(event.cell, editValue, editor.value) + : true; if (acceptChange) { if (!!specialValue) { specialValue.text = editor.value; @@ -154,7 +172,7 @@ export class TextCellRenderer implements ICanvasCellRenderer { // Re-focus table this._engine.requestFocus(); }; - editor.addEventListener("blur", blurListener); + editor.addEventListener('blur', blurListener); setTimeout(() => { editor.focus(); @@ -184,11 +202,18 @@ export class TextCellRenderer implements ICanvasCellRenderer { * @param ctx to render with * @param context of the current rendering cycle */ - public before(ctx: CanvasRenderingContext2D, context: IRenderContext): void { + public before( + ctx: CanvasRenderingContext2D, + context: IRenderContext + ): void { ctx.font = `${this._defaultOptions.fontSize}px ${this._defaultOptions.fontFamily}`; ctx.fillStyle = Colors.toStyleStr(this._defaultOptions.color); - ctx.textBaseline = AlignmentUtil.verticalAlignmentToStyleStr(this._defaultOptions.verticalAlignment) as CanvasTextBaseline; - ctx.textAlign = AlignmentUtil.horizontalAlignmentToStyleStr(this._defaultOptions.horizontalAlignment) as CanvasTextAlign; + ctx.textBaseline = AlignmentUtil.verticalAlignmentToStyleStr( + this._defaultOptions.verticalAlignment + ) as CanvasTextBaseline; + ctx.textAlign = AlignmentUtil.horizontalAlignmentToStyleStr( + this._defaultOptions.horizontalAlignment + ) as CanvasTextAlign; } /** @@ -221,44 +246,70 @@ export class TextCellRenderer implements ICanvasCellRenderer { * @param cell to render * @param bounds to render cell in */ - public render(ctx: CanvasRenderingContext2D, cell: ICell, bounds: IRectangle): void { + public render( + ctx: CanvasRenderingContext2D, + cell: ICell, + bounds: IRectangle + ): void { if (cell.value === undefined || cell.value === null) { return; } // Check if the value is of the special text cell renderer interface for more customization - const isSpecialCellRendererValue: boolean = typeof cell.value === "object" && "text" in cell.value; - const specialValue: ITextCellRendererValue | null = isSpecialCellRendererValue ? cell.value as ITextCellRendererValue : null; + const isSpecialCellRendererValue: boolean = + typeof cell.value === 'object' && 'text' in cell.value; + const specialValue: ITextCellRendererValue | null = + isSpecialCellRendererValue + ? (cell.value as ITextCellRendererValue) + : null; - const text: string = isSpecialCellRendererValue ? specialValue.text : `${cell.value}`; + const text: string = isSpecialCellRendererValue + ? specialValue.text + : `${cell.value}`; let lineWrap: boolean = this._defaultOptions.useLineWrapping; let lineHeight: number = this._defaultOptions.lineHeight; - let verticalAlignment: VerticalAlignment = this._defaultOptions.verticalAlignment; - let horizontalAlignment: HorizontalAlignment = this._defaultOptions.horizontalAlignment; + let verticalAlignment: VerticalAlignment = + this._defaultOptions.verticalAlignment; + let horizontalAlignment: HorizontalAlignment = + this._defaultOptions.horizontalAlignment; // Override options if necessary let savedContext: boolean = false; if (isSpecialCellRendererValue && !!specialValue.options) { // Paragraph options - if (specialValue.options.useLineWrapping !== undefined && specialValue.options.useLineWrapping !== null) { + if ( + specialValue.options.useLineWrapping !== undefined && + specialValue.options.useLineWrapping !== null + ) { lineWrap = specialValue.options.useLineWrapping; } - if (specialValue.options.lineHeight !== undefined && specialValue.options.lineHeight !== null) { + if ( + specialValue.options.lineHeight !== undefined && + specialValue.options.lineHeight !== null + ) { lineHeight = specialValue.options.lineHeight; } // Font settings - const hasCustomFontSize: boolean = specialValue.options.fontSize !== undefined && specialValue.options.fontSize !== null; - const hasCustomFontFamily: boolean = specialValue.options.fontFamily !== undefined && specialValue.options.fontFamily !== null; + const hasCustomFontSize: boolean = + specialValue.options.fontSize !== undefined && + specialValue.options.fontSize !== null; + const hasCustomFontFamily: boolean = + specialValue.options.fontFamily !== undefined && + specialValue.options.fontFamily !== null; if (hasCustomFontFamily || hasCustomFontSize) { if (!savedContext) { savedContext = true; ctx.save(); } - const fontSize: number = hasCustomFontSize ? specialValue.options.fontSize : this._defaultOptions.fontSize; - const fontFamily: string = hasCustomFontFamily ? specialValue.options.fontFamily : this._defaultOptions.fontFamily; + const fontSize: number = hasCustomFontSize + ? specialValue.options.fontSize + : this._defaultOptions.fontSize; + const fontFamily: string = hasCustomFontFamily + ? specialValue.options.fontFamily + : this._defaultOptions.fontFamily; ctx.font = `${fontSize}px ${fontFamily}`; } @@ -275,8 +326,12 @@ export class TextCellRenderer implements ICanvasCellRenderer { } // Alignment settings - const hasCustomVerticalAlignment: boolean = specialValue.options.verticalAlignment !== undefined && specialValue.options.verticalAlignment !== null; - const hasCustomHorizontalAlignment: boolean = specialValue.options.horizontalAlignment !== undefined && specialValue.options.horizontalAlignment !== null; + const hasCustomVerticalAlignment: boolean = + specialValue.options.verticalAlignment !== undefined && + specialValue.options.verticalAlignment !== null; + const hasCustomHorizontalAlignment: boolean = + specialValue.options.horizontalAlignment !== undefined && + specialValue.options.horizontalAlignment !== null; if (hasCustomVerticalAlignment || hasCustomHorizontalAlignment) { if (!savedContext) { savedContext = true; @@ -284,12 +339,18 @@ export class TextCellRenderer implements ICanvasCellRenderer { } if (hasCustomVerticalAlignment) { - ctx.textBaseline = AlignmentUtil.verticalAlignmentToStyleStr(specialValue.options.verticalAlignment) as CanvasTextBaseline; + ctx.textBaseline = + AlignmentUtil.verticalAlignmentToStyleStr( + specialValue.options.verticalAlignment + ) as CanvasTextBaseline; verticalAlignment = specialValue.options.verticalAlignment; } if (hasCustomHorizontalAlignment) { - ctx.textAlign = AlignmentUtil.horizontalAlignmentToStyleStr(specialValue.options.horizontalAlignment) as CanvasTextAlign; - horizontalAlignment = specialValue.options.horizontalAlignment; + ctx.textAlign = AlignmentUtil.horizontalAlignmentToStyleStr( + specialValue.options.horizontalAlignment + ) as CanvasTextAlign; + horizontalAlignment = + specialValue.options.horizontalAlignment; } } } @@ -298,7 +359,8 @@ export class TextCellRenderer implements ICanvasCellRenderer { // Check cells cache first to check whether the paragraph to render has already been cached. if (!!cell.viewportCache) { - const cache: ITextCellRendererCache = cell.viewportCache as ITextCellRendererCache; + const cache: ITextCellRendererCache = + cell.viewportCache as ITextCellRendererCache; // Use cached paragraph if cache is still valid if (cache.text === text && cache.paragraph.width === bounds.width) { @@ -320,14 +382,22 @@ export class TextCellRenderer implements ICanvasCellRenderer { */ cell.viewportCache = { paragraph, - text + text, } as ITextCellRendererCache; } - const clip: boolean = !TextCellRenderer._fitsInBounds(paragraph, bounds); + const clip: boolean = !TextCellRenderer._fitsInBounds( + paragraph, + bounds + ); if (clip) { const clippingRegion = new Path2D(); - clippingRegion.rect(bounds.left, bounds.top, bounds.width, bounds.height); + clippingRegion.rect( + bounds.left, + bounds.top, + bounds.width, + bounds.height + ); if (!savedContext) { savedContext = true; @@ -347,12 +417,14 @@ export class TextCellRenderer implements ICanvasCellRenderer { break; } - let yOffset: number = Math.round(TextCellRenderer._calculateLineYOffset( - paragraph.lines.length, - lineHeight, - bounds, - verticalAlignment - )); + let yOffset: number = Math.round( + TextCellRenderer._calculateLineYOffset( + paragraph.lines.length, + lineHeight, + bounds, + verticalAlignment + ) + ); for (const line of paragraph.lines) { ctx.fillText(line.text, xOffset, yOffset); @@ -385,7 +457,11 @@ export class TextCellRenderer implements ICanvasCellRenderer { if (lineCount === 1) { return bounds.top + bounds.height; } else { - return bounds.top + bounds.height - (lineHeight * (lineCount - 1)); + return ( + bounds.top + + bounds.height - + lineHeight * (lineCount - 1) + ); } } } @@ -395,7 +471,7 @@ export class TextCellRenderer implements ICanvasCellRenderer { * This may be a HTML representation of the value (for example for copying formatting, lists, ...). */ public getCopyValue(cell: ICell): string { - return cell.value !== null ? `${cell.value}` : ""; + return cell.value !== null ? `${cell.value}` : ''; } /** @@ -403,7 +479,10 @@ export class TextCellRenderer implements ICanvasCellRenderer { * @param paragraph to check whether it fits in the cell bounds * @param bounds of the cell */ - private static _fitsInBounds(paragraph: IParagraph, bounds: IRectangle): boolean { + private static _fitsInBounds( + paragraph: IParagraph, + bounds: IRectangle + ): boolean { let totalHeight: number = 0; for (const line of paragraph.lines) { @@ -428,7 +507,6 @@ export class TextCellRenderer implements ICanvasCellRenderer { public onDisappearing(cell: ICell): void { // Do nothing } - } /** @@ -439,7 +517,6 @@ export class TextCellRenderer implements ICanvasCellRenderer { * anymore. */ interface ITextCellRendererCache { - /** * The cached paragraph. */ @@ -449,5 +526,4 @@ interface ITextCellRendererCache { * Text for which the paragraph has been created. */ text: string; - } diff --git a/src/renderer/canvas/index.ts b/src/renderer/canvas/index.ts new file mode 100644 index 0000000..9887b0d --- /dev/null +++ b/src/renderer/canvas/index.ts @@ -0,0 +1,6 @@ +export { IRenderContext, CanvasRenderer } from './canvas-renderer'; +export { + ICanvasRendererOptions, + fillOptions as fillCanvasRendererOptions, +} from './options'; +export * from './cell'; diff --git a/src/renderer/canvas/options.ts b/src/renderer/canvas/options.ts index 4414e69..52b16a1 100644 --- a/src/renderer/canvas/options.ts +++ b/src/renderer/canvas/options.ts @@ -1,7 +1,19 @@ -import {fillOptions as fillScrollBarOptions, IScrollBarOptions} from "../options/scrollbar"; -import {fillOptions as fillSelectionOptions, ISelectionRenderingOptions} from "../options/selection"; -import {fillOptions as fillScrollingOptions, IScrollingOptions} from "../options/scrolling"; -import {fillOptions as fillRowColumnResizingOptions, IRowColumnResizingOptions} from "../options/row-column-resizing"; +import { + fillOptions as fillScrollBarOptions, + IScrollBarOptions, +} from '../options/scrollbar'; +import { + fillOptions as fillSelectionOptions, + ISelectionRenderingOptions, +} from '../options/selection'; +import { + fillOptions as fillScrollingOptions, + IScrollingOptions, +} from '../options/scrolling'; +import { + fillOptions as fillRowColumnResizingOptions, + IRowColumnResizingOptions, +} from '../options/row-column-resizing'; /** * Duration in milliseconds used to throttle high-rate events @@ -13,7 +25,6 @@ const DEFAULT_LAZY_RENDERING_THROTTLE_DURATION: number = 10; * Options for the HTML5 canvas renderer. */ export interface ICanvasRendererOptions { - /** * Lazy rendering throttle duration in milliseconds. * This is used to throttle events that occur in high-rate, such as scrolling @@ -42,7 +53,6 @@ export interface ICanvasRendererOptions { * Options for resizing rows and columns. */ rowColumnResizing?: IRowColumnResizingOptions; - } /** @@ -54,8 +64,12 @@ export const fillOptions = (options?: ICanvasRendererOptions) => { options = {}; } - if (options.lazyRenderingThrottleDuration === undefined || options.lazyRenderingThrottleDuration === null) { - options.lazyRenderingThrottleDuration = DEFAULT_LAZY_RENDERING_THROTTLE_DURATION; + if ( + options.lazyRenderingThrottleDuration === undefined || + options.lazyRenderingThrottleDuration === null + ) { + options.lazyRenderingThrottleDuration = + DEFAULT_LAZY_RENDERING_THROTTLE_DURATION; } if (!options.scrollBar) { diff --git a/src/renderer/cell/cell-renderer.ts b/src/renderer/cell/cell-renderer.ts index 563a5c8..8b2a272 100644 --- a/src/renderer/cell/cell-renderer.ts +++ b/src/renderer/cell/cell-renderer.ts @@ -1,7 +1,7 @@ -import {IRectangle} from "../../util/rect"; -import {ICell} from "../../cell/cell"; -import {TableEngine} from "../../table-engine"; -import {ICellRendererEventListener} from "./event/cell-renderer-event-listener"; +import { IRectangle } from '../../util'; +import { ICell } from '../../cell'; +import { TableEngine } from '../../table-engine'; +import { ICellRendererEventListener } from './event'; /** * Renderer responsible for a single cell. @@ -9,7 +9,6 @@ import {ICellRendererEventListener} from "./event/cell-renderer-event-listener"; * C: Rendering context used to actually render something (for example a CanvasRenderingContext2D) */ export interface ICellRenderer { - /** * Initialize the cell renderer. * This is only called once. @@ -29,7 +28,7 @@ export interface ICellRenderer { * @param cell to render * @param bounds to render cell in */ - render(ctx: C, cell: ICell, bounds: IRectangle): void + render(ctx: C, cell: ICell, bounds: IRectangle): void; /** * Get the event listeners on cells for this cell renderer. @@ -47,5 +46,4 @@ export interface ICellRenderer { * @param cell that is disappearing */ onDisappearing(cell: ICell): void; - } diff --git a/src/renderer/cell/event/cell-renderer-event-listener.ts b/src/renderer/cell/event/cell-renderer-event-listener.ts index 5efa31e..916da06 100644 --- a/src/renderer/cell/event/cell-renderer-event-listener.ts +++ b/src/renderer/cell/event/cell-renderer-event-listener.ts @@ -1,12 +1,11 @@ -import {ICellRendererKeyboardEvent} from "./cell-renderer-keyboard-event"; -import {ICellRendererMouseEvent} from "./cell-renderer-mouse-event"; -import {ICellRendererFocusEvent} from "./cell-renderer-focus-event"; +import { ICellRendererKeyboardEvent } from './cell-renderer-keyboard-event'; +import { ICellRendererMouseEvent } from './cell-renderer-mouse-event'; +import { ICellRendererFocusEvent } from './cell-renderer-focus-event'; /** * Event listeners on a cell renderer. */ export interface ICellRendererEventListener { - /** * On mouse down event listener. * @param event that occurred @@ -54,5 +53,4 @@ export interface ICellRendererEventListener { * @param event that occurred */ onBlur?: (event: ICellRendererFocusEvent) => void; - } diff --git a/src/renderer/cell/event/cell-renderer-event.ts b/src/renderer/cell/event/cell-renderer-event.ts index 9d00516..346e5e3 100644 --- a/src/renderer/cell/event/cell-renderer-event.ts +++ b/src/renderer/cell/event/cell-renderer-event.ts @@ -1,11 +1,9 @@ -import {ICell} from "../../../cell/cell"; -import {IPoint} from "../../../util/point"; +import { ICell } from '../../../cell'; /** * Event propagated to a cell renderer. */ export interface ICellRendererEvent { - /** * Cell the event occurs on. */ @@ -15,5 +13,4 @@ export interface ICellRendererEvent { * Whether to prevent the default action of the table. */ preventDefault: boolean; - } diff --git a/src/renderer/cell/event/cell-renderer-focus-event.ts b/src/renderer/cell/event/cell-renderer-focus-event.ts index 76972a8..bb78b5d 100644 --- a/src/renderer/cell/event/cell-renderer-focus-event.ts +++ b/src/renderer/cell/event/cell-renderer-focus-event.ts @@ -1,8 +1,6 @@ -import {ICellRendererEvent} from "./cell-renderer-event"; +import { ICellRendererEvent } from './cell-renderer-event'; /** * Focus or blur event passed to a cell renderer. */ -export interface ICellRendererFocusEvent extends ICellRendererEvent { - -} +export interface ICellRendererFocusEvent extends ICellRendererEvent {} diff --git a/src/renderer/cell/event/cell-renderer-keyboard-event.ts b/src/renderer/cell/event/cell-renderer-keyboard-event.ts index c4f1cd0..131cbc2 100644 --- a/src/renderer/cell/event/cell-renderer-keyboard-event.ts +++ b/src/renderer/cell/event/cell-renderer-keyboard-event.ts @@ -1,13 +1,11 @@ -import {ICellRendererEvent} from "./cell-renderer-event"; +import { ICellRendererEvent } from './cell-renderer-event'; /** * Keyboard event passed to a cell renderer. */ export interface ICellRendererKeyboardEvent extends ICellRendererEvent { - /** * Original keyboard event that led to this event. */ originalEvent: KeyboardEvent; - } diff --git a/src/renderer/cell/event/cell-renderer-mouse-event.ts b/src/renderer/cell/event/cell-renderer-mouse-event.ts index a56ac2c..0c051fe 100644 --- a/src/renderer/cell/event/cell-renderer-mouse-event.ts +++ b/src/renderer/cell/event/cell-renderer-mouse-event.ts @@ -1,11 +1,10 @@ -import {ICellRendererEvent} from "./cell-renderer-event"; -import {IPoint} from "../../../util/point"; +import { ICellRendererEvent } from './cell-renderer-event'; +import { IPoint } from '../../../util'; /** * Mouse event passed to a cell renderer. */ export interface ICellRendererMouseEvent extends ICellRendererEvent { - /** * Mouse offset within the bounds of the rectangle the event * occurred on (set for every mouse event except mouse out). @@ -16,5 +15,4 @@ export interface ICellRendererMouseEvent extends ICellRendererEvent { * Original mouse event. */ originalEvent: MouseEvent; - } diff --git a/src/renderer/cell/event/index.ts b/src/renderer/cell/event/index.ts new file mode 100644 index 0000000..38edd92 --- /dev/null +++ b/src/renderer/cell/event/index.ts @@ -0,0 +1,5 @@ +export { ICellRendererEvent } from './cell-renderer-event'; +export { ICellRendererEventListener } from './cell-renderer-event-listener'; +export { ICellRendererFocusEvent } from './cell-renderer-focus-event'; +export { ICellRendererKeyboardEvent } from './cell-renderer-keyboard-event'; +export { ICellRendererMouseEvent } from './cell-renderer-mouse-event'; diff --git a/src/renderer/cell/index.ts b/src/renderer/cell/index.ts new file mode 100644 index 0000000..44cc689 --- /dev/null +++ b/src/renderer/cell/index.ts @@ -0,0 +1,2 @@ +export { ICellRenderer } from './cell-renderer'; +export * from './event'; diff --git a/src/renderer/index.ts b/src/renderer/index.ts new file mode 100644 index 0000000..e62a4f5 --- /dev/null +++ b/src/renderer/index.ts @@ -0,0 +1,12 @@ +export { ITableEngineRenderer } from './renderer'; +export { RendererType } from './renderers'; +export { RendererFactory } from './renderer-factory'; +export { + IRendererOptions, + IViewOptions, + fillOptions as fillRendererOptions, +} from './renderer-options'; +export * from './options'; +export * from './cell'; +export * from './canvas'; +export * from './util'; diff --git a/src/renderer/options/index.ts b/src/renderer/options/index.ts new file mode 100644 index 0000000..0fffd4b --- /dev/null +++ b/src/renderer/options/index.ts @@ -0,0 +1,18 @@ +export { + ICopyHandleRenderingOptions, + ISelectionRenderingOptions, + ISelectionColors, + fillOptions as fillSelectionRenderingOptions, +} from './selection'; +export { + IScrollBarOptions, + fillOptions as fillScrollbarOptions, +} from './scrollbar'; +export { + IScrollingOptions, + fillOptions as fillScrollingOptions, +} from './scrolling'; +export { + IRowColumnResizingOptions, + fillOptions as fillRowColumnResizingOptions, +} from './row-column-resizing'; diff --git a/src/renderer/options/row-column-resizing.ts b/src/renderer/options/row-column-resizing.ts index 69620ab..bef4111 100644 --- a/src/renderer/options/row-column-resizing.ts +++ b/src/renderer/options/row-column-resizing.ts @@ -1,78 +1,95 @@ -import {ICellModel} from "../../cell/model/cell-model.interface"; -import {ISelectionModel} from "../../selection/model/selection-model.interface"; -import {ISelection} from "../../selection/selection"; -import {CellRangeUtil} from "../../cell/range/cell-range-util"; -import {IColor} from "../../util/color"; -import {Colors} from "../../util/colors"; +import { CellRangeUtil, ICellModel } from '../../cell'; +import { ISelection, ISelectionModel } from '../../selection'; +import { Colors, IColor } from '../../util'; /** * Default count of rows to let the user resize columns in. */ -export const DEFAULT_ROW_COUNT: number = 1; +const DEFAULT_ROW_COUNT: number = 1; /** * Default count of columns to let the user resize rows in. */ -export const DEFAULT_COLUMN_COUNT: number = 1; +const DEFAULT_COLUMN_COUNT: number = 1; /** * Default size of the resizer. */ -export const DEFAULT_RESIZER_SIZE: number = 5; +const DEFAULT_RESIZER_SIZE: number = 5; /** * Default minimum row size to resize to. */ -export const DEFAULT_MIN_ROW_SIZE: number = 20; +const DEFAULT_MIN_ROW_SIZE: number = 20; /** * Default minimum column size to resize to. */ -export const DEFAULT_MIN_COLUMN_SIZE: number = 20; +const DEFAULT_MIN_COLUMN_SIZE: number = 20; /** * Default thickness of the resizer line. */ -export const DEFAULT_RESIZER_LINE_THICKNESS: number = 1; +const DEFAULT_RESIZER_LINE_THICKNESS: number = 1; /** * Default color of the resizer line. */ -export const DEFAULT_RESIZER_LINE_COLOR: IColor = Colors.BLACK; +const DEFAULT_RESIZER_LINE_COLOR: IColor = Colors.BLACK; /** * The default resizing handler. */ -export const DEFAULT_RESIZING_HANDLER: (newSize: number, isRow: boolean, index: number, cellModel: ICellModel, selectionModel: ISelectionModel) => boolean = (newSize, isRow, index, cellModel, selectionModel) => { +const DEFAULT_RESIZING_HANDLER: ( + newSize: number, + isRow: boolean, + index: number, + cellModel: ICellModel, + selectionModel: ISelectionModel +) => boolean = (newSize, isRow, index, cellModel, selectionModel) => { const primary: ISelection | null = selectionModel.getPrimary(); /* Check if the row/column is contained in the primary selection and the first row/column of the other axis is contained as well. */ - const isMultiRowColumnResize: boolean = !!primary && CellRangeUtil.contains({ - startRow: isRow ? index : 1, - endRow: isRow ? index : cellModel.getRowCount() - 1, - startColumn: isRow ? 1 : index, - endColumn: isRow ? cellModel.getColumnCount() - 1 : index - }, primary.range) && CellRangeUtil.size(primary.range) > 1; + const isMultiRowColumnResize: boolean = + !!primary && + CellRangeUtil.contains( + { + startRow: isRow ? index : 1, + endRow: isRow ? index : cellModel.getRowCount() - 1, + startColumn: isRow ? 1 : index, + endColumn: isRow ? cellModel.getColumnCount() - 1 : index, + }, + primary.range + ) && + CellRangeUtil.size(primary.range) > 1; if (isMultiRowColumnResize) { // Resize a bunch of rows/columns if (isRow) { const indices: number[] = []; - for (let i = primary.range.startRow; i <= primary.range.endRow; i++) { + for ( + let i = primary.range.startRow; + i <= primary.range.endRow; + i++ + ) { indices.push(i); } cellModel.resizeRows(indices, newSize); } else { const indices: number[] = []; - for (let i = primary.range.startColumn; i <= primary.range.endColumn; i++) { + for ( + let i = primary.range.startColumn; + i <= primary.range.endColumn; + i++ + ) { indices.push(i); } - cellModel.resizeColumns(indices, newSize) + cellModel.resizeColumns(indices, newSize); } } else { // Only resize a single row/column @@ -90,7 +107,6 @@ export const DEFAULT_RESIZING_HANDLER: (newSize: number, isRow: boolean, index: * Options for resizing rows and columns. */ export interface IRowColumnResizingOptions { - /** * Whether the user is allowed to resize rows or columns. */ @@ -142,8 +158,13 @@ export interface IRowColumnResizingOptions { * @param selectionModel to get the current selection with (if needed) * @returns whether we need to repaint afterwards */ - resizingHandler?: (newSize: number, isRow: boolean, index: number, cellModel: ICellModel, selectionModel: ISelectionModel) => boolean; - + resizingHandler?: ( + newSize: number, + isRow: boolean, + index: number, + cellModel: ICellModel, + selectionModel: ISelectionModel + ) => boolean; } /** @@ -179,7 +200,10 @@ export const fillOptions = (options?: IRowColumnResizingOptions) => { options.minColumnSize = DEFAULT_MIN_COLUMN_SIZE; } - if (options.resizerLineThickness === undefined || options.resizerLineThickness === null) { + if ( + options.resizerLineThickness === undefined || + options.resizerLineThickness === null + ) { options.resizerLineThickness = DEFAULT_RESIZER_LINE_THICKNESS; } diff --git a/src/renderer/options/scrollbar.ts b/src/renderer/options/scrollbar.ts index 991fd29..94867c1 100644 --- a/src/renderer/options/scrollbar.ts +++ b/src/renderer/options/scrollbar.ts @@ -1,4 +1,4 @@ -import {IColor} from "../../util/color"; +import { IColor } from '../../util'; /** * Default size of the scrollbar. @@ -23,13 +23,17 @@ const DEFAULT_SCROLLBAR_RADIUS: number = 5; /** * Default scrollbar color. */ -const DEFAULT_SCROLLBAR_COLOR: IColor = {red: 0, green: 0, blue: 0, alpha: 0.6}; +const DEFAULT_SCROLLBAR_COLOR: IColor = { + red: 0, + green: 0, + blue: 0, + alpha: 0.6, +}; /** * Options regarding the scrollbar to display. */ export interface IScrollBarOptions { - /** * RGBA color of the scrollbar using floating point * numbers. @@ -56,7 +60,6 @@ export interface IScrollBarOptions { * Radius of the scrollbars rounded corners. */ cornerRadius?: number; - } /** @@ -88,7 +91,5 @@ export const fillOptions = (options?: IScrollBarOptions) => { options.cornerRadius = DEFAULT_SCROLLBAR_RADIUS; } - return options; }; - diff --git a/src/renderer/options/scrolling.ts b/src/renderer/options/scrolling.ts index 2ba12f6..9932678 100644 --- a/src/renderer/options/scrolling.ts +++ b/src/renderer/options/scrolling.ts @@ -15,7 +15,6 @@ const DEFAULT_TOUCH_SCROLLING_ACCELERATION: number = -600; * Options regarding scrolling. */ export interface IScrollingOptions { - /** * Speed factor for touch scrolling. * The higher, the more impact a scroll when swiping fast. @@ -28,7 +27,6 @@ export interface IScrollingOptions { * touch swipes on the phone to scroll the table. */ touchScrollingAcceleration?: number; - } /** @@ -40,14 +38,21 @@ export const fillOptions = (options?: IScrollingOptions) => { options = {}; } - if (options.touchScrollingSpeedFactor === undefined || options.touchScrollingSpeedFactor === null) { - options.touchScrollingSpeedFactor = DEFAULT_TOUCH_SCROLLING_SPEED_FACTOR; + if ( + options.touchScrollingSpeedFactor === undefined || + options.touchScrollingSpeedFactor === null + ) { + options.touchScrollingSpeedFactor = + DEFAULT_TOUCH_SCROLLING_SPEED_FACTOR; } - if (options.touchScrollingAcceleration === undefined || options.touchScrollingAcceleration === null) { - options.touchScrollingAcceleration = DEFAULT_TOUCH_SCROLLING_ACCELERATION; + if ( + options.touchScrollingAcceleration === undefined || + options.touchScrollingAcceleration === null + ) { + options.touchScrollingAcceleration = + DEFAULT_TOUCH_SCROLLING_ACCELERATION; } return options; }; - diff --git a/src/renderer/options/selection.ts b/src/renderer/options/selection.ts index 02edcd8..c7b5d94 100644 --- a/src/renderer/options/selection.ts +++ b/src/renderer/options/selection.ts @@ -1,44 +1,84 @@ -import {IColor} from "../../util/color"; +import { IColor } from '../../util'; /** * Default primary selection border color. */ -const DEFAULT_PRIMARY_SELECTION_BORDER_COLOR: IColor = {red: 255, green: 51, blue: 102, alpha: 1.0}; +const DEFAULT_PRIMARY_SELECTION_BORDER_COLOR: IColor = { + red: 255, + green: 51, + blue: 102, + alpha: 1.0, +}; /** * Default primary selection background color. */ -const DEFAULT_PRIMARY_SELECTION_BACKGROUND_COLOR: IColor = {red: 204, green: 0, blue: 51, alpha: 0.1}; +const DEFAULT_PRIMARY_SELECTION_BACKGROUND_COLOR: IColor = { + red: 204, + green: 0, + blue: 51, + alpha: 0.1, +}; /** * Default primary selection border color when table is not focused. */ -const DEFAULT_PRIMARY_SELECTION_BORDER_COLOR_UNFOCUSED: IColor = {red: 255, green: 102, blue: 140, alpha: 0.5}; +const DEFAULT_PRIMARY_SELECTION_BORDER_COLOR_UNFOCUSED: IColor = { + red: 255, + green: 102, + blue: 140, + alpha: 0.5, +}; /** * Default primary selection background color when table is not focused. */ -const DEFAULT_PRIMARY_SELECTION_BACKGROUND_COLOR_UNFOCUSED: IColor = {red: 150, green: 150, blue: 150, alpha: 0.1}; +const DEFAULT_PRIMARY_SELECTION_BACKGROUND_COLOR_UNFOCUSED: IColor = { + red: 150, + green: 150, + blue: 150, + alpha: 0.1, +}; /** * Default secondary selection border color. */ -const DEFAULT_SECONDARY_SELECTION_BORDER_COLOR: IColor = {red: 150, green: 150, blue: 150, alpha: 0.75}; +const DEFAULT_SECONDARY_SELECTION_BORDER_COLOR: IColor = { + red: 150, + green: 150, + blue: 150, + alpha: 0.75, +}; /** * Default secondary selection background color. */ -const DEFAULT_SECONDARY_SELECTION_BACKGROUND_COLOR: IColor = {red: 50, green: 50, blue: 50, alpha: 0.1}; +const DEFAULT_SECONDARY_SELECTION_BACKGROUND_COLOR: IColor = { + red: 50, + green: 50, + blue: 50, + alpha: 0.1, +}; /** * Default secondary selection border color when table is not focused. */ -const DEFAULT_SECONDARY_SELECTION_BORDER_COLOR_UNFOCUSED: IColor = {red: 200, green: 200, blue: 200, alpha: 0.75}; +const DEFAULT_SECONDARY_SELECTION_BORDER_COLOR_UNFOCUSED: IColor = { + red: 200, + green: 200, + blue: 200, + alpha: 0.75, +}; /** * Default secondary selection background color when table is not focused. */ -const DEFAULT_SECONDARY_SELECTION_BACKGROUND_COLOR_UNFOCUSED: IColor = {red: 150, green: 150, blue: 150, alpha: 0.1}; +const DEFAULT_SECONDARY_SELECTION_BACKGROUND_COLOR_UNFOCUSED: IColor = { + red: 150, + green: 150, + blue: 150, + alpha: 0.1, +}; /** * Default selection border size. @@ -69,7 +109,6 @@ const DEFAULT_COPY_HANDLE_PADDING: number = 2; * Options regarding the selection to display. */ export interface ISelectionRenderingOptions { - /** * Size of the selection border. */ @@ -102,14 +141,12 @@ export interface ISelectionRenderingOptions { * Rendering options for the copy-handle (if enabled). */ copyHandle?: ICopyHandleRenderingOptions; - } /** * Rendering options for the copy-handle. */ export interface ICopyHandleRenderingOptions { - /** * Size of the copy-handle (width and height). */ @@ -119,14 +156,12 @@ export interface ICopyHandleRenderingOptions { * Padding from the selection rectangle border. */ padding?: number; - } /** * Colors of a selection. */ export interface ISelectionColors { - /** * Background color of the selection rectangle. */ @@ -146,7 +181,6 @@ export interface ISelectionColors { * Color of the border when the table is not focused. */ borderColorUnfocused?: IColor; - } /** @@ -175,7 +209,8 @@ export const fillOptions = (options?: ISelectionRenderingOptions) => { } if (!options.primary.backgroundColor) { - options.primary.backgroundColor = DEFAULT_PRIMARY_SELECTION_BACKGROUND_COLOR; + options.primary.backgroundColor = + DEFAULT_PRIMARY_SELECTION_BACKGROUND_COLOR; } if (!options.primary.borderColor) { @@ -183,30 +218,39 @@ export const fillOptions = (options?: ISelectionRenderingOptions) => { } if (!options.primary.backgroundColorUnfocused) { - options.primary.backgroundColorUnfocused = DEFAULT_PRIMARY_SELECTION_BACKGROUND_COLOR_UNFOCUSED; + options.primary.backgroundColorUnfocused = + DEFAULT_PRIMARY_SELECTION_BACKGROUND_COLOR_UNFOCUSED; } if (!options.primary.borderColorUnfocused) { - options.primary.borderColorUnfocused = DEFAULT_PRIMARY_SELECTION_BORDER_COLOR_UNFOCUSED; + options.primary.borderColorUnfocused = + DEFAULT_PRIMARY_SELECTION_BORDER_COLOR_UNFOCUSED; } if (!options.secondary.backgroundColor) { - options.secondary.backgroundColor = DEFAULT_SECONDARY_SELECTION_BACKGROUND_COLOR; + options.secondary.backgroundColor = + DEFAULT_SECONDARY_SELECTION_BACKGROUND_COLOR; } if (!options.secondary.borderColor) { - options.secondary.borderColor = DEFAULT_SECONDARY_SELECTION_BORDER_COLOR; + options.secondary.borderColor = + DEFAULT_SECONDARY_SELECTION_BORDER_COLOR; } if (!options.secondary.backgroundColorUnfocused) { - options.secondary.backgroundColorUnfocused = DEFAULT_SECONDARY_SELECTION_BACKGROUND_COLOR_UNFOCUSED; + options.secondary.backgroundColorUnfocused = + DEFAULT_SECONDARY_SELECTION_BACKGROUND_COLOR_UNFOCUSED; } if (!options.secondary.borderColorUnfocused) { - options.secondary.borderColorUnfocused = DEFAULT_SECONDARY_SELECTION_BORDER_COLOR_UNFOCUSED; + options.secondary.borderColorUnfocused = + DEFAULT_SECONDARY_SELECTION_BORDER_COLOR_UNFOCUSED; } - if (options.autoScrollingSpeed === undefined || options.autoScrollingSpeed === null) { + if ( + options.autoScrollingSpeed === undefined || + options.autoScrollingSpeed === null + ) { options.autoScrollingSpeed = DEFAULT_AUTO_SCROLL_SPEED; } @@ -214,14 +258,19 @@ export const fillOptions = (options?: ISelectionRenderingOptions) => { options.copyHandle = {}; } - if (options.copyHandle.size === null || options.copyHandle.size === undefined) { + if ( + options.copyHandle.size === null || + options.copyHandle.size === undefined + ) { options.copyHandle.size = DEFAULT_COPY_HANDLE_SIZE; } - if (options.copyHandle.padding === null || options.copyHandle.padding === undefined) { + if ( + options.copyHandle.padding === null || + options.copyHandle.padding === undefined + ) { options.copyHandle.padding = DEFAULT_COPY_HANDLE_PADDING; } return options; }; - diff --git a/src/renderer/renderer-factory.ts b/src/renderer/renderer-factory.ts index 1583a87..7034416 100644 --- a/src/renderer/renderer-factory.ts +++ b/src/renderer/renderer-factory.ts @@ -1,32 +1,41 @@ -import {ITableEngineRenderer} from "./renderer"; -import {RendererType} from "./renderers"; -import {CanvasRenderer} from "./canvas/canvas-renderer"; +import { ITableEngineRenderer } from './renderer'; +import { RendererType } from './renderers'; +import { CanvasRenderer } from './canvas'; /** * Factory for table-engine renderers. */ export class RendererFactory { - /** * Mapping of renderer types to their concrete implementation supplier. */ - private static _AVAILABLE_RENDERERS: Map ITableEngineRenderer>; + private static _AVAILABLE_RENDERERS: Map< + RendererType, + () => ITableEngineRenderer + >; /** * Initialize the renderer map. */ private static _initializeRendererMap(): void { // Check if already initialized - const isAlreadyInitialized: boolean = !!RendererFactory._AVAILABLE_RENDERERS; + const isAlreadyInitialized: boolean = + !!RendererFactory._AVAILABLE_RENDERERS; if (isAlreadyInitialized) { return; // Nothing to do } // Initialize map - RendererFactory._AVAILABLE_RENDERERS = new Map ITableEngineRenderer>() + RendererFactory._AVAILABLE_RENDERERS = new Map< + RendererType, + () => ITableEngineRenderer + >(); // Register renderers - RendererFactory.registerRenderer(RendererType.CANVAS, () => new CanvasRenderer()); + RendererFactory.registerRenderer( + RendererType.CANVAS, + () => new CanvasRenderer() + ); } /** @@ -35,7 +44,10 @@ export class RendererFactory { * @param type of the renderer to register * @param initializer supplier to initialize the renderer instance */ - public static registerRenderer(type: RendererType, initializer: () => ITableEngineRenderer): void { + public static registerRenderer( + type: RendererType, + initializer: () => ITableEngineRenderer + ): void { this._initializeRendererMap(); this._AVAILABLE_RENDERERS.set(type, initializer); @@ -45,16 +57,19 @@ export class RendererFactory { * Retrieve a new instance of a renderer for the given type. * @param type of the renderer to fetch */ - public static getRendererInstance(type: RendererType): ITableEngineRenderer { + public static getRendererInstance( + type: RendererType + ): ITableEngineRenderer { this._initializeRendererMap(); const rendererInitializer = this._AVAILABLE_RENDERERS.get(type); if (!rendererInitializer) { - throw new Error(`There is no initializer for the renderer type '${RendererType[type]}' defined`); + throw new Error( + `There is no initializer for the renderer type '${RendererType[type]}' defined` + ); } return rendererInitializer(); } - } diff --git a/src/renderer/options.ts b/src/renderer/renderer-options.ts similarity index 71% rename from src/renderer/options.ts rename to src/renderer/renderer-options.ts index 9a710c7..2e0d9e3 100644 --- a/src/renderer/options.ts +++ b/src/renderer/renderer-options.ts @@ -1,22 +1,24 @@ -import {RendererType} from "./renderers"; -import {fillOptions as fillCanvasRendererOptions, ICanvasRendererOptions} from "./canvas/options"; -import {INotificationService} from "../util/notification/notification-service"; +import { RendererType } from './renderers'; +import { + fillOptions as fillCanvasRendererOptions, + ICanvasRendererOptions, +} from './canvas/options'; +import { INotificationService } from '../util'; /** * The default renderer type. */ -export const DEFAULT_RENDERER_TYPE: RendererType = RendererType.CANVAS; +const DEFAULT_RENDERER_TYPE: RendererType = RendererType.CANVAS; /** * Default limit when copying cells. */ -export const DEFAULT_MAX_CELL_COUNT_TO_COPY: number = 10000; +const DEFAULT_MAX_CELL_COUNT_TO_COPY: number = 10000; /** * Options for the renderer to use. */ export interface IRendererOptions { - /** * Type of renderer to use. */ @@ -41,14 +43,12 @@ export interface IRendererOptions { * Notification service to publish infos, warnings or error over. */ notificationService?: INotificationService; - } /** * Options customizing the table view. */ export interface IViewOptions { - /** * Number of fixed rows. */ @@ -64,7 +64,6 @@ export interface IViewOptions { * or negative if no limit. */ maxCellCountToCopy?: number; - } /** @@ -80,15 +79,24 @@ export const fillOptions = (options?: IRendererOptions) => { options.view = {}; } - if (options.view.fixedRows === undefined || options.view.fixedRows === null) { + if ( + options.view.fixedRows === undefined || + options.view.fixedRows === null + ) { options.view.fixedRows = 0; } - if (options.view.fixedColumns === undefined || options.view.fixedColumns === null) { + if ( + options.view.fixedColumns === undefined || + options.view.fixedColumns === null + ) { options.view.fixedColumns = 0; } - if (options.view.maxCellCountToCopy === undefined || options.view.maxCellCountToCopy === null) { + if ( + options.view.maxCellCountToCopy === undefined || + options.view.maxCellCountToCopy === null + ) { options.view.maxCellCountToCopy = DEFAULT_MAX_CELL_COUNT_TO_COPY; } diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index d0080f8..5117fc2 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -1,23 +1,25 @@ -import {ICellRenderer} from "./cell/cell-renderer"; -import {TableEngine} from "../table-engine"; -import {IOverlayManager} from "../overlay/overlay-manager"; -import {ITableEngineOptions} from "../options"; -import {IPoint} from "../util/point"; -import {IRectangle} from "../util/rect"; +import { ICellRenderer } from './cell'; +import { TableEngine } from '../table-engine'; +import { IOverlayManager } from '../overlay'; +import { ITableEngineOptions } from '../options'; +import { IPoint, IRectangle } from '../util'; /** * Representation of a renderer of the table engine. * It is responsible for rendering the table. */ export interface ITableEngineRenderer extends IOverlayManager { - /** * Initialize the renderer with the given options on the passed HTML container. * @param container to initialize renderer in * @param engine reference to the table-engine * @param options of the table engine (including renderer options) */ - initialize(container: HTMLElement, engine: TableEngine, options: ITableEngineOptions): Promise; + initialize( + container: HTMLElement, + engine: TableEngine, + options: ITableEngineOptions + ): Promise; /** * (Re)-Render the table. @@ -83,5 +85,4 @@ export interface ITableEngineRenderer extends IOverlayManager { * @param column to scroll to */ scrollTo(row: number, column: number): void; - } diff --git a/src/renderer/renderers.ts b/src/renderer/renderers.ts index 112b0c2..1017514 100644 --- a/src/renderer/renderers.ts +++ b/src/renderer/renderers.ts @@ -2,17 +2,13 @@ * All available renderers. */ export enum RendererType { - - /** - * Renderer using HTML5 canvas to draw a table. - */ - CANVAS, - - /** - * A custom renderer you may define yourself. - */ - CUSTOM - + /** + * Renderer using HTML5 canvas to draw a table. + */ + CANVAS, + + /** + * A custom renderer you may define yourself. + */ + CUSTOM, } - - diff --git a/src/renderer/util/canvas.ts b/src/renderer/util/canvas.ts index 38198a0..cbafae1 100644 --- a/src/renderer/util/canvas.ts +++ b/src/renderer/util/canvas.ts @@ -1,11 +1,9 @@ -import {IRectangle} from "../../util/rect"; -import {IColor} from "../../util/color"; +import { IRectangle } from '../../util'; /** * Utility methods regarding HTML5 canvas. */ export class CanvasUtil { - /** * Set the given width and height to the passed canvas HTML element. * @param element the canvas element to set the size to @@ -13,7 +11,12 @@ export class CanvasUtil { * @param height new height of the element to set * @param devicePixelRatio to use (optional) if not specified the browser default is used */ - public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number, devicePixelRatio?: number): void { + public static setCanvasSize( + element: HTMLCanvasElement, + width: number, + height: number, + devicePixelRatio?: number + ): void { /* We honor window.devicePixelRatio here to support high-DPI screens. To support High-DPI screens we will set the canvas element size twice: @@ -41,7 +44,11 @@ export class CanvasUtil { * @param rect to draw * @param radius of the corners */ - public static makeRoundRectPath(ctx: CanvasRenderingContext2D, rect: IRectangle, radius: number): void { + public static makeRoundRectPath( + ctx: CanvasRenderingContext2D, + rect: IRectangle, + radius: number + ): void { ctx.beginPath(); // Start with top side (without corners) @@ -49,19 +56,34 @@ export class CanvasUtil { ctx.lineTo(rect.left + rect.width - radius, rect.top); // Make top-right corner - ctx.quadraticCurveTo(rect.left + rect.width, rect.top, rect.left + rect.width, rect.top + radius); + ctx.quadraticCurveTo( + rect.left + rect.width, + rect.top, + rect.left + rect.width, + rect.top + radius + ); // Make right side (without corners) ctx.lineTo(rect.left + rect.width, rect.top + rect.height - radius); // Make bottom-right corner - ctx.quadraticCurveTo(rect.left + rect.width, rect.top + rect.height, rect.left + rect.width - radius, rect.top + rect.height); + ctx.quadraticCurveTo( + rect.left + rect.width, + rect.top + rect.height, + rect.left + rect.width - radius, + rect.top + rect.height + ); // Make bottom side (without corners) ctx.lineTo(rect.left + radius, rect.top + rect.height); // Make bottom-left corner - ctx.quadraticCurveTo(rect.left, rect.top + rect.height, rect.left, rect.top + rect.height - radius); + ctx.quadraticCurveTo( + rect.left, + rect.top + rect.height, + rect.left, + rect.top + rect.height - radius + ); // Make left side (without corners) ctx.lineTo(rect.left, rect.top + radius); @@ -71,5 +93,4 @@ export class CanvasUtil { ctx.closePath(); } - } diff --git a/src/renderer/util/index.ts b/src/renderer/util/index.ts new file mode 100644 index 0000000..7b32a67 --- /dev/null +++ b/src/renderer/util/index.ts @@ -0,0 +1,2 @@ +export { CanvasUtil } from './canvas'; +export { ScrollUtil } from './scroll'; diff --git a/src/renderer/util/scroll.ts b/src/renderer/util/scroll.ts index d77e95d..235098d 100644 --- a/src/renderer/util/scroll.ts +++ b/src/renderer/util/scroll.ts @@ -2,14 +2,17 @@ * Utility methods regarding scrolling. */ export class ScrollUtil { - /** * Determine the amount to scroll from the given wheel event. * @param canvasElement the canvas element to draw on * @param vertical whether to determine the vertical or horizontal scroll offset * @param event of the wheel */ - public static determineScrollOffsetFromEvent(canvasElement: HTMLCanvasElement, vertical: boolean, event: WheelEvent): number { + public static determineScrollOffsetFromEvent( + canvasElement: HTMLCanvasElement, + vertical: boolean, + event: WheelEvent + ): number { const scrollVertically: boolean = !event.shiftKey; const delta: number = vertical ? event.deltaY : event.deltaX; @@ -19,10 +22,14 @@ export class ScrollUtil { case event.DOM_DELTA_LINE: // Each deltaY means to scroll a line return delta * 25 * window.devicePixelRatio; case event.DOM_DELTA_PAGE: // Each deltaY means to scroll by a page (the tables height) - return delta * (scrollVertically ? canvasElement.height : canvasElement.width); + return ( + delta * + (scrollVertically + ? canvasElement.height + : canvasElement.width) + ); default: throw new Error(`WheelEvent deltaMode unsupported`); } } - } diff --git a/src/selection/index.ts b/src/selection/index.ts new file mode 100644 index 0000000..53920b7 --- /dev/null +++ b/src/selection/index.ts @@ -0,0 +1,8 @@ +export { + ICopyHandleOptions, + ISelectionOptions, + fillOptions as fillSelectionOptions, + ROW_COLUMN_HEADER_TRANSFORM, +} from './options'; +export { IInitialPosition, ISelection } from './selection'; +export * from './model'; diff --git a/src/selection/model/index.ts b/src/selection/model/index.ts new file mode 100644 index 0000000..8437739 --- /dev/null +++ b/src/selection/model/index.ts @@ -0,0 +1,2 @@ +export { ISelectionModel } from './selection-model.interface'; +export { SelectionModel } from './selection-model'; diff --git a/src/selection/model/selection-model.interface.ts b/src/selection/model/selection-model.interface.ts index 17cab21..7dee965 100644 --- a/src/selection/model/selection-model.interface.ts +++ b/src/selection/model/selection-model.interface.ts @@ -1,11 +1,10 @@ -import {IInitialPosition, ISelection} from "../selection"; -import {ICellRange} from "../../cell/range/cell-range"; +import { IInitialPosition, ISelection } from '../selection'; +import { ICellRange } from '../../cell'; /** * Representation of the table-engines selection model. */ export interface ISelectionModel { - /** * Get the primary selection (if any). */ @@ -36,7 +35,11 @@ export interface ISelectionModel { * @param validate whether to validate the passed selection first * @param subtract whether to subtract from existing selections when needed */ - addSelection(selection: ISelection, validate: boolean, subtract: boolean): void; + addSelection( + selection: ISelection, + validate: boolean, + subtract: boolean + ): void; /** * Modify the passed selection, that is already in the selection model. @@ -47,7 +50,13 @@ export interface ISelectionModel { * @param subtract whether to subtract from existing selections when needed * @returns whether the selection model changed */ - modifySelection(selection: ISelection, newRange: ICellRange, newInitial: IInitialPosition, validate: boolean, subtract: boolean): boolean; + modifySelection( + selection: ISelection, + newRange: ICellRange, + newInitial: IInitialPosition, + validate: boolean, + subtract: boolean + ): boolean; /** * Remove a selection already existing in the model. @@ -68,7 +77,12 @@ export interface ISelectionModel { * @param jump whether to jump to the very end in the specified direction * @returns whether the selection changed */ - extendSelection(selection: ISelection, xDiff: number, yDiff: number, jump: boolean): boolean; + extendSelection( + selection: ISelection, + xDiff: number, + yDiff: number, + jump: boolean + ): boolean; /** * Move the passed selection in the given direction. @@ -78,7 +92,12 @@ export interface ISelectionModel { * @param jump whether to jump to the very end in the specified direction * @returns whether the selection changed */ - moveSelection(selection: ISelection, xDiff: number, yDiff: number, jump: boolean): boolean; + moveSelection( + selection: ISelection, + xDiff: number, + yDiff: number, + jump: boolean + ): boolean; /** * Move the initial in the current primary selection (if any) @@ -87,5 +106,4 @@ export interface ISelectionModel { * @param yDiff vertical offset to move by */ moveInitial(xDiff: number, yDiff: number): void; - } diff --git a/src/selection/model/selection-model.ts b/src/selection/model/selection-model.ts index 0a2372d..203e369 100644 --- a/src/selection/model/selection-model.ts +++ b/src/selection/model/selection-model.ts @@ -1,16 +1,18 @@ -import {ISelectionModel} from "./selection-model.interface"; -import {IInitialPosition, ISelection} from "../selection"; -import {CellRange, ICellRange} from "../../cell/range/cell-range"; -import {ICellModel} from "../../cell/model/cell-model.interface"; -import {ICell} from "../../cell/cell"; -import {CellRangeUtil} from "../../cell/range/cell-range-util"; -import {ITableEngineOptions} from "../../options"; +import { ISelectionModel } from './selection-model.interface'; +import { IInitialPosition, ISelection } from '../selection'; +import { + CellRange, + CellRangeUtil, + ICell, + ICellModel, + ICellRange, +} from '../../cell'; +import { ITableEngineOptions } from '../../options'; /** * Model managing a table selection. */ export class SelectionModel implements ISelectionModel { - /** * Selections currently in the model. */ @@ -24,8 +26,7 @@ export class SelectionModel implements ISelectionModel { constructor( private readonly _cellModel: ICellModel, private readonly _options: ITableEngineOptions - ) { - } + ) {} /** * Get the primary selection (if any). @@ -44,7 +45,9 @@ export class SelectionModel implements ISelectionModel { */ public setPrimary(index: number): void { if (index < 0 || index >= this._selections.length) { - throw new Error(`Cannot set selection with index ${index} to be primary as there exists no selection with that index`); + throw new Error( + `Cannot set selection with index ${index} to be primary as there exists no selection with that index` + ); } this._primaryIndex = index; @@ -54,7 +57,7 @@ export class SelectionModel implements ISelectionModel { if (!primary.initial) { primary.initial = { row: primary.range.startRow, - column: primary.range.startColumn + column: primary.range.startColumn, }; } } @@ -72,7 +75,11 @@ export class SelectionModel implements ISelectionModel { * @param validate whether to validate the selection first * @param subtract whether to subtract from existing selections when needed */ - public addSelection(selection: ISelection, validate: boolean, subtract: boolean): void { + public addSelection( + selection: ISelection, + validate: boolean, + subtract: boolean + ): void { if (!this._options.selection.allowMultiSelection) { this.clear(); } @@ -92,7 +99,13 @@ export class SelectionModel implements ISelectionModel { // When there is a selection transform we need to consult that first // whether the selection is possible! if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, false)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + false + ) + ) { return; // Selection not allowed! } } @@ -111,7 +124,13 @@ export class SelectionModel implements ISelectionModel { * @param subtract whether to subtract from existing selections when needed * @returns whether the selection model changed */ - public modifySelection(selection: ISelection, newRange: ICellRange, newInitial: IInitialPosition, validate: boolean, subtract: boolean): boolean { + public modifySelection( + selection: ISelection, + newRange: ICellRange, + newInitial: IInitialPosition, + validate: boolean, + subtract: boolean + ): boolean { const oldRange = selection.range; const oldInitial = selection.initial; @@ -143,7 +162,13 @@ export class SelectionModel implements ISelectionModel { // When there is a selection transform we need to consult that first // whether the selection is possible! if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, false)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + false + ) + ) { undoChange = true; } } @@ -155,8 +180,13 @@ export class SelectionModel implements ISelectionModel { return false; } else { - const rangeChanged = !CellRangeUtil.equals(selection.range, oldRange); - const initialChanged = selection.initial.row !== oldInitial.row || selection.initial.column !== oldInitial.column; + const rangeChanged = !CellRangeUtil.equals( + selection.range, + oldRange + ); + const initialChanged = + selection.initial.row !== oldInitial.row || + selection.initial.column !== oldInitial.column; return rangeChanged || initialChanged || subtractChange; } @@ -180,14 +210,23 @@ export class SelectionModel implements ISelectionModel { * @param subtract whether to also subtract if needed * @returns the selections to add and remove or null if the selection is not allowed */ - private _validateSelection(selection: ISelection, subtract: boolean): IValidationResult | null { + private _validateSelection( + selection: ISelection, + subtract: boolean + ): IValidationResult | null { SelectionModel._validateCellRange(selection.range); this._validateCellRangeContainAllMergedCells(selection); // When there is a selection transform we need to consult that first // whether the selection is possible! if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, false)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + false + ) + ) { return null; // Selection not allowed } } @@ -197,7 +236,7 @@ export class SelectionModel implements ISelectionModel { } else { return { toAdd: [selection], - toRemove: [] + toRemove: [], }; } } @@ -213,7 +252,7 @@ export class SelectionModel implements ISelectionModel { // Trivial case - cannot be enclosed return { toAdd: [selection], - toRemove: [] + toRemove: [], }; } @@ -222,7 +261,10 @@ export class SelectionModel implements ISelectionModel { continue; } - const isEnclosed: boolean = CellRangeUtil.contains(selection.range, s.range); + const isEnclosed: boolean = CellRangeUtil.contains( + selection.range, + s.range + ); if (isEnclosed) { // Divide selection that contains the new selection const toAdd: ISelection[] = []; @@ -230,8 +272,11 @@ export class SelectionModel implements ISelectionModel { // Check if the new selection is exactly the old one -> return just the initial row/column as new selection if (CellRangeUtil.equals(selection.range, s.range)) { toAdd.push({ - range: CellRange.fromSingleRowColumn(selection.initial.row, selection.initial.column), - initial: selection.initial + range: CellRange.fromSingleRowColumn( + selection.initial.row, + selection.initial.column + ), + initial: selection.initial, }); } else { // Add top selection (if necessary) @@ -241,8 +286,8 @@ export class SelectionModel implements ISelectionModel { startRow: s.range.startRow, endRow: selection.range.startRow - 1, startColumn: s.range.startColumn, - endColumn: s.range.endColumn - } + endColumn: s.range.endColumn, + }, }); } @@ -253,8 +298,8 @@ export class SelectionModel implements ISelectionModel { startRow: selection.range.startRow, endRow: selection.range.endRow, startColumn: s.range.startColumn, - endColumn: selection.range.startColumn - 1 - } + endColumn: selection.range.startColumn - 1, + }, }); } @@ -265,8 +310,8 @@ export class SelectionModel implements ISelectionModel { startRow: selection.range.startRow, endRow: selection.range.endRow, startColumn: selection.range.endColumn + 1, - endColumn: s.range.endColumn - } + endColumn: s.range.endColumn, + }, }); } @@ -277,8 +322,8 @@ export class SelectionModel implements ISelectionModel { startRow: selection.range.endRow + 1, endRow: s.range.endRow, startColumn: s.range.startColumn, - endColumn: s.range.endColumn - } + endColumn: s.range.endColumn, + }, }); } } @@ -293,7 +338,7 @@ export class SelectionModel implements ISelectionModel { // Is not contained return { toAdd: [selection], - toRemove: [] + toRemove: [], }; } @@ -301,7 +346,9 @@ export class SelectionModel implements ISelectionModel { * Validate the passed selection to completely contain all merged cells. * @param selection to validate */ - private _validateCellRangeContainAllMergedCells(selection: ISelection): void { + private _validateCellRangeContainAllMergedCells( + selection: ISelection + ): void { while (this._updateCellRangeToContainAllMergedCells(selection.range)) { // Continue to update cell ranges until no change has been found anymore } @@ -312,9 +359,15 @@ export class SelectionModel implements ISelectionModel { * @param range the range to update * @returns whether there was a change and this process needs to be repeated */ - private _updateCellRangeToContainAllMergedCells(range: ICellRange): boolean { + private _updateCellRangeToContainAllMergedCells( + range: ICellRange + ): boolean { for (let row = range.startRow; row <= range.endRow; row++) { - for (let column = range.startColumn; column <= range.endColumn; column++) { + for ( + let column = range.startColumn; + column <= range.endColumn; + column++ + ) { const cell: ICell | null = this._cellModel.getCell(row, column); if (!!cell) { let foundChange = false; @@ -378,7 +431,12 @@ export class SelectionModel implements ISelectionModel { */ public isSelected(rowIndex: number, columnIndex: number): boolean { for (const s of this._selections) { - if (CellRangeUtil.contains(CellRange.fromSingleRowColumn(rowIndex, columnIndex), s.range)) { + if ( + CellRangeUtil.contains( + CellRange.fromSingleRowColumn(rowIndex, columnIndex), + s.range + ) + ) { return true; } } @@ -394,14 +452,23 @@ export class SelectionModel implements ISelectionModel { * @param jump whether to jump to the very end in the specified direction * @returns whether the selection changed */ - public extendSelection(selection: ISelection, xDiff: number, yDiff: number, jump: boolean): boolean { + public extendSelection( + selection: ISelection, + xDiff: number, + yDiff: number, + jump: boolean + ): boolean { if (xDiff !== 0) { if (xDiff < 0) { if (selection.initial.column < selection.range.endColumn) { // Shrink the selection const newColumn = jump ? selection.initial.column - : this._findColumnOfPreviousVisibleCell(selection.range.endColumn, selection.initial.row, false); + : this._findColumnOfPreviousVisibleCell( + selection.range.endColumn, + selection.initial.row, + false + ); if (newColumn === -1) { return false; } @@ -409,7 +476,13 @@ export class SelectionModel implements ISelectionModel { const oldValue = selection.range.endColumn; selection.range.endColumn = newColumn; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range.endColumn = oldValue; // Set old value return false; } @@ -419,8 +492,16 @@ export class SelectionModel implements ISelectionModel { } else { // Extend the selection const newColumn = jump - ? this._findColumnOfNextVisibleCell(-1, selection.initial.row, false) - : this._findColumnOfPreviousVisibleCell(selection.range.startColumn, selection.initial.row, false); + ? this._findColumnOfNextVisibleCell( + -1, + selection.initial.row, + false + ) + : this._findColumnOfPreviousVisibleCell( + selection.range.startColumn, + selection.initial.row, + false + ); if (newColumn === -1) { return false; } @@ -428,7 +509,13 @@ export class SelectionModel implements ISelectionModel { const oldValue = selection.range.startColumn; selection.range.startColumn = newColumn; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range.startColumn = oldValue; // Set old value return false; } @@ -441,7 +528,11 @@ export class SelectionModel implements ISelectionModel { // Shrink the selection const newColumn = jump ? selection.initial.column - : this._findColumnOfNextVisibleCell(selection.range.startColumn, selection.initial.row, false); + : this._findColumnOfNextVisibleCell( + selection.range.startColumn, + selection.initial.row, + false + ); if (newColumn === -1) { return false; } @@ -449,7 +540,13 @@ export class SelectionModel implements ISelectionModel { const oldValue = selection.range.startColumn; selection.range.startColumn = newColumn; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range.startColumn = oldValue; // Set old value return false; } @@ -459,8 +556,16 @@ export class SelectionModel implements ISelectionModel { } else { // Extend the selection const newColumn = jump - ? this._findColumnOfPreviousVisibleCell(this._cellModel.getColumnCount(), selection.initial.row, false) - : this._findColumnOfNextVisibleCell(selection.range.endColumn, selection.initial.row, false); + ? this._findColumnOfPreviousVisibleCell( + this._cellModel.getColumnCount(), + selection.initial.row, + false + ) + : this._findColumnOfNextVisibleCell( + selection.range.endColumn, + selection.initial.row, + false + ); if (newColumn === -1) { return false; } @@ -468,7 +573,13 @@ export class SelectionModel implements ISelectionModel { const oldValue = selection.range.endColumn; selection.range.endColumn = newColumn; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range.endColumn = oldValue; // Set old value return false; } @@ -485,7 +596,11 @@ export class SelectionModel implements ISelectionModel { // Shrink the selection const newRow = jump ? selection.initial.row - : this._findRowOfPreviousVisibleCell(selection.range.endRow, selection.initial.column, false); + : this._findRowOfPreviousVisibleCell( + selection.range.endRow, + selection.initial.column, + false + ); if (newRow === -1) { return false; } @@ -493,7 +608,13 @@ export class SelectionModel implements ISelectionModel { const oldValue = selection.range.endRow; selection.range.endRow = newRow; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range.endRow = oldValue; // Set old value return false; } @@ -503,8 +624,16 @@ export class SelectionModel implements ISelectionModel { } else { // Extend the selection const newRow = jump - ? this._findRowOfNextVisibleCell(-1, selection.initial.column, false) - : this._findRowOfPreviousVisibleCell(selection.range.startRow, selection.initial.column, false); + ? this._findRowOfNextVisibleCell( + -1, + selection.initial.column, + false + ) + : this._findRowOfPreviousVisibleCell( + selection.range.startRow, + selection.initial.column, + false + ); if (newRow === -1) { return false; } @@ -512,7 +641,13 @@ export class SelectionModel implements ISelectionModel { const oldValue = selection.range.startRow; selection.range.startRow = newRow; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range.startRow = oldValue; // Set old value return false; } @@ -525,7 +660,11 @@ export class SelectionModel implements ISelectionModel { // Shrink the selection const newRow = jump ? selection.initial.row - : this._findRowOfNextVisibleCell(selection.range.startRow, selection.initial.column, false); + : this._findRowOfNextVisibleCell( + selection.range.startRow, + selection.initial.column, + false + ); if (newRow === -1) { return false; } @@ -533,7 +672,13 @@ export class SelectionModel implements ISelectionModel { const oldValue = selection.range.startRow; selection.range.startRow = newRow; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range.startRow = oldValue; // Set old value return false; } @@ -543,8 +688,16 @@ export class SelectionModel implements ISelectionModel { } else { // Extend the selection const newRow = jump - ? this._findRowOfPreviousVisibleCell(this._cellModel.getRowCount(), selection.initial.column, false) - : this._findRowOfNextVisibleCell(selection.range.endRow, selection.initial.column, false); + ? this._findRowOfPreviousVisibleCell( + this._cellModel.getRowCount(), + selection.initial.column, + false + ) + : this._findRowOfNextVisibleCell( + selection.range.endRow, + selection.initial.column, + false + ); if (newRow === -1) { return false; } @@ -552,7 +705,13 @@ export class SelectionModel implements ISelectionModel { const oldValue = selection.range.endRow; selection.range.endRow = newRow; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range.endRow = oldValue; // Set old value return false; } @@ -575,23 +734,45 @@ export class SelectionModel implements ISelectionModel { * @param jump whether to jump to the very end in the specified direction * @returns whether the selection changed */ - public moveSelection(selection: ISelection, xDiff: number, yDiff: number, jump: boolean): boolean { + public moveSelection( + selection: ISelection, + xDiff: number, + yDiff: number, + jump: boolean + ): boolean { if (xDiff !== 0) { if (xDiff < 0) { // Find previous cell to select (if any) const previousColumn = jump - ? this._findColumnOfNextVisibleCell(-1, selection.initial.row, false) - : this._findColumnOfPreviousVisibleCell(selection.initial.column, selection.initial.row, false); + ? this._findColumnOfNextVisibleCell( + -1, + selection.initial.row, + false + ) + : this._findColumnOfPreviousVisibleCell( + selection.initial.column, + selection.initial.row, + false + ); if (previousColumn === -1) { return false; } const oldRange = selection.range; const oldInitial = selection.initial.column; - selection.range = this._findCellRange(selection.initial.row, previousColumn); + selection.range = this._findCellRange( + selection.initial.row, + previousColumn + ); selection.initial.column = previousColumn; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range = oldRange; selection.initial.column = oldInitial; return false; @@ -602,18 +783,35 @@ export class SelectionModel implements ISelectionModel { } else { // Find next cell to select (if any) const nextColumn = jump - ? this._findColumnOfPreviousVisibleCell(this._cellModel.getColumnCount(), selection.initial.row, false) - : this._findColumnOfNextVisibleCell(selection.initial.column, selection.initial.row, false); + ? this._findColumnOfPreviousVisibleCell( + this._cellModel.getColumnCount(), + selection.initial.row, + false + ) + : this._findColumnOfNextVisibleCell( + selection.initial.column, + selection.initial.row, + false + ); if (nextColumn === -1) { return false; } const oldRange = selection.range; const oldInitial = selection.initial.column; - selection.range = this._findCellRange(selection.initial.row, nextColumn); + selection.range = this._findCellRange( + selection.initial.row, + nextColumn + ); selection.initial.column = nextColumn; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range = oldRange; selection.initial.column = oldInitial; return false; @@ -628,18 +826,35 @@ export class SelectionModel implements ISelectionModel { if (yDiff < 0) { // Find previous cell to select (if any) const previousRow = jump - ? this._findRowOfNextVisibleCell(-1, selection.initial.column, false) - : this._findRowOfPreviousVisibleCell(selection.initial.row, selection.initial.column, false); + ? this._findRowOfNextVisibleCell( + -1, + selection.initial.column, + false + ) + : this._findRowOfPreviousVisibleCell( + selection.initial.row, + selection.initial.column, + false + ); if (previousRow === -1) { return false; } const oldRange = selection.range; const oldInitial = selection.initial.row; - selection.range = this._findCellRange(previousRow, selection.initial.column); + selection.range = this._findCellRange( + previousRow, + selection.initial.column + ); selection.initial.row = previousRow; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range = oldRange; selection.initial.row = oldInitial; return false; @@ -650,18 +865,35 @@ export class SelectionModel implements ISelectionModel { } else { // Find next cell to select (if any) const nextRow = jump - ? this._findRowOfPreviousVisibleCell(this._cellModel.getRowCount(), selection.initial.row, false) - : this._findRowOfNextVisibleCell(selection.initial.row, selection.initial.column, false); + ? this._findRowOfPreviousVisibleCell( + this._cellModel.getRowCount(), + selection.initial.row, + false + ) + : this._findRowOfNextVisibleCell( + selection.initial.row, + selection.initial.column, + false + ); if (nextRow === -1) { return false; } const oldRange = selection.range; const oldInitial = selection.initial.row; - selection.range = this._findCellRange(nextRow, selection.initial.column); + selection.range = this._findCellRange( + nextRow, + selection.initial.column + ); selection.initial.row = nextRow; if (!!this._options.selection.selectionTransform) { - if (!this._options.selection.selectionTransform(selection, this._cellModel, true)) { + if ( + !this._options.selection.selectionTransform( + selection, + this._cellModel, + true + ) + ) { selection.range = oldRange; selection.initial.row = oldInitial; return false; @@ -688,7 +920,7 @@ export class SelectionModel implements ISelectionModel { startRow: cell.range.startRow, endRow: cell.range.endRow, startColumn: cell.range.startColumn, - endColumn: cell.range.endColumn + endColumn: cell.range.endColumn, }; } else { range = CellRange.fromSingleRowColumn(row, column); @@ -709,60 +941,102 @@ export class SelectionModel implements ISelectionModel { return; } - const cellRange: ICellRange = this._cellModel.getCell(primary.initial.row, primary.initial.column)?.range ?? { + const cellRange: ICellRange = this._cellModel.getCell( + primary.initial.row, + primary.initial.column + )?.range ?? { startRow: primary.initial.row, endRow: primary.initial.row, startColumn: primary.initial.column, - endColumn: primary.initial.column + endColumn: primary.initial.column, }; // Make sure initial is at the correct position for the movement in case of a merged cell if (!CellRangeUtil.isSingleRowColumnRange(cellRange)) { if (xDiff !== 0) { if (xDiff > 0) { - primary.initial.column = this._cellModel.findPreviousVisibleColumn(cellRange.endColumn); + primary.initial.column = + this._cellModel.findPreviousVisibleColumn( + cellRange.endColumn + ); } else { - primary.initial.column = this._cellModel.findNextVisibleColumn(cellRange.startColumn); + primary.initial.column = + this._cellModel.findNextVisibleColumn( + cellRange.startColumn + ); } } if (yDiff !== 0) { if (xDiff > 0) { - primary.initial.row = this._cellModel.findPreviousVisibleRow(cellRange.endRow); + primary.initial.row = + this._cellModel.findPreviousVisibleRow( + cellRange.endRow + ); } else { - primary.initial.row = this._cellModel.findNextVisibleRow(cellRange.startRow); + primary.initial.row = this._cellModel.findNextVisibleRow( + cellRange.startRow + ); } } } // Check bounds of the selection we can move the initial to - const firstVisibleRow: number = this._cellModel.findNextVisibleRow(primary.range.startRow); - const firstVisibleColumn: number = this._cellModel.findNextVisibleColumn(primary.range.startColumn); - const lastVisibleRow: number = this._cellModel.findPreviousVisibleRow(primary.range.endRow); - const lastVisibleColumn: number = this._cellModel.findPreviousVisibleColumn(primary.range.endColumn); + const firstVisibleRow: number = this._cellModel.findNextVisibleRow( + primary.range.startRow + ); + const firstVisibleColumn: number = + this._cellModel.findNextVisibleColumn(primary.range.startColumn); + const lastVisibleRow: number = this._cellModel.findPreviousVisibleRow( + primary.range.endRow + ); + const lastVisibleColumn: number = + this._cellModel.findPreviousVisibleColumn(primary.range.endColumn); if (xDiff !== 0) { if (xDiff > 0) { // Check if we can move initial to the right - const nextColumn: number = this._findColumnOfNextVisibleCell(primary.initial.column, primary.initial.row, false); + const nextColumn: number = this._findColumnOfNextVisibleCell( + primary.initial.column, + primary.initial.row, + false + ); if (nextColumn === -1 || nextColumn > lastVisibleColumn) { // Wrap around primary.initial.column = firstVisibleColumn; // Check if we're still in the same cell range - if (CellRangeUtil.contains(CellRange.fromSingleRowColumn(primary.initial.row, primary.initial.column), cellRange)) { + if ( + CellRangeUtil.contains( + CellRange.fromSingleRowColumn( + primary.initial.row, + primary.initial.column + ), + cellRange + ) + ) { // Make sure initial is set to last row to properly wrap initial selection - primary.initial.row = this._cellModel.findPreviousVisibleRow(cellRange.endRow); + primary.initial.row = + this._cellModel.findPreviousVisibleRow( + cellRange.endRow + ); } - const nextRow: number = this._findRowOfNextVisibleCell(primary.initial.row, primary.initial.column, true); + const nextRow: number = this._findRowOfNextVisibleCell( + primary.initial.row, + primary.initial.column, + true + ); if (nextRow === -1 || nextRow > lastVisibleRow) { // Go to next selection (if any) or start with first initial position in the primary selection again if (this.getSelections().length > 1) { // Go to next selection this._primaryIndex += 1; - if (this._primaryIndex >= this.getSelections().length) { + if ( + this._primaryIndex >= + this.getSelections().length + ) { this._primaryIndex = 0; } @@ -770,8 +1044,12 @@ export class SelectionModel implements ISelectionModel { // Set initial position properly newPrimary.initial = { - row: this._cellModel.findNextVisibleRow(newPrimary.range.startRow), - column: this._cellModel.findNextVisibleColumn(newPrimary.range.startColumn) + row: this._cellModel.findNextVisibleRow( + newPrimary.range.startRow + ), + column: this._cellModel.findNextVisibleColumn( + newPrimary.range.startColumn + ), }; } else { // Start with first initial position again @@ -786,34 +1064,63 @@ export class SelectionModel implements ISelectionModel { } } else { // Check if we can move initial to the left - const previousColumn: number = this._findColumnOfPreviousVisibleCell(primary.initial.column, primary.initial.row, false); - - if (previousColumn === -1 || previousColumn < firstVisibleColumn) { + const previousColumn: number = + this._findColumnOfPreviousVisibleCell( + primary.initial.column, + primary.initial.row, + false + ); + + if ( + previousColumn === -1 || + previousColumn < firstVisibleColumn + ) { // Wrap around primary.initial.column = lastVisibleColumn; // Check if we're still in the same cell range - if (CellRangeUtil.contains(CellRange.fromSingleRowColumn(primary.initial.row, primary.initial.column), cellRange)) { + if ( + CellRangeUtil.contains( + CellRange.fromSingleRowColumn( + primary.initial.row, + primary.initial.column + ), + cellRange + ) + ) { // Make sure initial is set to first row to properly wrap initial selection - primary.initial.row = this._cellModel.findNextVisibleRow(cellRange.startRow); + primary.initial.row = + this._cellModel.findNextVisibleRow( + cellRange.startRow + ); } - const previousRow: number = this._findRowOfPreviousVisibleCell(primary.initial.row, primary.initial.column, true); + const previousRow: number = + this._findRowOfPreviousVisibleCell( + primary.initial.row, + primary.initial.column, + true + ); if (previousRow === -1 || previousRow < firstVisibleRow) { // Go to previous selection (if any) or start with first initial position in the primary selection again if (this.getSelections().length > 1) { // Go to previous selection this._primaryIndex -= 1; if (this._primaryIndex < 0) { - this._primaryIndex = this.getSelections().length - 1; + this._primaryIndex = + this.getSelections().length - 1; } const newPrimary: ISelection = this.getPrimary(); // Set initial position properly newPrimary.initial = { - row: this._cellModel.findPreviousVisibleRow(newPrimary.range.endRow), - column: this._cellModel.findPreviousVisibleColumn(newPrimary.range.endColumn) + row: this._cellModel.findPreviousVisibleRow( + newPrimary.range.endRow + ), + column: this._cellModel.findPreviousVisibleColumn( + newPrimary.range.endColumn + ), }; } else { // Start with last initial position again @@ -832,25 +1139,48 @@ export class SelectionModel implements ISelectionModel { if (yDiff !== 0) { if (yDiff > 0) { // Check if we can move initial down - const nextRow: number = this._findRowOfNextVisibleCell(primary.initial.row, primary.initial.column, false); + const nextRow: number = this._findRowOfNextVisibleCell( + primary.initial.row, + primary.initial.column, + false + ); if (nextRow === -1 || nextRow > lastVisibleRow) { // Wrap around primary.initial.row = firstVisibleRow; // Check if we're still in the same cell range - if (CellRangeUtil.contains(CellRange.fromSingleRowColumn(primary.initial.row, primary.initial.column), cellRange)) { + if ( + CellRangeUtil.contains( + CellRange.fromSingleRowColumn( + primary.initial.row, + primary.initial.column + ), + cellRange + ) + ) { // Make sure initial is set to last column to properly wrap initial selection - primary.initial.column = this._cellModel.findPreviousVisibleColumn(cellRange.endColumn); + primary.initial.column = + this._cellModel.findPreviousVisibleColumn( + cellRange.endColumn + ); } - const nextColumn: number = this._findColumnOfNextVisibleCell(primary.initial.column, primary.initial.row, true); + const nextColumn: number = + this._findColumnOfNextVisibleCell( + primary.initial.column, + primary.initial.row, + true + ); if (nextColumn === -1 || nextColumn > lastVisibleColumn) { // Go to next selection (if any) or start with first initial position in the primary selection again if (this.getSelections().length > 1) { // Go to next selection this._primaryIndex += 1; - if (this._primaryIndex >= this.getSelections().length) { + if ( + this._primaryIndex >= + this.getSelections().length + ) { this._primaryIndex = 0; } @@ -858,8 +1188,12 @@ export class SelectionModel implements ISelectionModel { // Set initial position properly newPrimary.initial = { - row: this._cellModel.findNextVisibleRow(newPrimary.range.startRow), - column: this._cellModel.findNextVisibleColumn(newPrimary.range.startColumn) + row: this._cellModel.findNextVisibleRow( + newPrimary.range.startRow + ), + column: this._cellModel.findNextVisibleColumn( + newPrimary.range.startColumn + ), }; } else { // Start with first initial position again @@ -874,34 +1208,62 @@ export class SelectionModel implements ISelectionModel { } } else { // Check if we can move initial up - const previousRow: number = this._findRowOfPreviousVisibleCell(primary.initial.row, primary.initial.column, false); + const previousRow: number = this._findRowOfPreviousVisibleCell( + primary.initial.row, + primary.initial.column, + false + ); if (previousRow === -1 || previousRow < firstVisibleRow) { // Wrap around primary.initial.row = lastVisibleRow; // Check if we're still in the same cell range - if (CellRangeUtil.contains(CellRange.fromSingleRowColumn(primary.initial.row, primary.initial.column), cellRange)) { + if ( + CellRangeUtil.contains( + CellRange.fromSingleRowColumn( + primary.initial.row, + primary.initial.column + ), + cellRange + ) + ) { // Make sure initial is set to first column to properly wrap initial selection - primary.initial.column = this._cellModel.findNextVisibleColumn(cellRange.startColumn); + primary.initial.column = + this._cellModel.findNextVisibleColumn( + cellRange.startColumn + ); } - const previousColumn: number = this._findColumnOfPreviousVisibleCell(primary.initial.column, primary.initial.row, true); - if (previousColumn === -1 || previousColumn < firstVisibleColumn) { + const previousColumn: number = + this._findColumnOfPreviousVisibleCell( + primary.initial.column, + primary.initial.row, + true + ); + if ( + previousColumn === -1 || + previousColumn < firstVisibleColumn + ) { // Go to previous selection (if any) or start with first initial position in the primary selection again if (this.getSelections().length > 1) { // Go to previous selection this._primaryIndex -= 1; if (this._primaryIndex < 0) { - this._primaryIndex = this.getSelections().length - 1; + this._primaryIndex = + this.getSelections().length - 1; } const newPrimary: ISelection = this.getPrimary(); // Set initial position properly newPrimary.initial = { - row: this._cellModel.findPreviousVisibleRow(newPrimary.range.endRow), - column: this._cellModel.findPreviousVisibleColumn(newPrimary.range.endColumn) + row: this._cellModel.findPreviousVisibleRow( + newPrimary.range.endRow + ), + column: this._cellModel.findPreviousVisibleColumn( + newPrimary.range.endColumn + ), }; } else { // Start with last initial position again @@ -925,21 +1287,34 @@ export class SelectionModel implements ISelectionModel { * @param allowSameCell whether to allow resulting in a row in the same cell (when having a merged cell) * @return the next visible index or -1 */ - private _findRowOfNextVisibleCell(afterRow: number, column: number, allowSameCell: boolean): number { - const cell: ICell | null = afterRow >= 0 - ? this._cellModel.getCell(afterRow, column) - : null; + private _findRowOfNextVisibleCell( + afterRow: number, + column: number, + allowSameCell: boolean + ): number { + const cell: ICell | null = + afterRow >= 0 ? this._cellModel.getCell(afterRow, column) : null; let nextVisibleRowCell: ICell = null; let nextVisibleRow: number = afterRow; do { - nextVisibleRow = this._cellModel.findNextVisibleRow(nextVisibleRow + 1); + nextVisibleRow = this._cellModel.findNextVisibleRow( + nextVisibleRow + 1 + ); if (nextVisibleRow === -1) { return -1; } - nextVisibleRowCell = this._cellModel.getCell(nextVisibleRow, column); - } while (nextVisibleRowCell !== null && cell !== null && (nextVisibleRowCell === cell && !allowSameCell)); + nextVisibleRowCell = this._cellModel.getCell( + nextVisibleRow, + column + ); + } while ( + nextVisibleRowCell !== null && + cell !== null && + nextVisibleRowCell === cell && + !allowSameCell + ); return nextVisibleRow; } @@ -951,21 +1326,36 @@ export class SelectionModel implements ISelectionModel { * @param allowSameCell whether to allow resulting in a row in the same cell (when having a merged cell) * @return the previous visible index or -1 */ - private _findRowOfPreviousVisibleCell(beforeRow: number, column: number, allowSameCell: boolean): number { - const cell: ICell | null = beforeRow <= this._cellModel.getRowCount() - 1 - ? this._cellModel.getCell(beforeRow, column) - : null; + private _findRowOfPreviousVisibleCell( + beforeRow: number, + column: number, + allowSameCell: boolean + ): number { + const cell: ICell | null = + beforeRow <= this._cellModel.getRowCount() - 1 + ? this._cellModel.getCell(beforeRow, column) + : null; let previousVisibleRowCell: ICell = null; let previousVisibleRow: number = beforeRow; do { - previousVisibleRow = this._cellModel.findPreviousVisibleRow(previousVisibleRow - 1); + previousVisibleRow = this._cellModel.findPreviousVisibleRow( + previousVisibleRow - 1 + ); if (previousVisibleRow === -1) { return -1; } - previousVisibleRowCell = this._cellModel.getCell(previousVisibleRow, column); - } while (previousVisibleRowCell !== null && cell !== null && (previousVisibleRowCell === cell && !allowSameCell)); + previousVisibleRowCell = this._cellModel.getCell( + previousVisibleRow, + column + ); + } while ( + previousVisibleRowCell !== null && + cell !== null && + previousVisibleRowCell === cell && + !allowSameCell + ); return previousVisibleRow; } @@ -977,21 +1367,34 @@ export class SelectionModel implements ISelectionModel { * @param allowSameCell whether to allow resulting in a column in the same cell (when having a merged cell) * @return the next visible index or -1 */ - private _findColumnOfNextVisibleCell(afterColumn: number, row: number, allowSameCell: boolean): number { - const cell: ICell | null = afterColumn >= 0 - ? this._cellModel.getCell(row, afterColumn) - : null; + private _findColumnOfNextVisibleCell( + afterColumn: number, + row: number, + allowSameCell: boolean + ): number { + const cell: ICell | null = + afterColumn >= 0 ? this._cellModel.getCell(row, afterColumn) : null; let nextVisibleColumnCell: ICell = null; let nextVisibleColumn: number = afterColumn; do { - nextVisibleColumn = this._cellModel.findNextVisibleColumn(nextVisibleColumn + 1); + nextVisibleColumn = this._cellModel.findNextVisibleColumn( + nextVisibleColumn + 1 + ); if (nextVisibleColumn === -1) { return -1; } - nextVisibleColumnCell = this._cellModel.getCell(row, nextVisibleColumn); - } while (nextVisibleColumnCell !== null && cell !== null && (nextVisibleColumnCell === cell && !allowSameCell)); + nextVisibleColumnCell = this._cellModel.getCell( + row, + nextVisibleColumn + ); + } while ( + nextVisibleColumnCell !== null && + cell !== null && + nextVisibleColumnCell === cell && + !allowSameCell + ); return nextVisibleColumn; } @@ -1003,32 +1406,45 @@ export class SelectionModel implements ISelectionModel { * @param allowSameCell whether to allow resulting in a column in the same cell (when having a merged cell) * @return the previous visible index or -1 */ - private _findColumnOfPreviousVisibleCell(beforeColumn: number, row: number, allowSameCell: boolean): number { - const cell: ICell | null = beforeColumn <= this._cellModel.getColumnCount() - 1 - ? this._cellModel.getCell(row, beforeColumn) - : null; + private _findColumnOfPreviousVisibleCell( + beforeColumn: number, + row: number, + allowSameCell: boolean + ): number { + const cell: ICell | null = + beforeColumn <= this._cellModel.getColumnCount() - 1 + ? this._cellModel.getCell(row, beforeColumn) + : null; let previousVisibleColumnCell: ICell = null; let previousVisibleColumn: number = beforeColumn; do { - previousVisibleColumn = this._cellModel.findPreviousVisibleColumn(previousVisibleColumn - 1); + previousVisibleColumn = this._cellModel.findPreviousVisibleColumn( + previousVisibleColumn - 1 + ); if (previousVisibleColumn === -1) { return -1; } - previousVisibleColumnCell = this._cellModel.getCell(row, previousVisibleColumn); - } while (previousVisibleColumnCell !== null && cell !== null && (previousVisibleColumnCell === cell && !allowSameCell)); + previousVisibleColumnCell = this._cellModel.getCell( + row, + previousVisibleColumn + ); + } while ( + previousVisibleColumnCell !== null && + cell !== null && + previousVisibleColumnCell === cell && + !allowSameCell + ); return previousVisibleColumn; } - } /** * Result of a selection validation. */ interface IValidationResult { - /** * Selections to remove. */ @@ -1038,5 +1454,4 @@ interface IValidationResult { * Selections to add. */ toAdd: ISelection[]; - } diff --git a/src/selection/options.ts b/src/selection/options.ts index eb82bc8..79e33a5 100644 --- a/src/selection/options.ts +++ b/src/selection/options.ts @@ -1,12 +1,15 @@ -import {ISelection} from "./selection"; -import {ICellModel} from "../cell/model/cell-model.interface"; -import {ICellRange} from "../cell/range/cell-range"; +import { ISelection } from './selection'; +import { ICellModel, ICellRange } from '../cell'; /** * Selection transform that will transform selections that start in the first row * or column to fill the entire selected row or column. */ -export const ROW_COLUMN_HEADER_TRANSFORM: (selection: ISelection, cellModel: ICellModel, causedByMove: boolean) => boolean = (selection, cellModel, causedByMove: boolean) => { +export const ROW_COLUMN_HEADER_TRANSFORM: ( + selection: ISelection, + cellModel: ICellModel, + causedByMove: boolean +) => boolean = (selection, cellModel, causedByMove: boolean) => { if (causedByMove) { // Prevent selection of the first row and column by move (keyboard navigation). if (selection.initial.row === 0) { @@ -35,11 +38,11 @@ export const ROW_COLUMN_HEADER_TRANSFORM: (selection: ISelection, cellModel: ICe startRow: 1, endRow: cellModel.getRowCount() - 1, startColumn: 1, - endColumn: cellModel.getColumnCount() - 1 + endColumn: cellModel.getColumnCount() - 1, }; selection.initial = { row: 1, - column: 1 + column: 1, }; } else if (selection.initial.row === 0) { // Select whole column @@ -47,11 +50,11 @@ export const ROW_COLUMN_HEADER_TRANSFORM: (selection: ISelection, cellModel: ICe startRow: 1, endRow: cellModel.getRowCount() - 1, startColumn: selection.range.startColumn, - endColumn: selection.range.endColumn + endColumn: selection.range.endColumn, }; selection.initial = { row: 1, - column: selection.initial.column + column: selection.initial.column, }; } else if (selection.initial.column === 0) { // Select whole row @@ -59,11 +62,11 @@ export const ROW_COLUMN_HEADER_TRANSFORM: (selection: ISelection, cellModel: ICe startRow: selection.range.startRow, endRow: selection.range.endRow, startColumn: 1, - endColumn: cellModel.getColumnCount() - 1 + endColumn: cellModel.getColumnCount() - 1, }; selection.initial = { row: selection.initial.row, - column: 1 + column: 1, }; } } @@ -75,7 +78,6 @@ export const ROW_COLUMN_HEADER_TRANSFORM: (selection: ISelection, cellModel: ICe * Options for the selection model. */ export interface ISelectionOptions { - /** * Whether multiple selections are allowed. */ @@ -88,7 +90,11 @@ export interface ISelectionOptions { * @param causedByMove whether the selection was caused by a move (keyboard navigation) * @returns whether the selection is allowed */ - selectionTransform?: (selection: ISelection, cellModel: ICellModel, causedByMove: boolean) => boolean; + selectionTransform?: ( + selection: ISelection, + cellModel: ICellModel, + causedByMove: boolean + ) => boolean; /** * Options for a copy-handle. @@ -98,7 +104,6 @@ export interface ISelectionOptions { * that you might drag to invoke a copy-like operation. */ copyHandle?: ICopyHandleOptions; - } /** @@ -108,7 +113,6 @@ export interface ISelectionOptions { * that you might drag to invoke a copy-like operation. */ export interface ICopyHandleOptions { - /** * Whether to show the copy handle on the primary selection. * Note that the copy-handle will only be shown when there is a single selection rectangle. @@ -121,7 +125,6 @@ export interface ICopyHandleOptions { * @param target the target cell range (range after dropping the handle) */ copyHandler?: (origin: ICellRange, target: ICellRange) => void; - } /** @@ -133,7 +136,10 @@ export const fillOptions = (options?: ISelectionOptions) => { options = {}; } - if (options.allowMultiSelection === undefined || options.allowMultiSelection === null) { + if ( + options.allowMultiSelection === undefined || + options.allowMultiSelection === null + ) { options.allowMultiSelection = true; } @@ -141,7 +147,10 @@ export const fillOptions = (options?: ISelectionOptions) => { options.copyHandle = {}; } - if (options.copyHandle.showCopyHandle === null || options.copyHandle.showCopyHandle === undefined) { + if ( + options.copyHandle.showCopyHandle === null || + options.copyHandle.showCopyHandle === undefined + ) { options.copyHandle.showCopyHandle = false; } diff --git a/src/selection/selection.ts b/src/selection/selection.ts index 26905e6..3e5e388 100644 --- a/src/selection/selection.ts +++ b/src/selection/selection.ts @@ -1,10 +1,9 @@ -import {ICellRange} from "../cell/range/cell-range"; +import { ICellRange } from '../cell'; /** * Representation of a selection. */ export interface ISelection { - /** * Range of the selection. */ @@ -14,7 +13,6 @@ export interface ISelection { * Initial position of the selection (if primary selection). */ initial?: IInitialPosition; - } /** @@ -23,7 +21,6 @@ export interface ISelection { * when navigating over merged cells. */ export interface IInitialPosition { - /** * Initial selected row. */ @@ -33,5 +30,4 @@ export interface IInitialPosition { * Initial selected column. */ column: number; - } diff --git a/src/table-engine.ts b/src/table-engine.ts index 481f9aa..2dd4f7e 100644 --- a/src/table-engine.ts +++ b/src/table-engine.ts @@ -1,24 +1,21 @@ -import {ICellModel} from "./cell/model/cell-model.interface"; -import {fillOptions, ITableEngineOptions} from "./options"; -import {ITableEngineRenderer} from "./renderer/renderer"; -import {RendererFactory} from "./renderer/renderer-factory"; -import {Observable, Subject} from "rxjs"; -import {ITableEngineEvent} from "./event/event"; -import {TableEngineEventType} from "./event/event-type"; -import {ICellRenderer} from "./renderer/cell/cell-renderer"; -import {ISelectionModel} from "./selection/model/selection-model.interface"; -import {SelectionModel} from "./selection/model/selection-model"; -import {IBorderModel} from "./border/model/border-model.interface"; -import {BorderModel} from "./border/model/border-model"; -import {IOverlayManager} from "./overlay/overlay-manager"; -import {IPoint} from "./util/point"; -import {IRectangle} from "./util/rect"; +import { ICellModel } from './cell'; +import { fillOptions, ITableEngineOptions } from './options'; +import { + ICellRenderer, + ITableEngineRenderer, + RendererFactory, +} from './renderer'; +import { Observable, Subject } from 'rxjs'; +import { ITableEngineEvent, TableEngineEventType } from './event'; +import { ISelectionModel, SelectionModel } from './selection'; +import { BorderModel, IBorderModel } from './border'; +import { IOverlayManager } from './overlay'; +import { IPoint, IRectangle } from './util'; /** * Entry point of the table engine library. */ export class TableEngine { - /** * HTML element used as container to the table engine. */ @@ -52,7 +49,8 @@ export class TableEngine { /** * Subject that will emit events when something meaningful happens in the table engine. */ - private readonly _events: Subject = new Subject(); + private readonly _events: Subject = + new Subject(); /** * Whether the engine is already initialized. @@ -66,7 +64,11 @@ export class TableEngine { * @param cellModel to use as cell model for the table engine * @param options used to modify the default behavior of the table engine */ - constructor(container: HTMLElement, cellModel: ICellModel, options?: ITableEngineOptions) { + constructor( + container: HTMLElement, + cellModel: ICellModel, + options?: ITableEngineOptions + ) { this._container = container; this._cellModel = cellModel; @@ -74,13 +76,18 @@ export class TableEngine { this._options = fillOptions(options); // Initialize selection model - this._selectionModel = new SelectionModel(this._cellModel, this._options); + this._selectionModel = new SelectionModel( + this._cellModel, + this._options + ); // Initialize border model this._borderModel = new BorderModel(this._cellModel, this._options); // Initialize renderer - this._renderer = RendererFactory.getRendererInstance(this._options.renderer.type); + this._renderer = RendererFactory.getRendererInstance( + this._options.renderer.type + ); } /** @@ -93,7 +100,7 @@ export class TableEngine { this._isInitialized = true; this._events.next({ - type: TableEngineEventType.RENDERER_READY + type: TableEngineEventType.RENDERER_READY, }); this.repaint(); @@ -209,7 +216,9 @@ export class TableEngine { */ public registerCellRenderer(renderer: ICellRenderer): void { if (this._isInitialized) { - throw new Error("Cannot register renderers when table-engine is already initialized"); + throw new Error( + 'Cannot register renderers when table-engine is already initialized' + ); } this._renderer.registerCellRenderer(renderer); @@ -224,5 +233,4 @@ export class TableEngine { this._events.complete(); } - } diff --git a/src/util/alignment/alignment-util.ts b/src/util/alignment/alignment-util.ts index 70f4b71..fa7a09c 100644 --- a/src/util/alignment/alignment-util.ts +++ b/src/util/alignment/alignment-util.ts @@ -1,23 +1,24 @@ -import {HorizontalAlignment} from "./horizontal-alignment"; -import {VerticalAlignment} from "./vertical-alignment"; +import { HorizontalAlignment } from './horizontal-alignment'; +import { VerticalAlignment } from './vertical-alignment'; /** * Utility providing methods helping with dealing with alignments. */ export class AlignmentUtil { - /** * Convert a horizontal alignment to its style string representation. * @param alignment to convert */ - public static horizontalAlignmentToStyleStr(alignment: HorizontalAlignment): string { + public static horizontalAlignmentToStyleStr( + alignment: HorizontalAlignment + ): string { switch (alignment) { case HorizontalAlignment.CENTER: - return "center"; + return 'center'; case HorizontalAlignment.LEFT: - return "left"; + return 'left'; case HorizontalAlignment.RIGHT: - return "right"; + return 'right'; } } @@ -25,15 +26,16 @@ export class AlignmentUtil { * Convert a vertical alignment to its style string representation. * @param alignment to convert */ - public static verticalAlignmentToStyleStr(alignment: VerticalAlignment): string { + public static verticalAlignmentToStyleStr( + alignment: VerticalAlignment + ): string { switch (alignment) { case VerticalAlignment.TOP: - return "top"; + return 'top'; case VerticalAlignment.MIDDLE: - return "middle"; + return 'middle'; case VerticalAlignment.BOTTOM: - return "bottom"; + return 'bottom'; } } - } diff --git a/src/util/alignment/horizontal-alignment.ts b/src/util/alignment/horizontal-alignment.ts index 22aaa12..62cd781 100644 --- a/src/util/alignment/horizontal-alignment.ts +++ b/src/util/alignment/horizontal-alignment.ts @@ -4,5 +4,5 @@ export enum HorizontalAlignment { LEFT, CENTER, - RIGHT + RIGHT, } diff --git a/src/util/alignment/index.ts b/src/util/alignment/index.ts new file mode 100644 index 0000000..84f4ad6 --- /dev/null +++ b/src/util/alignment/index.ts @@ -0,0 +1,3 @@ +export { AlignmentUtil } from './alignment-util'; +export { HorizontalAlignment } from './horizontal-alignment'; +export { VerticalAlignment } from './vertical-alignment'; diff --git a/src/util/alignment/vertical-alignment.ts b/src/util/alignment/vertical-alignment.ts index e854117..93434be 100644 --- a/src/util/alignment/vertical-alignment.ts +++ b/src/util/alignment/vertical-alignment.ts @@ -4,5 +4,5 @@ export enum VerticalAlignment { TOP, MIDDLE, - BOTTOM + BOTTOM, } diff --git a/src/util/clipboard/clipboard-util.ts b/src/util/clipboard/clipboard-util.ts index 51474f3..fbd5d9c 100644 --- a/src/util/clipboard/clipboard-util.ts +++ b/src/util/clipboard/clipboard-util.ts @@ -1,24 +1,22 @@ -import {ICellRange} from "../../cell/range/cell-range"; -import {ICellModel} from "../../cell/model/cell-model.interface"; -import {ICell} from "../../cell/cell"; +import { ICell, ICellModel, ICellRange } from '../../cell'; /** * Utility methods for dealing with the clipboard. */ export class ClipboardUtil { - /** * Set the clipboard to the passed content. * Content may be HTML formatted. * @param content to set to the clipboard */ public static setClipboardContent(content: string): void { - const clipboardDummyElement: HTMLElement = document.createElement("div"); + const clipboardDummyElement: HTMLElement = + document.createElement('div'); // Hide the dummy element from the user clipboardDummyElement.style.position = 'absolute'; clipboardDummyElement.style.left = '-9999px'; - clipboardDummyElement.style.userSelect = "text"; + clipboardDummyElement.style.userSelect = 'text'; // Add to DOM document.body.appendChild(clipboardDummyElement); @@ -30,7 +28,7 @@ export class ClipboardUtil { window.getSelection().selectAllChildren(clipboardDummyElement); // Copy the selected content to the clipboard - document.execCommand("copy"); + document.execCommand('copy'); // Remove dummy element again from DOM document.body.removeChild(clipboardDummyElement); @@ -47,7 +45,7 @@ export class ClipboardUtil { cellModel: ICellModel, copyValueMapper: (cell: ICell) => string ): string { - let result: string = ""; + let result: string = '
'; const alreadySeenCells: Set = new Set(); // Set to prevent from including merged cells multiple times for (let row = range.startRow; row <= range.endRow; row++) { @@ -56,16 +54,21 @@ export class ClipboardUtil { continue; } - result += ""; + result += ''; - for (let column = range.startColumn; column <= range.endColumn; column++) { - const isColumnHidden: boolean = cellModel.isColumnHidden(column); + for ( + let column = range.startColumn; + column <= range.endColumn; + column++ + ) { + const isColumnHidden: boolean = + cellModel.isColumnHidden(column); if (isColumnHidden) { continue; } const cell: ICell | null = cellModel.getCell(row, column); - let copyValue: string = ""; + let copyValue: string = ''; let rowSpan: number = 1; let columnSpan: number = 1; let ignore: boolean = false; @@ -76,7 +79,8 @@ export class ClipboardUtil { copyValue = copyValueMapper(cell); rowSpan = cell.range.endRow - cell.range.startRow + 1; - columnSpan = cell.range.endColumn - cell.range.startColumn + 1; + columnSpan = + cell.range.endColumn - cell.range.startColumn + 1; } else { ignore = true; } @@ -84,7 +88,7 @@ export class ClipboardUtil { // Add to table string if (!ignore) { - result += " 1) { result += ` rowspan="${rowSpan}"`; } @@ -95,12 +99,11 @@ export class ClipboardUtil { } } - result += ""; + result += ''; } - result += "
"; + result += ''; return result; } - } diff --git a/src/util/clipboard/index.ts b/src/util/clipboard/index.ts new file mode 100644 index 0000000..1a017b8 --- /dev/null +++ b/src/util/clipboard/index.ts @@ -0,0 +1 @@ +export { ClipboardUtil } from './clipboard-util'; diff --git a/src/util/color.ts b/src/util/color.ts index 1e1e660..d0d128e 100644 --- a/src/util/color.ts +++ b/src/util/color.ts @@ -2,7 +2,6 @@ * Representation of a color. */ export interface IColor { - /** * Red value in range [0; 255]. */ @@ -22,5 +21,4 @@ export interface IColor { * Alpha value in range [0.0; 1.0]. */ alpha: number; - } diff --git a/src/util/colors.ts b/src/util/colors.ts index b06ba95..251833d 100644 --- a/src/util/colors.ts +++ b/src/util/colors.ts @@ -1,24 +1,73 @@ -import {IColor} from "./color"; +import { IColor } from './color'; /** * Collection of frequently used colors. */ export class Colors { + public static readonly WHITE: IColor = { + red: 255, + green: 255, + blue: 255, + alpha: 1.0, + }; + public static readonly BLACK: IColor = { + red: 0, + green: 0, + blue: 0, + alpha: 1.0, + }; + public static readonly TRANSPARENT: IColor = { + red: 0, + green: 0, + blue: 0, + alpha: 0.0, + }; - public static readonly WHITE: IColor = {red: 255, green: 255, blue: 255, alpha: 1.0}; - public static readonly BLACK: IColor = {red: 0, green: 0, blue: 0, alpha: 1.0}; - public static readonly TRANSPARENT: IColor = {red: 0, green: 0, blue: 0, alpha: 0.0}; + public static readonly DARKGRAY: IColor = { + red: 70, + green: 70, + blue: 70, + alpha: 1.0, + }; + public static readonly GRAY: IColor = { + red: 127, + green: 127, + blue: 127, + alpha: 1.0, + }; + public static readonly LIGHTGRAY: IColor = { + red: 192, + green: 192, + blue: 192, + alpha: 1.0, + }; - public static readonly DARKGRAY: IColor = {red: 70, green: 70, blue: 70, alpha: 1.0}; - public static readonly GRAY: IColor = {red: 127, green: 127, blue: 127, alpha: 1.0}; - public static readonly LIGHTGRAY: IColor = {red: 192, green: 192, blue: 192, alpha: 1.0}; + public static readonly CORAL: IColor = { + red: 255, + green: 51, + blue: 102, + alpha: 1.0, + }; - public static readonly CORAL: IColor = {red: 255, green: 51, blue: 102, alpha: 1.0}; + public static readonly DARKCORAL: IColor = { + red: 225, + green: 21, + blue: 72, + alpha: 1.0, + }; - public static readonly DARKCORAL: IColor = {red: 225, green: 21, blue: 72, alpha: 1.0}; - - public static readonly ORANGE: IColor = {red: 255, green: 165, blue: 0, alpha: 1.0}; - public static readonly BRIGHT_ORANGE: IColor = {red: 255, green: 190, blue: 0, alpha: 1.0}; + public static readonly ORANGE: IColor = { + red: 255, + green: 165, + blue: 0, + alpha: 1.0, + }; + public static readonly BRIGHT_ORANGE: IColor = { + red: 255, + green: 190, + blue: 0, + alpha: 1.0, + }; /** * Convert the passed color to a CSS color style representation. @@ -27,5 +76,4 @@ export class Colors { public static toStyleStr(color: IColor): string { return `rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha})`; } - } diff --git a/src/util/index.ts b/src/util/index.ts new file mode 100644 index 0000000..eef6b4c --- /dev/null +++ b/src/util/index.ts @@ -0,0 +1,8 @@ +export { ISize } from './size'; +export { IRectangle } from './rect'; +export { IPoint } from './point'; +export { IColor } from './color'; +export { Colors } from './colors'; +export * from './alignment'; +export * from './clipboard'; +export * from './notification'; diff --git a/src/util/notification/impl/copy-performance-warning.ts b/src/util/notification/impl/copy-performance-warning.ts index ff3f9ce..79574f4 100644 --- a/src/util/notification/impl/copy-performance-warning.ts +++ b/src/util/notification/impl/copy-performance-warning.ts @@ -1,9 +1,8 @@ -import {INotification} from "../notification"; -import {NotificationType} from "../notification-type"; -import {NotificationIDs} from "../notification-ids"; +import { INotification } from '../notification'; +import { NotificationType } from '../notification-type'; +import { NotificationIDs } from '../notification-ids'; export class CopyPerformanceWarningNotification implements INotification { - /** * Limit of cells to copy. */ @@ -43,5 +42,4 @@ export class CopyPerformanceWarningNotification implements INotification { set callback(value: (copyAnyway: boolean) => void) { this._callback = value; } - } diff --git a/src/util/notification/impl/copy.ts b/src/util/notification/impl/copy.ts index 21cf67c..97b44b8 100644 --- a/src/util/notification/impl/copy.ts +++ b/src/util/notification/impl/copy.ts @@ -1,12 +1,11 @@ -import {INotification} from "../notification"; -import {NotificationType} from "../notification-type"; -import {NotificationIDs} from "../notification-ids"; +import { INotification } from '../notification'; +import { NotificationType } from '../notification-type'; +import { NotificationIDs } from '../notification-ids'; /** * Copy notification signaling a successful copy operation. */ export class CopyNotification implements INotification { - get id(): string { return NotificationIDs.COPY; } @@ -16,7 +15,6 @@ export class CopyNotification implements INotification { } get message(): string { - return "Copied selected cells."; + return 'Copied selected cells.'; } - } diff --git a/src/util/notification/impl/index.ts b/src/util/notification/impl/index.ts new file mode 100644 index 0000000..b34c0ab --- /dev/null +++ b/src/util/notification/impl/index.ts @@ -0,0 +1,2 @@ +export { CopyNotification } from './copy'; +export { CopyPerformanceWarningNotification } from './copy-performance-warning'; diff --git a/src/util/notification/index.ts b/src/util/notification/index.ts new file mode 100644 index 0000000..43eb804 --- /dev/null +++ b/src/util/notification/index.ts @@ -0,0 +1,5 @@ +export { INotification } from './notification'; +export { NotificationIDs } from './notification-ids'; +export { INotificationService } from './notification-service'; +export { NotificationType } from './notification-type'; +export * from './impl'; diff --git a/src/util/notification/notification-ids.ts b/src/util/notification/notification-ids.ts index 2adf45f..0e40fdb 100644 --- a/src/util/notification/notification-ids.ts +++ b/src/util/notification/notification-ids.ts @@ -2,16 +2,15 @@ * Available notification IDs. */ export class NotificationIDs { - /** * Warning that is emitted, when trying to copy more cells than configured using the * IViewOptions.maxCellCountToCopy option. */ - public static readonly COPY_PERFORMANCE_WARNING: string = "copy.performance-warning"; + public static readonly COPY_PERFORMANCE_WARNING: string = + 'copy.performance-warning'; /** * Info about a successful copy operation. */ - public static readonly COPY: string = "copy.success"; - + public static readonly COPY: string = 'copy.success'; } diff --git a/src/util/notification/notification-service.ts b/src/util/notification/notification-service.ts index 61fd9f5..f77b8f9 100644 --- a/src/util/notification/notification-service.ts +++ b/src/util/notification/notification-service.ts @@ -1,14 +1,12 @@ -import {INotification} from "./notification"; +import { INotification } from './notification'; /** * Service receiving notifications from the table (infos and warnings). */ export interface INotificationService { - /** * Called on a notification. * @param notification that has been received */ notify(notification: INotification): void; - } diff --git a/src/util/notification/notification-type.ts b/src/util/notification/notification-type.ts index 22d4734..9c85780 100644 --- a/src/util/notification/notification-type.ts +++ b/src/util/notification/notification-type.ts @@ -2,9 +2,7 @@ * Available notification types. */ export enum NotificationType { - INFO, WARNING, - ERROR - + ERROR, } diff --git a/src/util/notification/notification.ts b/src/util/notification/notification.ts index dd67847..79ab2f6 100644 --- a/src/util/notification/notification.ts +++ b/src/util/notification/notification.ts @@ -1,10 +1,9 @@ -import {NotificationType} from "./notification-type"; +import { NotificationType } from './notification-type'; /** * A notification that may carry information of warnings to the user. */ export interface INotification { - /** * Type of the notification. */ @@ -19,5 +18,4 @@ export interface INotification { * Message of the notification. */ message: string; - } diff --git a/src/util/point.ts b/src/util/point.ts index ef688b9..6e7e41c 100644 --- a/src/util/point.ts +++ b/src/util/point.ts @@ -2,7 +2,6 @@ * Representation of a point. */ export interface IPoint { - /** * X-coordinate. */ @@ -12,5 +11,4 @@ export interface IPoint { * Y-coordinate. */ y: number; - } diff --git a/src/util/rect.ts b/src/util/rect.ts index 872e969..ee59344 100644 --- a/src/util/rect.ts +++ b/src/util/rect.ts @@ -2,7 +2,6 @@ * Representation of a rectangle. */ export interface IRectangle { - /** * Left offset of the rectangle. */ @@ -22,5 +21,4 @@ export interface IRectangle { * Height of the rectangle. */ height: number; - } diff --git a/webpack.config.js b/webpack.config.js index 0ee1848..147dca2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,22 +1,33 @@ -const path = require("path"); +const path = require('path'); + +const CircularDependencyPlugin = require('circular-dependency-plugin'); module.exports = { - mode: "production", - entry: "./src/table-engine.ts", + mode: 'production', + entry: './src/table-engine.ts', module: { rules: [ { test: /\.tsx?$/, - use: "ts-loader", + use: 'ts-loader', exclude: /node_modules/, }, ], }, resolve: { - extensions: [".tsx", ".ts", ".js"], + extensions: ['.tsx', '.ts', '.js'], }, output: { - filename: "bundle.js", - path: path.resolve(__dirname, "lib/bundled"), + filename: 'bundle.js', + path: path.resolve(__dirname, 'lib/bundled'), }, + plugins: [ + new CircularDependencyPlugin({ + exclude: /a\.js|node_modules/, + include: /src/, + failOnError: true, + allowAsyncCycles: false, + cwd: process.cwd(), + }), + ], }; diff --git a/yarn.lock b/yarn.lock index b3a5e3b..25115fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1105,6 +1105,11 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== +circular-dependency-plugin@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" + integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" @@ -2473,6 +2478,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== +prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + pretty-format@^26.0.0, pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" From be1f52fc36ca2f247579e0b16a9e9c150269748e Mon Sep 17 00:00:00 2001 From: bennyboer Date: Fri, 22 Jul 2022 17:35:26 +0200 Subject: [PATCH 2/5] adding correct entry for webpack --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 147dca2..3cd44f4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,7 +4,7 @@ const CircularDependencyPlugin = require('circular-dependency-plugin'); module.exports = { mode: 'production', - entry: './src/table-engine.ts', + entry: './src/index.ts', module: { rules: [ { From 66302c6f243a86ff72a683da259f5cd0c6d3cf31 Mon Sep 17 00:00:00 2001 From: bennyboer Date: Fri, 22 Jul 2022 20:12:10 +0200 Subject: [PATCH 3/5] Building bundle --- package.json | 1 + src/renderer/canvas/canvas-renderer.ts | 30 +++++++++++++++----------- webpack.config.js | 29 ++++++++++++++++--------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 6f7ddad..08cbc07 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "lib/**/*" ], "main": "lib/bundled/bundle.js", + "type": "module", "scripts": { "build": "tsc", "test": "jest --config jest.config.js", diff --git a/src/renderer/canvas/canvas-renderer.ts b/src/renderer/canvas/canvas-renderer.ts index c53ce93..601d640 100644 --- a/src/renderer/canvas/canvas-renderer.ts +++ b/src/renderer/canvas/canvas-renderer.ts @@ -3746,18 +3746,24 @@ export class CanvasRenderer implements ITableEngineRenderer { oldCells.nonFixedCells.cellRange, newCells.nonFixedCells.cellRange ); - this._cleanupCellViewportCachesForOverlappingCellRanges( - oldCells.fixedRowCells.cellRange, - newCells.fixedRowCells.cellRange - ); - this._cleanupCellViewportCachesForOverlappingCellRanges( - oldCells.fixedColumnCells.cellRange, - newCells.fixedColumnCells.cellRange - ); - this._cleanupCellViewportCachesForOverlappingCellRanges( - oldCells.fixedCornerCells.cellRange, - newCells.fixedCornerCells.cellRange - ); + if (!!oldCells.fixedRowCells && !!newCells.fixedRowCells) { + this._cleanupCellViewportCachesForOverlappingCellRanges( + oldCells.fixedRowCells.cellRange, + newCells.fixedRowCells.cellRange + ); + } + if (!!oldCells.fixedColumnCells && !!newCells.fixedColumnCells) { + this._cleanupCellViewportCachesForOverlappingCellRanges( + oldCells.fixedColumnCells.cellRange, + newCells.fixedColumnCells.cellRange + ); + } + if (!!oldCells.fixedCornerCells && !!newCells.fixedCornerCells) { + this._cleanupCellViewportCachesForOverlappingCellRanges( + oldCells.fixedCornerCells.cellRange, + newCells.fixedCornerCells.cellRange + ); + } } /** diff --git a/webpack.config.js b/webpack.config.js index 3cd44f4..48d8012 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,10 +1,25 @@ -const path = require('path'); +import path from 'path'; +import { fileURLToPath } from 'url'; +import CircularDependencyPlugin from 'circular-dependency-plugin'; -const CircularDependencyPlugin = require('circular-dependency-plugin'); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); -module.exports = { +export default { mode: 'production', entry: './src/index.ts', + experiments: { + outputModule: true, + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'lib/bundled'), + library: { + type: 'module', + }, + }, module: { rules: [ { @@ -14,13 +29,6 @@ module.exports = { }, ], }, - resolve: { - extensions: ['.tsx', '.ts', '.js'], - }, - output: { - filename: 'bundle.js', - path: path.resolve(__dirname, 'lib/bundled'), - }, plugins: [ new CircularDependencyPlugin({ exclude: /a\.js|node_modules/, @@ -30,4 +38,5 @@ module.exports = { cwd: process.cwd(), }), ], + devtool: 'source-map', }; From 87cd5b5470a950fe0e4f1b2dd17037cebea2f6d0 Mon Sep 17 00:00:00 2001 From: bennyboer Date: Fri, 22 Jul 2022 20:20:53 +0200 Subject: [PATCH 4/5] Jest ESM --- .github/workflows/build.yml | 4 ++-- .github/workflows/npm-deployment.yml | 3 ++- jest.config.js | 4 ++-- package.json | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b8d9462..a801532 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: yarn install --frozen-lockfile - name: Build library run: | - npm run build + yarn build - name: Test library run: | - npm run test + yarn test diff --git a/.github/workflows/npm-deployment.yml b/.github/workflows/npm-deployment.yml index 64fce2c..2989ac3 100644 --- a/.github/workflows/npm-deployment.yml +++ b/.github/workflows/npm-deployment.yml @@ -17,7 +17,8 @@ jobs: yarn install --frozen-lockfile - name: Build library run: | - npm run build + yarn build + yarn bundle - name: Publish library run: npm publish env: diff --git a/jest.config.js b/jest.config.js index e0677fd..907e2d6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,5 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', - testPathIgnorePatterns: ["lib", "example"] + testPathIgnorePatterns: ['lib', 'example'], }; diff --git a/package.json b/package.json index 08cbc07..02b64fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "table-engine", - "version": "0.1.3", + "version": "0.1.4", "description": "Library to visualize huge tables in web environments", "files": [ "lib/**/*" From afeef7e069171cf23d809d6c68d0fde2baa3056f Mon Sep 17 00:00:00 2001 From: bennyboer Date: Fri, 22 Jul 2022 20:24:33 +0200 Subject: [PATCH 5/5] Added usage md scaffold --- README.md | 4 ++++ docs/usage.md | 1 + 2 files changed, 5 insertions(+) create mode 100644 docs/usage.md diff --git a/README.md b/README.md index d29c586..61a6c18 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ We aim to provide a high-performance table library that may display nearly infin really noticing. Normal HTML table elements get pretty laggy when getting large and are thus not always an option. This library builds around HTML5 canvas to draw the table from scratch, thus achieving a smooth user experience. +## Installation and usage + +Checkout the usage and installation file [here](docs/usage.md). + ## Getting started First and foremost install NodeJS (Check one of the workflow files under `.github/workflows` for the current version we use). diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..1333ed7 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1 @@ +TODO