summaryrefslogtreecommitdiff
path: root/web/src/components/grid/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'web/src/components/grid/index.ts')
-rw-r--r--web/src/components/grid/index.ts276
1 files changed, 0 insertions, 276 deletions
diff --git a/web/src/components/grid/index.ts b/web/src/components/grid/index.ts
deleted file mode 100644
index 3189409..0000000
--- a/web/src/components/grid/index.ts
+++ /dev/null
@@ -1,276 +0,0 @@
-import NotiveElement, { customElement, eventHandler } from "../../element";
-import h from "../../html";
-import { CellRef } from "../../types";
-import cellAtCoord from "./cellAtCoord";
-import drawGrid, { GridStyles } from "./drawGrid";
-import drawSelection, { SelectionStyles } from "./drawSelection";
-import "./index.css";
-import { getRenderedCell, RenderedGrid } from "./renderGrid";
-import { extendSelection, GridSelection } from "./selection";
-
-@customElement("ntv-grid")
-export class NotiveGridElement extends NotiveElement {
- #internals: ElementInternals = this.attachInternals();
-
- #grid?: RenderedGrid;
-
- get grid(): RenderedGrid | undefined {
- return this.#grid;
- }
-
- set grid(grid: RenderedGrid | undefined) {
- this.#grid = grid;
- this.draw();
- }
-
- #selection?: GridSelection;
-
- get selection() {
- return this.#selection;
- }
-
- set selection(selection: GridSelection | undefined) {
- this.#selection = selection;
- this.drawSelection();
- }
-
- @eventHandler("ntv:grid:selectionchange")
- ongridselectionchange?: (event: GridSelectionChangeEvent) => any;
-
- @eventHandler("ntv:grid:cellchange")
- oncellchange?: (event: GridCellChangeEvent) => any;
-
- canvas: HTMLCanvasElement = h.canvas({
- onmousedown: (event) => {
- if (event.button !== 0) return;
- if (!this.grid) return;
- const cellRef = this.#mouseEventCellRef(event);
- if (!cellRef) return;
- this.startSelecting(cellRef);
- },
- ondblclick: (event) => {
- if (!this.grid) return;
- const cellRef = this.#mouseEventCellRef(event);
- if (!cellRef) return;
- this.startEditing(cellRef);
- },
- });
-
- selectionCanvas: HTMLCanvasElement = h.canvas({
- dataset: { selection: "true" },
- });
-
- connectedCallback() {
- this.append(this.canvas, this.selectionCanvas);
- this.draw();
- this.drawSelection();
- }
-
- draw() {
- if (!this.grid) return;
-
- const ctx = this.canvas.getContext("2d");
-
- if (!ctx) throw new Error("Unable to get canvas context");
-
- this.canvas.setAttribute("width", this.grid.rect.width + "px");
- this.canvas.setAttribute("height", this.grid.rect.height + "px");
-
- drawGrid(ctx, this.getGridStyles(), this.grid);
- }
-
- drawSelection() {
- if (!this.grid) return;
-
- const ctx = this.selectionCanvas.getContext("2d");
-
- if (!ctx) throw new Error("Unable to get canvas context");
-
- this.selectionCanvas.setAttribute("width", this.grid.rect.width + "px");
- this.selectionCanvas.setAttribute("height", this.grid.rect.height + "px");
-
- drawSelection(
- ctx,
- this.getSelectionStyles(),
- this.grid,
- this.#pendingSelection ?? this.selection,
- {
- pending: !!this.#pendingSelection,
- },
- );
- }
-
- getGridStyles(): GridStyles {
- const style = window.getComputedStyle(this);
- const val = (k: string) => style.getPropertyValue(k);
-
- return {
- bgFill: val("--grid-bg-fill"),
- borderStroke: val("--grid-border-stroke"),
- cellStroke: val("--grid-cell-stroke"),
- cellValueFont: val("font"),
- cellValueLineHeight: val("line-height"),
- };
- }
-
- getSelectionStyles(): SelectionStyles {
- const style = window.getComputedStyle(this);
- const val = (k: string) => style.getPropertyValue(k);
-
- return {
- activeCellStroke: val("--grid-active-cell-stroke"),
- selectionRangeFill: val("--grid-selection-range-fill"),
- selectionRangeStroke: val("--grid-selection-range-stroke"),
- };
- }
-
- #pendingSelection?: GridSelection;
- #selectionAbortController?: AbortController;
-
- startSelecting(cellRef: CellRef) {
- if (!this.grid || this.#pendingSelection) return;
-
- this.#internals.states.add("selecting");
-
- this.#selectionAbortController = new AbortController();
- const { signal } = this.#selectionAbortController;
-
- window.addEventListener(
- "mousemove",
- (event) => {
- const cellRef = this.#mouseEventCellRef(event);
- if (!cellRef) return;
- this.#pendingSelection = extendSelection(
- this.#pendingSelection,
- cellRef,
- );
- this.drawSelection();
- },
- { signal },
- );
-
- window.addEventListener("mouseup", () => this.#finishSelecting(), {
- signal,
- });
-
- window.addEventListener(
- "keydown",
- (event) => {
- event.preventDefault();
- if (event.key === "Escape") {
- this.#pendingSelection = undefined;
- this.#finishSelecting();
- }
- },
- { signal },
- );
-
- this.#pendingSelection = extendSelection(undefined, cellRef);
- this.drawSelection();
- }
-
- #finishSelecting() {
- this.#selectionAbortController?.abort();
- this.#selectionAbortController = undefined;
- this.#internals.states.delete("selecting");
- if (this.#pendingSelection) {
- this.dispatchEvent(new GridSelectionChangeEvent(this.#pendingSelection));
- }
- this.#pendingSelection = undefined;
- this.drawSelection();
- }
-
- #mouseEventCellRef(
- this: NotiveGridElement,
- event: MouseEvent,
- ): CellRef | undefined {
- if (!this.grid) return;
- const clientRect = this.canvas.getBoundingClientRect();
- const x = event.x - clientRect.x;
- const y = event.y - clientRect.y;
- return cellAtCoord(this.grid, x, y);
- }
-
- #editingCellRef?: CellRef;
-
- #editInput: HTMLInputElement = h.input({
- dataset: { edit: "true" },
- onblur: () => this.#finishEditing(),
- onkeydown: (event) => {
- switch (event.key) {
- case "Enter":
- this.#finishEditing();
- break;
-
- case "Escape":
- this.#cancelEditing();
- break;
- }
- },
- });
-
- startEditing(cellRef: CellRef) {
- if (!this.grid) return;
-
- const cell = getRenderedCell(this.grid, cellRef);
-
- if (!cell) return;
-
- this.#editingCellRef = cellRef;
-
- this.append(this.#editInput);
-
- this.#editInput.value = cell.value || "";
-
- Object.assign(this.#editInput.style, {
- left: cell.rect.topLeft.x + 2 + "px",
- top: cell.rect.topLeft.y + 2 + "px",
- width: cell.rect.width - 3 + "px",
- height: cell.rect.height - 3 + "px",
- });
-
- this.#editInput.focus();
- }
-
- #cancelEditing() {
- this.#editInput.remove();
- }
-
- #finishEditing() {
- this.#editInput.remove();
-
- if (!this.grid || !this.#editingCellRef) return;
-
- this.dispatchEvent(
- new GridCellChangeEvent(this.#editingCellRef, this.#editInput.value),
- );
- }
-}
-
-export default NotiveGridElement.makeFactory();
-
-export class GridSelectionChangeEvent extends Event {
- static readonly TYPE = "ntv:grid:selectionchange";
-
- constructor(public selection: GridSelection) {
- super(GridSelectionChangeEvent.TYPE);
- }
-}
-
-export class GridCellChangeEvent extends Event {
- static readonly TYPE = "ntv:grid:cellchange";
-
- constructor(
- public cellRef: CellRef,
- public value: string | undefined,
- ) {
- super(GridCellChangeEvent.TYPE);
- }
-}
-
-declare global {
- interface HTMLElementEventMap {
- [GridSelectionChangeEvent.TYPE]: GridSelectionChangeEvent;
- [GridCellChangeEvent.TYPE]: GridCellChangeEvent;
- }
-}