diff --git a/apis/nucleus/src/__tests__/viz.test.js b/apis/nucleus/src/__tests__/viz.test.js index 8c2f1e40e..923742e4a 100644 --- a/apis/nucleus/src/__tests__/viz.test.js +++ b/apis/nucleus/src/__tests__/viz.test.js @@ -18,6 +18,7 @@ describe('viz', () => { let setSnOptions; let setSnContext; let setSnPlugins; + let getExtensionDefinition; let setModel; let takeSnapshot; let exportImage; @@ -33,6 +34,7 @@ describe('viz', () => { setSnOptions = jest.fn(); setSnContext = jest.fn(); setSnPlugins = jest.fn(); + getExtensionDefinition = jest.fn(); setModel = jest.fn(); takeSnapshot = jest.fn(); exportImage = jest.fn(); @@ -44,6 +46,7 @@ describe('viz', () => { setSnOptions, setSnContext, setSnPlugins, + getExtensionDefinition, setModel, takeSnapshot, exportImage, diff --git a/apis/nucleus/src/components/Cell.jsx b/apis/nucleus/src/components/Cell.jsx index cff4f78f7..5c1a3f5fa 100644 --- a/apis/nucleus/src/components/Cell.jsx +++ b/apis/nucleus/src/components/Cell.jsx @@ -39,6 +39,17 @@ const CellBody = { className: 'njs-cell-body', }; +function support(prop, supportObject, layout) { + const value = supportObject[prop]; + if (typeof value === 'function') { + return value.call(null, layout); + } + if (typeof value === 'boolean') { + return value; + } + return false; +} + const initialState = (err) => ({ loading: false, loaded: false, @@ -460,7 +471,6 @@ const Cell = forwardRef( return () => {}; }, [types, state.sn, model, selections, layout, appLayout, language]); - // Long running query useEffect(() => { if (!validating) { @@ -477,6 +487,19 @@ const Cell = forwardRef( getQae() { return state.sn.generator.qae; }, + getExtensionDefinition() { + return state.sn.generator.definition.ext; + }, + // allow input of supportObject ot override when flipped to table + support(type, supportObject, outerLayout) { + if (layout && state.loaded && !state.error) { + const suppObj = supportObject || state.sn.generator.definition.ext?.support; + if (suppObj) { + return support(type, suppObj, outerLayout || layout); + } + } + return false; + }, toggleFocus(active) { if (typeof state.sn.component.focus === 'function') { if (active) { @@ -486,6 +509,9 @@ const Cell = forwardRef( } } }, + setOnBlurHandler(cb) { + focusHandler.current.blurCallback = cb; + }, setSnOptions, setSnPlugins, setModel, diff --git a/apis/nucleus/src/components/NebulaApp.jsx b/apis/nucleus/src/components/NebulaApp.jsx index f66d889a6..52dab25d8 100644 --- a/apis/nucleus/src/components/NebulaApp.jsx +++ b/apis/nucleus/src/components/NebulaApp.jsx @@ -94,7 +94,7 @@ export default function boot({ app, context }) { { toggleFocusOfCells(cellIdToFocus) { Object.keys(cells).forEach((i) => { - cells[i].current.toggleFocus(i === cellIdToFocus); + cells[i].current?.toggleFocus(i === cellIdToFocus); }); }, cells, diff --git a/apis/nucleus/src/viz.js b/apis/nucleus/src/viz.js index b0f317292..84e4ac847 100644 --- a/apis/nucleus/src/viz.js +++ b/apis/nucleus/src/viz.js @@ -25,6 +25,10 @@ export default function viz({ let onMount = null; let onRenderResolve = null; let viewDataObjectId; + let originalExtensionDef; + let originalLayout; + let successfulRender = false; + const mounted = new Promise((resolve) => { onMount = resolve; }); @@ -37,6 +41,7 @@ export default function viz({ override?.(); // from options.onInitialRender onRenderResolve(); // internal promise in viz to wait for render onRender(); // from RenderConfig + successfulRender = true; }; let initialSnOptions = {}; @@ -194,15 +199,26 @@ export default function viz({ }); newModel = await halo.app.createSessionObject(propertyTree.qProperty); viewDataObjectId = newModel.id; + originalExtensionDef = cellRef.current.getExtensionDefinition(); + originalLayout = await model.getLayout(); } else if (viewDataObjectId && showDataView !== true) { newModel = model; await halo.app.destroySessionObject(viewDataObjectId); viewDataObjectId = undefined; + originalExtensionDef = undefined; + originalLayout = undefined; } if (newModel) { cellRef.current.setModel(newModel); } }, + /** + * Whether or not the chart has the data view toggled on. + * @type {boolean} + */ + get viewDataToggled() { + return viewDataObjectId !== undefined; + }, /** * Listens to custom events from inside the visualization. See useEmitter * @param {string} eventName Event name to listen to @@ -237,6 +253,25 @@ export default function viz({ await rendered; return cellRef.current.takeSnapshot(); }, + // ===== undocumented experimental API - use at own risk ====== + /** + * valid types: viewData, cssScaling, snapshot, exportData, exploration + * questionable types: supportRefresh, quickMobile, fullscreen + * deprecated?: sharing + * + */ + support(type) { + if (mountedReference && successfulRender) { + return cellRef.current.support(type, originalExtensionDef?.support, originalLayout); + } + return false; + }, + toggleFocus(focus) { + cellRef.current.toggleFocus(focus); + }, + setOnBlurHandler(cb) { + cellRef.current.setOnBlurHandler(cb); + }, // ===== unexposed experimental API - use at own risk ====== __DO_NOT_USE__: { mount(element) { diff --git a/apis/stardust/api-spec/spec.json b/apis/stardust/api-spec/spec.json index fd7cf1c4b..f263b3219 100644 --- a/apis/stardust/api-spec/spec.json +++ b/apis/stardust/api-spec/spec.json @@ -1413,6 +1413,10 @@ } ] }, + "viewDataToggled": { + "description": "Whether or not the chart has the data view toggled on.", + "type": "boolean" + }, "addListener": { "description": "Listens to custom events from inside the visualization. See useEmitter", "kind": "function", @@ -1723,6 +1727,33 @@ } } }, + "Plugin": { + "description": "An object literal containing meta information about the plugin and a function containing the plugin implementation.", + "stability": "experimental", + "availability": { + "since": "1.2.0" + }, + "kind": "interface", + "entries": { + "info": { + "description": "Object that can hold various meta info about the plugin", + "kind": "object", + "entries": { + "name": { + "description": "The name of the plugin", + "type": "string" + } + } + }, + "fn": { + "description": "The implementation of the plugin. Input and return value is up to the plugin implementation to decide based on its purpose.", + "type": "function" + } + }, + "examples": [ + "const plugin = {\n info: {\n name: \"example-plugin\",\n type: \"meta-type\",\n },\n fn: () => {\n // Plugin implementation goes here\n }\n};" + ] + }, "Field": { "kind": "alias", "items": { @@ -1874,33 +1905,6 @@ } } }, - "Plugin": { - "description": "An object literal containing meta information about the plugin and a function containing the plugin implementation.", - "stability": "experimental", - "availability": { - "since": "1.2.0" - }, - "kind": "interface", - "entries": { - "info": { - "description": "Object that can hold various meta info about the plugin", - "kind": "object", - "entries": { - "name": { - "description": "The name of the plugin", - "type": "string" - } - } - }, - "fn": { - "description": "The implementation of the plugin. Input and return value is up to the plugin implementation to decide based on its purpose.", - "type": "function" - } - }, - "examples": [ - "const plugin = {\n info: {\n name: \"example-plugin\",\n type: \"meta-type\",\n },\n fn: () => {\n // Plugin implementation goes here\n }\n};" - ] - }, "LoadType": { "kind": "interface", "params": [ diff --git a/apis/stardust/types/index.d.ts b/apis/stardust/types/index.d.ts index 4cc6046ee..9a61501f8 100644 --- a/apis/stardust/types/index.d.ts +++ b/apis/stardust/types/index.d.ts @@ -453,6 +453,8 @@ declare namespace stardust { */ toggleDataView(showDataView?: boolean): void; + viewDataToggled: boolean; + /** * Listens to custom events from inside the visualization. See useEmitter * @param eventName Event name to listen to @@ -556,6 +558,16 @@ declare namespace stardust { } + /** + * An object literal containing meta information about the plugin and a function containing the plugin implementation. + */ + interface Plugin { + info: { + name: string; + }; + fn: ()=>void; + } + type Field = string | qix.NxDimension | qix.NxMeasure | stardust.LibraryField; /** @@ -597,16 +609,6 @@ declare namespace stardust { type: "dimension" | "measure"; } - /** - * An object literal containing meta information about the plugin and a function containing the plugin implementation. - */ - interface Plugin { - info: { - name: string; - }; - fn: ()=>void; - } - interface LoadType { (type: { name: string; diff --git a/apis/supernova/src/creator.js b/apis/supernova/src/creator.js index 6b2b5fac9..862715490 100644 --- a/apis/supernova/src/creator.js +++ b/apis/supernova/src/creator.js @@ -216,6 +216,11 @@ function createWithHooks(generator, opts, galaxy) { return generator.component.runMenu(this, menu, event, menuBuilder); }, focus() { + const ref = generator.component.getImperativeHandle(this); + if (ref && typeof ref.focus === 'function') { + ref.focus(); + return; + } generator.component.focus(this); }, blur() {