@thoughtspot/visual-embed-sdk 1.49.0 → 1.49.2
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/cjs/package.json +1 -1
- package/cjs/src/css-variables.d.ts +17 -0
- package/cjs/src/css-variables.d.ts.map +1 -1
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +4 -1
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +97 -0
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/conversation.d.ts +8 -0
- package/cjs/src/embed/conversation.d.ts.map +1 -1
- package/cjs/src/embed/conversation.js +2 -1
- package/cjs/src/embed/conversation.js.map +1 -1
- package/cjs/src/embed/conversation.spec.js +12 -0
- package/cjs/src/embed/conversation.spec.js.map +1 -1
- package/cjs/src/embed/events.spec.js +5 -0
- package/cjs/src/embed/events.spec.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +4 -1
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +11 -0
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts +38 -0
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +168 -11
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +237 -0
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/mixpanel-service.js +2 -2
- package/cjs/src/mixpanel-service.js.map +1 -1
- package/cjs/src/types.d.ts +87 -4
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +47 -3
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/logger.js +2 -2
- package/cjs/src/utils/logger.js.map +1 -1
- package/cjs/src/utils/processTrigger.d.ts.map +1 -1
- package/cjs/src/utils/processTrigger.js +6 -5
- package/cjs/src/utils/processTrigger.js.map +1 -1
- package/cjs/src/utils/processTrigger.spec.js +13 -0
- package/cjs/src/utils/processTrigger.spec.js.map +1 -1
- package/cjs/src/utils/sdk-version.d.ts +2 -0
- package/cjs/src/utils/sdk-version.d.ts.map +1 -0
- package/cjs/src/utils/sdk-version.js +7 -0
- package/cjs/src/utils/sdk-version.js.map +1 -0
- package/cjs/src/utils.d.ts +10 -0
- package/cjs/src/utils.d.ts.map +1 -1
- package/cjs/src/utils.js +23 -1
- package/cjs/src/utils.js.map +1 -1
- package/cjs/src/utils.spec.js +48 -0
- package/cjs/src/utils.spec.js.map +1 -1
- package/dist/{index-_UGCSSDR.js → index-CS7yJ2Jj.js} +1 -1
- package/dist/src/css-variables.d.ts +17 -0
- package/dist/src/css-variables.d.ts.map +1 -1
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/conversation.d.ts +8 -0
- package/dist/src/embed/conversation.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.d.ts +38 -0
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/types.d.ts +87 -4
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/processTrigger.d.ts.map +1 -1
- package/dist/src/utils/sdk-version.d.ts +2 -0
- package/dist/src/utils/sdk-version.d.ts.map +1 -0
- package/dist/src/utils.d.ts +10 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +255 -23
- package/dist/tsembed-react.js +254 -22
- package/dist/tsembed.es.js +255 -23
- package/dist/tsembed.js +254 -22
- package/dist/visual-embed-sdk-react-full.d.ts +148 -3
- package/dist/visual-embed-sdk-react.d.ts +148 -3
- package/dist/visual-embed-sdk.d.ts +148 -3
- package/lib/package.json +1 -1
- package/lib/src/css-variables.d.ts +17 -0
- package/lib/src/css-variables.d.ts.map +1 -1
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +4 -1
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +97 -0
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/conversation.d.ts +8 -0
- package/lib/src/embed/conversation.d.ts.map +1 -1
- package/lib/src/embed/conversation.js +2 -1
- package/lib/src/embed/conversation.js.map +1 -1
- package/lib/src/embed/conversation.spec.js +12 -0
- package/lib/src/embed/conversation.spec.js.map +1 -1
- package/lib/src/embed/events.spec.js +5 -0
- package/lib/src/embed/events.spec.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +4 -1
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +11 -0
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +38 -0
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +168 -11
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +237 -0
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/mixpanel-service.js +1 -1
- package/lib/src/mixpanel-service.js.map +1 -1
- package/lib/src/types.d.ts +87 -4
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +47 -3
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/logger.js +1 -1
- package/lib/src/utils/logger.js.map +1 -1
- package/lib/src/utils/processTrigger.d.ts.map +1 -1
- package/lib/src/utils/processTrigger.js +6 -5
- package/lib/src/utils/processTrigger.js.map +1 -1
- package/lib/src/utils/processTrigger.spec.js +13 -0
- package/lib/src/utils/processTrigger.spec.js.map +1 -1
- package/lib/src/utils/sdk-version.d.ts +2 -0
- package/lib/src/utils/sdk-version.d.ts.map +1 -0
- package/lib/src/utils/sdk-version.js +3 -0
- package/lib/src/utils/sdk-version.js.map +1 -0
- package/lib/src/utils.d.ts +10 -0
- package/lib/src/utils.d.ts.map +1 -1
- package/lib/src/utils.js +21 -0
- package/lib/src/utils.js.map +1 -1
- package/lib/src/utils.spec.js +49 -1
- package/lib/src/utils.spec.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +148 -3
- package/package.json +1 -1
- package/src/css-variables.ts +20 -0
- package/src/embed/app.spec.ts +109 -0
- package/src/embed/app.ts +4 -0
- package/src/embed/conversation.spec.ts +17 -0
- package/src/embed/conversation.ts +10 -0
- package/src/embed/events.spec.ts +7 -0
- package/src/embed/liveboard.spec.ts +15 -0
- package/src/embed/liveboard.ts +4 -0
- package/src/embed/ts-embed.spec.ts +294 -0
- package/src/embed/ts-embed.ts +181 -10
- package/src/mixpanel-service.ts +1 -1
- package/src/types.ts +86 -3
- package/src/utils/logger.ts +1 -1
- package/src/utils/processTrigger.spec.ts +23 -0
- package/src/utils/processTrigger.ts +8 -6
- package/src/utils/sdk-version.ts +3 -0
- package/src/utils.spec.ts +63 -0
- package/src/utils.ts +24 -0
package/src/embed/ts-embed.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
getCustomisations,
|
|
29
29
|
getRuntimeFilters,
|
|
30
30
|
getDOMNode,
|
|
31
|
+
querySelectorAcrossShadowRoot,
|
|
31
32
|
getFilterQuery,
|
|
32
33
|
getQueryParamString,
|
|
33
34
|
getRuntimeParameters,
|
|
@@ -68,7 +69,7 @@ import {
|
|
|
68
69
|
} from '../types';
|
|
69
70
|
import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
|
|
70
71
|
import { processEventData, processAuthFailure } from '../utils/processData';
|
|
71
|
-
import { version } from '
|
|
72
|
+
import { version } from '../utils/sdk-version';
|
|
72
73
|
import {
|
|
73
74
|
getAuthPromise,
|
|
74
75
|
renderInQueue,
|
|
@@ -95,6 +96,14 @@ import {
|
|
|
95
96
|
*/
|
|
96
97
|
export const THOUGHTSPOT_PARAM_PREFIX = 'ts-';
|
|
97
98
|
const TS_EMBED_ID = '_thoughtspot-embed';
|
|
99
|
+
/**
|
|
100
|
+
* dataset key used to stash a custom preRenderContainer's original inline
|
|
101
|
+
* `position` while we override it to `relative`. Stored on the container (not
|
|
102
|
+
* per-instance) so the override can be reverted on destroy even when multiple
|
|
103
|
+
* pre-rendered embeds share the same container.
|
|
104
|
+
*/
|
|
105
|
+
const PRERENDER_CONTAINER_ORIGINAL_POSITION_KEY = 'tsEmbedOriginalPosition';
|
|
106
|
+
const PRERENDER_WRAPPER_ID_PREFIX = 'tsEmbed-pre-render-wrapper-';
|
|
98
107
|
|
|
99
108
|
/**
|
|
100
109
|
* The event id map from v2 event names to v1 event id
|
|
@@ -197,6 +206,10 @@ export class TsEmbed {
|
|
|
197
206
|
|
|
198
207
|
private resizeObserver: ResizeObserver;
|
|
199
208
|
|
|
209
|
+
private preRenderContainerEl: HTMLElement | null = null;
|
|
210
|
+
|
|
211
|
+
private containerScrollListener: (() => void) | null = null;
|
|
212
|
+
|
|
200
213
|
protected hostEventClient: HostEventClient;
|
|
201
214
|
|
|
202
215
|
protected isReadyForRenderPromise;
|
|
@@ -1152,6 +1165,91 @@ export class TsEmbed {
|
|
|
1152
1165
|
return placeholder;
|
|
1153
1166
|
}
|
|
1154
1167
|
|
|
1168
|
+
/**
|
|
1169
|
+
* Resolves the configured preRenderContainer to a live element, falling
|
|
1170
|
+
* back to `document.body`. A string selector is re-queried on every call so
|
|
1171
|
+
* a remounted container (E.g.: React replacing the node) resolves to the
|
|
1172
|
+
* fresh element; an element passed directly cannot be re-resolved.
|
|
1173
|
+
*/
|
|
1174
|
+
private resolvePreRenderContainerTarget(): HTMLElement {
|
|
1175
|
+
const containerConfig = this.viewConfig.preRenderContainer;
|
|
1176
|
+
let container: Element | null = null;
|
|
1177
|
+
if (typeof containerConfig === 'string') {
|
|
1178
|
+
try {
|
|
1179
|
+
// Resolve against the host's shadow root too, so a selector can
|
|
1180
|
+
// target a container inside the same shadow DOM as the embed —
|
|
1181
|
+
// document.querySelector alone cannot pierce shadow boundaries.
|
|
1182
|
+
container = querySelectorAcrossShadowRoot(containerConfig, this.hostElement);
|
|
1183
|
+
} catch (e) {
|
|
1184
|
+
logger.error(`Invalid CSS selector for preRenderContainer: ${containerConfig}`, e);
|
|
1185
|
+
}
|
|
1186
|
+
} else if (containerConfig) {
|
|
1187
|
+
container = containerConfig;
|
|
1188
|
+
}
|
|
1189
|
+
return (container as HTMLElement) ?? document.body;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Makes the container a positioning context for the absolutely positioned
|
|
1194
|
+
* wrapper, stashing the original inline `position` on the element (once) so
|
|
1195
|
+
* destroy() can restore it exactly, leaving no trace. Recording it on the
|
|
1196
|
+
* element rather than per-instance lets the override be reverted even when
|
|
1197
|
+
* embeds share the same container.
|
|
1198
|
+
*/
|
|
1199
|
+
private applyPreRenderContainerPositioning(container: HTMLElement): void {
|
|
1200
|
+
if (container === document.body) {
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
const pos = window.getComputedStyle(container).position;
|
|
1204
|
+
if (pos === 'static') {
|
|
1205
|
+
if (container.dataset[PRERENDER_CONTAINER_ORIGINAL_POSITION_KEY] === undefined) {
|
|
1206
|
+
container.dataset[
|
|
1207
|
+
PRERENDER_CONTAINER_ORIGINAL_POSITION_KEY
|
|
1208
|
+
] = container.style.position;
|
|
1209
|
+
}
|
|
1210
|
+
container.style.position = 'relative';
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Re-attaches the wrapper to a live container when the previously resolved
|
|
1216
|
+
* one has been detached or no longer holds the wrapper — e.g. the host app
|
|
1217
|
+
* remounted a custom preRenderContainer, which would otherwise leave a stale
|
|
1218
|
+
* reference and collapse the wrapper. Only string selectors can be
|
|
1219
|
+
* re-resolved; a container passed as an element is left untouched.
|
|
1220
|
+
*/
|
|
1221
|
+
private reconcilePreRenderContainer(): void {
|
|
1222
|
+
const wrapper = this.preRenderWrapper;
|
|
1223
|
+
const stored = this.preRenderContainerEl;
|
|
1224
|
+
// Nothing to reconcile until this instance resolved its container.
|
|
1225
|
+
if (!wrapper || !stored) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const storedIsLive = stored === document.body || document.contains(stored);
|
|
1229
|
+
if (storedIsLive && stored.contains(wrapper)) {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const resolved = this.resolvePreRenderContainerTarget();
|
|
1233
|
+
// Re-resolution yielded the same (still stale) element — nothing we can
|
|
1234
|
+
// do, e.g. a detached container passed as an HTMLElement.
|
|
1235
|
+
if (resolved === stored && stored.contains(wrapper)) {
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (this.containerScrollListener && stored !== resolved) {
|
|
1239
|
+
if (stored !== document.body) {
|
|
1240
|
+
stored.removeEventListener('scroll', this.containerScrollListener);
|
|
1241
|
+
}
|
|
1242
|
+
if (resolved !== document.body) {
|
|
1243
|
+
resolved.addEventListener('scroll', this.containerScrollListener);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
this.applyPreRenderContainerPositioning(resolved);
|
|
1247
|
+
if (wrapper.parentNode !== resolved) {
|
|
1248
|
+
resolved.appendChild(wrapper);
|
|
1249
|
+
}
|
|
1250
|
+
this.preRenderContainerEl = resolved;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1155
1253
|
protected insertIntoDOMForPreRender(child: string | Node): void {
|
|
1156
1254
|
const preRenderChild = this.createPreRenderChild(child);
|
|
1157
1255
|
const preRenderWrapper = this.createPreRenderWrapper();
|
|
@@ -1175,7 +1273,10 @@ export class TsEmbed {
|
|
|
1175
1273
|
this.hidePreRender();
|
|
1176
1274
|
}
|
|
1177
1275
|
|
|
1178
|
-
|
|
1276
|
+
const targetContainer = this.resolvePreRenderContainerTarget();
|
|
1277
|
+
this.preRenderContainerEl = targetContainer;
|
|
1278
|
+
this.applyPreRenderContainerPositioning(targetContainer);
|
|
1279
|
+
targetContainer.appendChild(preRenderWrapper);
|
|
1179
1280
|
}
|
|
1180
1281
|
|
|
1181
1282
|
private showPreRenderByDefault = false;
|
|
@@ -1735,6 +1836,55 @@ export class TsEmbed {
|
|
|
1735
1836
|
return tsParams;
|
|
1736
1837
|
}
|
|
1737
1838
|
|
|
1839
|
+
/**
|
|
1840
|
+
* Reverts the custom preRenderContainer's `position` to the value it had
|
|
1841
|
+
* before we overrode it to `relative` (see insertIntoDOMForPreRender).
|
|
1842
|
+
*
|
|
1843
|
+
* We restore the original inline value rather than forcing `static`, and we
|
|
1844
|
+
* skip the restore if another preRender wrapper is still mounted inside the
|
|
1845
|
+
* same container — a shared container still needs the positioning context.
|
|
1846
|
+
*/
|
|
1847
|
+
private restorePreRenderContainerPosition(): void {
|
|
1848
|
+
const container = this.preRenderContainerEl as HTMLElement | null;
|
|
1849
|
+
if (!container || container === document.body) {
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
// Drop our reference up front so a destroyed embed never pins a
|
|
1853
|
+
// detached container in memory; restoration uses the local handle.
|
|
1854
|
+
this.preRenderContainerEl = null;
|
|
1855
|
+
const originalPosition = container.dataset[PRERENDER_CONTAINER_ORIGINAL_POSITION_KEY];
|
|
1856
|
+
if (originalPosition === undefined) {
|
|
1857
|
+
// We never overrode this container's position; nothing to restore.
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
// This instance's own wrapper has already been removed by now, so any
|
|
1861
|
+
// match here belongs to another embed still sharing the container — it
|
|
1862
|
+
// continues to rely on the positioning context, so leave it in place.
|
|
1863
|
+
const hasOtherWrapper = container.querySelector(
|
|
1864
|
+
`[id^="${PRERENDER_WRAPPER_ID_PREFIX}"]`,
|
|
1865
|
+
);
|
|
1866
|
+
if (hasOtherWrapper) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
container.style.position = originalPosition;
|
|
1870
|
+
delete container.dataset[PRERENDER_CONTAINER_ORIGINAL_POSITION_KEY];
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* Detaches and clears the container scroll listener, if one is attached.
|
|
1875
|
+
*/
|
|
1876
|
+
private removeContainerScrollListener(): void {
|
|
1877
|
+
if (!this.containerScrollListener) {
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
const customContainer =
|
|
1881
|
+
this.preRenderContainerEl && this.preRenderContainerEl !== document.body
|
|
1882
|
+
? this.preRenderContainerEl
|
|
1883
|
+
: null;
|
|
1884
|
+
customContainer?.removeEventListener('scroll', this.containerScrollListener);
|
|
1885
|
+
this.containerScrollListener = null;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1738
1888
|
/**
|
|
1739
1889
|
* Destroys the ThoughtSpot embed, and remove any nodes from the DOM.
|
|
1740
1890
|
* @version SDK: 1.19.1 | ThoughtSpot: *
|
|
@@ -1742,8 +1892,10 @@ export class TsEmbed {
|
|
|
1742
1892
|
public destroy(): void {
|
|
1743
1893
|
try {
|
|
1744
1894
|
this.removeFullscreenChangeHandler();
|
|
1895
|
+
this.removeContainerScrollListener();
|
|
1745
1896
|
this.unsubscribeToEvents();
|
|
1746
1897
|
this.preRenderWrapper?.remove();
|
|
1898
|
+
this.restorePreRenderContainerPosition();
|
|
1747
1899
|
if (!this.isRendered) {
|
|
1748
1900
|
return;
|
|
1749
1901
|
}
|
|
@@ -1856,15 +2008,21 @@ export class TsEmbed {
|
|
|
1856
2008
|
|
|
1857
2009
|
this.syncPreRenderStyle();
|
|
1858
2010
|
|
|
2011
|
+
const customContainer =
|
|
2012
|
+
this.preRenderContainerEl && this.preRenderContainerEl !== document.body
|
|
2013
|
+
? this.preRenderContainerEl
|
|
2014
|
+
: null;
|
|
2015
|
+
if (customContainer && !this.containerScrollListener) {
|
|
2016
|
+
this.containerScrollListener = () => this.syncPreRenderStyle();
|
|
2017
|
+
customContainer.addEventListener('scroll', this.containerScrollListener);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
1859
2020
|
if (!this.viewConfig.doNotTrackPreRenderSize) {
|
|
1860
2021
|
const observeTarget = (this.insertedDomEl as HTMLElement) ?? this.hostElement;
|
|
1861
2022
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
1862
2023
|
entries.forEach((entry) => {
|
|
1863
|
-
if (entry.
|
|
1864
|
-
|
|
1865
|
-
width: `${entry.contentRect.width}px`,
|
|
1866
|
-
height: `${entry.contentRect.height}px`,
|
|
1867
|
-
});
|
|
2024
|
+
if (entry.target === observeTarget) {
|
|
2025
|
+
this.syncPreRenderStyle();
|
|
1868
2026
|
}
|
|
1869
2027
|
});
|
|
1870
2028
|
});
|
|
@@ -1905,11 +2063,22 @@ export class TsEmbed {
|
|
|
1905
2063
|
logger.error(ERROR_MESSAGE.SYNC_STYLE_CALLED_BEFORE_RENDER);
|
|
1906
2064
|
return;
|
|
1907
2065
|
}
|
|
2066
|
+
// Self-heal if the resolved container was remounted/detached, so we
|
|
2067
|
+
// never measure a stale node (which would collapse the wrapper).
|
|
2068
|
+
this.reconcilePreRenderContainer();
|
|
1908
2069
|
const elBoundingClient = this.getPreRenderPlaceHolderElement().getBoundingClientRect();
|
|
1909
2070
|
|
|
2071
|
+
const containerEl =
|
|
2072
|
+
this.preRenderContainerEl && this.preRenderContainerEl !== document.body
|
|
2073
|
+
? this.preRenderContainerEl
|
|
2074
|
+
: null;
|
|
2075
|
+
const containerRect = containerEl?.getBoundingClientRect() ?? { x: 0, y: 0 };
|
|
2076
|
+
const scrollX = containerEl ? containerEl.scrollLeft : window.scrollX;
|
|
2077
|
+
const scrollY = containerEl ? containerEl.scrollTop : window.scrollY;
|
|
2078
|
+
|
|
1910
2079
|
setStyleProperties(this.preRenderWrapper, {
|
|
1911
|
-
top: `${elBoundingClient.y +
|
|
1912
|
-
left: `${elBoundingClient.x +
|
|
2080
|
+
top: `${elBoundingClient.y - containerRect.y + scrollY}px`,
|
|
2081
|
+
left: `${elBoundingClient.x - containerRect.x + scrollX}px`,
|
|
1913
2082
|
width: `${elBoundingClient.width}px`,
|
|
1914
2083
|
height: `${elBoundingClient.height}px`,
|
|
1915
2084
|
position: 'absolute',
|
|
@@ -1938,6 +2107,8 @@ export class TsEmbed {
|
|
|
1938
2107
|
};
|
|
1939
2108
|
setStyleProperties(this.preRenderWrapper, preRenderHideStyles);
|
|
1940
2109
|
|
|
2110
|
+
this.removeContainerScrollListener();
|
|
2111
|
+
|
|
1941
2112
|
if (this.resizeObserver) {
|
|
1942
2113
|
this.resizeObserver.disconnect();
|
|
1943
2114
|
}
|
|
@@ -1959,7 +2130,7 @@ export class TsEmbed {
|
|
|
1959
2130
|
*/
|
|
1960
2131
|
public getPreRenderIds() {
|
|
1961
2132
|
return {
|
|
1962
|
-
wrapper:
|
|
2133
|
+
wrapper: `${PRERENDER_WRAPPER_ID_PREFIX}${this.viewConfig.preRenderId}`,
|
|
1963
2134
|
child: `tsEmbed-pre-render-child-${this.viewConfig.preRenderId}`,
|
|
1964
2135
|
placeHolder: `tsEmbed-pre-render-placeholder-${this.viewConfig.preRenderId}`,
|
|
1965
2136
|
};
|
package/src/mixpanel-service.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as mixpanel from 'mixpanel-browser';
|
|
|
2
2
|
import { logger } from './utils/logger';
|
|
3
3
|
import { SessionInfo } from './utils/sessionInfoService';
|
|
4
4
|
import { ERROR_MESSAGE } from './errors';
|
|
5
|
-
import { version } from '
|
|
5
|
+
import { version } from './utils/sdk-version';
|
|
6
6
|
|
|
7
7
|
export const EndPoints = {
|
|
8
8
|
CONFIG: '/callosum/v1/system/config',
|
package/src/types.ts
CHANGED
|
@@ -1108,6 +1108,45 @@ export interface BaseViewConfig extends ApiInterceptFlags {
|
|
|
1108
1108
|
* ```
|
|
1109
1109
|
*/
|
|
1110
1110
|
doNotTrackPreRenderSize?: boolean;
|
|
1111
|
+
/**
|
|
1112
|
+
* The DOM element or CSS selector string specifying the container into which
|
|
1113
|
+
* the pre-rendered wrapper is inserted. Defaults to `document.body` when not
|
|
1114
|
+
* specified.
|
|
1115
|
+
*
|
|
1116
|
+
* **When to use a target container:** set this when the pre-render should
|
|
1117
|
+
* live somewhere other than `document.body` — for example when `<body>` has
|
|
1118
|
+
* `overflow: hidden` and scrolling is handled by an inner element, or when
|
|
1119
|
+
* you need the wrapper to sit inside a specific stacking/positioning context.
|
|
1120
|
+
* The wrapper is positioned to track the embedding element within this
|
|
1121
|
+
* container, so choose the scrollable/positioned ancestor you want it
|
|
1122
|
+
* aligned to.
|
|
1123
|
+
*
|
|
1124
|
+
* **Pass a stable container:** the wrapper is mounted into the resolved
|
|
1125
|
+
* container once, during `preRender()`. The container must stay mounted for
|
|
1126
|
+
* the lifetime of the pre-render — if the host app unmounts and remounts it
|
|
1127
|
+
* (for example React replacing the node), the wrapper is orphaned on the
|
|
1128
|
+
* detached node. Mount the container above the part of the tree that
|
|
1129
|
+
* re-renders so its identity is stable. Prefer a CSS selector string over a
|
|
1130
|
+
* raw element: a selector can be re-resolved to the fresh node on the next
|
|
1131
|
+
* reposition, whereas an element reference cannot be recovered once detached.
|
|
1132
|
+
*
|
|
1133
|
+
* When the embed host lives inside a Shadow DOM, a selector string is
|
|
1134
|
+
* resolved against that shadow root as well (since `document.querySelector`
|
|
1135
|
+
* cannot pierce shadow boundaries); pass the element directly if the
|
|
1136
|
+
* container lives in a different root.
|
|
1137
|
+
*
|
|
1138
|
+
* @type {string | HTMLElement}
|
|
1139
|
+
* @version SDK: 1.49.2 | ThoughtSpot: *
|
|
1140
|
+
* @example
|
|
1141
|
+
* ```js
|
|
1142
|
+
* const embed = new LiveboardEmbed('#tsEmbed', {
|
|
1143
|
+
* preRenderId: 'my-liveboard',
|
|
1144
|
+
* // Prefer a selector for a stable, scrollable container.
|
|
1145
|
+
* preRenderContainer: '#my-scroll-container',
|
|
1146
|
+
* });
|
|
1147
|
+
* ```
|
|
1148
|
+
*/
|
|
1149
|
+
preRenderContainer?: string | HTMLElement;
|
|
1111
1150
|
/**
|
|
1112
1151
|
* Enable the V2 shell. This can provide performance benefits
|
|
1113
1152
|
* due to a lighter-weight shell.
|
|
@@ -4150,11 +4189,30 @@ export enum HostEvent {
|
|
|
4150
4189
|
/**
|
|
4151
4190
|
* Navigate to a specific page in the embedded ThoughtSpot application.
|
|
4152
4191
|
* This is the same as calling `appEmbed.navigateToPage(path, true)`.
|
|
4153
|
-
*
|
|
4154
|
-
*
|
|
4192
|
+
*
|
|
4193
|
+
* Accepts either a plain value or an object with `path`
|
|
4194
|
+
* and an optional `replace` flag.
|
|
4195
|
+
*
|
|
4196
|
+
* @param data - A string path, a numeric history delta, or an object
|
|
4197
|
+
* `{ path: string | number, replace?: boolean }`.
|
|
4198
|
+
* - `path` — the route to navigate to, or a history delta such as `1`
|
|
4199
|
+
* or `-1` (calls `window.history.go()`).
|
|
4200
|
+
* - `replace` — when `true`, replaces the current history entry instead
|
|
4201
|
+
* of pushing a new one (uses `window.location.replace`).
|
|
4202
|
+
*
|
|
4155
4203
|
* @example
|
|
4156
4204
|
* ```js
|
|
4157
|
-
*
|
|
4205
|
+
* // Preferred: use navigateToPage directly
|
|
4206
|
+
* appEmbed.navigateToPage(-1);
|
|
4207
|
+
*
|
|
4208
|
+
* // Numeric delta — go back one step
|
|
4209
|
+
* appEmbed.trigger(HostEvent.Navigate, -1);
|
|
4210
|
+
*
|
|
4211
|
+
* // String path — push a new history entry
|
|
4212
|
+
* appEmbed.trigger(HostEvent.Navigate, 'home');
|
|
4213
|
+
*
|
|
4214
|
+
* // Object format — replace current history entry
|
|
4215
|
+
* appEmbed.trigger(HostEvent.Navigate, { path: 'home', replace: true }); // SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
|
|
4158
4216
|
* ```
|
|
4159
4217
|
* @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1.sw
|
|
4160
4218
|
*/
|
|
@@ -6149,6 +6207,7 @@ export enum Param {
|
|
|
6149
6207
|
EnableLiveboardDataCache = 'enableLiveboardDataCache',
|
|
6150
6208
|
SpotterFileUploadEnabled = 'spotterFileUploadEnabled',
|
|
6151
6209
|
SpotterFileUploadFileTypes = 'spotterFileUploadFileTypes',
|
|
6210
|
+
IsStarterPromptsEnabled = 'enableStarterPrompts',
|
|
6152
6211
|
}
|
|
6153
6212
|
|
|
6154
6213
|
/**
|
|
@@ -6801,6 +6860,16 @@ export enum Action {
|
|
|
6801
6860
|
* @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1.sw
|
|
6802
6861
|
*/
|
|
6803
6862
|
AddToFavorites = 'addToFavorites',
|
|
6863
|
+
/**
|
|
6864
|
+
* Remove from Favorites option in home page v3 sidebar.
|
|
6865
|
+
* Allows removing an object from the user's favorites list from home-page v3 sidebar.
|
|
6866
|
+
* @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
|
|
6867
|
+
* @example
|
|
6868
|
+
* ```js
|
|
6869
|
+
* disabledActions: [Action.RemoveFromFavorites]
|
|
6870
|
+
* ```
|
|
6871
|
+
*/
|
|
6872
|
+
RemoveFromFavorites = 'removeFromFavorites',
|
|
6804
6873
|
/**
|
|
6805
6874
|
* The edit icon on Liveboards (Classic experience).
|
|
6806
6875
|
* @example
|
|
@@ -7879,6 +7948,20 @@ export enum Action {
|
|
|
7879
7948
|
* ```
|
|
7880
7949
|
*/
|
|
7881
7950
|
SpotterViz = 'spotterViz',
|
|
7951
|
+
/**
|
|
7952
|
+
* The reference-mode toggle button inside the SpotterViz chat input.
|
|
7953
|
+
* When users enable reference mode, clicking tiles, filters, or
|
|
7954
|
+
* parameters adds them as chat context. Hide this action to remove
|
|
7955
|
+
* the reference-mode capability entirely.
|
|
7956
|
+
* Visible by default.
|
|
7957
|
+
* @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
|
|
7958
|
+
* @example
|
|
7959
|
+
* ```js
|
|
7960
|
+
* hiddenActions: [Action.SpotterVizReferenceMode]
|
|
7961
|
+
* disabledActions: [Action.SpotterVizReferenceMode]
|
|
7962
|
+
* ```
|
|
7963
|
+
*/
|
|
7964
|
+
SpotterVizReferenceMode = 'spotterVizReferenceMode',
|
|
7882
7965
|
/**
|
|
7883
7966
|
* Clears browser cache and fetches new data for liveboard ChartViz Containers.
|
|
7884
7967
|
* Requires `enableLiveboardDataCache` to be enabled.
|
package/src/utils/logger.ts
CHANGED
|
@@ -94,6 +94,29 @@ describe('Unit test for processTrigger', () => {
|
|
|
94
94
|
await expect(triggerPromise).resolves.toEqual(null);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
test('should clear the timeout when response arrives before TRIGGER_TIMEOUT', async () => {
|
|
98
|
+
const messageType = HostEvent.Search;
|
|
99
|
+
const thoughtSpotHost = 'http://localhost:3000';
|
|
100
|
+
const data = {};
|
|
101
|
+
mockMessageChannel();
|
|
102
|
+
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
|
|
103
|
+
|
|
104
|
+
const triggerPromise = _processTriggerInstance.processTrigger(
|
|
105
|
+
iFrame,
|
|
106
|
+
messageType,
|
|
107
|
+
thoughtSpotHost,
|
|
108
|
+
data,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const res = { data: { test: '123' } };
|
|
112
|
+
messageChannelMock.port1.onmessage(res);
|
|
113
|
+
|
|
114
|
+
expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
115
|
+
await expect(triggerPromise).resolves.toEqual(res.data);
|
|
116
|
+
|
|
117
|
+
clearTimeoutSpy.mockRestore();
|
|
118
|
+
});
|
|
119
|
+
|
|
97
120
|
test('should close channel.port1 when timeout exceeds TRIGGER_TIMEOUT', async () => {
|
|
98
121
|
const messageType = HostEvent.Search;
|
|
99
122
|
const thoughtSpotHost = 'http://localhost:3000';
|
|
@@ -70,7 +70,15 @@ export function processTrigger(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const channel = new MessageChannel();
|
|
73
|
+
|
|
74
|
+
// Close the messageChannel and resolve the promise if timeout.
|
|
75
|
+
const timeoutId = setTimeout(() => {
|
|
76
|
+
channel.port1.close();
|
|
77
|
+
res(new Error(ERROR_MESSAGE.TRIGGER_TIMED_OUT));
|
|
78
|
+
}, TRIGGER_TIMEOUT);
|
|
79
|
+
|
|
73
80
|
channel.port1.onmessage = ({ data: responseData }) => {
|
|
81
|
+
clearTimeout(timeoutId);
|
|
74
82
|
channel.port1.close();
|
|
75
83
|
const error = responseData?.error || responseData?.data?.error;
|
|
76
84
|
if (error) {
|
|
@@ -80,12 +88,6 @@ export function processTrigger(
|
|
|
80
88
|
}
|
|
81
89
|
};
|
|
82
90
|
|
|
83
|
-
// Close the messageChannel and resolve the promise if timeout.
|
|
84
|
-
setTimeout(() => {
|
|
85
|
-
channel.port1.close();
|
|
86
|
-
res(new Error(ERROR_MESSAGE.TRIGGER_TIMED_OUT));
|
|
87
|
-
}, TRIGGER_TIMEOUT);
|
|
88
|
-
|
|
89
91
|
return postIframeMessage(iFrame, { type: messageType, data, context }, thoughtSpotHost, channel);
|
|
90
92
|
});
|
|
91
93
|
}
|
package/src/utils.spec.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
resetValueFromWindow,
|
|
27
27
|
validateHttpUrl,
|
|
28
28
|
setParamIfDefined,
|
|
29
|
+
querySelectorAcrossShadowRoot,
|
|
29
30
|
} from './utils';
|
|
30
31
|
import { RuntimeFilterOp } from './types';
|
|
31
32
|
import { logger } from './utils/logger';
|
|
@@ -855,6 +856,68 @@ describe('getClippingAncestors', () => {
|
|
|
855
856
|
});
|
|
856
857
|
});
|
|
857
858
|
|
|
859
|
+
describe('querySelectorAcrossShadowRoot', () => {
|
|
860
|
+
it('should resolve a selector from the light DOM document', () => {
|
|
861
|
+
const el = document.createElement('div');
|
|
862
|
+
el.id = 'light-dom-target';
|
|
863
|
+
document.body.appendChild(el);
|
|
864
|
+
|
|
865
|
+
expect(querySelectorAcrossShadowRoot('#light-dom-target')).toBe(el);
|
|
866
|
+
|
|
867
|
+
el.remove();
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it('should resolve a selector inside the reference node shadow root when the document misses', () => {
|
|
871
|
+
const host = document.createElement('div');
|
|
872
|
+
document.body.appendChild(host);
|
|
873
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
874
|
+
const container = document.createElement('div');
|
|
875
|
+
container.id = 'shadow-target';
|
|
876
|
+
const reference = document.createElement('div');
|
|
877
|
+
shadow.appendChild(container);
|
|
878
|
+
shadow.appendChild(reference);
|
|
879
|
+
|
|
880
|
+
// document.querySelector cannot see into the shadow root.
|
|
881
|
+
expect(document.querySelector('#shadow-target')).toBeNull();
|
|
882
|
+
expect(querySelectorAcrossShadowRoot('#shadow-target', reference)).toBe(container);
|
|
883
|
+
|
|
884
|
+
host.remove();
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it('should prefer the document match over the shadow root', () => {
|
|
888
|
+
const lightEl = document.createElement('div');
|
|
889
|
+
lightEl.id = 'shared-id';
|
|
890
|
+
document.body.appendChild(lightEl);
|
|
891
|
+
|
|
892
|
+
const host = document.createElement('div');
|
|
893
|
+
document.body.appendChild(host);
|
|
894
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
895
|
+
const shadowEl = document.createElement('div');
|
|
896
|
+
shadowEl.id = 'shared-id';
|
|
897
|
+
const reference = document.createElement('div');
|
|
898
|
+
shadow.appendChild(shadowEl);
|
|
899
|
+
shadow.appendChild(reference);
|
|
900
|
+
|
|
901
|
+
expect(querySelectorAcrossShadowRoot('#shared-id', reference)).toBe(lightEl);
|
|
902
|
+
|
|
903
|
+
lightEl.remove();
|
|
904
|
+
host.remove();
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
it('should return null when nothing matches and the reference node is in the light DOM', () => {
|
|
908
|
+
const reference = document.createElement('div');
|
|
909
|
+
document.body.appendChild(reference);
|
|
910
|
+
|
|
911
|
+
expect(querySelectorAcrossShadowRoot('#does-not-exist', reference)).toBeNull();
|
|
912
|
+
|
|
913
|
+
reference.remove();
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it('should return null when no reference node is provided and the document misses', () => {
|
|
917
|
+
expect(querySelectorAcrossShadowRoot('#does-not-exist')).toBeNull();
|
|
918
|
+
});
|
|
919
|
+
});
|
|
920
|
+
|
|
858
921
|
describe('getEffectiveClippingAncestors', () => {
|
|
859
922
|
it('should return an empty list when element is missing', () => {
|
|
860
923
|
expect(getEffectiveClippingAncestors(null as unknown as HTMLElement)).toEqual([]);
|
package/src/utils.ts
CHANGED
|
@@ -291,6 +291,30 @@ export function getDOMNode(domSelector: DOMSelector): HTMLElement {
|
|
|
291
291
|
return typeof domSelector === 'string' ? document.querySelector(domSelector) : domSelector;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Resolves a CSS selector to an element. `document.querySelector` cannot pierce
|
|
296
|
+
* shadow boundaries, so when the document lookup misses and a reference node is
|
|
297
|
+
* provided, this also searches the shadow root the reference node lives in —
|
|
298
|
+
* letting a selector target an element inside the same shadow root as the embed
|
|
299
|
+
* instead of silently falling back to the light DOM.
|
|
300
|
+
* @param selector CSS selector string.
|
|
301
|
+
* @param referenceNode Node whose shadow root is searched when the document lookup misses.
|
|
302
|
+
*/
|
|
303
|
+
export function querySelectorAcrossShadowRoot(
|
|
304
|
+
selector: string,
|
|
305
|
+
referenceNode?: Node | null,
|
|
306
|
+
): Element | null {
|
|
307
|
+
const fromDocument = document.querySelector(selector);
|
|
308
|
+
if (fromDocument) {
|
|
309
|
+
return fromDocument;
|
|
310
|
+
}
|
|
311
|
+
const rootNode = referenceNode?.getRootNode?.();
|
|
312
|
+
if (typeof ShadowRoot !== 'undefined' && rootNode instanceof ShadowRoot) {
|
|
313
|
+
return rootNode.querySelector(selector);
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
294
318
|
export const deepMerge = (target: any, source: any) => merge(target, source);
|
|
295
319
|
|
|
296
320
|
export const getOperationNameFromQuery = (query: string) => {
|