vastgrid 0.1.1 → 0.2.0
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.
- package/README.md +175 -13
- package/dist/canvas/geometry.d.ts +92 -0
- package/dist/canvas/geometry.d.ts.map +1 -0
- package/dist/canvas/geometry.js +197 -0
- package/dist/canvas/geometry.js.map +1 -0
- package/dist/canvas/grid-canvas.d.ts +161 -0
- package/dist/canvas/grid-canvas.d.ts.map +1 -0
- package/dist/canvas/grid-canvas.js +1146 -0
- package/dist/canvas/grid-canvas.js.map +1 -0
- package/dist/canvas/hooks.d.ts +61 -0
- package/dist/canvas/hooks.d.ts.map +1 -0
- package/dist/canvas/hooks.js +2 -0
- package/dist/canvas/hooks.js.map +1 -0
- package/dist/canvas/keys.d.ts +26 -0
- package/dist/canvas/keys.d.ts.map +1 -0
- package/dist/canvas/keys.js +133 -0
- package/dist/canvas/keys.js.map +1 -0
- package/dist/canvas/layout.d.ts +26 -0
- package/dist/canvas/layout.d.ts.map +1 -0
- package/dist/canvas/layout.js +73 -0
- package/dist/canvas/layout.js.map +1 -0
- package/dist/canvas/scroll.d.ts +31 -0
- package/dist/canvas/scroll.d.ts.map +1 -0
- package/dist/canvas/scroll.js +111 -0
- package/dist/canvas/scroll.js.map +1 -0
- package/dist/canvas/text.d.ts +23 -0
- package/dist/canvas/text.d.ts.map +1 -0
- package/dist/canvas/text.js +142 -0
- package/dist/canvas/text.js.map +1 -0
- package/dist/{theme.d.ts → canvas/theme.d.ts} +8 -0
- package/dist/canvas/theme.d.ts.map +1 -0
- package/dist/canvas/theme.js +62 -0
- package/dist/canvas/theme.js.map +1 -0
- package/dist/core/cache.d.ts +48 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +222 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/checked.d.ts +20 -0
- package/dist/core/checked.d.ts.map +1 -0
- package/dist/core/checked.js +61 -0
- package/dist/core/checked.js.map +1 -0
- package/dist/core/client.d.ts +33 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +103 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/conditions.d.ts +48 -0
- package/dist/core/conditions.d.ts.map +1 -0
- package/dist/core/conditions.js +105 -0
- package/dist/core/conditions.js.map +1 -0
- package/dist/core/evaluate.d.ts +20 -0
- package/dist/core/evaluate.d.ts.map +1 -0
- package/dist/core/evaluate.js +210 -0
- package/dist/core/evaluate.js.map +1 -0
- package/dist/core/filters.d.ts +51 -0
- package/dist/core/filters.d.ts.map +1 -0
- package/dist/core/filters.js +303 -0
- package/dist/core/filters.js.map +1 -0
- package/dist/core/format.d.ts +14 -0
- package/dist/core/format.d.ts.map +1 -0
- package/dist/core/format.js +303 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/measures.d.ts +26 -0
- package/dist/core/measures.d.ts.map +1 -0
- package/dist/core/measures.js +105 -0
- package/dist/core/measures.js.map +1 -0
- package/dist/core/request-ids.d.ts +20 -0
- package/dist/core/request-ids.d.ts.map +1 -0
- package/dist/core/request-ids.js +55 -0
- package/dist/core/request-ids.js.map +1 -0
- package/dist/core/rows.d.ts +79 -0
- package/dist/core/rows.d.ts.map +1 -0
- package/dist/core/rows.js +173 -0
- package/dist/core/rows.js.map +1 -0
- package/dist/core/store.d.ts +233 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +1162 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/totals.d.ts +24 -0
- package/dist/core/totals.d.ts.map +1 -0
- package/dist/core/totals.js +49 -0
- package/dist/core/totals.js.map +1 -0
- package/dist/core/trace.d.ts +7 -0
- package/dist/core/trace.d.ts.map +1 -0
- package/dist/core/trace.js +26 -0
- package/dist/core/trace.js.map +1 -0
- package/dist/core/transports.d.ts +50 -0
- package/dist/core/transports.d.ts.map +1 -0
- package/dist/core/transports.js +160 -0
- package/dist/core/transports.js.map +1 -0
- package/dist/core/types.d.ts +150 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +45 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core.d.ts +12 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +11 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -7
- package/dist/index.js.map +1 -1
- package/dist/react/accessible-rows.d.ts +13 -0
- package/dist/react/accessible-rows.d.ts.map +1 -0
- package/dist/react/accessible-rows.js +320 -0
- package/dist/react/accessible-rows.js.map +1 -0
- package/dist/react/cell-editor.d.ts +22 -0
- package/dist/react/cell-editor.d.ts.map +1 -0
- package/dist/react/cell-editor.js +431 -0
- package/dist/react/cell-editor.js.map +1 -0
- package/dist/react/cell-menu.d.ts +18 -0
- package/dist/react/cell-menu.d.ts.map +1 -0
- package/dist/react/cell-menu.js +299 -0
- package/dist/react/cell-menu.js.map +1 -0
- package/dist/react/column-menu.d.ts +16 -0
- package/dist/react/column-menu.d.ts.map +1 -0
- package/dist/react/column-menu.js +1311 -0
- package/dist/react/column-menu.js.map +1 -0
- package/dist/react/columns-menu.d.ts +7 -0
- package/dist/react/columns-menu.d.ts.map +1 -0
- package/dist/react/columns-menu.js +249 -0
- package/dist/react/columns-menu.js.map +1 -0
- package/dist/react/conditions-menu.d.ts +10 -0
- package/dist/react/conditions-menu.d.ts.map +1 -0
- package/dist/react/conditions-menu.js +795 -0
- package/dist/react/conditions-menu.js.map +1 -0
- package/dist/react/context.d.ts +29 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/context.js +18 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/data-grid.d.ts +79 -0
- package/dist/react/data-grid.d.ts.map +1 -0
- package/dist/react/data-grid.js +653 -0
- package/dist/react/data-grid.js.map +1 -0
- package/dist/react/detail-panel.d.ts +22 -0
- package/dist/react/detail-panel.d.ts.map +1 -0
- package/dist/react/detail-panel.js +242 -0
- package/dist/react/detail-panel.js.map +1 -0
- package/dist/react/export-menu.d.ts +11 -0
- package/dist/react/export-menu.d.ts.map +1 -0
- package/dist/react/export-menu.js +304 -0
- package/dist/react/export-menu.js.map +1 -0
- package/dist/react/find-bar.d.ts +11 -0
- package/dist/react/find-bar.d.ts.map +1 -0
- package/dist/react/find-bar.js +303 -0
- package/dist/react/find-bar.js.map +1 -0
- package/dist/react/format-form.d.ts +12 -0
- package/dist/react/format-form.d.ts.map +1 -0
- package/dist/react/format-form.js +863 -0
- package/dist/react/format-form.js.map +1 -0
- package/dist/react/formula-menu.d.ts +6 -0
- package/dist/react/formula-menu.d.ts.map +1 -0
- package/dist/react/formula-menu.js +343 -0
- package/dist/react/formula-menu.js.map +1 -0
- package/dist/react/grid-body.d.ts +33 -0
- package/dist/react/grid-body.d.ts.map +1 -0
- package/dist/react/grid-body.js +1389 -0
- package/dist/react/grid-body.js.map +1 -0
- package/dist/react/group-menu.d.ts +7 -0
- package/dist/react/group-menu.d.ts.map +1 -0
- package/dist/react/group-menu.js +508 -0
- package/dist/react/group-menu.js.map +1 -0
- package/dist/react/hooks.d.ts +33 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +161 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/messages.d.ts +180 -0
- package/dist/react/messages.d.ts.map +1 -0
- package/dist/react/messages.js +414 -0
- package/dist/react/messages.js.map +1 -0
- package/dist/react/save.d.ts +9 -0
- package/dist/react/save.d.ts.map +1 -0
- package/dist/react/save.js +102 -0
- package/dist/react/save.js.map +1 -0
- package/dist/react/static-rows.d.ts +9 -0
- package/dist/react/static-rows.d.ts.map +1 -0
- package/dist/react/static-rows.js +176 -0
- package/dist/react/static-rows.js.map +1 -0
- package/dist/react/status-bar.d.ts +12 -0
- package/dist/react/status-bar.d.ts.map +1 -0
- package/dist/react/status-bar.js +413 -0
- package/dist/react/status-bar.js.map +1 -0
- package/dist/react/toolbar-popover.d.ts +14 -0
- package/dist/react/toolbar-popover.d.ts.map +1 -0
- package/dist/react/toolbar-popover.js +143 -0
- package/dist/react/toolbar-popover.js.map +1 -0
- package/dist/react/toolbar.d.ts +13 -0
- package/dist/react/toolbar.d.ts.map +1 -0
- package/dist/react/toolbar.js +344 -0
- package/dist/react/toolbar.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +45 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.css +496 -316
- package/package.json +42 -17
- package/src/canvas/geometry.ts +197 -0
- package/src/canvas/grid-canvas.ts +1222 -0
- package/src/canvas/hooks.ts +61 -0
- package/src/canvas/keys.ts +96 -0
- package/src/canvas/layout.ts +77 -0
- package/src/canvas/scroll.ts +120 -0
- package/src/canvas/text.ts +155 -0
- package/src/{theme.ts → canvas/theme.ts} +14 -1
- package/src/core/cache.ts +247 -0
- package/src/core/checked.ts +64 -0
- package/src/core/client.ts +103 -0
- package/src/core/conditions.ts +89 -0
- package/src/core/evaluate.ts +221 -0
- package/src/core/filters.ts +268 -0
- package/src/core/format.ts +309 -0
- package/src/core/measures.ts +99 -0
- package/src/core/request-ids.ts +54 -0
- package/src/core/rows.ts +213 -0
- package/src/core/store.ts +1122 -0
- package/src/core/totals.ts +59 -0
- package/src/core/trace.ts +44 -0
- package/src/core/transports.ts +177 -0
- package/src/core/types.ts +168 -0
- package/src/core.ts +39 -0
- package/src/index.ts +24 -12
- package/src/react/accessible-rows.tsx +242 -0
- package/src/react/cell-editor.tsx +216 -0
- package/src/react/cell-menu.tsx +166 -0
- package/src/react/column-menu.tsx +619 -0
- package/src/react/columns-menu.tsx +116 -0
- package/src/react/conditions-menu.tsx +289 -0
- package/src/react/context.ts +33 -0
- package/src/react/data-grid.tsx +286 -0
- package/src/react/detail-panel.tsx +92 -0
- package/src/react/export-menu.tsx +112 -0
- package/src/react/find-bar.tsx +112 -0
- package/src/react/format-form.tsx +421 -0
- package/src/react/formula-menu.tsx +163 -0
- package/src/react/grid-body.tsx +744 -0
- package/src/react/group-menu.tsx +214 -0
- package/src/react/hooks.ts +104 -0
- package/src/react/messages.ts +556 -0
- package/src/react/save.ts +42 -0
- package/src/react/static-rows.tsx +59 -0
- package/src/react/status-bar.tsx +175 -0
- package/src/react/toolbar-popover.tsx +69 -0
- package/src/react/toolbar.tsx +152 -0
- package/src/server.ts +58 -0
- package/src/styles.css +496 -316
- package/dist/components/bars.d.ts +0 -14
- package/dist/components/bars.d.ts.map +0 -1
- package/dist/components/bars.js +0 -68
- package/dist/components/bars.js.map +0 -1
- package/dist/components/cell-inspector.d.ts +0 -11
- package/dist/components/cell-inspector.d.ts.map +0 -1
- package/dist/components/cell-inspector.js +0 -37
- package/dist/components/cell-inspector.js.map +0 -1
- package/dist/components/columns-panel.d.ts +0 -12
- package/dist/components/columns-panel.d.ts.map +0 -1
- package/dist/components/columns-panel.js +0 -11
- package/dist/components/columns-panel.js.map +0 -1
- package/dist/components/filter-panel.d.ts +0 -22
- package/dist/components/filter-panel.d.ts.map +0 -1
- package/dist/components/filter-panel.js +0 -169
- package/dist/components/filter-panel.js.map +0 -1
- package/dist/components/find-bar.d.ts +0 -9
- package/dist/components/find-bar.d.ts.map +0 -1
- package/dist/components/find-bar.js +0 -46
- package/dist/components/find-bar.js.map +0 -1
- package/dist/components/popover.d.ts +0 -38
- package/dist/components/popover.d.ts.map +0 -1
- package/dist/components/popover.js +0 -104
- package/dist/components/popover.js.map +0 -1
- package/dist/components/stats-panel.d.ts +0 -12
- package/dist/components/stats-panel.d.ts.map +0 -1
- package/dist/components/stats-panel.js +0 -40
- package/dist/components/stats-panel.js.map +0 -1
- package/dist/controller.d.ts +0 -227
- package/dist/controller.d.ts.map +0 -1
- package/dist/controller.js +0 -845
- package/dist/controller.js.map +0 -1
- package/dist/data-grid.d.ts +0 -51
- package/dist/data-grid.d.ts.map +0 -1
- package/dist/data-grid.js +0 -704
- package/dist/data-grid.js.map +0 -1
- package/dist/labels.d.ts +0 -9
- package/dist/labels.d.ts.map +0 -1
- package/dist/labels.js +0 -38
- package/dist/labels.js.map +0 -1
- package/dist/messages.d.ts +0 -99
- package/dist/messages.d.ts.map +0 -1
- package/dist/messages.js +0 -276
- package/dist/messages.js.map +0 -1
- package/dist/renderer.d.ts +0 -52
- package/dist/renderer.d.ts.map +0 -1
- package/dist/renderer.js +0 -522
- package/dist/renderer.js.map +0 -1
- package/dist/text.d.ts +0 -14
- package/dist/text.d.ts.map +0 -1
- package/dist/text.js +0 -63
- package/dist/text.js.map +0 -1
- package/dist/theme.d.ts.map +0 -1
- package/dist/theme.js +0 -54
- package/dist/theme.js.map +0 -1
- package/src/components/bars.tsx +0 -180
- package/src/components/cell-inspector.tsx +0 -70
- package/src/components/columns-panel.tsx +0 -59
- package/src/components/filter-panel.tsx +0 -293
- package/src/components/find-bar.tsx +0 -104
- package/src/components/popover.tsx +0 -194
- package/src/components/stats-panel.tsx +0 -153
- package/src/controller.ts +0 -945
- package/src/data-grid.tsx +0 -1035
- package/src/labels.ts +0 -46
- package/src/messages.ts +0 -390
- package/src/renderer.ts +0 -535
- package/src/text.ts +0 -55
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { canonicalJson, type Plan } from "vastgrid-spec";
|
|
2
|
+
import { measuresPlan, totalMeasures } from "./measures.js";
|
|
3
|
+
import type { View } from "./rows.js";
|
|
4
|
+
import type { GridSnapshot } from "./types.js";
|
|
5
|
+
|
|
6
|
+
type Totals = NonNullable<GridSnapshot["totals"]>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The totals row of the view on screen (§5.1): read again when the view, its edits or the totals change, with
|
|
10
|
+
* `measure`, and shown with `show`; a read that fails goes to `fail`. The totals on screen stay until the new ones
|
|
11
|
+
* come.
|
|
12
|
+
*/
|
|
13
|
+
export class TotalsReader {
|
|
14
|
+
/** The revision, edits and plan of the totals read last; empty without them, null to read them again. */
|
|
15
|
+
private key: string | null = "";
|
|
16
|
+
private abort: AbortController | null = null;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly measure: (plan: Plan, revision: string | null, signal: AbortSignal) => Promise<Totals>,
|
|
20
|
+
private readonly show: (totals: Totals | null) => void,
|
|
21
|
+
private readonly fail: (error: unknown) => void,
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
/** Stops the read in flight, and reads the totals row again at the next `follow`, as for a new connection. */
|
|
25
|
+
reset() {
|
|
26
|
+
this.key = null;
|
|
27
|
+
this.abort?.abort();
|
|
28
|
+
this.abort = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Reads the totals row again if the view on screen, its edits or the totals changed. */
|
|
32
|
+
follow(view: View | null, snapshot: GridSnapshot) {
|
|
33
|
+
const measures = totalMeasures(snapshot.displayed);
|
|
34
|
+
// A view that is being made is totalled once it is ready.
|
|
35
|
+
if (view && measures.length > 0 && snapshot.view?.status !== "ready") return;
|
|
36
|
+
const plan = view && measures.length > 0 ? measuresPlan(view.plan, measures) : null;
|
|
37
|
+
const key = view && plan ? `${view.revision}\u0000${view.edits}\u0000${canonicalJson(plan)}` : "";
|
|
38
|
+
if (key === this.key) return;
|
|
39
|
+
this.key = key;
|
|
40
|
+
this.abort?.abort();
|
|
41
|
+
this.abort = null;
|
|
42
|
+
if (!view || !plan) {
|
|
43
|
+
if (snapshot.totals) this.show(null);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const abort = new AbortController();
|
|
47
|
+
this.abort = abort;
|
|
48
|
+
this.measure(plan, view.revision, abort.signal)
|
|
49
|
+
.then(
|
|
50
|
+
(totals) => {
|
|
51
|
+
if (!abort.signal.aborted) this.show(totals);
|
|
52
|
+
},
|
|
53
|
+
(error) => {
|
|
54
|
+
if (!abort.signal.aborted) this.fail(error);
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
.catch(() => {});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grid's tracks in the Performance panel of Chrome's DevTools, next to React's (§9 of react.md):
|
|
3
|
+
* requests, commands and canvas frames, in development builds only. `console.timeStamp` costs little when
|
|
4
|
+
* nothing records.
|
|
5
|
+
*/
|
|
6
|
+
declare const process: { env: { NODE_ENV?: string } };
|
|
7
|
+
|
|
8
|
+
/** Whether this is a development build: bundlers replace `process.env.NODE_ENV`, as they do for React. */
|
|
9
|
+
function development(): boolean {
|
|
10
|
+
try {
|
|
11
|
+
return process.env.NODE_ENV !== "production";
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Color = "primary" | "secondary" | "tertiary" | "error";
|
|
18
|
+
|
|
19
|
+
const stamp =
|
|
20
|
+
typeof console !== "undefined" && typeof console.timeStamp === "function" && development()
|
|
21
|
+
? (console.timeStamp as unknown as (
|
|
22
|
+
label: string,
|
|
23
|
+
start: number,
|
|
24
|
+
end: number,
|
|
25
|
+
track: string,
|
|
26
|
+
group: string,
|
|
27
|
+
color: Color,
|
|
28
|
+
) => void)
|
|
29
|
+
: null;
|
|
30
|
+
|
|
31
|
+
/** The time to give `track` as the start of an entry. */
|
|
32
|
+
export function now(): number {
|
|
33
|
+
return typeof performance === "undefined" ? 0 : performance.now();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Adds an entry from `start` to now to one of the grid's tracks. */
|
|
37
|
+
export function track(
|
|
38
|
+
label: string,
|
|
39
|
+
start: number,
|
|
40
|
+
lane: "requests" | "commands" | "canvas",
|
|
41
|
+
color: Color = "primary",
|
|
42
|
+
) {
|
|
43
|
+
stamp?.(label, start, now(), lane, "vastgrid", color);
|
|
44
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ARROW_MEDIA_TYPE, readAttached, toStructuredEncoding } from "vastgrid-spec";
|
|
2
|
+
import type { Transport } from "./client.js";
|
|
3
|
+
import type { RequestMessage } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/** An engine in the same thread: it answers request messages, and may send events (§8.5). */
|
|
6
|
+
export interface Engine {
|
|
7
|
+
handle(message: unknown): Promise<unknown>;
|
|
8
|
+
subscribe?(listener: (event: unknown) => void): () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Carries messages to an engine in the same thread, as function calls with blocks as bytes (§8.8). */
|
|
12
|
+
export function localTransport(engine: Engine): Transport {
|
|
13
|
+
const transport: Transport = { send: (request) => engine.handle(request) };
|
|
14
|
+
if (engine.subscribe) {
|
|
15
|
+
const subscribe = engine.subscribe.bind(engine);
|
|
16
|
+
transport.subscribe = (listener) => subscribe(listener);
|
|
17
|
+
}
|
|
18
|
+
return transport;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The side of a worker that `workerTransport` talks to: `self` in a worker, a `MessagePort`, or anything that carries
|
|
23
|
+
* messages as they do (VS Code's webview, a notebook widget's messages).
|
|
24
|
+
*/
|
|
25
|
+
export interface WorkerLike {
|
|
26
|
+
postMessage(message: unknown, transfer?: Transferable[]): void;
|
|
27
|
+
addEventListener(type: "message", listener: (event: MessageEvent) => void): void;
|
|
28
|
+
removeEventListener(type: "message", listener: (event: MessageEvent) => void): void;
|
|
29
|
+
start?(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Carries messages to an engine in a Web Worker (or behind a `MessagePort`), which answers with
|
|
34
|
+
* `serveEngine`. Blocks travel as bytes and their buffers are transferred (§8.8).
|
|
35
|
+
*/
|
|
36
|
+
export function workerTransport(worker: WorkerLike): Transport & { close(): void } {
|
|
37
|
+
const waiting = new Map<number, (response: unknown) => void>();
|
|
38
|
+
const listeners = new Set<(event: unknown) => void>();
|
|
39
|
+
const receive = (event: MessageEvent) => {
|
|
40
|
+
const data = event.data as { id?: unknown; event?: unknown } | null;
|
|
41
|
+
if (data === null || typeof data !== "object") return;
|
|
42
|
+
if (typeof data.event === "string") {
|
|
43
|
+
for (const listener of listeners) listener(data);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (typeof data.id !== "number") return;
|
|
47
|
+
const resolve = waiting.get(data.id);
|
|
48
|
+
waiting.delete(data.id);
|
|
49
|
+
resolve?.(data);
|
|
50
|
+
};
|
|
51
|
+
worker.addEventListener("message", receive);
|
|
52
|
+
worker.start?.();
|
|
53
|
+
return {
|
|
54
|
+
send(request: RequestMessage) {
|
|
55
|
+
if (request.op === "cancel") {
|
|
56
|
+
worker.postMessage(request);
|
|
57
|
+
return Promise.resolve(undefined);
|
|
58
|
+
}
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
waiting.set(request.id, resolve);
|
|
61
|
+
worker.postMessage(request);
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
subscribe(listener) {
|
|
65
|
+
listeners.add(listener);
|
|
66
|
+
return () => {
|
|
67
|
+
listeners.delete(listener);
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
close() {
|
|
71
|
+
worker.removeEventListener("message", receive);
|
|
72
|
+
waiting.clear();
|
|
73
|
+
listeners.clear();
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Answers the requests that `workerTransport` sends, in a worker (`serveEngine(engine, self)`). */
|
|
79
|
+
export function serveEngine(engine: Engine, scope: WorkerLike): () => void {
|
|
80
|
+
const receive = (event: MessageEvent) => {
|
|
81
|
+
void engine.handle(event.data).then((response) => {
|
|
82
|
+
if (response === undefined) return;
|
|
83
|
+
const block = (response as { result?: { block?: { bytes?: unknown }; data?: { bytes?: unknown } } }).result;
|
|
84
|
+
const bytes = block?.block?.bytes ?? block?.data?.bytes;
|
|
85
|
+
scope.postMessage(response, bytes instanceof Uint8Array ? [bytes.buffer as ArrayBuffer] : []);
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
scope.addEventListener("message", receive);
|
|
89
|
+
scope.start?.();
|
|
90
|
+
const stop = engine.subscribe?.((event) => scope.postMessage(event));
|
|
91
|
+
return () => {
|
|
92
|
+
scope.removeEventListener("message", receive);
|
|
93
|
+
stop?.();
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface HttpTransportOptions {
|
|
98
|
+
/** `fetch`, by default the global one. */
|
|
99
|
+
fetch?: typeof fetch;
|
|
100
|
+
/** Headers sent with every request, such as an authorization. */
|
|
101
|
+
headers?: Record<string, string>;
|
|
102
|
+
/** A URL of Server-Sent Events that carries the engine's events (§8.8). */
|
|
103
|
+
events?: string;
|
|
104
|
+
/** The URL under which exports without a target are downloaded; `<url>/exports/` by default. */
|
|
105
|
+
exports?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Carries messages over HTTP (§8.8): each request is a `POST` of its JSON, and the answers of `rows` and `export`
|
|
110
|
+
* come with their blocks attached. Aborting a request aborts its HTTP request.
|
|
111
|
+
*/
|
|
112
|
+
export function httpTransport(url: string, options: HttpTransportOptions = {}): Transport {
|
|
113
|
+
const request = options.fetch ?? ((...args: Parameters<typeof fetch>) => fetch(...args));
|
|
114
|
+
const exports = options.exports ?? `${url.replace(/\/+$/, "")}/exports/`;
|
|
115
|
+
// While the event stream connects, requests wait: an engine sends each event to the streams open at the time, so
|
|
116
|
+
// a table opened before would finish loading without the client hearing it.
|
|
117
|
+
let connecting: Promise<void> | null = null;
|
|
118
|
+
const transport: Transport = {
|
|
119
|
+
cancels: "signal",
|
|
120
|
+
exportUrl: (target) => `${exports}${encodeURIComponent(target)}`,
|
|
121
|
+
async send(message, { signal }) {
|
|
122
|
+
if (connecting) await connecting;
|
|
123
|
+
const response = await request(url, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"content-type": "application/json",
|
|
127
|
+
accept: `${ARROW_MEDIA_TYPE}, application/json`,
|
|
128
|
+
...options.headers,
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify(message),
|
|
131
|
+
signal,
|
|
132
|
+
});
|
|
133
|
+
const type = response.headers.get("content-type") ?? "";
|
|
134
|
+
if (type.startsWith(ARROW_MEDIA_TYPE)) return readAttached(new Uint8Array(await response.arrayBuffer()));
|
|
135
|
+
return toStructuredEncoding((await response.json()) as Record<string, unknown>);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
const events = options.events;
|
|
139
|
+
if (events !== undefined) {
|
|
140
|
+
transport.subscribe = (listener) => {
|
|
141
|
+
const source = new EventSource(events);
|
|
142
|
+
let open = () => {};
|
|
143
|
+
const opening = new Promise<void>((resolve) => {
|
|
144
|
+
open = resolve;
|
|
145
|
+
});
|
|
146
|
+
connecting = opening;
|
|
147
|
+
// Open, or failed: a stream that cannot connect does not hold the requests back.
|
|
148
|
+
const settle = () => {
|
|
149
|
+
open();
|
|
150
|
+
if (connecting === opening) connecting = null;
|
|
151
|
+
};
|
|
152
|
+
source.onopen = settle;
|
|
153
|
+
source.onerror = settle;
|
|
154
|
+
source.onmessage = (event) => {
|
|
155
|
+
try {
|
|
156
|
+
listener(JSON.parse(event.data as string));
|
|
157
|
+
} catch {
|
|
158
|
+
// An event that is not JSON is not an event of §8.5.
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
return () => {
|
|
162
|
+
settle();
|
|
163
|
+
source.close();
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return transport;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Carries messages through a Server Function (`"use server"`), which passes each to an engine on the server and
|
|
172
|
+
* returns its answer (§7 of react.md). The function receives what any client may send: it checks every
|
|
173
|
+
* request with `checkRequest`, as engines do.
|
|
174
|
+
*/
|
|
175
|
+
export function serverFunctionTransport(action: (request: RequestMessage) => Promise<unknown>): Transport {
|
|
176
|
+
return { send: (request) => action(request) };
|
|
177
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Capabilities,
|
|
3
|
+
Column,
|
|
4
|
+
CommandError,
|
|
5
|
+
DisplayedColumn,
|
|
6
|
+
ErrorCode,
|
|
7
|
+
GridDocument,
|
|
8
|
+
GridSession,
|
|
9
|
+
JsonValue,
|
|
10
|
+
Operation,
|
|
11
|
+
Plan,
|
|
12
|
+
RowKey,
|
|
13
|
+
} from "vastgrid-spec";
|
|
14
|
+
|
|
15
|
+
/** The table's edits version and the number of cells written so far (§8.2 of the specification). */
|
|
16
|
+
export interface Edits {
|
|
17
|
+
version: number;
|
|
18
|
+
cells: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** What `open` and `info` give (§8.2). */
|
|
22
|
+
export interface TableInfo {
|
|
23
|
+
/** The engine's id of the open table, for later requests. */
|
|
24
|
+
table: string;
|
|
25
|
+
revision: string | null;
|
|
26
|
+
edits: Edits;
|
|
27
|
+
status: "loading" | "ready" | "error";
|
|
28
|
+
progress: number | null;
|
|
29
|
+
/** Null while the table loads. */
|
|
30
|
+
rowCount: number | null;
|
|
31
|
+
readableRows: number;
|
|
32
|
+
columns: Column[];
|
|
33
|
+
capabilities: Capabilities;
|
|
34
|
+
format?: string;
|
|
35
|
+
byteSize?: number;
|
|
36
|
+
notes?: string[];
|
|
37
|
+
sheets?: { name: string; hidden?: boolean }[];
|
|
38
|
+
sheet?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
/** The file changed on disk since the table, which has written cells, was read (§7.6); `reload` reads it again. */
|
|
41
|
+
fileChanged?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A view as `rows` reports it (§8.2, §9). */
|
|
45
|
+
export interface ViewInfo {
|
|
46
|
+
hash: string;
|
|
47
|
+
status: "building" | "ready";
|
|
48
|
+
rowCount: number;
|
|
49
|
+
rowCountExact: boolean;
|
|
50
|
+
readableRows: number;
|
|
51
|
+
columns: Column[];
|
|
52
|
+
edits: Edits;
|
|
53
|
+
partial?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The result of `rows` (§8.2), with its block in the structured encoding. */
|
|
57
|
+
export interface RowsResult {
|
|
58
|
+
view: ViewInfo;
|
|
59
|
+
start: number;
|
|
60
|
+
end: number;
|
|
61
|
+
rowIds: number[] | null;
|
|
62
|
+
block: { bytes: Uint8Array };
|
|
63
|
+
truncated?: Record<string, number[]>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The `read` options of `open` (§8.2). */
|
|
67
|
+
export interface OpenOptions {
|
|
68
|
+
sheet?: string;
|
|
69
|
+
headerRow?: number;
|
|
70
|
+
keepEmptyRows?: boolean;
|
|
71
|
+
keepEmptyColumns?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A request (§8.1). */
|
|
75
|
+
export interface RequestMessage {
|
|
76
|
+
v: 2;
|
|
77
|
+
id: number;
|
|
78
|
+
op: Operation;
|
|
79
|
+
[member: string]: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Rows read on a server for the first render (§7 of react.md): the table, the plan they were read for, and
|
|
84
|
+
* the answer of `rows`. A store given them shows these rows before it reads any.
|
|
85
|
+
*/
|
|
86
|
+
export interface InitialView {
|
|
87
|
+
table: TableInfo;
|
|
88
|
+
plan: Plan;
|
|
89
|
+
rows: RowsResult;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A failed request (§8.7), or a response that does not follow §8 (`internal`). */
|
|
93
|
+
export class GridError extends Error {
|
|
94
|
+
readonly code: ErrorCode;
|
|
95
|
+
readonly reason?: string;
|
|
96
|
+
readonly path?: string;
|
|
97
|
+
readonly details?: Record<string, unknown>;
|
|
98
|
+
/** The operation of the request that failed. */
|
|
99
|
+
readonly op?: Operation;
|
|
100
|
+
|
|
101
|
+
constructor(
|
|
102
|
+
code: ErrorCode,
|
|
103
|
+
message: string,
|
|
104
|
+
extra: { reason?: string; path?: string; details?: Record<string, unknown>; op?: Operation } = {},
|
|
105
|
+
) {
|
|
106
|
+
super(message);
|
|
107
|
+
this.name = "GridError";
|
|
108
|
+
this.code = code;
|
|
109
|
+
if (extra.reason !== undefined) this.reason = extra.reason;
|
|
110
|
+
if (extra.path !== undefined) this.path = extra.path;
|
|
111
|
+
if (extra.details !== undefined) this.details = extra.details;
|
|
112
|
+
if (extra.op !== undefined) this.op = extra.op;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** What a dispatched command came to: its output, or why it failed (§4.2 of react.md). */
|
|
117
|
+
export type CommandOutcome = { ok: true; output?: unknown } | { ok: false; error: CommandError | GridError };
|
|
118
|
+
|
|
119
|
+
/** One update of a `write` request (§7.2 of the specification). */
|
|
120
|
+
export interface WriteUpdate {
|
|
121
|
+
readonly key: RowKey;
|
|
122
|
+
readonly column: string;
|
|
123
|
+
readonly value: JsonValue;
|
|
124
|
+
readonly previous?: JsonValue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The last write that failed (§6.6 of react.md), which `retryWrite` sends again and `discardWrite` forgets.
|
|
129
|
+
* `retry` tells how it can be sent again: the same write (`"retry"`), after an `edit_conflict` without the
|
|
130
|
+
* `previous` values (`"overwrite"`), or not at all (null): the table has another revision now, or the engine
|
|
131
|
+
* refused the values or the cells.
|
|
132
|
+
*/
|
|
133
|
+
export interface FailedWrite {
|
|
134
|
+
/** The command whose write it was. */
|
|
135
|
+
readonly command: string;
|
|
136
|
+
readonly updates: readonly WriteUpdate[];
|
|
137
|
+
readonly revision: string | null;
|
|
138
|
+
readonly rowKey?: string | readonly string[];
|
|
139
|
+
readonly error: GridError;
|
|
140
|
+
readonly retry: "retry" | "overwrite" | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** What the grid shows. A snapshot is never changed: the store makes a new one for every change. */
|
|
144
|
+
export interface GridSnapshot {
|
|
145
|
+
readonly document: GridDocument;
|
|
146
|
+
readonly session: GridSession;
|
|
147
|
+
/** Null until the table is open. */
|
|
148
|
+
readonly table: TableInfo | null;
|
|
149
|
+
/** The view on screen; null until its first rows are read. */
|
|
150
|
+
readonly view: ViewInfo | null;
|
|
151
|
+
/** The displayed columns of the view on screen (§5.1.1), in display order. */
|
|
152
|
+
readonly displayed: readonly DisplayedColumn[];
|
|
153
|
+
/** A plan whose view is being read to replace the one on screen. */
|
|
154
|
+
readonly pending: { readonly plan: Plan } | null;
|
|
155
|
+
readonly status: "idle" | "opening" | "ready" | "error";
|
|
156
|
+
/** The last failure that belongs to no command. */
|
|
157
|
+
readonly error: GridError | null;
|
|
158
|
+
/** The last write that failed, until it is sent again or discarded. */
|
|
159
|
+
readonly failedWrite: FailedWrite | null;
|
|
160
|
+
/**
|
|
161
|
+
* The totals row (§5.1): the total of each displayed column that has one, over the whole view on screen, with
|
|
162
|
+
* the types of the values; null without totals, or for a view that groups rows.
|
|
163
|
+
*/
|
|
164
|
+
readonly totals: {
|
|
165
|
+
readonly values: Readonly<Record<string, JsonValue>>;
|
|
166
|
+
readonly columns: Readonly<Record<string, Column>>;
|
|
167
|
+
} | null;
|
|
168
|
+
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grid without React (§4 of react.md): the store, the client of the messages and the transports. Runs in
|
|
3
|
+
* browsers, workers and servers.
|
|
4
|
+
*/
|
|
5
|
+
export { BLOCK_ROWS } from "./core/cache.js";
|
|
6
|
+
export { GridClient, type Transport } from "./core/client.js";
|
|
7
|
+
export { type Handler, RequestIds } from "./core/request-ids.js";
|
|
8
|
+
export type { Viewport } from "./core/rows.js";
|
|
9
|
+
export {
|
|
10
|
+
CACHE_BYTES,
|
|
11
|
+
createGridStore,
|
|
12
|
+
type DispatchOptions,
|
|
13
|
+
GridStore,
|
|
14
|
+
type GridStoreOptions,
|
|
15
|
+
} from "./core/store.js";
|
|
16
|
+
export {
|
|
17
|
+
type Engine,
|
|
18
|
+
type HttpTransportOptions,
|
|
19
|
+
httpTransport,
|
|
20
|
+
localTransport,
|
|
21
|
+
serveEngine,
|
|
22
|
+
serverFunctionTransport,
|
|
23
|
+
type WorkerLike,
|
|
24
|
+
workerTransport,
|
|
25
|
+
} from "./core/transports.js";
|
|
26
|
+
export {
|
|
27
|
+
type CommandOutcome,
|
|
28
|
+
type Edits,
|
|
29
|
+
type FailedWrite,
|
|
30
|
+
GridError,
|
|
31
|
+
type GridSnapshot,
|
|
32
|
+
type InitialView,
|
|
33
|
+
type OpenOptions,
|
|
34
|
+
type RequestMessage,
|
|
35
|
+
type RowsResult,
|
|
36
|
+
type TableInfo,
|
|
37
|
+
type ViewInfo,
|
|
38
|
+
type WriteUpdate,
|
|
39
|
+
} from "./core/types.js";
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export type { CommandName, CommandOutputs, GridCommand, GridDocument, GridSession } from "vastgrid-spec";
|
|
4
|
+
export type { Geometry, Hit } from "./canvas/geometry.js";
|
|
5
|
+
export { GridCanvas, type Settled } from "./canvas/grid-canvas.js";
|
|
6
|
+
export type { CellAction, CellHooks, CellStyle, ColumnGroup, DrawCellArgs, GridCell } from "./canvas/hooks.js";
|
|
7
|
+
export { isApple, type KeyInput, keyCommand } from "./canvas/keys.js";
|
|
8
|
+
/**
|
|
9
|
+
* vastgrid 0.2 for React (react.md): `DataGrid`, the hooks that read and change its store, and what the store
|
|
10
|
+
* and the transports give (also in `vastgrid/core`, without React).
|
|
11
|
+
*/
|
|
12
|
+
export * from "./core.js";
|
|
13
|
+
export { DataGrid, type DataGridHandle, type DataGridProps, GridProvider } from "./react/data-grid.js";
|
|
14
|
+
export type { DetailPanelProps } from "./react/detail-panel.js";
|
|
15
|
+
export type { CellActionEvent } from "./react/grid-body.js";
|
|
2
16
|
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export {
|
|
12
|
-
export { CanvasRenderer } from "./renderer.js";
|
|
13
|
-
export { type GridTheme, resolveTheme } from "./theme.js";
|
|
17
|
+
readRows,
|
|
18
|
+
shallowEqual,
|
|
19
|
+
useCreateGridStore,
|
|
20
|
+
useGridDispatch,
|
|
21
|
+
useGridRows,
|
|
22
|
+
useGridSelector,
|
|
23
|
+
useGridStore,
|
|
24
|
+
} from "./react/hooks.js";
|
|
25
|
+
export { type GridMessages, messagesFor } from "./react/messages.js";
|