From 715996aceb4d4dc96410464f60727b98a289be08 Mon Sep 17 00:00:00 2001 From: Josh Kingsley Date: Thu, 30 Oct 2025 13:25:02 +0200 Subject: refactor(web): move selection code --- web/src/components/grid/drawSelection.ts | 6 +-- web/src/components/grid/index.ts | 87 +++++++++++++++++--------------- web/src/components/grid/selection.ts | 23 +++++++++ 3 files changed, 71 insertions(+), 45 deletions(-) create mode 100644 web/src/components/grid/selection.ts (limited to 'web/src/components/grid') diff --git a/web/src/components/grid/drawSelection.ts b/web/src/components/grid/drawSelection.ts index e1024a8..1b8c2ed 100644 --- a/web/src/components/grid/drawSelection.ts +++ b/web/src/components/grid/drawSelection.ts @@ -1,7 +1,7 @@ -import { RangeSelection, Selection } from "../../selection"; import { CellRef } from "../../types"; import excursion from "./excursion"; import { getRenderedCell, RenderedCell, RenderedGrid } from "./renderGrid"; +import { GridSelection } from "./selection"; export interface SelectionStyles { activeCellStroke: string; @@ -76,7 +76,7 @@ export default function drawSelection( ctx: CanvasRenderingContext2D, styles: SelectionStyles, grid: RenderedGrid, - selection: Selection | undefined, + selection: GridSelection | undefined, { pending }: { pending: boolean }, ) { ctx.clearRect(0, 0, grid.rect.width, grid.rect.height); @@ -87,7 +87,7 @@ export default function drawSelection( if (!activeCell) return; - if (selection instanceof RangeSelection) { + if (selection.range) { drawCellRange(ctx, styles, grid, selection.range[0], selection.range[1], { stroke: !pending, }); diff --git a/web/src/components/grid/index.ts b/web/src/components/grid/index.ts index 6c7f735..78bb14e 100644 --- a/web/src/components/grid/index.ts +++ b/web/src/components/grid/index.ts @@ -1,12 +1,12 @@ import NotiveElement, { customElement, eventHandler } from "../../element"; import h from "../../html"; -import { ActiveCellSelection, Selection } from "../../selection"; 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 { @@ -14,25 +14,26 @@ export class NotiveGridElement extends NotiveElement { grid?: RenderedGrid; - #selection?: Selection; + #selection?: GridSelection; get selection() { return this.#selection; } - set selection(selection: Selection | undefined) { + set selection(selection: GridSelection | undefined) { this.#selection = selection; this.drawSelection(); } @eventHandler("ntv:grid:selectionchange") - ongridselectionchange?: (event: GridSelectionEvent) => any; + 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; @@ -114,7 +115,7 @@ export class NotiveGridElement extends NotiveElement { }; } - #pendingSelection?: Selection; + #pendingSelection?: GridSelection; #selectionAbortController?: AbortController; startSelecting(cellRef: CellRef) { @@ -123,12 +124,6 @@ export class NotiveGridElement extends NotiveElement { this.#internals.states.add("selecting"); this.#selectionAbortController = new AbortController(); - - this.#selectionAbortController.signal.addEventListener("abort", () => { - this.#internals.states.delete("selecting"); - this.#selectionAbortController = undefined; - }); - const { signal } = this.#selectionAbortController; window.addEventListener( @@ -136,33 +131,43 @@ export class NotiveGridElement extends NotiveElement { (event) => { const cellRef = this.#mouseEventCellRef(event); if (!cellRef) return; - this.#pendingSelection = this.#pendingSelection?.extend(cellRef); + this.#pendingSelection = extendSelection( + this.#pendingSelection, + cellRef, + ); this.drawSelection(); }, { signal }, ); - window.addEventListener( - "mouseup", - () => { - this.#selectionAbortController?.abort(); - - if (!this.#pendingSelection) return; - - this.dispatchEvent( - new GridSelectionEvent( - "ntv:grid:selectionchange", - this.#pendingSelection, - ), - ); + window.addEventListener("mouseup", () => this.#finishSelecting(), { + signal, + }); - this.#pendingSelection = undefined; - this.drawSelection(); + window.addEventListener( + "keydown", + (event) => { + event.preventDefault(); + if (event.key === "Escape") { + this.#pendingSelection = undefined; + this.#finishSelecting(); + } }, { signal }, ); - this.#pendingSelection = new ActiveCellSelection(this.grid.id, cellRef); + 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(); } @@ -235,30 +240,28 @@ export class NotiveGridElement extends NotiveElement { export default NotiveGridElement.makeFactory(); -export class GridSelectionEvent extends Event { - selection: Selection; +export class GridSelectionChangeEvent extends Event { + static readonly TYPE = "ntv:grid:selectionchange"; - constructor(type: string, selection: Selection) { - super(type); - this.selection = selection; + constructor(public selection: GridSelection) { + super(GridSelectionChangeEvent.TYPE); } } export class GridCellChangeEvent extends Event { - cellRef: CellRef; - value?: string; - - constructor(cellRef: CellRef, value: string | undefined) { - super("ntv:grid:cellchange"); + static readonly TYPE = "ntv:grid:cellchange"; - this.cellRef = cellRef; - this.value = value; + constructor( + public cellRef: CellRef, + public value: string | undefined, + ) { + super(GridCellChangeEvent.TYPE); } } declare global { interface HTMLElementEventMap { - "ntv:grid:selectionchange": GridSelectionEvent; - "ntv:grid:cellchange": GridCellChangeEvent; + [GridSelectionChangeEvent.TYPE]: GridSelectionChangeEvent; + [GridCellChangeEvent.TYPE]: GridCellChangeEvent; } } diff --git a/web/src/components/grid/selection.ts b/web/src/components/grid/selection.ts new file mode 100644 index 0000000..a24bbf5 --- /dev/null +++ b/web/src/components/grid/selection.ts @@ -0,0 +1,23 @@ +import { CellRef, cellRefEquals } from "../../types"; + +export type CellRange = [start: CellRef, end: CellRef]; + +export interface GridSelection { + activeCellRef: CellRef; + range?: CellRange; +} + +export function extendSelection( + selection: GridSelection | undefined, + cellRef: CellRef, +): GridSelection { + if (!selection || cellRefEquals(selection.activeCellRef, cellRef)) { + return { activeCellRef: cellRef }; + } + + if (selection.range) { + return { ...selection, range: [selection.range[0], cellRef] }; + } + + return { ...selection, range: [selection.activeCellRef, cellRef] }; +} -- cgit v1.2.3