diff options
| author | Josh Kingsley <josh@joshkingsley.me> | 2025-11-23 19:27:57 +0200 |
|---|---|---|
| committer | Josh Kingsley <josh@joshkingsley.me> | 2025-11-23 19:27:57 +0200 |
| commit | 602145c956bb594ca0d0e10601cc4ad1a71cf70d (patch) | |
| tree | d9f9980bd2054cff5819d01379f5c1c55f8eb66d /web/src/components/grid/index.ts | |
| parent | c2a6efb1b761014a90d90373cad47a14054af40b (diff) | |
feat: integrate web and doc packages
Diffstat (limited to 'web/src/components/grid/index.ts')
| -rw-r--r-- | web/src/components/grid/index.ts | 276 |
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; - } -} |
