@perspective-dev/react 4.0.1 → 4.1.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.
@@ -7,24 +7,5 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import * as React from "react";
11
- import type * as psp from "@perspective-dev/client";
12
- import type * as pspViewer from "@perspective-dev/viewer";
13
- export interface PerspectiveViewerProps {
14
- table?: psp.Table | Promise<psp.Table>;
15
- config?: pspViewer.ViewerConfigUpdate;
16
- onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
17
- onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
18
- onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
19
- className?: string | undefined;
20
- hidden?: boolean | undefined;
21
- id?: string | undefined;
22
- slot?: string | undefined;
23
- style?: React.CSSProperties | undefined;
24
- tabIndex?: number | undefined;
25
- title?: string | undefined;
26
- }
27
- /**
28
- * A React wrapper component for `<perspective-viewer>` Custom Element.
29
- */
30
- export declare const PerspectiveViewer: React.FC<PerspectiveViewerProps>;
10
+ export * from "./viewer";
11
+ export * from "./workspace";
package/dist/esm/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import*as i from"react";import{jsx as d}from"react/jsx-runtime";function c(e,t,n){i.useEffect(()=>{if(!n)return;let s=new AbortController,a=l=>n(l.detail);return e?.addEventListener(t,a,{signal:s.signal}),()=>s.abort()},[e,n])}function r(e){let[t,n]=i.useState(null);return i.useEffect(()=>()=>{t?.delete()},[t]),i.useEffect(()=>{e.table?t?.load(e.table):t?.eject()},[t,e.table]),i.useEffect(()=>{e.table&&e.config&&t?.restore(e.config)},[t,e.table,JSON.stringify(e.config)]),c(t,"perspective-click",e.onClick),c(t,"perspective-select",e.onSelect),c(t,"perspective-config-update",e.onConfigUpdate),d("perspective-viewer",{ref:n,id:e.id,className:e.className,hidden:e.hidden,slot:e.slot,style:e.style,tabIndex:e.tabIndex,title:e.title})}var o=i.memo(r);export{o as PerspectiveViewer};
1
+ import*as o from"react";import*as d from"react";function n(e,t,s){d.useEffect(()=>{if(!s||!e)return;let p=new AbortController,c=a=>s(a.detail);return e?.addEventListener(t,c,{signal:p.signal}),()=>p.abort()},[e,s])}import{jsx as g}from"react/jsx-runtime";function m(e){let[t,s]=o.useState(null);return o.useEffect(()=>()=>{t?.delete()},[t]),o.useEffect(()=>{e.client?t?.load(e.client):t?.eject()},[t,e.client]),o.useEffect(()=>{e.client&&e.config&&t?.restore(e.config)},[t,e.client,JSON.stringify(e.config)]),n(t,"perspective-click",e.onClick),n(t,"perspective-select",e.onSelect),n(t,"perspective-config-update",e.onConfigUpdate),g("perspective-viewer",{ref:s,id:e.id,className:e.className,hidden:e.hidden,slot:e.slot,style:e.style,tabIndex:e.tabIndex,title:e.title})}var C=o.memo(m);import*as r from"react";import{jsx as k}from"react/jsx-runtime";var P=r.forwardRef(({client:e,layout:t,onLayoutUpdate:s,onNewView:p,onToggleGlobalFilter:c,...a},f)=>{let[i,v]=r.useState();return r.useImperativeHandle(f,()=>i,[i]),r.useEffect(()=>{i&&t&&i.restore(t)},[i,t]),r.useEffect(()=>{i&&e&&i.load(e)},[i,e]),n(i,"workspace-new-view",p),n(i,"workspace-layout-update",s?({layout:l})=>i?.save().then(u=>s(u)):void 0),n(i,"workspace-toggle-global-filter",c),k("perspective-workspace",{ref:l=>v(l??void 0),...a})}),W=r.memo(P);export{C as PerspectiveViewer,W as PerspectiveWorkspace};
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/index.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\n/**\n *\n * # See Also\n *\n * [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)\n * project from the Perspective GitHub repo.\n *\n * @module\n */\n\nimport * as React from \"react\";\nimport type * as psp from \"@perspective-dev/client\";\nimport type * as pspViewer from \"@perspective-dev/viewer\";\n\nfunction usePspListener<A>(\n viewer: HTMLElement | null,\n name: string,\n f?: (x: A) => void,\n) {\n React.useEffect(() => {\n if (!f) return;\n const ctx = new AbortController();\n const callback = (e: Event) => f((e as CustomEvent).detail);\n viewer?.addEventListener(name, callback, { signal: ctx.signal });\n return () => ctx.abort();\n }, [viewer, f]);\n}\n\nexport interface PerspectiveViewerProps {\n table?: psp.Table | Promise<psp.Table>;\n config?: pspViewer.ViewerConfigUpdate;\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\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 (props.table) {\n viewer?.load(props.table);\n } else {\n viewer?.eject();\n }\n }, [viewer, props.table]);\n\n React.useEffect(() => {\n if (props.table && props.config) {\n viewer?.restore(props.config);\n }\n }, [viewer, props.table, JSON.stringify(props.config)]);\n\n usePspListener(viewer, \"perspective-click\", props.onClick);\n usePspListener(viewer, \"perspective-select\", props.onSelect);\n usePspListener(viewer, \"perspective-config-update\", props.onConfigUpdate);\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 * A React wrapper component for `<perspective-viewer>` Custom Element.\n */\nexport const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(\n PerspectiveViewerImpl,\n);\n"],
5
- "mappings": "AAsBA,UAAYA,MAAW,QAiEf,cAAAC,MAAA,oBA7DR,SAASC,EACLC,EACAC,EACAC,EACF,CACQ,YAAU,IAAM,CAClB,GAAI,CAACA,EAAG,OACR,IAAMC,EAAM,IAAI,gBACVC,EAAYC,GAAaH,EAAGG,EAAkB,MAAM,EAC1D,OAAAL,GAAQ,iBAAiBC,EAAMG,EAAU,CAAE,OAAQD,EAAI,MAAO,CAAC,EACxD,IAAMA,EAAI,MAAM,CAC3B,EAAG,CAACH,EAAQE,CAAC,CAAC,CAClB,CAoBA,SAASI,EAAsBC,EAA+B,CAC1D,GAAM,CAACP,EAAQQ,CAAS,EACd,WAAwD,IAAI,EAEtE,OAAM,YAAU,IACL,IAAM,CACTR,GAAQ,OAAO,CACnB,EACD,CAACA,CAAM,CAAC,EAEL,YAAU,IAAM,CACdO,EAAM,MACNP,GAAQ,KAAKO,EAAM,KAAK,EAExBP,GAAQ,MAAM,CAEtB,EAAG,CAACA,EAAQO,EAAM,KAAK,CAAC,EAElB,YAAU,IAAM,CACdA,EAAM,OAASA,EAAM,QACrBP,GAAQ,QAAQO,EAAM,MAAM,CAEpC,EAAG,CAACP,EAAQO,EAAM,MAAO,KAAK,UAAUA,EAAM,MAAM,CAAC,CAAC,EAEtDR,EAAeC,EAAQ,oBAAqBO,EAAM,OAAO,EACzDR,EAAeC,EAAQ,qBAAsBO,EAAM,QAAQ,EAC3DR,EAAeC,EAAQ,4BAA6BO,EAAM,cAAc,EAGpET,EAAC,sBACG,IAAKU,EACL,GAAID,EAAM,GACV,UAAWA,EAAM,UACjB,OAAQA,EAAM,OACd,KAAMA,EAAM,KACZ,MAAOA,EAAM,MACb,SAAUA,EAAM,SAChB,MAAOA,EAAM,MACjB,CAER,CAKO,IAAME,EAA4D,OACrEH,CACJ",
6
- "names": ["React", "jsx", "usePspListener", "viewer", "name", "f", "ctx", "callback", "e", "PerspectiveViewerImpl", "props", "setViewer", "PerspectiveViewer"]
3
+ "sources": ["../../src/viewer.tsx", "../../src/utils.tsx", "../../src/workspace.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 (props.client) {\n viewer?.load(props.client);\n } else {\n viewer?.eject();\n }\n }, [viewer, props.client]);\n\n React.useEffect(() => {\n if (props.client && props.config) {\n viewer?.restore(props.config);\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(viewer, \"perspective-config-update\", props.onConfigUpdate);\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;\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) {\n React.useEffect(() => {\n if (!cb || !el) return;\n const ctx = new AbortController();\n const callback = (e: Event) => cb((e as CustomEvent).detail);\n el?.addEventListener(event, callback, { signal: ctx.signal });\n return () => ctx.abort();\n }, [el, cb]);\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 type * as psp from \"@perspective-dev/client\";\nimport type * as psp_viewer from \"@perspective-dev/viewer\";\nimport type * as pspWorkspace from \"@perspective-dev/workspace\";\nimport { PerspectiveWorkspaceConfig } from \"@perspective-dev/workspace\";\nimport * as utils from \"./utils\";\nimport * as React from \"react\";\n\nexport interface NewViewEventDetail {\n config: psp_viewer.ViewerConfigUpdate;\n widget: pspWorkspace.PerspectiveViewerWidget;\n}\n\nexport interface ToggleGloalFilterEventDetail {\n widget: pspWorkspace.PerspectiveViewerWidget;\n isGlobalFilter: boolean;\n}\n\ninterface PerspectiveWorkspaceProps extends React.HTMLAttributes<HTMLElement> {\n client: psp.Client | Promise<psp.Client>;\n layout: PerspectiveWorkspaceConfig;\n onLayoutUpdate?: (layout: PerspectiveWorkspaceConfig) => void;\n onNewView?: (detail: NewViewEventDetail) => void;\n onToggleGlobalFilter?: (detail: ToggleGloalFilterEventDetail) => void;\n}\n\nconst PerspectiveWorkspaceImpl = React.forwardRef<\n pspWorkspace.HTMLPerspectiveWorkspaceElement | undefined,\n PerspectiveWorkspaceProps\n>(\n (\n {\n client,\n layout,\n onLayoutUpdate,\n onNewView,\n onToggleGlobalFilter,\n ...htmlAttributes\n },\n ref,\n ) => {\n const [workspace, setWorkspace] =\n React.useState<pspWorkspace.HTMLPerspectiveWorkspaceElement>();\n\n React.useImperativeHandle(ref, () => workspace, [workspace]);\n React.useEffect(() => {\n if (workspace && layout) {\n workspace.restore(layout);\n }\n }, [workspace, layout]);\n\n React.useEffect(() => {\n if (workspace && client) {\n workspace.load(client);\n }\n }, [workspace, client]);\n\n utils.usePspListener(workspace, \"workspace-new-view\", onNewView);\n utils.usePspListener(\n workspace,\n \"workspace-layout-update\",\n onLayoutUpdate\n ? ({ layout }: { layout: PerspectiveWorkspaceConfig }) =>\n workspace?.save().then((x) => onLayoutUpdate(x))\n : undefined,\n );\n\n utils.usePspListener(\n workspace,\n \"workspace-toggle-global-filter\",\n onToggleGlobalFilter,\n );\n\n return (\n <perspective-workspace\n ref={(r) => setWorkspace(r ?? undefined)}\n {...htmlAttributes}\n ></perspective-workspace>\n );\n },\n);\n\nexport const PerspectiveWorkspace = React.memo(PerspectiveWorkspaceImpl);\n"],
5
+ "mappings": "AAYA,UAAYA,MAAW,QCAvB,UAAYC,MAAW,QAEhB,SAASC,EACZC,EACAC,EACAC,EACF,CACQ,YAAU,IAAM,CAClB,GAAI,CAACA,GAAM,CAACF,EAAI,OAChB,IAAMG,EAAM,IAAI,gBACVC,EAAYC,GAAaH,EAAIG,EAAkB,MAAM,EAC3D,OAAAL,GAAI,iBAAiBC,EAAOG,EAAU,CAAE,OAAQD,EAAI,MAAO,CAAC,EACrD,IAAMA,EAAI,MAAM,CAC3B,EAAG,CAACH,EAAIE,CAAE,CAAC,CACf,CDoBQ,cAAAI,MAAA,oBA7BR,SAASC,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,CACdD,EAAM,OACNC,GAAQ,KAAKD,EAAM,MAAM,EAEzBC,GAAQ,MAAM,CAEtB,EAAG,CAACA,EAAQD,EAAM,MAAM,CAAC,EAEnB,YAAU,IAAM,CACdA,EAAM,QAAUA,EAAM,QACtBC,GAAQ,QAAQD,EAAM,MAAM,CAEpC,EAAG,CAACC,EAAQD,EAAM,OAAQ,KAAK,UAAUA,EAAM,MAAM,CAAC,CAAC,EAEvDG,EAAeF,EAAQ,oBAAqBD,EAAM,OAAO,EACzDG,EAAeF,EAAQ,qBAAsBD,EAAM,QAAQ,EAC3DG,EAAeF,EAAQ,4BAA6BD,EAAM,cAAc,EAGpEF,EAAC,sBACG,IAAKI,EACL,GAAIF,EAAM,GACV,UAAWA,EAAM,UACjB,OAAQA,EAAM,OACd,KAAMA,EAAM,KACZ,MAAOA,EAAM,MACb,SAAUA,EAAM,SAChB,MAAOA,EAAM,MACjB,CAER,CA0BO,IAAMI,EAA4D,OACrEL,CACJ,EEpEA,UAAYM,MAAW,QAoEX,cAAAC,MAAA,oBAhDZ,IAAMC,EAAiC,aAInC,CACI,CACI,OAAAC,EACA,OAAAC,EACA,eAAAC,EACA,UAAAC,EACA,qBAAAC,EACA,GAAGC,CACP,EACAC,IACC,CACD,GAAM,CAACC,EAAWC,CAAY,EACpB,WAAuD,EAEjE,OAAM,sBAAoBF,EAAK,IAAMC,EAAW,CAACA,CAAS,CAAC,EACrD,YAAU,IAAM,CACdA,GAAaN,GACbM,EAAU,QAAQN,CAAM,CAEhC,EAAG,CAACM,EAAWN,CAAM,CAAC,EAEhB,YAAU,IAAM,CACdM,GAAaP,GACbO,EAAU,KAAKP,CAAM,CAE7B,EAAG,CAACO,EAAWP,CAAM,CAAC,EAEhBS,EAAeF,EAAW,qBAAsBJ,CAAS,EACzDM,EACFF,EACA,0BACAL,EACM,CAAC,CAAE,OAAAD,CAAO,IACNM,GAAW,KAAK,EAAE,KAAMG,GAAMR,EAAeQ,CAAC,CAAC,EACnD,MACV,EAEMD,EACFF,EACA,iCACAH,CACJ,EAGIN,EAAC,yBACG,IAAMa,GAAMH,EAAaG,GAAK,MAAS,EACtC,GAAGN,EACP,CAET,CACJ,EAEaO,EAA6B,OAAKb,CAAwB",
6
+ "names": ["React", "React", "usePspListener", "el", "event", "cb", "ctx", "callback", "e", "jsx", "PerspectiveViewerImpl", "props", "viewer", "setViewer", "usePspListener", "PerspectiveViewer", "React", "jsx", "PerspectiveWorkspaceImpl", "client", "layout", "onLayoutUpdate", "onNewView", "onToggleGlobalFilter", "htmlAttributes", "ref", "workspace", "setWorkspace", "usePspListener", "x", "r", "PerspectiveWorkspace"]
7
7
  }
@@ -0,0 +1 @@
1
+ export declare function usePspListener<A>(el: HTMLElement | undefined | null, event: string, cb?: (x: A) => void): void;
@@ -0,0 +1,24 @@
1
+ import * as React from "react";
2
+ import type * as psp from "@perspective-dev/client";
3
+ import type * as pspViewer from "@perspective-dev/viewer";
4
+ /**
5
+ * Props for the `<PerspectiveViewer>` component.
6
+ */
7
+ export interface PerspectiveViewerProps {
8
+ client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
9
+ config?: pspViewer.ViewerConfigUpdate;
10
+ onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
11
+ onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
12
+ onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
13
+ className?: string | undefined;
14
+ hidden?: boolean | undefined;
15
+ id?: string | undefined;
16
+ slot?: string | undefined;
17
+ style?: React.CSSProperties | undefined;
18
+ tabIndex?: number | undefined;
19
+ title?: string | undefined;
20
+ }
21
+ /**
22
+ * A React wrapper component for `<perspective-viewer>` Custom Element.
23
+ */
24
+ export declare const PerspectiveViewer: React.FC<PerspectiveViewerProps>;
@@ -0,0 +1,22 @@
1
+ import type * as psp from "@perspective-dev/client";
2
+ import type * as psp_viewer from "@perspective-dev/viewer";
3
+ import type * as pspWorkspace from "@perspective-dev/workspace";
4
+ import { PerspectiveWorkspaceConfig } from "@perspective-dev/workspace";
5
+ import * as React from "react";
6
+ export interface NewViewEventDetail {
7
+ config: psp_viewer.ViewerConfigUpdate;
8
+ widget: pspWorkspace.PerspectiveViewerWidget;
9
+ }
10
+ export interface ToggleGloalFilterEventDetail {
11
+ widget: pspWorkspace.PerspectiveViewerWidget;
12
+ isGlobalFilter: boolean;
13
+ }
14
+ interface PerspectiveWorkspaceProps extends React.HTMLAttributes<HTMLElement> {
15
+ client: psp.Client | Promise<psp.Client>;
16
+ layout: PerspectiveWorkspaceConfig;
17
+ onLayoutUpdate?: (layout: PerspectiveWorkspaceConfig) => void;
18
+ onNewView?: (detail: NewViewEventDetail) => void;
19
+ onToggleGlobalFilter?: (detail: ToggleGloalFilterEventDetail) => void;
20
+ }
21
+ export declare const PerspectiveWorkspace: React.MemoExoticComponent<React.ForwardRefExoticComponent<PerspectiveWorkspaceProps & React.RefAttributes<pspWorkspace.HTMLPerspectiveWorkspaceElement | undefined>>>;
22
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-dev/react",
3
- "version": "4.0.1",
3
+ "version": "4.1.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "keywords": [],
@@ -16,6 +16,7 @@
16
16
  "dependencies": {
17
17
  "@perspective-dev/client": "",
18
18
  "@perspective-dev/viewer": "",
19
+ "@perspective-dev/workspace": "",
19
20
  "@types/react": "^18",
20
21
  "react": "^18",
21
22
  "react-dom": "^18"
package/src/index.tsx CHANGED
@@ -20,87 +20,5 @@
20
20
  * @module
21
21
  */
22
22
 
23
- import * as React from "react";
24
- import type * as psp from "@perspective-dev/client";
25
- import type * as pspViewer from "@perspective-dev/viewer";
26
-
27
- function usePspListener<A>(
28
- viewer: HTMLElement | null,
29
- name: string,
30
- f?: (x: A) => void,
31
- ) {
32
- React.useEffect(() => {
33
- if (!f) return;
34
- const ctx = new AbortController();
35
- const callback = (e: Event) => f((e as CustomEvent).detail);
36
- viewer?.addEventListener(name, callback, { signal: ctx.signal });
37
- return () => ctx.abort();
38
- }, [viewer, f]);
39
- }
40
-
41
- export interface PerspectiveViewerProps {
42
- table?: psp.Table | Promise<psp.Table>;
43
- config?: pspViewer.ViewerConfigUpdate;
44
- onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
45
- onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
46
- onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
47
-
48
- // Applicable props from `React.HTMLAttributes`, which we cannot extend
49
- // directly because Perspective changes the signature of `onClick`.
50
- className?: string | undefined;
51
- hidden?: boolean | undefined;
52
- id?: string | undefined;
53
- slot?: string | undefined;
54
- style?: React.CSSProperties | undefined;
55
- tabIndex?: number | undefined;
56
- title?: string | undefined;
57
- }
58
-
59
- function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
60
- const [viewer, setViewer] =
61
- React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);
62
-
63
- React.useEffect(() => {
64
- return () => {
65
- viewer?.delete();
66
- };
67
- }, [viewer]);
68
-
69
- React.useEffect(() => {
70
- if (props.table) {
71
- viewer?.load(props.table);
72
- } else {
73
- viewer?.eject();
74
- }
75
- }, [viewer, props.table]);
76
-
77
- React.useEffect(() => {
78
- if (props.table && props.config) {
79
- viewer?.restore(props.config);
80
- }
81
- }, [viewer, props.table, JSON.stringify(props.config)]);
82
-
83
- usePspListener(viewer, "perspective-click", props.onClick);
84
- usePspListener(viewer, "perspective-select", props.onSelect);
85
- usePspListener(viewer, "perspective-config-update", props.onConfigUpdate);
86
-
87
- return (
88
- <perspective-viewer
89
- ref={setViewer}
90
- id={props.id}
91
- className={props.className}
92
- hidden={props.hidden}
93
- slot={props.slot}
94
- style={props.style}
95
- tabIndex={props.tabIndex}
96
- title={props.title}
97
- />
98
- );
99
- }
100
-
101
- /**
102
- * A React wrapper component for `<perspective-viewer>` Custom Element.
103
- */
104
- export const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(
105
- PerspectiveViewerImpl,
106
- );
23
+ export * from "./viewer";
24
+ export * from "./workspace";
package/src/utils.tsx ADDED
@@ -0,0 +1,27 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import * as React from "react";
14
+
15
+ export function usePspListener<A>(
16
+ el: HTMLElement | undefined | null,
17
+ event: string,
18
+ cb?: (x: A) => void,
19
+ ) {
20
+ React.useEffect(() => {
21
+ if (!cb || !el) return;
22
+ const ctx = new AbortController();
23
+ const callback = (e: Event) => cb((e as CustomEvent).detail);
24
+ el?.addEventListener(event, callback, { signal: ctx.signal });
25
+ return () => ctx.abort();
26
+ }, [el, cb]);
27
+ }
package/src/viewer.tsx ADDED
@@ -0,0 +1,86 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import * as React from "react";
14
+ import type * as psp from "@perspective-dev/client";
15
+ import type * as pspViewer from "@perspective-dev/viewer";
16
+ import { usePspListener } from "./utils";
17
+
18
+ function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
19
+ const [viewer, setViewer] =
20
+ React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);
21
+
22
+ React.useEffect(() => {
23
+ return () => {
24
+ viewer?.delete();
25
+ };
26
+ }, [viewer]);
27
+
28
+ React.useEffect(() => {
29
+ if (props.client) {
30
+ viewer?.load(props.client);
31
+ } else {
32
+ viewer?.eject();
33
+ }
34
+ }, [viewer, props.client]);
35
+
36
+ React.useEffect(() => {
37
+ if (props.client && props.config) {
38
+ viewer?.restore(props.config);
39
+ }
40
+ }, [viewer, props.client, JSON.stringify(props.config)]);
41
+
42
+ usePspListener(viewer, "perspective-click", props.onClick);
43
+ usePspListener(viewer, "perspective-select", props.onSelect);
44
+ usePspListener(viewer, "perspective-config-update", props.onConfigUpdate);
45
+
46
+ return (
47
+ <perspective-viewer
48
+ ref={setViewer}
49
+ id={props.id}
50
+ className={props.className}
51
+ hidden={props.hidden}
52
+ slot={props.slot}
53
+ style={props.style}
54
+ tabIndex={props.tabIndex}
55
+ title={props.title}
56
+ />
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Props for the `<PerspectiveViewer>` component.
62
+ */
63
+ export interface PerspectiveViewerProps {
64
+ client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
65
+ config?: pspViewer.ViewerConfigUpdate;
66
+ onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
67
+ onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
68
+ onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
69
+
70
+ // Applicable props from `React.HTMLAttributes`, which we cannot extend
71
+ // directly because Perspective changes the signature of `onClick`.
72
+ className?: string | undefined;
73
+ hidden?: boolean | undefined;
74
+ id?: string | undefined;
75
+ slot?: string | undefined;
76
+ style?: React.CSSProperties | undefined;
77
+ tabIndex?: number | undefined;
78
+ title?: string | undefined;
79
+ }
80
+
81
+ /**
82
+ * A React wrapper component for `<perspective-viewer>` Custom Element.
83
+ */
84
+ export const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(
85
+ PerspectiveViewerImpl,
86
+ );
@@ -0,0 +1,94 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import type * as psp from "@perspective-dev/client";
14
+ import type * as psp_viewer from "@perspective-dev/viewer";
15
+ import type * as pspWorkspace from "@perspective-dev/workspace";
16
+ import { PerspectiveWorkspaceConfig } from "@perspective-dev/workspace";
17
+ import * as utils from "./utils";
18
+ import * as React from "react";
19
+
20
+ export interface NewViewEventDetail {
21
+ config: psp_viewer.ViewerConfigUpdate;
22
+ widget: pspWorkspace.PerspectiveViewerWidget;
23
+ }
24
+
25
+ export interface ToggleGloalFilterEventDetail {
26
+ widget: pspWorkspace.PerspectiveViewerWidget;
27
+ isGlobalFilter: boolean;
28
+ }
29
+
30
+ interface PerspectiveWorkspaceProps extends React.HTMLAttributes<HTMLElement> {
31
+ client: psp.Client | Promise<psp.Client>;
32
+ layout: PerspectiveWorkspaceConfig;
33
+ onLayoutUpdate?: (layout: PerspectiveWorkspaceConfig) => void;
34
+ onNewView?: (detail: NewViewEventDetail) => void;
35
+ onToggleGlobalFilter?: (detail: ToggleGloalFilterEventDetail) => void;
36
+ }
37
+
38
+ const PerspectiveWorkspaceImpl = React.forwardRef<
39
+ pspWorkspace.HTMLPerspectiveWorkspaceElement | undefined,
40
+ PerspectiveWorkspaceProps
41
+ >(
42
+ (
43
+ {
44
+ client,
45
+ layout,
46
+ onLayoutUpdate,
47
+ onNewView,
48
+ onToggleGlobalFilter,
49
+ ...htmlAttributes
50
+ },
51
+ ref,
52
+ ) => {
53
+ const [workspace, setWorkspace] =
54
+ React.useState<pspWorkspace.HTMLPerspectiveWorkspaceElement>();
55
+
56
+ React.useImperativeHandle(ref, () => workspace, [workspace]);
57
+ React.useEffect(() => {
58
+ if (workspace && layout) {
59
+ workspace.restore(layout);
60
+ }
61
+ }, [workspace, layout]);
62
+
63
+ React.useEffect(() => {
64
+ if (workspace && client) {
65
+ workspace.load(client);
66
+ }
67
+ }, [workspace, client]);
68
+
69
+ utils.usePspListener(workspace, "workspace-new-view", onNewView);
70
+ utils.usePspListener(
71
+ workspace,
72
+ "workspace-layout-update",
73
+ onLayoutUpdate
74
+ ? ({ layout }: { layout: PerspectiveWorkspaceConfig }) =>
75
+ workspace?.save().then((x) => onLayoutUpdate(x))
76
+ : undefined,
77
+ );
78
+
79
+ utils.usePspListener(
80
+ workspace,
81
+ "workspace-toggle-global-filter",
82
+ onToggleGlobalFilter,
83
+ );
84
+
85
+ return (
86
+ <perspective-workspace
87
+ ref={(r) => setWorkspace(r ?? undefined)}
88
+ {...htmlAttributes}
89
+ ></perspective-workspace>
90
+ );
91
+ },
92
+ );
93
+
94
+ export const PerspectiveWorkspace = React.memo(PerspectiveWorkspaceImpl);
@@ -14,6 +14,7 @@ import perspective from "@perspective-dev/client";
14
14
  import perspective_viewer from "@perspective-dev/viewer";
15
15
  import "@perspective-dev/viewer-datagrid";
16
16
  import "@perspective-dev/viewer-d3fc";
17
+ import "@perspective-dev/workspace";
17
18
 
18
19
  // @ts-ignore
19
20
  import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm?url";
@@ -26,12 +27,21 @@ await Promise.all([
26
27
  perspective_viewer.init_client(fetch(CLIENT_WASM)),
27
28
  ]);
28
29
 
29
- import type * as psp from "@perspective-dev/client";
30
+ import * as psp from "@perspective-dev/client";
30
31
  import type * as pspViewer from "@perspective-dev/viewer";
31
32
 
32
33
  // @ts-ignore
33
34
  import SUPERSTORE_ARROW from "superstore-arrow/superstore.lz4.arrow?url";
34
35
 
36
+ import * as React from "react";
37
+ import {
38
+ PerspectiveViewer,
39
+ PerspectiveWorkspace,
40
+ } from "@perspective-dev/react";
41
+
42
+ import "@perspective-dev/viewer/dist/css/themes.css";
43
+ import "./index.css";
44
+
35
45
  const WORKER = await perspective.worker();
36
46
 
37
47
  async function createNewSuperstoreTable(): Promise<psp.Table> {
@@ -45,12 +55,6 @@ const CONFIG: pspViewer.ViewerConfigUpdate = {
45
55
  group_by: ["State"],
46
56
  };
47
57
 
48
- import * as React from "react";
49
- import { PerspectiveViewer } from "@perspective-dev/react";
50
-
51
- import "@perspective-dev/viewer/dist/css/themes.css";
52
- import "./index.css";
53
-
54
58
  interface ToolbarState {
55
59
  mounted: boolean;
56
60
  table?: Promise<psp.Table>;
package/test/js/index.css CHANGED
@@ -52,3 +52,17 @@ button {
52
52
  font-family: "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo",
53
53
  "Consolas", "Liberation Mono", monospace;
54
54
  }
55
+
56
+ .workspace-container {
57
+ display: flex;
58
+ flex-direction: column;
59
+
60
+ .workspace-toolbar {
61
+ display: flex;
62
+ flex-direction: row;
63
+ }
64
+
65
+ perspective-workspace {
66
+ height: 100vh;
67
+ }
68
+ }
@@ -11,10 +11,9 @@
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
13
  import { test, expect } from "@playwright/experimental-ct-react";
14
- import { PerspectiveViewer } from "@perspective-dev/react";
15
- import React from "react";
16
14
 
17
15
  import { App } from "./basic.story";
16
+ import { EmptyWorkspace, SingleView } from "./workspace.story";
18
17
 
19
18
  test.describe("Perspective React", () => {
20
19
  test("The viewer loads with data in it", async ({ page, mount }) => {
@@ -26,4 +25,65 @@ test.describe("Perspective React", () => {
26
25
 
27
26
  expect(count).toBe(2);
28
27
  });
28
+
29
+ test("React workspace functionality", async ({ page, mount }) => {
30
+ const comp = await mount(<EmptyWorkspace />);
31
+ const toggleMount = comp.locator("button.toggle-mount");
32
+ const addViewer = comp.locator("button.add-viewer");
33
+ const workspace = comp.locator("perspective-workspace");
34
+ const viewer = comp.locator("perspective-viewer");
35
+ await toggleMount.waitFor();
36
+ await addViewer.click();
37
+ await addViewer.click();
38
+ await addViewer.click();
39
+ await page.waitForFunction(
40
+ () =>
41
+ document.querySelector("perspective-workspace")!.children
42
+ .length === 3,
43
+ );
44
+
45
+ await expect(viewer).toHaveCount(3);
46
+ await toggleMount.click();
47
+ await workspace.waitFor({ state: "detached" });
48
+ await toggleMount.click();
49
+ await workspace.waitFor();
50
+ await page.waitForFunction(
51
+ () =>
52
+ document.querySelector("perspective-workspace")!.children
53
+ .length === 3,
54
+ );
55
+ await expect(viewer).toHaveCount(3);
56
+ });
57
+
58
+ test("Adding a viewer in single-document mode leaves SDM", async ({
59
+ page,
60
+ mount,
61
+ }) => {
62
+ const name = "abcdef";
63
+ const comp = await mount(<SingleView name={name} />);
64
+ const addViewer = comp.locator("button.add-viewer");
65
+ const viewer = comp.locator("perspective-viewer");
66
+ const settingsBtn = comp.locator(`perspective-viewer #settings_button`);
67
+
68
+ await settingsBtn.waitFor();
69
+ await addViewer.waitFor();
70
+ await addViewer.click();
71
+ await page.waitForFunction(
72
+ () =>
73
+ document.querySelector("perspective-workspace")!.children
74
+ .length === 2,
75
+ );
76
+ expect(await viewer.count()).toBe(2);
77
+ await settingsBtn.first().click();
78
+ const settingsPanel = viewer.locator("#settings_panel");
79
+ await settingsPanel.waitFor();
80
+ await addViewer.click();
81
+ await page.waitForFunction(
82
+ () =>
83
+ document.querySelector("perspective-workspace")!.children
84
+ .length === 3,
85
+ );
86
+ expect(await viewer.count()).toBe(3);
87
+ await settingsPanel.waitFor({ state: "detached" });
88
+ });
29
89
  });
@@ -0,0 +1,154 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+ import "@perspective-dev/workspace";
13
+
14
+ import {
15
+ HTMLPerspectiveWorkspaceElement,
16
+ PerspectiveWorkspaceConfig,
17
+ } from "@perspective-dev/workspace";
18
+
19
+ import * as React from "react";
20
+
21
+ import { PerspectiveWorkspace } from "@perspective-dev/react";
22
+
23
+ import perspective_viewer from "@perspective-dev/viewer";
24
+ import "@perspective-dev/viewer-datagrid";
25
+ import "@perspective-dev/viewer-d3fc";
26
+ import "@perspective-dev/workspace";
27
+ import * as Workspace from "@perspective-dev/workspace";
28
+
29
+ import * as perspective from "@perspective-dev/client";
30
+
31
+ import "@perspective-dev/viewer/dist/css/themes.css";
32
+ import "@perspective-dev/workspace/dist/css/pro.css";
33
+ import "./index.css";
34
+
35
+ // @ts-ignore
36
+ import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm?url";
37
+
38
+ // @ts-ignore
39
+ import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm?url";
40
+
41
+ await Promise.all([
42
+ perspective.init_server(fetch(SERVER_WASM)),
43
+ perspective_viewer.init_client(fetch(CLIENT_WASM)),
44
+ ]);
45
+
46
+ const CLIENT = await perspective.worker();
47
+
48
+ interface WorkspaceState {
49
+ layout: PerspectiveWorkspaceConfig;
50
+ mounted: boolean;
51
+ }
52
+
53
+ interface WorkspaceAppProps {
54
+ layout: PerspectiveWorkspaceConfig;
55
+ onSpecial?: () => void;
56
+ }
57
+
58
+ const WorkspaceApp: React.FC<WorkspaceAppProps> = (props) => {
59
+ const [state, setState] = React.useState<WorkspaceState>({
60
+ layout: props.layout,
61
+ mounted: true,
62
+ });
63
+
64
+ const onClickAddViewer = () => {
65
+ const name = window.crypto.randomUUID();
66
+ const data = `a,b,c\n${Math.random()},${Math.random()},${Math.random()}`;
67
+ CLIENT.table(data, { name });
68
+ const nextId = Workspace.genId(state.layout);
69
+ const layout = Workspace.addViewer(
70
+ state.layout,
71
+ {
72
+ table: name,
73
+ title: name,
74
+ },
75
+ nextId,
76
+ );
77
+
78
+ setState({
79
+ ...state,
80
+ layout,
81
+ });
82
+ };
83
+
84
+ const onClickToggleMount = () =>
85
+ setState((old) => ({ ...old, mounted: !state.mounted }));
86
+
87
+ const onLayoutUpdate = (layout: PerspectiveWorkspaceConfig) => {
88
+ setState({ ...state, layout });
89
+ };
90
+
91
+ React.useEffect(() => {
92
+ setState((s) => ({
93
+ ...s,
94
+ layout: props.layout,
95
+ }));
96
+ }, [props.layout]);
97
+
98
+ return (
99
+ <div className="workspace-container">
100
+ <div className="workspace-toolbar">
101
+ <button className="toggle-mount" onClick={onClickToggleMount}>
102
+ Toggle Mount
103
+ </button>
104
+ <button className="add-viewer" onClick={onClickAddViewer}>
105
+ Add Viewer
106
+ </button>
107
+ {props.onSpecial && (
108
+ <button className="special" onClick={props.onSpecial}>
109
+ Special Third Button
110
+ </button>
111
+ )}
112
+ </div>
113
+ {state.mounted && (
114
+ <PerspectiveWorkspace
115
+ client={CLIENT}
116
+ layout={state.layout}
117
+ onLayoutUpdate={onLayoutUpdate}
118
+ />
119
+ )}
120
+ </div>
121
+ );
122
+ };
123
+
124
+ /// Renders the app with a default empty workspace
125
+ export const EmptyWorkspace: React.FC = () => {
126
+ return (
127
+ <WorkspaceApp
128
+ layout={{ sizes: [1], viewers: {}, detail: { main: null } }}
129
+ />
130
+ );
131
+ };
132
+
133
+ export const SingleView: React.FC<{ name: string }> = ({ name }) => {
134
+ const _table = CLIENT.table("a,b,c\n1,2,3", { name });
135
+ const layout: PerspectiveWorkspaceConfig = {
136
+ sizes: [1],
137
+ detail: {
138
+ main: {
139
+ type: "tab-area",
140
+ currentIndex: 0,
141
+ widgets: [name],
142
+ },
143
+ },
144
+ viewers: {
145
+ [name]: {
146
+ table: name,
147
+ columns: ["a", "b", "c"],
148
+ title: name,
149
+ },
150
+ },
151
+ };
152
+
153
+ return <WorkspaceApp layout={layout} />;
154
+ };