summaryrefslogtreecommitdiff
path: root/packages/web/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web/src/components')
-rw-r--r--packages/web/src/components/app/index.css12
-rw-r--r--packages/web/src/components/app/index.ts93
-rw-r--r--packages/web/src/components/grid/cellAtCoord.ts40
-rw-r--r--packages/web/src/components/grid/drawGrid.ts91
-rw-r--r--packages/web/src/components/grid/drawSelection.ts97
-rw-r--r--packages/web/src/components/grid/excursion.ts8
-rw-r--r--packages/web/src/components/grid/index.css49
-rw-r--r--packages/web/src/components/grid/index.ts276
-rw-r--r--packages/web/src/components/grid/renderGrid.ts144
-rw-r--r--packages/web/src/components/grid/selection.ts28
-rw-r--r--packages/web/src/components/icons/index.ts19
-rw-r--r--packages/web/src/components/icons/svgs/minus16.svg3
-rw-r--r--packages/web/src/components/icons/svgs/plus16.svg3
-rw-r--r--packages/web/src/components/index.ts3
-rw-r--r--packages/web/src/components/toolbar/index.css48
-rw-r--r--packages/web/src/components/toolbar/index.ts70
16 files changed, 0 insertions, 984 deletions
diff --git a/packages/web/src/components/app/index.css b/packages/web/src/components/app/index.css
deleted file mode 100644
index aaf2ced..0000000
--- a/packages/web/src/components/app/index.css
+++ /dev/null
@@ -1,12 +0,0 @@
-ntv-app {
- display: block;
- padding: 1.5rem;
-}
-
-ntv-app > ntv-toolbar {
- margin-bottom: 1.5rem;
-}
-
-ntv-app > ntv-grid + ntv-grid {
- margin-top: 1.5rem;
-}
diff --git a/packages/web/src/components/app/index.ts b/packages/web/src/components/app/index.ts
deleted file mode 100644
index a2c0c9d..0000000
--- a/packages/web/src/components/app/index.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { produce } from "immer";
-import defaultDoc from "../../defaultDoc";
-import NotiveElement, { customElement } from "../../element";
-import {
- changeSelectedSubdivisions,
- getSelectedSubdivisionsCount,
-} from "../../grid";
-import { Doc } from "../../types";
-import ntvGrid, { NotiveGridElement } from "../grid";
-import renderGrid from "../grid/renderGrid";
-import { GridSelection } from "../grid/selection";
-import ntvToolbar from "../toolbar";
-import "./index.css";
-
-@customElement("ntv-app")
-export class NotiveAppElement extends NotiveElement {
- doc: Doc = defaultDoc();
-
- #selectedGridId?: string;
- #selection?: GridSelection;
-
- setSelection(gridId: string, selection: GridSelection) {
- const grid = this.doc.grids.find((grid) => grid.id === gridId);
- if (!grid) throw new Error("Invalid grid ID");
-
- this.#selectedGridId = gridId;
- this.#selection = selection;
- this.#updateGridSelections();
-
- this.#toolbar.subdivisions = getSelectedSubdivisionsCount(grid, selection);
- }
-
- clearSelection() {
- this.#selectedGridId = undefined;
- this.#selection = undefined;
- this.#updateGridSelections();
- this.#toolbar.subdivisions = undefined;
- }
-
- #updateGridSelections() {
- this.querySelectorAll<NotiveGridElement>("ntv-grid").forEach((grid) => {
- grid.selection =
- this.#selectedGridId === grid.grid?.id ? this.#selection : undefined;
- });
- }
-
- #toolbar = ntvToolbar({
- onsubdivisionschange: ({ subdivisions }) => {
- if (!subdivisions) return;
-
- const gridId = this.#selectedGridId;
- const selection = this.#selection;
-
- if (!gridId || !selection) return;
-
- const gridIndex = this.doc.grids.findIndex((grid) => grid.id === gridId);
-
- this.doc = produce(this.doc, (doc) => {
- doc.grids[gridIndex] = changeSelectedSubdivisions(
- this.doc.grids[gridIndex],
- selection,
- subdivisions,
- );
- });
-
- this.querySelector<NotiveGridElement>(
- `ntv-grid[data-grid-id="${gridId}"]`,
- )!.grid = renderGrid(this.doc.grids[gridIndex]);
-
- this.clearSelection();
- },
- });
-
- connectedCallback() {
- this.append(
- this.#toolbar,
- ...this.doc.grids.map((grid) =>
- ntvGrid({
- grid: renderGrid(grid),
- dataset: { gridId: grid.id },
- ongridselectionchange: (event) => {
- this.setSelection(grid.id, event.selection);
- },
- oncellchange: (event) => {
- console.log(event);
- },
- }),
- ),
- );
- }
-}
-
-export default NotiveAppElement.makeFactory();
diff --git a/packages/web/src/components/grid/cellAtCoord.ts b/packages/web/src/components/grid/cellAtCoord.ts
deleted file mode 100644
index dd594a4..0000000
--- a/packages/web/src/components/grid/cellAtCoord.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import Coord from "../../math/Coord";
-import { CellRef } from "../../types";
-import { RenderedGrid, RenderedRow } from "./renderGrid";
-
-function rowAtCoord(grid: RenderedGrid, coord: Coord): RenderedRow | undefined {
- if (coord.y <= grid.rect.topLeft.y) {
- return grid.renderedRows[0];
- }
-
- if (coord.y >= grid.rect.bottomRight.y) {
- return grid.renderedRows.at(-1);
- }
-
- return grid.renderedRows.find((row) =>
- row.rect.verticallyContainsCoord(coord),
- );
-}
-
-export default function cellAtCoord(
- grid: RenderedGrid,
- x: number,
- y: number,
-): CellRef | undefined {
- const coord = new Coord(x, y);
- const row = rowAtCoord(grid, coord);
-
- if (!row) return;
-
- if (x <= row.rect.topLeft.x) {
- return row.renderedCells[0]?.cellRef;
- }
-
- if (x >= row.rect.bottomRight.x) {
- return row.renderedCells.at(-1)?.cellRef;
- }
-
- return row.renderedCells.find((cell) =>
- cell.rect.horizontallyContainsCoord(coord),
- )?.cellRef;
-}
diff --git a/packages/web/src/components/grid/drawGrid.ts b/packages/web/src/components/grid/drawGrid.ts
deleted file mode 100644
index da83c8e..0000000
--- a/packages/web/src/components/grid/drawGrid.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { RangeSelection, Selection } from "../../selection";
-import { CellRef } from "../../types";
-import { getRenderedCell, RenderedCell, RenderedGrid } from "./renderGrid";
-
-export interface GridStyles {
- bgFill: string;
- borderStroke: string;
- cellStroke: string;
- cellValueFont: string;
- cellValueLineHeight: string;
-}
-
-function excursion(ctx: CanvasRenderingContext2D, f: () => void) {
- ctx.save();
- f();
- ctx.restore();
-}
-
-function fillBackground(
- ctx: CanvasRenderingContext2D,
- styles: GridStyles,
- grid: RenderedGrid,
-) {
- ctx.clearRect(0, 0, grid.rect.width, grid.rect.height);
- ctx.fillStyle = styles.bgFill;
- ctx.fillRect(0, 0, grid.rect.width, grid.rect.height);
-}
-
-function strokeGrid(
- ctx: CanvasRenderingContext2D,
- styles: GridStyles,
- grid: RenderedGrid,
-) {
- ctx.strokeStyle = styles.borderStroke;
- ctx.strokeRect(0.5, 0.5, grid.rect.width - 1, grid.rect.height - 1);
-}
-
-function strokeGridLines(
- ctx: CanvasRenderingContext2D,
- styles: GridStyles,
- grid: RenderedGrid,
-) {
- ctx.strokeStyle = styles.cellStroke;
-
- grid.renderedRows.forEach((row, renderedRowIndex) => {
- const isLastRow = renderedRowIndex === grid.renderedRows.length - 1;
-
- row.renderedCells.forEach((cell, cellIndex) => {
- const { topLeft, width, height } = cell.rect;
- const isLastCell = cellIndex === row.renderedCells.length - 1;
-
- ctx.strokeRect(
- topLeft.x + 0.5,
- topLeft.y + 0.5,
- isLastCell ? width - 1 : width,
- isLastRow ? height - 1 : height,
- );
- });
- });
-}
-
-function drawCellValues(
- ctx: CanvasRenderingContext2D,
- styles: GridStyles,
- grid: RenderedGrid,
-) {
- grid.renderedRows.forEach((row) =>
- row.renderedCells.forEach((cell) => {
- if (!cell.value) return;
- ctx.fillStyle = "white";
- ctx.textAlign = "center";
- ctx.font = styles.cellValueFont;
- ctx.fillText(
- cell.value,
- cell.rect.center.x,
- cell.rect.center.y + parseInt(styles.cellValueLineHeight) / 4,
- );
- }),
- );
-}
-
-export default function drawGrid(
- ctx: CanvasRenderingContext2D,
- styles: GridStyles,
- grid: RenderedGrid,
-) {
- excursion(ctx, () => fillBackground(ctx, styles, grid));
- excursion(ctx, () => strokeGridLines(ctx, styles, grid));
- excursion(ctx, () => strokeGrid(ctx, styles, grid));
- excursion(ctx, () => drawCellValues(ctx, styles, grid));
-}
diff --git a/packages/web/src/components/grid/drawSelection.ts b/packages/web/src/components/grid/drawSelection.ts
deleted file mode 100644
index 1b8c2ed..0000000
--- a/packages/web/src/components/grid/drawSelection.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { CellRef } from "../../types";
-import excursion from "./excursion";
-import { getRenderedCell, RenderedCell, RenderedGrid } from "./renderGrid";
-import { GridSelection } from "./selection";
-
-export interface SelectionStyles {
- activeCellStroke: string;
- selectionRangeFill: string;
- selectionRangeStroke: string;
-}
-
-function strokeActiveCell(
- ctx: CanvasRenderingContext2D,
- styles: SelectionStyles,
- grid: RenderedGrid,
- cell: RenderedCell,
-) {
- excursion(ctx, () => {
- const isLastCell = cell.rect.bottomRight.x === grid.rect.bottomRight.x;
- const isLastRow = cell.rect.bottomRight.y === grid.rect.bottomRight.y;
-
- ctx.strokeStyle = styles.activeCellStroke;
- ctx.lineWidth = 2;
-
- ctx.strokeRect(
- cell.rect.topLeft.x + 1,
- cell.rect.topLeft.y + 1,
- isLastCell ? cell.rect.width - 2 : cell.rect.width - 1,
- isLastRow ? cell.rect.height - 2 : cell.rect.height - 1,
- );
- });
-}
-
-function drawCellRange(
- ctx: CanvasRenderingContext2D,
- styles: SelectionStyles,
- grid: RenderedGrid,
- start: CellRef,
- end: CellRef,
- { stroke }: { stroke: boolean },
-) {
- excursion(ctx, () => {
- const startCell = getRenderedCell(grid, start);
- const endCell = getRenderedCell(grid, end);
-
- if (!startCell || !endCell) return;
-
- const rect = startCell.rect.extend(endCell.rect);
-
- const isRightEdge = rect.bottomRight.x === grid.rect.bottomRight.x;
- const isBottomEdge = rect.bottomRight.y === grid.rect.bottomRight.y;
-
- ctx.fillStyle = styles.selectionRangeFill;
-
- ctx.fillRect(
- rect.topLeft.x + 1,
- rect.topLeft.y + 1,
- isRightEdge ? rect.width - 2 : rect.width - 1,
- isBottomEdge ? rect.height - 2 : rect.height - 1,
- );
-
- if (!stroke) return;
-
- ctx.strokeStyle = styles.selectionRangeStroke;
-
- ctx.strokeRect(
- rect.topLeft.x + 0.5,
- rect.topLeft.y + 0.5,
- isRightEdge ? rect.width - 1 : rect.width,
- isBottomEdge ? rect.height - 1 : rect.height,
- );
- });
-}
-
-export default function drawSelection(
- ctx: CanvasRenderingContext2D,
- styles: SelectionStyles,
- grid: RenderedGrid,
- selection: GridSelection | undefined,
- { pending }: { pending: boolean },
-) {
- ctx.clearRect(0, 0, grid.rect.width, grid.rect.height);
-
- if (!selection) return;
-
- const activeCell = getRenderedCell(grid, selection.activeCellRef);
-
- if (!activeCell) return;
-
- if (selection.range) {
- drawCellRange(ctx, styles, grid, selection.range[0], selection.range[1], {
- stroke: !pending,
- });
- }
-
- strokeActiveCell(ctx, styles, grid, activeCell);
-}
diff --git a/packages/web/src/components/grid/excursion.ts b/packages/web/src/components/grid/excursion.ts
deleted file mode 100644
index 7752df1..0000000
--- a/packages/web/src/components/grid/excursion.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export default function excursion(
- ctx: CanvasRenderingContext2D,
- f: () => void,
-) {
- ctx.save();
- f();
- ctx.restore();
-}
diff --git a/packages/web/src/components/grid/index.css b/packages/web/src/components/grid/index.css
deleted file mode 100644
index c29f55d..0000000
--- a/packages/web/src/components/grid/index.css
+++ /dev/null
@@ -1,49 +0,0 @@
-@layer components {
- ntv-grid {
- display: block;
- position: relative;
-
- --grid-bg-fill: var(--color-neutral-900);
- --grid-border-stroke: var(--color-neutral-700);
- --grid-cell-stroke: var(--color-neutral-800);
- --grid-active-cell-stroke: var(--color-green-400);
- --grid-selection-range-fill: color-mix(
- in oklab,
- var(--color-green-400) 10%,
- transparent
- );
- --grid-selection-range-stroke: var(--color-green-400);
- font-size: 14px;
- }
-
- ntv-grid > canvas {
- display: block;
- }
-
- ntv-grid > canvas[data-selection] {
- position: absolute;
- top: 0;
- left: 0;
- pointer-events: none;
- }
-
- :has(ntv-grid:state(selecting))
- > ntv-grid:not(:state(selecting))
- > canvas[data-selection] {
- display: none;
- }
-
- ntv-grid input[data-edit] {
- 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]:focus-visible {
- outline: none;
- }
-}
diff --git a/packages/web/src/components/grid/index.ts b/packages/web/src/components/grid/index.ts
deleted file mode 100644
index 3189409..0000000
--- a/packages/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;
- }
-}
diff --git a/packages/web/src/components/grid/renderGrid.ts b/packages/web/src/components/grid/renderGrid.ts
deleted file mode 100644
index 89938ec..0000000
--- a/packages/web/src/components/grid/renderGrid.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import Ratio from "../../math/Ratio";
-import Rect from "../../math/Rect";
-import { Cell, CellRef, Grid, Row, RowRef } from "../../types";
-
-export interface RenderedCell extends Cell {
- cellRef: CellRef;
- renderedRowIndex: number;
- rect: Rect;
- startRatio: Ratio;
- endRatio: Ratio;
-}
-
-export interface RenderedRow {
- rowRef: RowRef;
- rect: Rect;
- renderedCells: RenderedCell[];
-}
-
-export interface RenderedGrid extends Grid {
- rect: Rect;
- renderedRows: RenderedRow[];
-}
-
-function renderCell(
- grid: Grid,
- cell: Cell,
- cellRef: CellRef,
- renderedRowIndex: number,
- topLeftX: number,
- topLeftY: number,
- startRatio: Ratio,
-): RenderedCell {
- const width = cell.widthRatio
- .divideRatio(grid.baseCellWidthRatio)
- .multiplyRatio(Ratio.fromInteger(grid.baseCellSize))
- .toNumber();
-
- const rect = new Rect(topLeftX, topLeftY, width, grid.baseCellSize);
-
- const endRatio = startRatio.add(cell.widthRatio);
-
- return { ...cell, cellRef, rect, renderedRowIndex, startRatio, endRatio };
-}
-
-function renderRow(
- grid: Grid,
- row: Row,
- rowRef: RowRef,
- renderedRowIndex: number,
- topLeftY: number,
-): RenderedRow {
- if (row.cells.length === 0) {
- return {
- ...row,
- rowRef,
- rect: new Rect(0, topLeftY, 0, 0),
- renderedCells: [],
- };
- }
-
- let topLeftX = 0;
- let startRatio = Ratio.fromInteger(0);
-
- const renderedCells = row.cells.map((cell, cellIndex) => {
- const cellRef = { ...rowRef, cellIndex };
-
- const renderedCell = renderCell(
- grid,
- cell,
- cellRef,
- renderedRowIndex,
- topLeftX,
- topLeftY,
- startRatio,
- );
-
- topLeftX = renderedCell.rect.bottomRight.x;
- startRatio = renderedCell.endRatio;
-
- return renderedCell;
- });
-
- const { topLeft } = renderedCells[0].rect;
- const { bottomRight } = renderedCells.at(-1)!.rect;
-
- const rect = new Rect(
- topLeft.x,
- topLeft.y,
- bottomRight.x - topLeft.x,
- bottomRight.y - topLeft.y,
- );
-
- return { ...row, renderedCells, rect, rowRef };
-}
-
-function renderRows(grid: Grid): RenderedRow[] {
- const renderedRows: RenderedRow[] = [];
-
- let partIndex = 0;
- let rowIndex = 0;
- let topLeftY = 0;
- let renderedRowIndex = 0;
-
- while (true) {
- if (!grid.parts[partIndex]?.rows[rowIndex]) break;
-
- const row = grid.parts[partIndex].rows[rowIndex];
- const rowRef = { partIndex, rowIndex };
- const renderedRow = renderRow(
- grid,
- row,
- rowRef,
- renderedRowIndex,
- topLeftY,
- );
-
- topLeftY = renderedRow.rect.bottomRight.y;
- renderedRows.push(renderedRow);
-
- if (!grid.parts[++partIndex]) {
- partIndex = 0;
- rowIndex++;
- }
-
- renderedRowIndex++;
- }
-
- return renderedRows;
-}
-
-export default function renderGrid(grid: Grid) {
- const renderedRows = renderRows(grid);
- const rect = renderedRows[0].rect.extend(renderedRows.at(-1)!.rect);
- return { ...grid, rect, renderedRows };
-}
-
-export function getRenderedCell(
- grid: RenderedGrid,
- cellRef: CellRef,
-): RenderedCell | undefined {
- const renderedRowIndex =
- cellRef.rowIndex * grid.parts.length + cellRef.partIndex;
- return grid.renderedRows[renderedRowIndex]?.renderedCells[cellRef.cellIndex];
-}
diff --git a/packages/web/src/components/grid/selection.ts b/packages/web/src/components/grid/selection.ts
deleted file mode 100644
index 517f8ae..0000000
--- a/packages/web/src/components/grid/selection.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { CellRef, cellRefEquals } from "../../types";
-import { RenderedGrid } from "./renderGrid";
-
-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] };
-}
-
-export function getSelectionRange(selection: GridSelection): CellRange {
- return selection.range ?? [selection.activeCellRef, selection.activeCellRef];
-}
diff --git a/packages/web/src/components/icons/index.ts b/packages/web/src/components/icons/index.ts
deleted file mode 100644
index 5731026..0000000
--- a/packages/web/src/components/icons/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import plus16 from "./svgs/plus16.svg?raw";
-import minus16 from "./svgs/minus16.svg?raw";
-
-function makeIconFactory(source: string) {
- return (attrs?: object): SVGElement => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(source, "image/svg+xml");
- const svg = doc.documentElement as unknown as SVGElement;
-
- if (attrs) {
- Object.entries(attrs).forEach(([k, v]) => svg.setAttribute(k, v));
- }
-
- return svg;
- };
-}
-
-export const plus16Icon = makeIconFactory(plus16);
-export const minus16Icon = makeIconFactory(minus16);
diff --git a/packages/web/src/components/icons/svgs/minus16.svg b/packages/web/src/components/icons/svgs/minus16.svg
deleted file mode 100644
index d77dcfc..0000000
--- a/packages/web/src/components/icons/svgs/minus16.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
- <path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
-</svg>
diff --git a/packages/web/src/components/icons/svgs/plus16.svg b/packages/web/src/components/icons/svgs/plus16.svg
deleted file mode 100644
index 1d7b023..0000000
--- a/packages/web/src/components/icons/svgs/plus16.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
- <path d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z" />
-</svg>
diff --git a/packages/web/src/components/index.ts b/packages/web/src/components/index.ts
deleted file mode 100644
index b7f6f55..0000000
--- a/packages/web/src/components/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import "./app";
-import "./grid";
-import "./toolbar";
diff --git a/packages/web/src/components/toolbar/index.css b/packages/web/src/components/toolbar/index.css
deleted file mode 100644
index 653c326..0000000
--- a/packages/web/src/components/toolbar/index.css
+++ /dev/null
@@ -1,48 +0,0 @@
-@layer components {
- ntv-toolbar {
- display: flex;
- border-radius: 99999px;
- background: var(--color-neutral-900);
- width: min-content;
- }
-
- ntv-toolbar > section {
- display: flex;
- gap: 0.25rem;
- padding: 0.325rem;
- }
-
- ntv-toolbar button {
- border-radius: 99999px;
- background: var(--color-neutral-800);
- padding: 0 0.5rem;
- height: 1.25rem;
- color: white;
- font-weight: 600;
- font-size: 0.75rem;
- }
-
- ntv-toolbar button:hover {
- background: var(--color-green-400);
- color: var(--color-neutral-900);
- }
-
- ntv-toolbar button[data-icon] {
- display: flex;
- justify-content: center;
- align-items: center;
- aspect-ratio: 1;
- height: 1.25rem;
- }
-
- ntv-toolbar input {
- border: 1px solid var(--color-neutral-700);
- border-radius: 4px;
- background: var(--color-neutral-900);
- width: 2.5rem;
- height: 1.25rem;
- color: white;
- font-size: 0.75rem;
- text-align: center;
- }
-}
diff --git a/packages/web/src/components/toolbar/index.ts b/packages/web/src/components/toolbar/index.ts
deleted file mode 100644
index b8a383d..0000000
--- a/packages/web/src/components/toolbar/index.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import NotiveElement, { customElement, eventHandler } from "../../element";
-import h from "../../html";
-import { minus16Icon, plus16Icon } from "../icons";
-import "./index.css";
-
-export class SubdivisionsChangeEvent extends Event {
- static readonly TYPE = "ntv:toolbar:subdivisionschange";
-
- constructor(public subdivisions: number | undefined) {
- super(SubdivisionsChangeEvent.TYPE);
- }
-}
-
-@customElement("ntv-toolbar")
-class NotiveToolbarElement extends NotiveElement {
- #subdivisionsInputEl: HTMLInputElement = h.input({
- title: "Subdivisions",
- disabled: true,
- });
-
- get subdivisions(): number | undefined {
- if (this.#subdivisionsInputEl.value === "") return;
- return parseInt(this.#subdivisionsInputEl.value);
- }
-
- set subdivisions(n: number | undefined) {
- const m = n && Math.max(n, 1);
- this.#subdivisionsInputEl.value = m === undefined ? "" : m.toString();
- }
-
- @eventHandler(SubdivisionsChangeEvent.TYPE)
- onsubdivisionschange?: (event: SubdivisionsChangeEvent) => any;
-
- connectedCallback() {
- this.append(
- h.section(
- h.button(
- {
- dataset: { icon: "" },
- onclick: () => {
- if (!this.subdivisions) return;
- this.subdivisions = this.subdivisions - 1;
- this.dispatchEvent(
- new SubdivisionsChangeEvent(this.subdivisions),
- );
- },
- },
- h.span(minus16Icon()),
- ),
- this.#subdivisionsInputEl,
- h.button(
- {
- dataset: { icon: "" },
- onclick: () => {
- if (!this.subdivisions) return;
- this.subdivisions = this.subdivisions + 1;
- this.dispatchEvent(
- new SubdivisionsChangeEvent(this.subdivisions),
- );
- },
- },
- h.span(plus16Icon()),
- ),
- ),
- h.section(h.button("Play")),
- );
- }
-}
-
-export default NotiveToolbarElement.makeFactory();