From 7ef8366bfc43775bf26e71e77bddf31af829dfde Mon Sep 17 00:00:00 2001 From: Josh Kingsley Date: Wed, 29 Oct 2025 18:26:41 +0200 Subject: refactor(web): add decorators --- web/src/components/app/index.ts | 9 ++-- web/src/components/grid/index.css | 4 +- web/src/components/grid/index.ts | 99 ++++++++++++++++++------------------- web/src/components/toolbar/index.ts | 10 ++-- 4 files changed, 60 insertions(+), 62 deletions(-) (limited to 'web/src/components') diff --git a/web/src/components/app/index.ts b/web/src/components/app/index.ts index 195011f..c30249f 100644 --- a/web/src/components/app/index.ts +++ b/web/src/components/app/index.ts @@ -1,4 +1,5 @@ import defaultDoc from "../../defaultDoc"; +import NotiveElement, { customElement } from "../../element"; import { Selection } from "../../selection"; import { Doc } from "../../types"; import ntvGrid, { NotiveGridElement } from "../grid"; @@ -6,7 +7,8 @@ import renderGrid from "../grid/renderGrid"; import ntvToolbar from "../toolbar"; import "./index.css"; -export class NotiveAppElement extends HTMLElement { +@customElement("ntv-app") +export class NotiveAppElement extends NotiveElement { doc: Doc = defaultDoc(); #selection?: Selection; @@ -37,10 +39,11 @@ export class NotiveAppElement extends HTMLElement { ongridselectionchange: (event) => { this.selection = event.selection; }, + oncellchange: (event) => { + console.log(event); + }, }), ), ); } } - -customElements.define("ntv-app", NotiveAppElement); diff --git a/web/src/components/grid/index.css b/web/src/components/grid/index.css index 64153ed..c29f55d 100644 --- a/web/src/components/grid/index.css +++ b/web/src/components/grid/index.css @@ -33,7 +33,7 @@ display: none; } - ntv-grid input[data-edit-cell] { + ntv-grid input[data-edit] { position: absolute; vertical-align: baseline; background: var(--color-neutral-800); @@ -43,7 +43,7 @@ text-align: center; } - ntv-grid input[data-edit-cell]:focus-visible { + ntv-grid input[data-edit]:focus-visible { outline: none; } } diff --git a/web/src/components/grid/index.ts b/web/src/components/grid/index.ts index 2c00eb8..6c7f735 100644 --- a/web/src/components/grid/index.ts +++ b/web/src/components/grid/index.ts @@ -1,4 +1,5 @@ -import h, { type CreateElement } from "../../html"; +import NotiveElement, { customElement, eventHandler } from "../../element"; +import h from "../../html"; import { ActiveCellSelection, Selection } from "../../selection"; import { CellRef } from "../../types"; import cellAtCoord from "./cellAtCoord"; @@ -7,7 +8,8 @@ import drawSelection, { SelectionStyles } from "./drawSelection"; import "./index.css"; import { getRenderedCell, RenderedGrid } from "./renderGrid"; -export class NotiveGridElement extends HTMLElement { +@customElement("ntv-grid") +export class NotiveGridElement extends NotiveElement { #internals: ElementInternals = this.attachInternals(); grid?: RenderedGrid; @@ -23,28 +25,11 @@ export class NotiveGridElement extends HTMLElement { this.drawSelection(); } - #ongridselectionchange?: ((event: GridSelectionEvent) => any) | undefined; + @eventHandler("ntv:grid:selectionchange") + ongridselectionchange?: (event: GridSelectionEvent) => any; - get ongridselectionchange() { - return this.#ongridselectionchange; - } - - set ongridselectionchange( - handler: ((event: GridSelectionEvent) => any) | undefined, - ) { - if (this.#ongridselectionchange) { - this.removeEventListener( - "ntv:grid:selectionchange", - this.#ongridselectionchange, - ); - } - - this.#ongridselectionchange = handler; - - if (handler) { - this.addEventListener("ntv:grid:selectionchange", handler); - } - } + @eventHandler("ntv:grid:cellchange") + oncellchange?: (event: GridCellChangeEvent) => any; canvas: HTMLCanvasElement = h.canvas({ onmousedown: (event) => { @@ -53,6 +38,12 @@ export class NotiveGridElement extends HTMLElement { 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({ @@ -188,11 +179,9 @@ export class NotiveGridElement extends HTMLElement { #editingCellRef?: CellRef; - #editInputEl: HTMLInputElement = h.input({ - dataset: { editCell: "true" }, - onblur: () => { - this.#finishEditing(); - }, + #editInput: HTMLInputElement = h.input({ + dataset: { edit: "true" }, + onblur: () => this.#finishEditing(), onkeydown: (event) => { switch (event.key) { case "Enter": @@ -206,50 +195,45 @@ export class NotiveGridElement extends HTMLElement { }, }); - #canvasDoubleClickCallback(this: NotiveGridElement, event: MouseEvent) { + startEditing(cellRef: CellRef) { if (!this.grid) return; - const cellRef = this.#mouseEventCellRef(event); - - if (!cellRef) return; - const cell = getRenderedCell(this.grid, cellRef); if (!cell) return; this.#editingCellRef = cellRef; - this.append(this.#editInputEl); + this.append(this.#editInput); - this.#editInputEl.value = cell.value || ""; - this.#editInputEl.style.left = cell.rect.topLeft.x + 2 + "px"; - this.#editInputEl.style.top = cell.rect.topLeft.y + 2 + "px"; - this.#editInputEl.style.width = cell.rect.width - 3 + "px"; - this.#editInputEl.style.height = cell.rect.height - 3 + "px"; - this.#editInputEl.focus(); + 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.#editInputEl.remove(); + this.#editInput.remove(); } #finishEditing() { - this.#editInputEl.remove(); + this.#editInput.remove(); - if (!this.grid) return; + if (!this.grid || !this.#editingCellRef) return; - window.notive.setCellValue( - this.grid.id, - this.#editingCellRef!, - this.#editInputEl.value, + this.dispatchEvent( + new GridCellChangeEvent(this.#editingCellRef, this.#editInput.value), ); } } -customElements.define("ntv-grid", NotiveGridElement); - -export default ((...args: any[]): NotiveGridElement => - (h as any)["ntv-grid"](...args)) as CreateElement; +export default NotiveGridElement.makeFactory(); export class GridSelectionEvent extends Event { selection: Selection; @@ -260,8 +244,21 @@ export class GridSelectionEvent extends Event { } } +export class GridCellChangeEvent extends Event { + cellRef: CellRef; + value?: string; + + constructor(cellRef: CellRef, value: string | undefined) { + super("ntv:grid:cellchange"); + + this.cellRef = cellRef; + this.value = value; + } +} + declare global { interface HTMLElementEventMap { "ntv:grid:selectionchange": GridSelectionEvent; + "ntv:grid:cellchange": GridCellChangeEvent; } } diff --git a/web/src/components/toolbar/index.ts b/web/src/components/toolbar/index.ts index 1400174..84f6a65 100644 --- a/web/src/components/toolbar/index.ts +++ b/web/src/components/toolbar/index.ts @@ -1,6 +1,6 @@ +import NotiveElement, { customElement } from "../../element"; import h, { CreateElement } from "../../html"; import { ActiveCellSelection, RangeSelection } from "../../selection"; -import { RenderedGrid } from "../grid/renderGrid"; import "./index.css"; function getSelectedSubdivisionsCount(): number | undefined { @@ -23,7 +23,8 @@ function getSelectedSubdivisionsCount(): number | undefined { return Math.min(...selectedCells.map((cells) => cells.length)); } -class NotiveToolbarElement extends HTMLElement { +@customElement("ntv-toolbar") +class NotiveToolbarElement extends NotiveElement { #subdivisionsInputEl: HTMLInputElement = h.input({ title: "Subdivisions", placeholder: "-", @@ -127,7 +128,4 @@ class NotiveToolbarElement extends HTMLElement { } } -customElements.define("ntv-toolbar", NotiveToolbarElement); - -export default ((...args: any[]): NotiveToolbarElement => - (h as any)["ntv-toolbar"](...args)) as CreateElement; +export default NotiveToolbarElement.makeFactory(); -- cgit v1.2.3