@perspective-dev/react 5.1.0 → 5.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 ADDED
@@ -0,0 +1,148 @@
1
+ # `@perspective-dev/react`
2
+
3
+ [![npm](https://img.shields.io/npm/v/@perspective-dev/react.svg?style=for-the-badge)](https://www.npmjs.com/package/@perspective-dev/react)
4
+
5
+ React bindings for [Perspective](https://perspective-dev.github.io/), an
6
+ interactive analytics and data visualization component for large, real-time
7
+ and streaming datasets. This package wraps the
8
+ [`<perspective-viewer>`](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
9
+ Custom Element in an idiomatic, declarative React component,
10
+ `<PerspectiveViewer>`, which manages the element's imperative
11
+ `load()`/`restore()`/`delete()` lifecycle for you.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @perspective-dev/react
17
+ ```
18
+
19
+ `@perspective-dev/client` and `@perspective-dev/viewer` are installed as
20
+ dependencies, but you'll also want at least one plugin package for the
21
+ visualizations themselves:
22
+
23
+ ```bash
24
+ npm install @perspective-dev/viewer-datagrid @perspective-dev/viewer-charts
25
+ ```
26
+
27
+ ## Setup
28
+
29
+ Perspective's engine and UI are WebAssembly binaries which must be initialized
30
+ once, before the first `<PerspectiveViewer>` renders. Plugins register
31
+ themselves via import side effects. See the
32
+ [User Guide's bundling section](https://perspective-dev.github.io/guide/how_to/javascript/importing.html)
33
+ for bundler configuration details.
34
+
35
+ ```tsx
36
+ import perspective from "@perspective-dev/client";
37
+ import perspective_viewer from "@perspective-dev/viewer";
38
+ import "@perspective-dev/viewer-datagrid";
39
+ import "@perspective-dev/viewer-charts";
40
+ import "@perspective-dev/viewer/dist/css/themes.css";
41
+
42
+ import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm";
43
+ import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm";
44
+
45
+ await Promise.all([
46
+ perspective.init_server(fetch(SERVER_WASM)),
47
+ perspective_viewer.init_client(fetch(CLIENT_WASM)),
48
+ ]);
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ Create a `Table` (here in a Web Worker `Client`) and pass it — or a `Promise`
54
+ of it — to `<PerspectiveViewer>`:
55
+
56
+ ```tsx
57
+ import * as React from "react";
58
+ import { PerspectiveViewer } from "@perspective-dev/react";
59
+
60
+ const WORKER = await perspective.worker();
61
+
62
+ const TABLE = WORKER.table(
63
+ fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
64
+ { name: "superstore" },
65
+ );
66
+
67
+ const App: React.FC = () => (
68
+ <PerspectiveViewer
69
+ client={TABLE}
70
+ config={{ group_by: ["State"], plugin: "Y Bar" }}
71
+ />
72
+ );
73
+ ```
74
+
75
+ ## Props
76
+
77
+ | Prop | Type | Description |
78
+ | :--------------- | :------------------------------------------------------------ | :-------------------------------------------------------------- |
79
+ | `client` | `Client \| Table \| Promise<Client> \| Promise<Table>` | Data source. When `undefined`, the viewer `eject()`s. |
80
+ | `config` | `ViewerConfigUpdate \| WorkspaceConfigUpdate` | Declarative viewer state, applied via `restore()`. |
81
+ | `onConfigUpdate` | `(config: ViewerConfigUpdate) => void` | Called when the user reconfigures the viewer through its UI. |
82
+ | `onClick` | `(detail: PerspectiveClickEventDetail) => void` | Called when the user clicks a datapoint. |
83
+ | `onSelect` | `(detail: PerspectiveSelectEventDetail) => void` | Called when the user selects (or deselects) a datapoint or row. |
84
+
85
+ A subset of standard HTML attributes — `className`, `id`, `style`, `hidden`,
86
+ `slot`, `tabIndex` and `title` — is forwarded to the underlying element.
87
+
88
+ ### `client`
89
+
90
+ The viewer's data source, forwarded to
91
+ [`viewer.load()`](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
92
+ whenever it changes:
93
+
94
+ - A `Table` (or `Promise<Table>`) displays that table directly.
95
+ - A `Client` (e.g. from `perspective.worker()` or a WebSocket connection to a
96
+ remote server) connects the viewer to every table hosted by that client;
97
+ the table each panel displays is chosen by `config` or interactively by
98
+ the user.
99
+ - `undefined` ejects the viewer, returning it to an unloaded state without
100
+ unmounting it.
101
+
102
+ The component does not take ownership of the `Table` — delete it yourself
103
+ when it is no longer needed (e.g. `table.delete({ lazy: true })`).
104
+
105
+ ### `config`
106
+
107
+ Declarative viewer state — group-bys, splits, filters, sorts, expressions,
108
+ plugin and plugin config — applied with `restore()` whenever it (or `client`)
109
+ changes. A config with a `panels` property is treated as a multi-panel
110
+ workspace layout and applied with `restoreWorkspace()` instead. Configs are
111
+ compared structurally, so passing a fresh-but-equal object literal on each
112
+ render does not re-apply.
113
+
114
+ Combine `config` with `onConfigUpdate` to make the viewer a controlled
115
+ component — store the user's latest configuration in state (or persist it) and
116
+ pass it back down:
117
+
118
+ ```tsx
119
+ const App: React.FC = () => {
120
+ const [config, setConfig] = React.useState<pspViewer.ViewerConfigUpdate>({
121
+ group_by: ["Category"],
122
+ });
123
+
124
+ return (
125
+ <PerspectiveViewer
126
+ client={TABLE}
127
+ config={config}
128
+ onConfigUpdate={setConfig}
129
+ />
130
+ );
131
+ };
132
+ ```
133
+
134
+ ## Lifecycle
135
+
136
+ On unmount, the component calls the element's `delete()` method, freeing the
137
+ viewer's WebAssembly resources. Tables and clients are created outside the
138
+ component and are yours to manage; a `Table` passed as `client` survives
139
+ unmount and can be shown again by a later mount.
140
+
141
+ ## See also
142
+
143
+ - [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
144
+ — a complete bundler-configured project using this package, including a
145
+ multi-panel workspace config.
146
+ - [Perspective User Guide](https://perspective-dev.github.io/guide/)
147
+ - [`<perspective-viewer>` API documentation](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
148
+ - [`@perspective-dev/client` API documentation](https://perspective-dev.github.io/browser/modules/src_ts_perspective.browser.ts.html)
@@ -1,9 +1,58 @@
1
1
  /**
2
+ * React bindings for [Perspective](https://perspective-dev.github.io/).
3
+ *
4
+ * This module exports {@link PerspectiveViewer}, a declarative React wrapper
5
+ * for the `<perspective-viewer>` Custom Element. The component manages the
6
+ * element's imperative lifecycle — `load()` and `restore()` in response to
7
+ * prop changes, `delete()` on unmount — and exposes the element's Custom
8
+ * Events as React-style callback props.
9
+ *
10
+ * Perspective's WebAssembly engine and UI must be initialized (and at least
11
+ * one plugin package imported) before the first `<PerspectiveViewer>`
12
+ * renders:
13
+ *
14
+ * ```tsx
15
+ * import perspective from "@perspective-dev/client";
16
+ * import perspective_viewer from "@perspective-dev/viewer";
17
+ * import "@perspective-dev/viewer-datagrid";
18
+ * import "@perspective-dev/viewer-charts";
19
+ *
20
+ * import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm";
21
+ * import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm";
22
+ *
23
+ * await Promise.all([
24
+ * perspective.init_server(fetch(SERVER_WASM)),
25
+ * perspective_viewer.init_client(fetch(CLIENT_WASM)),
26
+ * ]);
27
+ * ```
28
+ *
29
+ * Then pass a `Table` (or `Client`, or a `Promise` of either) and an optional
30
+ * config:
31
+ *
32
+ * ```tsx
33
+ * import { PerspectiveViewer } from "@perspective-dev/react";
34
+ *
35
+ * const WORKER = await perspective.worker();
36
+ * const TABLE = WORKER.table(
37
+ * fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
38
+ * { name: "superstore" },
39
+ * );
40
+ *
41
+ * const App: React.FC = () => (
42
+ * <PerspectiveViewer
43
+ * client={TABLE}
44
+ * config={{ group_by: ["State"], plugin: "Y Bar" }}
45
+ * />
46
+ * );
47
+ * ```
2
48
  *
3
49
  * # See Also
4
50
  *
5
- * [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
6
- * project from the Perspective GitHub repo.
51
+ * - [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
52
+ * project from the Perspective GitHub repo, a complete bundler-configured
53
+ * application including a multi-panel workspace config.
54
+ * - [Perspective User Guide](https://perspective-dev.github.io/guide/)
55
+ * - [`<perspective-viewer>` API documentation](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
7
56
  *
8
57
  * @module
9
58
  */
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/viewer.tsx", "../../src/utils.tsx"],
4
- "sourcesContent": ["// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\nimport type * as psp from \"@perspective-dev/client\";\nimport type * as pspViewer from \"@perspective-dev/viewer\";\nimport { usePspListener } from \"./utils\";\n\nfunction PerspectiveViewerImpl(props: PerspectiveViewerProps) {\n const [viewer, setViewer] =\n React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);\n\n React.useEffect(() => {\n return () => {\n viewer?.delete();\n };\n }, [viewer]);\n\n React.useEffect(() => {\n if (!viewer) {\n return;\n }\n\n let cancelled = false;\n (async () => {\n if (!props.client) {\n await viewer.eject();\n return;\n }\n\n await viewer.load(props.client);\n if (cancelled || !props.config) {\n return;\n }\n\n if (\"panels\" in props.config) {\n await viewer.restoreWorkspace(props.config);\n } else {\n await viewer.restore(props.config);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [viewer, props.client, JSON.stringify(props.config)]);\n\n usePspListener(viewer, \"perspective-click\", props.onClick);\n usePspListener(viewer, \"perspective-select\", props.onSelect);\n usePspListener(\n viewer,\n \"perspective-config-update\",\n props.onConfigUpdate,\n (e) => e.detail.getConfig(),\n );\n\n return (\n <perspective-viewer\n ref={setViewer}\n id={props.id}\n className={props.className}\n hidden={props.hidden}\n slot={props.slot}\n style={props.style}\n tabIndex={props.tabIndex}\n title={props.title}\n />\n );\n}\n\n/**\n * Props for the `<PerspectiveViewer>` component.\n */\nexport interface PerspectiveViewerProps {\n client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;\n config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;\n onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;\n onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;\n onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;\n\n // Applicable props from `React.HTMLAttributes`, which we cannot extend\n // directly because Perspective changes the signature of `onClick`.\n className?: string | undefined;\n hidden?: boolean | undefined;\n id?: string | undefined;\n slot?: string | undefined;\n style?: React.CSSProperties | undefined;\n tabIndex?: number | undefined;\n title?: string | undefined;\n}\n\n/**\n * A React wrapper component for `<perspective-viewer>` Custom Element.\n */\nexport const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(\n PerspectiveViewerImpl,\n);\n", "// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\n\nexport function usePspListener<A>(\n el: HTMLElement | undefined | null,\n event: string,\n cb?: (x: A) => void,\n map: (e: CustomEvent) => A = (e) => e.detail,\n) {\n React.useEffect(() => {\n if (!cb || !el) return;\n const ctx = new AbortController();\n const callback = (e: Event) => cb(map(e as CustomEvent));\n el?.addEventListener(event, callback, { signal: ctx.signal });\n return () => ctx.abort();\n }, [el, cb]);\n}\n"],
5
- "mappings": "AAYA,UAAYA,MAAW,QCAvB,UAAYC,MAAW,QAEhB,SAASC,EACZC,EACAC,EACAC,EACAC,EAA8BC,GAAMA,EAAE,OACxC,CACQ,YAAU,IAAM,CAClB,GAAI,CAACF,GAAM,CAACF,EAAI,OAChB,IAAMK,EAAM,IAAI,gBACVC,EAAYF,GAAaF,EAAGC,EAAIC,CAAgB,CAAC,EACvD,OAAAJ,GAAI,iBAAiBC,EAAOK,EAAU,CAAE,OAAQD,EAAI,MAAO,CAAC,EACrD,IAAMA,EAAI,MAAM,CAC3B,EAAG,CAACL,EAAIE,CAAE,CAAC,CACf,CDVA,SAASK,EAAsBC,EAA+B,CAC1D,GAAM,CAACC,EAAQC,CAAS,EACd,WAAwD,IAAI,EAEtE,OAAM,YAAU,IACL,IAAM,CACTD,GAAQ,OAAO,CACnB,EACD,CAACA,CAAM,CAAC,EAEL,YAAU,IAAM,CAClB,GAAI,CAACA,EACD,OAGJ,IAAIE,EAAY,GAChB,OAAC,SAAY,CACT,GAAI,CAACH,EAAM,OAAQ,CACf,MAAMC,EAAO,MAAM,EACnB,MACJ,CAEA,MAAMA,EAAO,KAAKD,EAAM,MAAM,EAC1B,EAAAG,GAAa,CAACH,EAAM,UAIpB,WAAYA,EAAM,OAClB,MAAMC,EAAO,iBAAiBD,EAAM,MAAM,EAE1C,MAAMC,EAAO,QAAQD,EAAM,MAAM,EAEzC,GAAG,EAEI,IAAM,CACTG,EAAY,EAChB,CACJ,EAAG,CAACF,EAAQD,EAAM,OAAQ,KAAK,UAAUA,EAAM,MAAM,CAAC,CAAC,EAEvDI,EAAeH,EAAQ,oBAAqBD,EAAM,OAAO,EACzDI,EAAeH,EAAQ,qBAAsBD,EAAM,QAAQ,EAC3DI,EACIH,EACA,4BACAD,EAAM,eACLK,GAAMA,EAAE,OAAO,UAAU,CAC9B,EAGI,gBAAC,sBACG,IAAKH,EACL,GAAIF,EAAM,GACV,UAAWA,EAAM,UACjB,OAAQA,EAAM,OACd,KAAMA,EAAM,KACZ,MAAOA,EAAM,MACb,SAAUA,EAAM,SAChB,MAAOA,EAAM,MACjB,CAER,CA0BO,IAAMM,EAA4D,OACrEP,CACJ",
4
+ "sourcesContent": ["// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\nimport type * as psp from \"@perspective-dev/client\";\nimport type * as pspViewer from \"@perspective-dev/viewer\";\nimport { usePspListener } from \"./utils\";\n\nfunction PerspectiveViewerImpl(props: PerspectiveViewerProps) {\n const [viewer, setViewer] =\n React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);\n\n React.useEffect(() => {\n return () => {\n viewer?.delete();\n };\n }, [viewer]);\n\n React.useEffect(() => {\n if (!viewer) {\n return;\n }\n\n let cancelled = false;\n (async () => {\n if (!props.client) {\n await viewer.eject();\n return;\n }\n\n await viewer.load(props.client);\n if (cancelled || !props.config) {\n return;\n }\n\n if (\"panels\" in props.config) {\n await viewer.restoreWorkspace(props.config);\n } else {\n await viewer.restore(props.config);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [viewer, props.client, JSON.stringify(props.config)]);\n\n usePspListener(viewer, \"perspective-click\", props.onClick);\n usePspListener(viewer, \"perspective-select\", props.onSelect);\n usePspListener(\n viewer,\n \"perspective-config-update\",\n props.onConfigUpdate,\n (e) => e.detail.getConfig(),\n );\n\n return (\n <perspective-viewer\n ref={setViewer}\n id={props.id}\n className={props.className}\n hidden={props.hidden}\n slot={props.slot}\n style={props.style}\n tabIndex={props.tabIndex}\n title={props.title}\n />\n );\n}\n\n/**\n * Props for the {@link PerspectiveViewer} component.\n */\nexport interface PerspectiveViewerProps {\n /**\n * The viewer's data source, forwarded to the element's `load()` method\n * whenever it changes.\n *\n * - A `Table` (or `Promise<Table>`) displays that table directly.\n * - A `Client` (e.g. from `perspective.worker()` or a WebSocket\n * connection to a remote server) connects the viewer to every table\n * hosted by that client; the table each panel displays is chosen by\n * {@link PerspectiveViewerProps.config} or interactively by the user.\n * - `undefined` calls `eject()`, returning the viewer to an unloaded\n * state without unmounting it.\n *\n * The component does not take ownership of the `Table` \u2014 delete it\n * yourself when it is no longer needed, e.g.\n * `table.delete({ lazy: true })`.\n */\n client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;\n\n /**\n * Declarative viewer state \u2014 group-bys, splits, filters, sorts,\n * expressions, plugin and plugin config \u2014 applied with the element's\n * `restore()` method whenever it (or\n * {@link PerspectiveViewerProps.client}) changes. A config with a\n * `panels` property is treated as a multi-panel workspace layout and\n * applied with `restoreWorkspace()` instead.\n *\n * Configs are compared structurally, so passing a fresh-but-equal object\n * literal on each render does not re-apply. Combine with\n * {@link PerspectiveViewerProps.onConfigUpdate} to use the viewer as a\n * controlled component.\n */\n config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;\n\n /**\n * Called with the viewer's complete configuration whenever the user\n * reconfigures it through its UI (the element's\n * `\"perspective-config-update\"` Custom Event). Use this to persist the\n * user's view or reflect it back through\n * {@link PerspectiveViewerProps.config}.\n */\n onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;\n\n /**\n * Called when the user clicks a datapoint (the element's\n * `\"perspective-click\"` Custom Event), with the clicked row's data and a\n * filter config which would select it.\n */\n onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;\n\n /**\n * Called when the user selects or deselects a datapoint or row (the\n * element's `\"perspective-select\"` Custom Event).\n */\n onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;\n\n // Applicable props from `React.HTMLAttributes`, which we cannot extend\n // directly because Perspective changes the signature of `onClick`.\n\n /** Forwarded to the element's `class` attribute. */\n className?: string | undefined;\n\n /** Forwarded to the element's `hidden` attribute. */\n hidden?: boolean | undefined;\n\n /** Forwarded to the element's `id` attribute. */\n id?: string | undefined;\n\n /** Forwarded to the element's `slot` attribute. */\n slot?: string | undefined;\n\n /** Forwarded to the element's inline `style`. */\n style?: React.CSSProperties | undefined;\n\n /** Forwarded to the element's `tabindex` attribute. */\n tabIndex?: number | undefined;\n\n /** Forwarded to the element's `title` attribute. */\n title?: string | undefined;\n}\n\n/**\n * A declarative React wrapper for the `<perspective-viewer>` Custom Element.\n *\n * `<PerspectiveViewer>` manages the element's imperative lifecycle: it\n * `load()`s the {@link PerspectiveViewerProps.client}, `restore()`s the\n * {@link PerspectiveViewerProps.config} whenever either changes, subscribes\n * the `on*` callback props to the element's Custom Events, and `delete()`s\n * the viewer (freeing its WebAssembly resources) on unmount. Tables and\n * clients are created outside the component and are yours to manage \u2014 a\n * `Table` passed as `client` survives unmount and can be shown again by a\n * later mount.\n *\n * The component is memoized, so re-rendering a parent with unchanged props\n * does not touch the viewer.\n *\n * @example\n * ```tsx\n * const WORKER = await perspective.worker();\n * const TABLE = WORKER.table(\n * fetch(\"superstore.lz4.arrow\").then((resp) => resp.arrayBuffer()),\n * { name: \"superstore\" },\n * );\n *\n * const App: React.FC = () => {\n * const [config, setConfig] = React.useState<pspViewer.ViewerConfigUpdate>({\n * group_by: [\"State\"],\n * plugin: \"Y Bar\",\n * });\n *\n * return (\n * <PerspectiveViewer\n * client={TABLE}\n * config={config}\n * onConfigUpdate={setConfig}\n * />\n * );\n * };\n * ```\n */\nexport const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(\n PerspectiveViewerImpl,\n);\n", "// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\n\n/**\n * Subscribe `cb` to a Custom Event on `el` for the lifetime of the component,\n * projecting the event through `map` (by default, `e.detail`).\n */\nexport function usePspListener<A>(\n el: HTMLElement | undefined | null,\n event: string,\n cb?: (x: A) => void,\n map: (e: CustomEvent) => A = (e) => e.detail,\n) {\n React.useEffect(() => {\n if (!cb || !el) return;\n const ctx = new AbortController();\n const callback = (e: Event) => cb(map(e as CustomEvent));\n el?.addEventListener(event, callback, { signal: ctx.signal });\n return () => ctx.abort();\n }, [el, cb]);\n}\n"],
5
+ "mappings": "AAYA,UAAYA,MAAW,QCAvB,UAAYC,MAAW,QAMhB,SAASC,EACZC,EACAC,EACAC,EACAC,EAA8BC,GAAMA,EAAE,OACxC,CACQ,YAAU,IAAM,CAClB,GAAI,CAACF,GAAM,CAACF,EAAI,OAChB,IAAMK,EAAM,IAAI,gBACVC,EAAYF,GAAaF,EAAGC,EAAIC,CAAgB,CAAC,EACvD,OAAAJ,GAAI,iBAAiBC,EAAOK,EAAU,CAAE,OAAQD,EAAI,MAAO,CAAC,EACrD,IAAMA,EAAI,MAAM,CAC3B,EAAG,CAACL,EAAIE,CAAE,CAAC,CACf,CDdA,SAASK,EAAsBC,EAA+B,CAC1D,GAAM,CAACC,EAAQC,CAAS,EACd,WAAwD,IAAI,EAEtE,OAAM,YAAU,IACL,IAAM,CACTD,GAAQ,OAAO,CACnB,EACD,CAACA,CAAM,CAAC,EAEL,YAAU,IAAM,CAClB,GAAI,CAACA,EACD,OAGJ,IAAIE,EAAY,GAChB,OAAC,SAAY,CACT,GAAI,CAACH,EAAM,OAAQ,CACf,MAAMC,EAAO,MAAM,EACnB,MACJ,CAEA,MAAMA,EAAO,KAAKD,EAAM,MAAM,EAC1B,EAAAG,GAAa,CAACH,EAAM,UAIpB,WAAYA,EAAM,OAClB,MAAMC,EAAO,iBAAiBD,EAAM,MAAM,EAE1C,MAAMC,EAAO,QAAQD,EAAM,MAAM,EAEzC,GAAG,EAEI,IAAM,CACTG,EAAY,EAChB,CACJ,EAAG,CAACF,EAAQD,EAAM,OAAQ,KAAK,UAAUA,EAAM,MAAM,CAAC,CAAC,EAEvDI,EAAeH,EAAQ,oBAAqBD,EAAM,OAAO,EACzDI,EAAeH,EAAQ,qBAAsBD,EAAM,QAAQ,EAC3DI,EACIH,EACA,4BACAD,EAAM,eACLK,GAAMA,EAAE,OAAO,UAAU,CAC9B,EAGI,gBAAC,sBACG,IAAKH,EACL,GAAIF,EAAM,GACV,UAAWA,EAAM,UACjB,OAAQA,EAAM,OACd,KAAMA,EAAM,KACZ,MAAOA,EAAM,MACb,SAAUA,EAAM,SAChB,MAAOA,EAAM,MACjB,CAER,CA6HO,IAAMM,EAA4D,OACrEP,CACJ",
6
6
  "names": ["React", "React", "usePspListener", "el", "event", "cb", "map", "e", "ctx", "callback", "PerspectiveViewerImpl", "props", "viewer", "setViewer", "cancelled", "usePspListener", "e", "PerspectiveViewer"]
7
7
  }
@@ -1 +1,5 @@
1
+ /**
2
+ * Subscribe `cb` to a Custom Event on `el` for the lifetime of the component,
3
+ * projecting the event through `map` (by default, `e.detail`).
4
+ */
1
5
  export declare function usePspListener<A>(el: HTMLElement | undefined | null, event: string, cb?: (x: A) => void, map?: (e: CustomEvent) => A): void;
@@ -2,23 +2,111 @@ import * as React from "react";
2
2
  import type * as psp from "@perspective-dev/client";
3
3
  import type * as pspViewer from "@perspective-dev/viewer";
4
4
  /**
5
- * Props for the `<PerspectiveViewer>` component.
5
+ * Props for the {@link PerspectiveViewer} component.
6
6
  */
7
7
  export interface PerspectiveViewerProps {
8
+ /**
9
+ * The viewer's data source, forwarded to the element's `load()` method
10
+ * whenever it changes.
11
+ *
12
+ * - A `Table` (or `Promise<Table>`) displays that table directly.
13
+ * - A `Client` (e.g. from `perspective.worker()` or a WebSocket
14
+ * connection to a remote server) connects the viewer to every table
15
+ * hosted by that client; the table each panel displays is chosen by
16
+ * {@link PerspectiveViewerProps.config} or interactively by the user.
17
+ * - `undefined` calls `eject()`, returning the viewer to an unloaded
18
+ * state without unmounting it.
19
+ *
20
+ * The component does not take ownership of the `Table` — delete it
21
+ * yourself when it is no longer needed, e.g.
22
+ * `table.delete({ lazy: true })`.
23
+ */
8
24
  client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
25
+ /**
26
+ * Declarative viewer state — group-bys, splits, filters, sorts,
27
+ * expressions, plugin and plugin config — applied with the element's
28
+ * `restore()` method whenever it (or
29
+ * {@link PerspectiveViewerProps.client}) changes. A config with a
30
+ * `panels` property is treated as a multi-panel workspace layout and
31
+ * applied with `restoreWorkspace()` instead.
32
+ *
33
+ * Configs are compared structurally, so passing a fresh-but-equal object
34
+ * literal on each render does not re-apply. Combine with
35
+ * {@link PerspectiveViewerProps.onConfigUpdate} to use the viewer as a
36
+ * controlled component.
37
+ */
9
38
  config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;
39
+ /**
40
+ * Called with the viewer's complete configuration whenever the user
41
+ * reconfigures it through its UI (the element's
42
+ * `"perspective-config-update"` Custom Event). Use this to persist the
43
+ * user's view or reflect it back through
44
+ * {@link PerspectiveViewerProps.config}.
45
+ */
10
46
  onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
47
+ /**
48
+ * Called when the user clicks a datapoint (the element's
49
+ * `"perspective-click"` Custom Event), with the clicked row's data and a
50
+ * filter config which would select it.
51
+ */
11
52
  onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
53
+ /**
54
+ * Called when the user selects or deselects a datapoint or row (the
55
+ * element's `"perspective-select"` Custom Event).
56
+ */
12
57
  onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
58
+ /** Forwarded to the element's `class` attribute. */
13
59
  className?: string | undefined;
60
+ /** Forwarded to the element's `hidden` attribute. */
14
61
  hidden?: boolean | undefined;
62
+ /** Forwarded to the element's `id` attribute. */
15
63
  id?: string | undefined;
64
+ /** Forwarded to the element's `slot` attribute. */
16
65
  slot?: string | undefined;
66
+ /** Forwarded to the element's inline `style`. */
17
67
  style?: React.CSSProperties | undefined;
68
+ /** Forwarded to the element's `tabindex` attribute. */
18
69
  tabIndex?: number | undefined;
70
+ /** Forwarded to the element's `title` attribute. */
19
71
  title?: string | undefined;
20
72
  }
21
73
  /**
22
- * A React wrapper component for `<perspective-viewer>` Custom Element.
74
+ * A declarative React wrapper for the `<perspective-viewer>` Custom Element.
75
+ *
76
+ * `<PerspectiveViewer>` manages the element's imperative lifecycle: it
77
+ * `load()`s the {@link PerspectiveViewerProps.client}, `restore()`s the
78
+ * {@link PerspectiveViewerProps.config} whenever either changes, subscribes
79
+ * the `on*` callback props to the element's Custom Events, and `delete()`s
80
+ * the viewer (freeing its WebAssembly resources) on unmount. Tables and
81
+ * clients are created outside the component and are yours to manage — a
82
+ * `Table` passed as `client` survives unmount and can be shown again by a
83
+ * later mount.
84
+ *
85
+ * The component is memoized, so re-rendering a parent with unchanged props
86
+ * does not touch the viewer.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * const WORKER = await perspective.worker();
91
+ * const TABLE = WORKER.table(
92
+ * fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
93
+ * { name: "superstore" },
94
+ * );
95
+ *
96
+ * const App: React.FC = () => {
97
+ * const [config, setConfig] = React.useState<pspViewer.ViewerConfigUpdate>({
98
+ * group_by: ["State"],
99
+ * plugin: "Y Bar",
100
+ * });
101
+ *
102
+ * return (
103
+ * <PerspectiveViewer
104
+ * client={TABLE}
105
+ * config={config}
106
+ * onConfigUpdate={setConfig}
107
+ * />
108
+ * );
109
+ * };
110
+ * ```
23
111
  */
24
112
  export declare const PerspectiveViewer: React.FC<PerspectiveViewerProps>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-dev/react",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "React component wrappers for `<perspective-viewer>`",
5
5
  "keywords": [
6
6
  "perspective",
package/src/index.tsx CHANGED
@@ -11,11 +11,60 @@
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
13
  /**
14
+ * React bindings for [Perspective](https://perspective-dev.github.io/).
15
+ *
16
+ * This module exports {@link PerspectiveViewer}, a declarative React wrapper
17
+ * for the `<perspective-viewer>` Custom Element. The component manages the
18
+ * element's imperative lifecycle — `load()` and `restore()` in response to
19
+ * prop changes, `delete()` on unmount — and exposes the element's Custom
20
+ * Events as React-style callback props.
21
+ *
22
+ * Perspective's WebAssembly engine and UI must be initialized (and at least
23
+ * one plugin package imported) before the first `<PerspectiveViewer>`
24
+ * renders:
25
+ *
26
+ * ```tsx
27
+ * import perspective from "@perspective-dev/client";
28
+ * import perspective_viewer from "@perspective-dev/viewer";
29
+ * import "@perspective-dev/viewer-datagrid";
30
+ * import "@perspective-dev/viewer-charts";
31
+ *
32
+ * import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm";
33
+ * import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm";
34
+ *
35
+ * await Promise.all([
36
+ * perspective.init_server(fetch(SERVER_WASM)),
37
+ * perspective_viewer.init_client(fetch(CLIENT_WASM)),
38
+ * ]);
39
+ * ```
40
+ *
41
+ * Then pass a `Table` (or `Client`, or a `Promise` of either) and an optional
42
+ * config:
43
+ *
44
+ * ```tsx
45
+ * import { PerspectiveViewer } from "@perspective-dev/react";
46
+ *
47
+ * const WORKER = await perspective.worker();
48
+ * const TABLE = WORKER.table(
49
+ * fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
50
+ * { name: "superstore" },
51
+ * );
52
+ *
53
+ * const App: React.FC = () => (
54
+ * <PerspectiveViewer
55
+ * client={TABLE}
56
+ * config={{ group_by: ["State"], plugin: "Y Bar" }}
57
+ * />
58
+ * );
59
+ * ```
14
60
  *
15
61
  * # See Also
16
62
  *
17
- * [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
18
- * project from the Perspective GitHub repo.
63
+ * - [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
64
+ * project from the Perspective GitHub repo, a complete bundler-configured
65
+ * application including a multi-panel workspace config.
66
+ * - [Perspective User Guide](https://perspective-dev.github.io/guide/)
67
+ * - [`<perspective-viewer>` API documentation](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
19
68
  *
20
69
  * @module
21
70
  */
package/src/utils.tsx CHANGED
@@ -12,6 +12,10 @@
12
12
 
13
13
  import * as React from "react";
14
14
 
15
+ /**
16
+ * Subscribe `cb` to a Custom Event on `el` for the lifetime of the component,
17
+ * projecting the event through `map` (by default, `e.detail`).
18
+ */
15
19
  export function usePspListener<A>(
16
20
  el: HTMLElement | undefined | null,
17
21
  event: string,
package/src/viewer.tsx CHANGED
@@ -78,28 +78,127 @@ function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
78
78
  }
79
79
 
80
80
  /**
81
- * Props for the `<PerspectiveViewer>` component.
81
+ * Props for the {@link PerspectiveViewer} component.
82
82
  */
83
83
  export interface PerspectiveViewerProps {
84
+ /**
85
+ * The viewer's data source, forwarded to the element's `load()` method
86
+ * whenever it changes.
87
+ *
88
+ * - A `Table` (or `Promise<Table>`) displays that table directly.
89
+ * - A `Client` (e.g. from `perspective.worker()` or a WebSocket
90
+ * connection to a remote server) connects the viewer to every table
91
+ * hosted by that client; the table each panel displays is chosen by
92
+ * {@link PerspectiveViewerProps.config} or interactively by the user.
93
+ * - `undefined` calls `eject()`, returning the viewer to an unloaded
94
+ * state without unmounting it.
95
+ *
96
+ * The component does not take ownership of the `Table` — delete it
97
+ * yourself when it is no longer needed, e.g.
98
+ * `table.delete({ lazy: true })`.
99
+ */
84
100
  client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
101
+
102
+ /**
103
+ * Declarative viewer state — group-bys, splits, filters, sorts,
104
+ * expressions, plugin and plugin config — applied with the element's
105
+ * `restore()` method whenever it (or
106
+ * {@link PerspectiveViewerProps.client}) changes. A config with a
107
+ * `panels` property is treated as a multi-panel workspace layout and
108
+ * applied with `restoreWorkspace()` instead.
109
+ *
110
+ * Configs are compared structurally, so passing a fresh-but-equal object
111
+ * literal on each render does not re-apply. Combine with
112
+ * {@link PerspectiveViewerProps.onConfigUpdate} to use the viewer as a
113
+ * controlled component.
114
+ */
85
115
  config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;
116
+
117
+ /**
118
+ * Called with the viewer's complete configuration whenever the user
119
+ * reconfigures it through its UI (the element's
120
+ * `"perspective-config-update"` Custom Event). Use this to persist the
121
+ * user's view or reflect it back through
122
+ * {@link PerspectiveViewerProps.config}.
123
+ */
86
124
  onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
125
+
126
+ /**
127
+ * Called when the user clicks a datapoint (the element's
128
+ * `"perspective-click"` Custom Event), with the clicked row's data and a
129
+ * filter config which would select it.
130
+ */
87
131
  onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
132
+
133
+ /**
134
+ * Called when the user selects or deselects a datapoint or row (the
135
+ * element's `"perspective-select"` Custom Event).
136
+ */
88
137
  onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
89
138
 
90
139
  // Applicable props from `React.HTMLAttributes`, which we cannot extend
91
140
  // directly because Perspective changes the signature of `onClick`.
141
+
142
+ /** Forwarded to the element's `class` attribute. */
92
143
  className?: string | undefined;
144
+
145
+ /** Forwarded to the element's `hidden` attribute. */
93
146
  hidden?: boolean | undefined;
147
+
148
+ /** Forwarded to the element's `id` attribute. */
94
149
  id?: string | undefined;
150
+
151
+ /** Forwarded to the element's `slot` attribute. */
95
152
  slot?: string | undefined;
153
+
154
+ /** Forwarded to the element's inline `style`. */
96
155
  style?: React.CSSProperties | undefined;
156
+
157
+ /** Forwarded to the element's `tabindex` attribute. */
97
158
  tabIndex?: number | undefined;
159
+
160
+ /** Forwarded to the element's `title` attribute. */
98
161
  title?: string | undefined;
99
162
  }
100
163
 
101
164
  /**
102
- * A React wrapper component for `<perspective-viewer>` Custom Element.
165
+ * A declarative React wrapper for the `<perspective-viewer>` Custom Element.
166
+ *
167
+ * `<PerspectiveViewer>` manages the element's imperative lifecycle: it
168
+ * `load()`s the {@link PerspectiveViewerProps.client}, `restore()`s the
169
+ * {@link PerspectiveViewerProps.config} whenever either changes, subscribes
170
+ * the `on*` callback props to the element's Custom Events, and `delete()`s
171
+ * the viewer (freeing its WebAssembly resources) on unmount. Tables and
172
+ * clients are created outside the component and are yours to manage — a
173
+ * `Table` passed as `client` survives unmount and can be shown again by a
174
+ * later mount.
175
+ *
176
+ * The component is memoized, so re-rendering a parent with unchanged props
177
+ * does not touch the viewer.
178
+ *
179
+ * @example
180
+ * ```tsx
181
+ * const WORKER = await perspective.worker();
182
+ * const TABLE = WORKER.table(
183
+ * fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
184
+ * { name: "superstore" },
185
+ * );
186
+ *
187
+ * const App: React.FC = () => {
188
+ * const [config, setConfig] = React.useState<pspViewer.ViewerConfigUpdate>({
189
+ * group_by: ["State"],
190
+ * plugin: "Y Bar",
191
+ * });
192
+ *
193
+ * return (
194
+ * <PerspectiveViewer
195
+ * client={TABLE}
196
+ * config={config}
197
+ * onConfigUpdate={setConfig}
198
+ * />
199
+ * );
200
+ * };
201
+ * ```
103
202
  */
104
203
  export const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(
105
204
  PerspectiveViewerImpl,