@koeroesi86/canvas-grid 1.0.15 → 26.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +238 -0
  2. package/build/types/cell-background/index.d.ts +25 -0
  3. package/build/types/cell-value/index.d.ts +21 -0
  4. package/build/types/character/index.d.ts +21 -0
  5. package/build/types/header/index.d.ts +25 -0
  6. package/build/types/rectangle/index.d.ts +12 -0
  7. package/build/types/text/index.d.ts +31 -0
  8. package/build/types/types.d.ts +87 -0
  9. package/build/types/utils/canvas.d.ts +1 -0
  10. package/build/types/utils/collection.d.ts +18 -0
  11. package/build/types/utils/column-store.d.ts +16 -0
  12. package/build/types/utils/data-store.d.ts +49 -0
  13. package/build/types/utils/jsx.d.ts +40 -0
  14. package/build/types/utils/layer.d.ts +24 -0
  15. package/build/types/utils/loading.d.ts +17 -0
  16. package/build/types/utils/lru-cache.d.ts +11 -0
  17. package/build/types/utils/renderer-registry.d.ts +13 -0
  18. package/build/types/utils/renderer.d.ts +26 -0
  19. package/build/types/utils/row-store.d.ts +37 -0
  20. package/build/types/utils/selectors.d.ts +3 -0
  21. package/build/types/web-component.d.ts +189 -0
  22. package/build/web-component.js +1 -732
  23. package/build/web-component.js.map +1 -1
  24. package/build/web-component.mjs +1220 -0
  25. package/build/web-component.mjs.map +1 -0
  26. package/package.json +31 -32
  27. package/.babelrc +0 -14
  28. package/.eslintrc +0 -7
  29. package/.github/CODEOWNERS +0 -1
  30. package/.github/SECURITY.md +0 -9
  31. package/.github/dependabot.yml +0 -11
  32. package/.github/workflows/ci.yml +0 -32
  33. package/.github/workflows/codeql.yml +0 -26
  34. package/.github/workflows/publish.yml +0 -51
  35. package/components/cellBackground/index.jsx +0 -86
  36. package/components/cellValue/index.jsx +0 -80
  37. package/components/character/index.jsx +0 -82
  38. package/components/component.scss +0 -45
  39. package/components/example.js +0 -56
  40. package/components/header/index.jsx +0 -100
  41. package/components/rectangle/index.jsx +0 -65
  42. package/components/text/index.jsx +0 -131
  43. package/components/utils/collection.js +0 -58
  44. package/components/utils/drawingLayer.js +0 -66
  45. package/components/utils/jsx.js +0 -57
  46. package/components/utils/loading.js +0 -58
  47. package/components/utils/renderer.js +0 -45
  48. package/components/utils/selectors.js +0 -13
  49. package/components/web-component.js +0 -582
  50. package/config/htmlGenerator.js +0 -36
  51. package/nodemon.json +0 -5
  52. package/public/index.html +0 -29
  53. package/scripts/build.js +0 -30
  54. package/scripts/dev.js +0 -64
  55. package/server/example.js +0 -59
  56. package/server/exampleServe.js +0 -3
  57. package/webpack.config.js +0 -151
package/README.md CHANGED
@@ -1,3 +1,241 @@
1
1
  # Canvas Grid
2
2
 
3
3
  [![CI](https://github.com/Koeroesi86/canvas-grid/actions/workflows/ci.yml/badge.svg)](https://github.com/Koeroesi86/canvas-grid/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@koeroesi86/canvas-grid)](https://www.npmjs.com/package/@koeroesi86/canvas-grid)
5
+
6
+ A fast data grid rendered on `<canvas>`, shipped as a framework-agnostic custom element (`<canvas-grid>`). It virtualises rows and columns, flashes cells when their values change, and is built for streams of frequent updates.
7
+
8
+ - Plain web component: no framework required, works with React, Vue, Angular or vanilla JS
9
+ - Written in TypeScript, types included
10
+ - Optional AG Grid-style adapter (`agApi`) for `setRowData` / `updateRowData` / `setColumnDefs`
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ npm install @koeroesi86/canvas-grid
16
+ ```
17
+
18
+ Importing the package registers the `<canvas-grid>` element:
19
+
20
+ ```ts
21
+ import '@koeroesi86/canvas-grid';
22
+ ```
23
+
24
+ Or use the UMD build from a script tag (exposes `window.CanvasGrid`):
25
+
26
+ ```html
27
+ <script src="https://unpkg.com/@koeroesi86/canvas-grid/build/web-component.js"></script>
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ The element draws into its own box, so give it a size:
33
+
34
+ ```html
35
+ <style>
36
+ canvas-grid { display: block; width: 100%; height: 400px; }
37
+ </style>
38
+ <canvas-grid></canvas-grid>
39
+ ```
40
+
41
+ ```ts
42
+ import '@koeroesi86/canvas-grid';
43
+
44
+ const grid = document.querySelector('canvas-grid')!; // typed as CanvasGridComponent
45
+
46
+ grid.font = '12px "Roboto", sans-serif';
47
+
48
+ grid.setColumns([
49
+ 'name', // shorthand: id and header text
50
+ { id: 'price', displayName: 'Price', width: 80, valueFormatter: v => Number(v).toFixed(2) },
51
+ ]);
52
+
53
+ grid.setValues([
54
+ { id: 1, name: 'Apple', price: 1.2 },
55
+ { id: 2, name: 'Pear', price: 0.9 },
56
+ ]);
57
+
58
+ // later: update single rows (batched and throttled internally)
59
+ grid.queueUpdate({ id: 2, price: 1.0 });
60
+ ```
61
+
62
+ Before `setColumns` is called, the grid shows 50 numeric placeholder columns.
63
+
64
+ ## API
65
+
66
+ ### Data model
67
+
68
+ | Concept | Shape |
69
+ | --- | --- |
70
+ | Row | `{ id: string \| number, [column: string]: unknown }`. `id` identifies the row (see `getRowId`) |
71
+ | Column | `string \| number` (used as both id and header text), or an object (below) |
72
+
73
+ Column object fields (`ColumnDefinition`):
74
+
75
+ | Field | Default | Description |
76
+ | --- | --- | --- |
77
+ | `id` | required | Key of the row property shown in this column |
78
+ | `displayName` | – | Header text. Not derived from `id` for object columns |
79
+ | `width` | `100` | Column width in px |
80
+ | `valueFormatter` | `String(value)` | Converts the raw value to the displayed string. Runs lazily, only for cells being drawn, and again only when the cell's value changes |
81
+ | `renderers` | grid's `cellRenderers` | Renderers drawing a cell of this column, in order. Names of [registered renderers](#custom-renderers-and-layers) or renderer objects |
82
+ | `headerRenderer` | `'header'` | Renderer drawing this column's header (name or object) |
83
+
84
+ ### Methods
85
+
86
+ | Method | Description |
87
+ | --- | --- |
88
+ | `setColumns(columns)` | Replaces the column set |
89
+ | `setValues(rows)` | Replaces all rows. Pending updates are discarded |
90
+ | `queueUpdate(row)` | Adds or updates a row (merged by `id`); only the given columns change |
91
+ | `queueRemove(row)` | Removes the row with the same `id` |
92
+ | `transaction({ add, update, remove })` | Batch form of the above |
93
+ | `getRowData(id)` | `{ id, index, height, values }` for a row (`index` is its displayed position, `values` the raw values by column id), or `undefined` |
94
+ | `setSortModel(keys)` / `getSortModel()` | Orders the rows, see [Sorting](#sorting). An empty list restores the original order |
95
+ | `forceDraw()` | Clears and redraws everything |
96
+ | `registerRenderer(name, renderer)` | Makes a [renderer](#custom-renderers-and-layers) available to columns by name; an existing name is replaced |
97
+ | `unregisterRenderer(name)` | Removes a registered renderer |
98
+ | `addLayer(name, { alpha, before })` | Adds a canvas on top of the others (or below the layer named in `before`) |
99
+ | `removeLayer(name)` / `getLayer(name)` | Removes / looks up a layer (`{ name, canvas, ctx }`) |
100
+
101
+ Updates are throttled (50 ms) and changed cells flash for `flashInDuration + flashOutDuration`.
102
+
103
+ ### Properties
104
+
105
+ | Property | Default | Description |
106
+ | --- | --- | --- |
107
+ | `font` | `'12px monospace'` | Cell font (CSS font shorthand) |
108
+ | `headerFont` | `'13px monospace'` | Header font |
109
+ | `getRowId` | `(row) => row.id` | Derives the row id; assign your own to key rows differently |
110
+ | `showHeaders` | `true` | Show the header row |
111
+ | `showRowNumbers` | `true` | Show the row-number column |
112
+ | `rowNumbersWidth` | `30` | Width of the row-number column |
113
+ | `headerHeight` | `30` | Header row height |
114
+ | `cellHeight` | `20` | Row height. Set it before `setValues`, it applies to rows as they are created |
115
+ | `fps` | `30` | Maximum redraw rate |
116
+ | `cellRenderers` | `['background', 'value']` | Renderers used by columns without their own `renderers` |
117
+ | `flashInDuration` | `200` | Fade-in of the update flash in ms |
118
+ | `flashOutDuration` | `400` | Fade-out of the update flash in ms |
119
+
120
+ ### Sorting
121
+
122
+ ```js
123
+ grid.setSortModel([
124
+ { columnId: 'price', direction: 'desc' },
125
+ { columnId: 'name' },
126
+ ]);
127
+ ```
128
+
129
+ Keys apply in priority order; rows equal on every key keep their original order. By default numbers compare numerically, text with `Intl.Collator`, and empty values come last; a key can bring its own `compare(a, b)`. Row numbers follow the displayed order. The order is kept as rows are added or updated: new rows are placed by the model, and an update to a sorted column moves the row (re-sorting runs with the throttled updates, so it costs O(n log n) per batch). `setSortModel` works on raw values, so it does not depend on `valueFormatter`.
130
+
131
+ ### Custom renderers and layers
132
+
133
+ The grid draws onto stacked canvases called layers: `background`, `content` and `header` out of the box. A renderer draws into one layer and is registered under a name; columns select renderers by name. The built-in ones are `'background'` (fill, borders and the update flash), `'value'` (the text) and `'header'`.
134
+
135
+ ```ts
136
+ grid.addLayer('badges'); // on top of the existing layers
137
+
138
+ grid.registerRenderer('badge', {
139
+ layer: 'badges',
140
+ draw({ ctx, x, y, width, formatted }) {
141
+ ctx.fillStyle = formatted === 'sold out' ? '#c33' : '#3a3';
142
+ ctx.fillRect(x + width - 8, y + 4, 4, 4);
143
+ },
144
+ });
145
+
146
+ grid.setColumns([
147
+ { id: 'status', renderers: ['background', 'value', 'badge'] },
148
+ ]);
149
+ ```
150
+
151
+ A renderer is an object:
152
+
153
+ | Member | Description |
154
+ | --- | --- |
155
+ | `layer` | Name of the layer its `ctx` belongs to |
156
+ | `draw(cell)` | Draws one cell, header or row number |
157
+ | `isAnimating?(cell)` | Return `true` while the cell is animating. Cells are only redrawn when something changed (value, scroll, resize) or while a renderer reports animation, plus once after it stops |
158
+ | `clearCache?()` | Called when web fonts finish loading |
159
+
160
+ `cell` (`CellContext`) is one object reused for every cell, so read what you need while drawing and do not keep a reference to it:
161
+
162
+ | Field | Description |
163
+ | --- | --- |
164
+ | `ctx` | Context of the renderer's layer |
165
+ | `x`, `y`, `width`, `height` | Cell rectangle in canvas pixels |
166
+ | `row`, `column` | `{ id, index }` of the row and the column header; `row` is undefined for headers, `column` for row numbers |
167
+ | `value`, `formatted` | Raw value and the string after the column's `valueFormatter` |
168
+ | `updatedAt`, `age` | When the value last changed, and ms since then (`Infinity` if it never did) |
169
+ | `now`, `dt` | Frame timestamp and ms since the previous draw (the grid only draws when something changed, so this can be long) |
170
+ | `valueChanged` | The formatted value differs from what was drawn last time |
171
+ | `forced` | Everything is being redrawn (first draw, resize, scrolling) |
172
+ | `isScrolling` | The grid is being scrolled |
173
+ | `state` | Object that persists per cell between frames, for your own bookkeeping. Kept for the 2048 most recently drawn rows, so it resets for a row that was scrolled far away |
174
+
175
+ A renderer that animates, for example a pulse that lasts one second after a change:
176
+
177
+ ```ts
178
+ grid.registerRenderer('pulse', {
179
+ layer: 'badges',
180
+ isAnimating: ({ age }) => age < 1000,
181
+ draw({ ctx, x, y, width, height, age }) {
182
+ ctx.clearRect(x, y, width, height);
183
+ if (age >= 1000) return;
184
+ ctx.globalAlpha = 1 - age / 1000;
185
+ ctx.fillStyle = '#39f';
186
+ ctx.fillRect(x, y, width, height);
187
+ ctx.globalAlpha = 1;
188
+ },
189
+ });
190
+ ```
191
+
192
+ > `cellRenderer(value, x, y, ...)` and `headerRenderer(header, x)` with positional arguments were replaced by the renderer objects above.
193
+
194
+ ### AG Grid-style adapter
195
+
196
+ `grid.agApi` mirrors a small part of AG Grid's API for easier migration:
197
+
198
+ ```ts
199
+ grid.agApi.setColumnDefs(columns);
200
+ grid.agApi.setRowData(rows);
201
+ grid.agApi.updateRowData({ add: [], update: [], remove: [] });
202
+ grid.agApi.getRowNodeId = row => row.key;
203
+ ```
204
+
205
+ ### TypeScript
206
+
207
+ Types ship with the package. Besides the element class (default export) it exports `ColumnHeader`, `ColumnDefinition`, `Transaction`, `CellRenderer`, `CellContext`, `CellState`, `RendererRef`, `LayerOptions`, `SortKey` and `SortDirection`, and registers `canvas-grid` in `HTMLElementTagNameMap`, so `document.querySelector('canvas-grid')` is typed.
208
+
209
+ > The declarations are single-format. Bundler-style resolution (`moduleResolution: "bundler"`) works; strict Node16 ESM resolution may report the types as CommonJS.
210
+
211
+ ## Browser support
212
+
213
+ Modern evergreen browsers with Custom Elements v1 and Canvas 2D.
214
+
215
+ ## Development
216
+
217
+ Requires Node.js 22.18+ (the Node-side scripts are TypeScript run through Node's native type stripping).
218
+
219
+ ```sh
220
+ npm ci
221
+ npm run dev # Vite dev server with a random-data websocket source, http://localhost:3000
222
+ npm run build # type-check, then bundle (UMD + ESM) and emit declarations into build/
223
+ npm test # type-check, release-script tests and data store unit tests
224
+ npm run bench # data store benchmarks (BENCH_ROWS=1000,1000000 to change sizes)
225
+ npm run bench:scroll # scroll FPS and frame-time p95 in headless Chromium (needs `npx playwright install chromium`)
226
+ npm run serve-local # run only the example data server
227
+ ```
228
+
229
+ `DEV_PORT` (env or `.env`) changes the dev port.
230
+
231
+ Releases are published to npm from CI when a `vX.Y.Z` tag is pushed (see `.github/workflows/publish.yml`).
232
+
233
+ Pull requests also run the scroll benchmark against the base commit in CI (`.github/workflows/benchmark.yml`): base and head alternate three times on the same runner, and the job summary shows the median change. It fails on a regression of more than 30% in FPS or p95 frame time. Locally: `node scripts/compare-benchmarks.mts <path-to-base-checkout>`.
234
+
235
+ ## Security
236
+
237
+ See [SECURITY.md](.github/SECURITY.md).
238
+
239
+ ## License
240
+
241
+ [MIT](LICENSE)
@@ -0,0 +1,25 @@
1
+ import Renderer from '../utils/renderer';
2
+ import type { CellContext, CellRenderer } from '../types';
3
+ export interface CellBackgroundOptions {
4
+ layer: string;
5
+ enableFlashing: boolean;
6
+ background: string;
7
+ flashInDuration: number;
8
+ flashOutDuration: number;
9
+ flashRGB: string;
10
+ borderRightWidth: number;
11
+ borderRightColor: string;
12
+ borderBottomWidth: number;
13
+ borderBottomColor: string;
14
+ }
15
+ declare class CellBackground extends Renderer<CellBackgroundOptions> implements CellRenderer {
16
+ static readonly defaultOptions: CellBackgroundOptions;
17
+ constructor(options?: Partial<CellBackgroundOptions>);
18
+ get layer(): string;
19
+ /** Cells flash after their value changed, until the flash has faded out. */
20
+ isAnimating({ age }: Readonly<CellContext>): boolean;
21
+ /** Flash opacity for a cell updated `age` ms ago: fades in, then out, then 0. */
22
+ private getFlashAlpha;
23
+ draw({ ctx, x, y, width, height, age }: Readonly<CellContext>): void;
24
+ }
25
+ export default CellBackground;
@@ -0,0 +1,21 @@
1
+ import Renderer from '../utils/renderer';
2
+ import type { CellContext, CellRenderer } from '../types';
3
+ export interface CellValueOptions {
4
+ layer: string;
5
+ font: string;
6
+ color: string;
7
+ }
8
+ export default class CellValueRenderer extends Renderer<CellValueOptions> implements CellRenderer {
9
+ static readonly defaultOptions: CellValueOptions;
10
+ private readonly renderer;
11
+ constructor(options?: Partial<CellValueOptions>);
12
+ get layer(): string;
13
+ set font(font: string);
14
+ get font(): string;
15
+ set color(color: string);
16
+ get color(): string;
17
+ set pixelRatio(pixelRatio: number);
18
+ get pixelRatio(): number;
19
+ clearCache(): void;
20
+ draw({ ctx, x, y, width, height, formatted, valueChanged, forced }: Readonly<CellContext>): void;
21
+ }
@@ -0,0 +1,21 @@
1
+ import { CanvasRenderer } from '../utils/renderer';
2
+ import type { RenderProps } from '../types';
3
+ export interface CharacterOptions {
4
+ font: string;
5
+ color: string;
6
+ alpha: boolean;
7
+ clearBeforeRender: boolean;
8
+ }
9
+ export default class CharacterRenderer extends CanvasRenderer<CharacterOptions> {
10
+ static readonly defaultOptions: CharacterOptions;
11
+ private readonly charWidthCache;
12
+ private readonly canvas;
13
+ private readonly ctx;
14
+ constructor(options?: Partial<CharacterOptions>);
15
+ set font(font: string);
16
+ set color(color: string);
17
+ clearCache(): void;
18
+ protected cacheKey({ value, height }: RenderProps): string;
19
+ getCharacterWidth(character?: string): number;
20
+ render({ value, height }: RenderProps): HTMLCanvasElement;
21
+ }
@@ -0,0 +1,25 @@
1
+ import Renderer from '../utils/renderer';
2
+ import type { CellContext, CellRenderer } from '../types';
3
+ export interface HeaderOptions {
4
+ layer: string;
5
+ background: string;
6
+ font: string;
7
+ color: string;
8
+ borderRightWidth: number;
9
+ borderRightColor: string;
10
+ borderBottomWidth: number;
11
+ borderBottomColor: string;
12
+ }
13
+ declare class HeaderRenderer extends Renderer<HeaderOptions> implements CellRenderer {
14
+ static readonly defaultOptions: HeaderOptions;
15
+ private readonly textRenderer;
16
+ constructor(options?: Partial<HeaderOptions>);
17
+ get layer(): string;
18
+ set font(font: string);
19
+ get font(): string;
20
+ set pixelRatio(pixelRatio: number);
21
+ get pixelRatio(): number;
22
+ clearCache(): void;
23
+ draw({ ctx, x, y, width, height, formatted }: Readonly<CellContext>): void;
24
+ }
25
+ export default HeaderRenderer;
@@ -0,0 +1,12 @@
1
+ export interface RectProps {
2
+ x?: number;
3
+ y?: number;
4
+ width: number;
5
+ height: number;
6
+ color: string;
7
+ /** Opacity from 0 to 1, applied on top of the color. */
8
+ alpha?: number;
9
+ }
10
+ /** Solid rectangle, filled straight onto the target context. */
11
+ declare const Rect: import("../utils/jsx").Component<RectProps>;
12
+ export default Rect;
@@ -0,0 +1,31 @@
1
+ import Renderer from '../utils/renderer';
2
+ import type { RenderProps } from '../types';
3
+ export interface TextOptions {
4
+ ellipsis: string;
5
+ cellPadding: {
6
+ top: number;
7
+ right: number;
8
+ bottom: number;
9
+ left: number;
10
+ };
11
+ font: string;
12
+ color: string;
13
+ isEllipsisRight: boolean;
14
+ }
15
+ declare class TextRenderer extends Renderer<TextOptions> {
16
+ static readonly defaultOptions: TextOptions;
17
+ private readonly characterRenderer;
18
+ constructor(options?: Partial<TextOptions>);
19
+ set font(font: string);
20
+ get font(): string;
21
+ set color(color: string);
22
+ get color(): string;
23
+ set pixelRatio(pixelRatio: number);
24
+ get pixelRatio(): number;
25
+ clearCache(): void;
26
+ getTextWidth(text: string): number;
27
+ /** Draws the text clipped with an ellipsis; `(x, y)` is the offset of the text box from `(originX, originY)`. */
28
+ paint(ctx: CanvasRenderingContext2D, { value, x, y, width, height }: RenderProps, originX: number, originY: number): void;
29
+ readonly element: import("../utils/jsx").Component<RenderProps>;
30
+ }
31
+ export default TextRenderer;
@@ -0,0 +1,87 @@
1
+ export type Key = string | number;
2
+ export type RowId = Key;
3
+ export interface RowInput {
4
+ id: RowId;
5
+ [column: string]: unknown;
6
+ }
7
+ /** A row's position and size; its values live in the column stores, addressed by `slot`. */
8
+ export interface RowData {
9
+ id: RowId;
10
+ /** Position in the displayed (sorted) order. */
11
+ index: number;
12
+ /** Position in the column stores; stable until rows are removed. */
13
+ slot: number;
14
+ height: number;
15
+ }
16
+ export type SortDirection = 'asc' | 'desc';
17
+ export interface SortKey {
18
+ columnId: Key;
19
+ direction?: SortDirection;
20
+ /** Orders two values of the column; defaults to numbers numerically, then text, with empty values last. */
21
+ compare?: (a: unknown, b: unknown) => number;
22
+ }
23
+ /** Props of a drawable element (see utils/jsx); positions are relative to the draw origin. */
24
+ export interface RenderProps {
25
+ value?: string;
26
+ x?: number;
27
+ y?: number;
28
+ width?: number;
29
+ height?: number;
30
+ }
31
+ /** Free-form storage a renderer can keep per cell; it survives between frames. */
32
+ export type CellState = Record<string, unknown>;
33
+ export interface CellRow {
34
+ id: RowId;
35
+ index: number;
36
+ }
37
+ export interface CellColumn {
38
+ id: Key;
39
+ index: number;
40
+ displayName?: string;
41
+ }
42
+ /**
43
+ * Everything a renderer needs to draw one cell, header or row number.
44
+ *
45
+ * One object is reused for every cell, so read what you need while drawing and never keep a reference to it.
46
+ */
47
+ export interface CellContext {
48
+ /** Context of the layer the renderer is registered for. */
49
+ ctx: CanvasRenderingContext2D;
50
+ x: number;
51
+ y: number;
52
+ width: number;
53
+ height: number;
54
+ /** Undefined for headers and placeholder cells. */
55
+ row: CellRow | undefined;
56
+ /** Undefined for row numbers. */
57
+ column: CellColumn | undefined;
58
+ /** Raw value as stored. */
59
+ value: unknown;
60
+ /** Value after the column's `valueFormatter`; this is what the built-in renderers draw. */
61
+ formatted: string;
62
+ /** When the value last changed, in ms since the epoch. */
63
+ updatedAt: number | undefined;
64
+ /** Milliseconds since `updatedAt`; Infinity when the value never changed. */
65
+ age: number;
66
+ /** Timestamp of the current frame. */
67
+ now: number;
68
+ /** Milliseconds since the previous frame. */
69
+ dt: number;
70
+ /** The formatted value differs from what was drawn last time. */
71
+ valueChanged: boolean;
72
+ /** Everything has to be redrawn: first draw, resize or scrolling. */
73
+ forced: boolean;
74
+ isScrolling: boolean;
75
+ state: CellState;
76
+ }
77
+ /** Draws into one named layer. Register it with `grid.registerRenderer` and select it per column. */
78
+ export interface CellRenderer {
79
+ /** Name of the layer `ctx` belongs to. */
80
+ readonly layer: string;
81
+ draw(cell: Readonly<CellContext>): void;
82
+ /** Return true while the cell is animating; it is redrawn every frame, plus once more after it stops. */
83
+ isAnimating?(cell: Readonly<CellContext>): boolean;
84
+ clearCache?(): void;
85
+ }
86
+ /** A renderer, or the name it was registered under. */
87
+ export type RendererRef = string | CellRenderer;
@@ -0,0 +1 @@
1
+ export declare const getContext2d: (canvas: HTMLCanvasElement, options?: CanvasRenderingContext2DSettings) => CanvasRenderingContext2D;
@@ -0,0 +1,18 @@
1
+ import type { Key } from '../types';
2
+ /**
3
+ * Insertion-ordered map. Keys are compared as strings (1 and '1' are the same entry),
4
+ * and every operation is O(1) apart from iteration.
5
+ */
6
+ declare class Collection<T> {
7
+ private entries;
8
+ constructor();
9
+ get keys(): Key[];
10
+ get(key: Key): T | undefined;
11
+ set(key: Key, value: T): void;
12
+ add(value: T): void;
13
+ delete(key: Key): void;
14
+ clear(matcher?: (item: T, key: Key) => boolean): void;
15
+ forEach(fn: (item: T, key: Key) => void): void;
16
+ get length(): number;
17
+ }
18
+ export default Collection;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Values of one column for all rows, indexed by row slot. Two flat arrays instead of an object per cell.
3
+ * `undefined` means the row has no value in this column.
4
+ */
5
+ declare class ColumnStore {
6
+ values: unknown[];
7
+ /** When each value last changed, 0 when unknown; only allocated once a value changes after loading. */
8
+ private updatedAt;
9
+ constructor(length?: number);
10
+ get(slot: number): unknown;
11
+ getUpdatedAt(slot: number): number | undefined;
12
+ set(slot: number, value: unknown, updatedAt?: number): void;
13
+ /** Drops the values of the given slots (ascending) and shifts the following ones down. */
14
+ removeSlots(slots: readonly number[]): void;
15
+ }
16
+ export default ColumnStore;
@@ -0,0 +1,49 @@
1
+ import ColumnStore from './column-store';
2
+ import RowStore from './row-store';
3
+ import type { Key, RowId, RowInput, SortKey } from '../types';
4
+ export type RowIdGetter = (row: RowInput, index?: number) => RowId;
5
+ export interface DataStoreEnvironment {
6
+ getRowId: RowIdGetter;
7
+ getCellHeight: () => number;
8
+ }
9
+ /** Numbers numerically, then text, then anything else as text; empty values sort last. */
10
+ export declare const compareValues: (a: unknown, b: unknown) => number;
11
+ /**
12
+ * Cell values stored by column (one array per column, addressed by the row's slot) plus the row order,
13
+ * with the queued add/update/remove handling and the sort model. Independent of the DOM.
14
+ */
15
+ declare class DataStore {
16
+ rows: RowStore;
17
+ columns: Map<string, ColumnStore>;
18
+ /** Queued changes, applied in the order they were made. */
19
+ private pending;
20
+ private sortModel;
21
+ private sortDirty;
22
+ private environment;
23
+ constructor(environment: DataStoreEnvironment);
24
+ get hasPending(): boolean;
25
+ getSortModel(): readonly SortKey[];
26
+ /** Orders the rows by the given columns, in priority order; an empty model restores the insertion order. */
27
+ setSortModel(model: readonly SortKey[]): void;
28
+ /** Value of a cell, `undefined` when the row has none. */
29
+ getValue(rowId: RowId, columnKey: Key): unknown;
30
+ getUpdatedAt(rowId: RowId, columnKey: Key): number | undefined;
31
+ queueUpdate(row: RowInput): void;
32
+ queueRemove(row: RowInput): void;
33
+ clear(): void;
34
+ /** Replaces everything, dropping any queued changes. */
35
+ setRows(data: RowInput[]): void;
36
+ /**
37
+ * Applies the queued changes in the order they were queued; consecutive removals are applied together.
38
+ * Returns whether rows changed position (removed or re-sorted), which needs the whole grid to be redrawn.
39
+ */
40
+ flush(now?: number): boolean;
41
+ private column;
42
+ private addRow;
43
+ private removeRows;
44
+ private applyUpdate;
45
+ private applyValue;
46
+ private markSortDirty;
47
+ private sort;
48
+ }
49
+ export default DataStore;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Minimal declarative layer over the canvas 2D API.
3
+ *
4
+ * `<Group>` and friends do not draw when evaluated. They build a light tree of nodes, and calling
5
+ * `node.draw(ctx, x, y)` paints it, with `(x, y)` as the origin that child positions are relative to.
6
+ * Nothing is rasterized to an intermediate canvas unless a component opts into bitmap caching.
7
+ */
8
+ export interface VNode {
9
+ draw(ctx: CanvasRenderingContext2D, x: number, y: number): void;
10
+ }
11
+ /** `false`, `null` and `undefined` are skipped, so `{condition && <Rect />}` works. */
12
+ export type Child = VNode | false | null | undefined;
13
+ export type Children = Child | readonly Child[];
14
+ export interface ChildrenProps {
15
+ /** A single child, an array of them, or the mix of both that JSX produces for several children. */
16
+ children?: Children | readonly Children[];
17
+ }
18
+ export type Component<Props extends object> = (props: Props) => VNode;
19
+ declare global {
20
+ namespace JSX {
21
+ type Element = VNode;
22
+ interface ElementChildrenAttribute {
23
+ children: unknown;
24
+ }
25
+ interface IntrinsicElements {
26
+ }
27
+ }
28
+ }
29
+ /** Wraps a paint function into a leaf component; positions in `props` are relative to the draw origin. */
30
+ export declare const primitive: <Props extends object>(paint: (ctx: CanvasRenderingContext2D, props: Props, x: number, y: number) => void) => Component<Props>;
31
+ export interface GroupProps extends ChildrenProps {
32
+ x?: number;
33
+ y?: number;
34
+ }
35
+ /** Draws its children relative to its own offset. */
36
+ export declare const Group: Component<GroupProps>;
37
+ declare const React: {
38
+ createElement<Props extends object>(component: Component<Props & ChildrenProps>, props: Props | null, ...children: Children[]): VNode;
39
+ };
40
+ export default React;
@@ -0,0 +1,24 @@
1
+ export interface LayerOptions {
2
+ /** Transparent canvas. Opaque layers (`false`) draw faster; keep the bottom layer opaque. */
3
+ alpha?: boolean;
4
+ /** Name of an existing layer to stack this one below; defaults to the top. */
5
+ before?: string;
6
+ }
7
+ /** A named canvas stacked in the grid; renderers draw into its context. */
8
+ declare class Layer {
9
+ readonly name: string;
10
+ readonly canvas: HTMLCanvasElement;
11
+ readonly ctx: CanvasRenderingContext2D;
12
+ constructor(name: string, { alpha }?: Pick<LayerOptions, 'alpha'>);
13
+ /** Size in CSS pixels; the backing store is `pixelRatio` times larger. */
14
+ width: number;
15
+ height: number;
16
+ pixelRatio: number;
17
+ /**
18
+ * Resizes the canvas, scaling the context so renderers keep drawing in CSS pixels.
19
+ * Returns false, leaving the canvas untouched, when nothing changed (assigning a size clears the canvas).
20
+ */
21
+ resize(width: number, height: number, pixelRatio?: number): boolean;
22
+ clear(): void;
23
+ }
24
+ export default Layer;
@@ -0,0 +1,17 @@
1
+ import Renderer from './renderer';
2
+ interface LoadingOptions {
3
+ ctx: CanvasRenderingContext2D;
4
+ }
5
+ declare class Loading extends Renderer<LoadingOptions> {
6
+ private readonly ctx;
7
+ private readonly offCanvas;
8
+ private readonly offCtx;
9
+ private rotation;
10
+ private readonly speed;
11
+ private readonly count;
12
+ constructor(options: LoadingOptions);
13
+ updateLoader(): void;
14
+ renderLoader(): void;
15
+ render(): HTMLCanvasElement;
16
+ }
17
+ export default Loading;
@@ -0,0 +1,11 @@
1
+ /** Size-bounded map that evicts the least recently used entry first. */
2
+ declare class LruCache<T> {
3
+ private readonly entries;
4
+ private readonly limit;
5
+ constructor(limit: number);
6
+ get(key: string): T | undefined;
7
+ set(key: string, value: T): void;
8
+ clear(): void;
9
+ get size(): number;
10
+ }
11
+ export default LruCache;
@@ -0,0 +1,13 @@
1
+ import type { CellRenderer, RendererRef } from '../types';
2
+ /** Named renderers; columns refer to them by name or hand over a renderer directly. */
3
+ declare class RendererRegistry {
4
+ private readonly renderers;
5
+ private readonly warned;
6
+ register(name: string, renderer: CellRenderer): void;
7
+ unregister(name: string): void;
8
+ get(name: string): CellRenderer | undefined;
9
+ /** Resolves a reference without allocating; unknown names are reported once and yield undefined. */
10
+ resolve(ref: RendererRef): CellRenderer | undefined;
11
+ clearCaches(): void;
12
+ }
13
+ export default RendererRegistry;