summaryrefslogtreecommitdiff
path: root/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/src')
-rw-r--r--web/src/components/grid/index.css15
-rw-r--r--web/src/components/grid/index.ts80
-rw-r--r--web/src/index.css1
-rw-r--r--web/src/index.ts28
4 files changed, 115 insertions, 9 deletions
diff --git a/web/src/components/grid/index.css b/web/src/components/grid/index.css
index 477d40e..93d6f6f 100644
--- a/web/src/components/grid/index.css
+++ b/web/src/components/grid/index.css
@@ -1,5 +1,6 @@
ntv-grid {
display: block;
+ position: relative;
--grid-bg-fill: var(--color-neutral-900);
--grid-border-stroke: var(--color-neutral-700);
@@ -17,3 +18,17 @@ ntv-grid {
ntv-grid > canvas {
display: block;
}
+
+ntv-grid input[data-edit-cell] {
+ position: absolute;
+ vertical-align: baseline;
+ background: var(--color-neutral-800);
+ padding-right: 1px;
+ padding-bottom: 1px;
+ color: white;
+ text-align: center;
+}
+
+ntv-grid input[data-edit-cell]:focus-visible {
+ outline: none;
+}
diff --git a/web/src/components/grid/index.ts b/web/src/components/grid/index.ts
index 7d12a84..204ebea 100644
--- a/web/src/components/grid/index.ts
+++ b/web/src/components/grid/index.ts
@@ -3,6 +3,7 @@ import { CellRef } from "../../types";
import cellAtCoord from "./cellAtCoord";
import drawGrid, { getGridStyles } from "./drawGrid";
import "./index.css";
+import { getRenderedCell } from "./renderGrid";
class NotiveGridElement extends HTMLElement {
#gridId!: string;
@@ -20,7 +21,7 @@ class NotiveGridElement extends HTMLElement {
return window.notive.getGrid(this.#gridId)!;
}
- canvasEl: HTMLCanvasElement = h.canvas();
+ #canvasEl: HTMLCanvasElement = h.canvas();
connectedCallback() {
if (!this.gridId) {
@@ -30,17 +31,23 @@ class NotiveGridElement extends HTMLElement {
window.addEventListener("ntv:selectionchange", () => this.draw());
window.addEventListener("ntv:grid:change", () => this.draw());
- this.canvasEl.addEventListener(
+ this.#canvasEl.addEventListener(
"mousedown",
this.#canvasMouseDownCallback.bind(this),
);
- this.append(this.canvasEl);
+ this.#canvasEl.addEventListener(
+ "dblclick",
+ this.#canvasDoubleClickCallback.bind(this),
+ );
+
+ this.append(this.#canvasEl);
+
this.draw();
}
draw() {
- const ctx = this.canvasEl.getContext("2d");
+ const ctx = this.#canvasEl.getContext("2d");
if (!ctx) throw new Error("Unable to get canvas context");
@@ -48,8 +55,8 @@ class NotiveGridElement extends HTMLElement {
if (!grid) return;
- this.canvasEl.setAttribute("width", grid.rect.width + "px");
- this.canvasEl.setAttribute("height", grid.rect.height + "px");
+ this.#canvasEl.setAttribute("width", grid.rect.width + "px");
+ this.#canvasEl.setAttribute("height", grid.rect.height + "px");
const styles = getGridStyles(this);
@@ -66,7 +73,7 @@ class NotiveGridElement extends HTMLElement {
this: NotiveGridElement,
event: MouseEvent,
): CellRef | undefined {
- const clientRect = this.canvasEl.getBoundingClientRect();
+ const clientRect = this.#canvasEl.getBoundingClientRect();
const x = event.x - clientRect.x;
const y = event.y - clientRect.y;
return cellAtCoord(this.renderedGrid, x, y);
@@ -108,6 +115,65 @@ class NotiveGridElement extends HTMLElement {
this.#selectionAbortController = undefined;
window.notive.finishSelecting();
}
+
+ #editingCellRef?: CellRef;
+
+ #editInputEl: HTMLInputElement = h.input({
+ dataset: { editCell: "true" },
+ onblur: () => {
+ this.#finishEditing();
+ },
+ onkeydown: (event) => {
+ switch (event.key) {
+ case "Enter":
+ this.#finishEditing();
+ break;
+
+ case "Escape":
+ this.#cancelEditing();
+ break;
+ }
+ },
+ });
+
+ #canvasDoubleClickCallback(this: NotiveGridElement, event: MouseEvent) {
+ const cellRef = this.#mouseEventCellRef(event);
+
+ if (!cellRef) return;
+
+ const grid = window.notive.getGrid(this.gridId);
+
+ if (!grid) return;
+
+ const cell = getRenderedCell(grid, cellRef);
+
+ if (!cell) return;
+
+ this.#editingCellRef = cellRef;
+
+ this.append(this.#editInputEl);
+
+ 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();
+ }
+
+ #cancelEditing() {
+ this.#editInputEl.remove();
+ }
+
+ #finishEditing() {
+ this.#editInputEl.remove();
+
+ window.notive.setCellValue(
+ this.gridId,
+ this.#editingCellRef!,
+ this.#editInputEl.value,
+ );
+ }
}
customElements.define("ntv-grid", NotiveGridElement);
diff --git a/web/src/index.css b/web/src/index.css
index bd5ebf3..f100378 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -2,6 +2,7 @@
body {
background: var(--color-neutral-800);
+ user-select: none;
}
*:focus-visible {
diff --git a/web/src/index.ts b/web/src/index.ts
index f08aedc..97cfdf8 100644
--- a/web/src/index.ts
+++ b/web/src/index.ts
@@ -7,9 +7,8 @@ import renderGrid, {
} from "./components/grid/renderGrid";
function defaultDoc(): Doc {
- const defaultCells: Cell[] = Array.from({ length: 16 }, (_, i) => ({
+ const defaultCells: Cell[] = Array.from({ length: 16 }, () => ({
widthRatio: new Ratio(1, 16),
- value: i.toString(),
}));
return {
@@ -185,6 +184,31 @@ export default class Notive {
}),
);
}
+
+ setCellValue(gridId: string, cellRef: CellRef, value: string | undefined) {
+ const grid = this.doc.grids.find((grid) => grid.id === gridId);
+
+ if (!grid) return;
+
+ const cell =
+ grid.parts[cellRef.partIndex].rows[cellRef.rowIndex].cells[
+ cellRef.cellIndex
+ ];
+
+ grid.parts[cellRef.partIndex].rows[cellRef.rowIndex].cells[
+ cellRef.cellIndex
+ ] = { ...cell, value };
+
+ this.#gridsById = Object.fromEntries(
+ this.#doc.grids.map((grid) => [grid.id, renderGrid(grid)]),
+ );
+
+ window.dispatchEvent(
+ new CustomEvent("ntv:grid:change", {
+ detail: { gridId },
+ }),
+ );
+ }
}
window.notive = new Notive();