@thoughtspot/visual-embed-sdk 1.49.1 → 1.49.3
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 +4 -10
- 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 +7 -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 +6 -0
- 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/spotter-viz-utils.d.ts +29 -0
- package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -1
- package/cjs/src/embed/spotter-viz-utils.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 +166 -9
- 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/index.d.ts +2 -2
- package/cjs/src/index.d.ts.map +1 -1
- package/cjs/src/index.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/graphql/answerService/answerService.spec.js +15 -0
- package/cjs/src/utils/graphql/answerService/answerService.spec.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.d.ts +10 -0
- package/cjs/src/utils.d.ts.map +1 -1
- package/cjs/src/utils.js +25 -4
- 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-B6Rn561t.js → index-DsSQndIB.js} +303 -187
- 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 +7 -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 +6 -0
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/spotter-viz-utils.d.ts +29 -0
- package/dist/src/embed/spotter-viz-utils.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/index.d.ts +2 -2
- package/dist/src/index.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.d.ts +10 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +282 -35
- package/dist/tsembed-react.js +583 -220
- package/dist/tsembed.es.js +282 -35
- package/dist/tsembed.js +583 -220
- package/dist/visual-embed-sdk-react-full.d.ts +190 -4
- package/dist/visual-embed-sdk-react.d.ts +190 -4
- package/dist/visual-embed-sdk.d.ts +190 -4
- package/lib/package.json +4 -10
- 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 +7 -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 +6 -0
- 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/spotter-viz-utils.d.ts +29 -0
- package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -1
- package/lib/src/embed/spotter-viz-utils.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 +167 -10
- 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/index.d.ts +2 -2
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.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/graphql/answerService/answerService.spec.js +15 -0
- package/lib/src/utils/graphql/answerService/answerService.spec.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.d.ts +10 -0
- package/lib/src/utils.d.ts.map +1 -1
- package/lib/src/utils.js +22 -1
- 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 +190 -4
- package/package.json +4 -10
- package/src/css-variables.ts +20 -0
- package/src/embed/app.spec.ts +109 -0
- package/src/embed/app.ts +11 -1
- 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 +10 -0
- package/src/embed/spotter-viz-utils.ts +30 -0
- package/src/embed/ts-embed.spec.ts +294 -0
- package/src/embed/ts-embed.ts +180 -9
- package/src/index.ts +2 -1
- package/src/types.ts +86 -3
- package/src/utils/graphql/answerService/answerService.spec.ts +18 -0
- package/src/utils/processTrigger.spec.ts +23 -0
- package/src/utils/processTrigger.ts +8 -6
- package/src/utils.spec.ts +63 -0
- package/src/utils.ts +25 -1
|
@@ -32,6 +32,12 @@ export interface SpotterVizStarterPrompt {
|
|
|
32
32
|
* customStarterPrompts: [
|
|
33
33
|
* { id: '1', displayText: 'Top products', fullPrompt: 'What are the top products by revenue?' }
|
|
34
34
|
* ],
|
|
35
|
+
* // loaderHeadline and loaderTips require SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
|
|
36
|
+
* loaderHeadline: 'Crunching the numbers...',
|
|
37
|
+
* loaderTips: [
|
|
38
|
+
* { label: 'Tip', text: 'try asking about revenue by region' },
|
|
39
|
+
* { label: 'Tip', text: 'use natural language' },
|
|
40
|
+
* ],
|
|
35
41
|
* },
|
|
36
42
|
* })
|
|
37
43
|
* ```
|
|
@@ -76,6 +82,30 @@ export interface SpotterVizConfig {
|
|
|
76
82
|
* @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl
|
|
77
83
|
*/
|
|
78
84
|
inputChatPlaceholder?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Custom headline text shown in the SpotterViz loading state.
|
|
87
|
+
* Replaces the default loading headline.
|
|
88
|
+
* @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
|
|
89
|
+
*/
|
|
90
|
+
loaderHeadline?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Custom tips shown in the SpotterViz loading state.
|
|
93
|
+
* Replaces the default loading tips with the provided list.
|
|
94
|
+
* @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
|
|
95
|
+
*/
|
|
96
|
+
loaderTips?: SpotterVizLoaderTip[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A single tip shown in the SpotterViz loading state.
|
|
101
|
+
* @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
|
|
102
|
+
* @group Embed components
|
|
103
|
+
*/
|
|
104
|
+
export interface SpotterVizLoaderTip {
|
|
105
|
+
/** Short label rendered alongside the tip (e.g. "Tip"). */
|
|
106
|
+
label: string;
|
|
107
|
+
/** Tip body text. */
|
|
108
|
+
text: string;
|
|
79
109
|
}
|
|
80
110
|
|
|
81
111
|
export function buildSpotterVizAppInitData<T extends DefaultAppInitData>(
|
|
@@ -2479,6 +2479,10 @@ describe('Unit test case for ts embed', () => {
|
|
|
2479
2479
|
// The ResizeObserver now tracks the placeholder inside this.el,
|
|
2480
2480
|
// not this.el itself, so pass it as the target.
|
|
2481
2481
|
const preRenderPlaceholder = tsEmbedDiv.firstElementChild as HTMLElement;
|
|
2482
|
+
preRenderPlaceholder.getBoundingClientRect = jest.fn().mockReturnValue({
|
|
2483
|
+
x: 0, y: 0, width: 987, height: 297,
|
|
2484
|
+
top: 0, left: 0, bottom: 297, right: 987,
|
|
2485
|
+
});
|
|
2482
2486
|
resizeObserverCb([
|
|
2483
2487
|
{
|
|
2484
2488
|
target: preRenderPlaceholder,
|
|
@@ -2660,6 +2664,296 @@ describe('Unit test case for ts embed', () => {
|
|
|
2660
2664
|
);
|
|
2661
2665
|
(logger.error as any).mockClear();
|
|
2662
2666
|
});
|
|
2667
|
+
|
|
2668
|
+
describe('preRenderContainer', () => {
|
|
2669
|
+
it('should append preRenderWrapper to document.body when preRenderContainer is not set', async () => {
|
|
2670
|
+
createRootEleForEmbed();
|
|
2671
|
+
|
|
2672
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2673
|
+
preRenderId: 'container-default',
|
|
2674
|
+
liveboardId: 'myLiveboardId',
|
|
2675
|
+
});
|
|
2676
|
+
await libEmbed.preRender();
|
|
2677
|
+
|
|
2678
|
+
const preRenderIds = libEmbed.getPreRenderIds();
|
|
2679
|
+
const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
|
|
2680
|
+
expect(document.body.contains(preRenderWrapper)).toBe(true);
|
|
2681
|
+
});
|
|
2682
|
+
|
|
2683
|
+
it('should append preRenderWrapper to the element matching a CSS selector string', async () => {
|
|
2684
|
+
createRootEleForEmbed();
|
|
2685
|
+
const customContainer = document.createElement('div');
|
|
2686
|
+
customContainer.id = 'custom-pre-render-container';
|
|
2687
|
+
document.body.appendChild(customContainer);
|
|
2688
|
+
|
|
2689
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2690
|
+
preRenderId: 'container-selector',
|
|
2691
|
+
liveboardId: 'myLiveboardId',
|
|
2692
|
+
preRenderContainer: '#custom-pre-render-container',
|
|
2693
|
+
});
|
|
2694
|
+
await libEmbed.preRender();
|
|
2695
|
+
|
|
2696
|
+
const preRenderIds = libEmbed.getPreRenderIds();
|
|
2697
|
+
const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
|
|
2698
|
+
expect(customContainer.contains(preRenderWrapper)).toBe(true);
|
|
2699
|
+
|
|
2700
|
+
customContainer.remove();
|
|
2701
|
+
});
|
|
2702
|
+
|
|
2703
|
+
it('should fall back to document.body when the CSS selector string matches nothing', async () => {
|
|
2704
|
+
createRootEleForEmbed();
|
|
2705
|
+
|
|
2706
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2707
|
+
preRenderId: 'container-selector-miss',
|
|
2708
|
+
liveboardId: 'myLiveboardId',
|
|
2709
|
+
preRenderContainer: '#does-not-exist',
|
|
2710
|
+
});
|
|
2711
|
+
await libEmbed.preRender();
|
|
2712
|
+
|
|
2713
|
+
const preRenderIds = libEmbed.getPreRenderIds();
|
|
2714
|
+
const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
|
|
2715
|
+
expect(document.body.contains(preRenderWrapper)).toBe(true);
|
|
2716
|
+
});
|
|
2717
|
+
|
|
2718
|
+
it('should fall back to document.body and log an error when the CSS selector is invalid', async () => {
|
|
2719
|
+
createRootEleForEmbed();
|
|
2720
|
+
jest.spyOn(logger, 'error').mockImplementation(jest.fn());
|
|
2721
|
+
|
|
2722
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2723
|
+
preRenderId: 'container-selector-invalid',
|
|
2724
|
+
liveboardId: 'myLiveboardId',
|
|
2725
|
+
preRenderContainer: '###invalid###',
|
|
2726
|
+
});
|
|
2727
|
+
await libEmbed.preRender();
|
|
2728
|
+
|
|
2729
|
+
const preRenderIds = libEmbed.getPreRenderIds();
|
|
2730
|
+
const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
|
|
2731
|
+
expect(document.body.contains(preRenderWrapper)).toBe(true);
|
|
2732
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
2733
|
+
expect.stringContaining('Invalid CSS selector for preRenderContainer'),
|
|
2734
|
+
expect.any(Error),
|
|
2735
|
+
);
|
|
2736
|
+
(logger.error as any).mockClear();
|
|
2737
|
+
});
|
|
2738
|
+
|
|
2739
|
+
it('should append preRenderWrapper to an Element passed directly as preRenderContainer', async () => {
|
|
2740
|
+
createRootEleForEmbed();
|
|
2741
|
+
const customContainer = document.createElement('div');
|
|
2742
|
+
customContainer.id = 'custom-pre-render-element';
|
|
2743
|
+
document.body.appendChild(customContainer);
|
|
2744
|
+
|
|
2745
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2746
|
+
preRenderId: 'container-element',
|
|
2747
|
+
liveboardId: 'myLiveboardId',
|
|
2748
|
+
preRenderContainer: customContainer,
|
|
2749
|
+
});
|
|
2750
|
+
await libEmbed.preRender();
|
|
2751
|
+
|
|
2752
|
+
const preRenderIds = libEmbed.getPreRenderIds();
|
|
2753
|
+
const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
|
|
2754
|
+
expect(customContainer.contains(preRenderWrapper)).toBe(true);
|
|
2755
|
+
|
|
2756
|
+
customContainer.remove();
|
|
2757
|
+
});
|
|
2758
|
+
|
|
2759
|
+
it('should set the container position to relative when it is static', async () => {
|
|
2760
|
+
createRootEleForEmbed();
|
|
2761
|
+
const customContainer = document.createElement('div');
|
|
2762
|
+
customContainer.id = 'custom-pre-render-static';
|
|
2763
|
+
customContainer.style.position = 'static';
|
|
2764
|
+
document.body.appendChild(customContainer);
|
|
2765
|
+
|
|
2766
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2767
|
+
preRenderId: 'container-position-static',
|
|
2768
|
+
liveboardId: 'myLiveboardId',
|
|
2769
|
+
preRenderContainer: customContainer,
|
|
2770
|
+
});
|
|
2771
|
+
await libEmbed.preRender();
|
|
2772
|
+
|
|
2773
|
+
expect(customContainer.style.position).toBe('relative');
|
|
2774
|
+
|
|
2775
|
+
customContainer.remove();
|
|
2776
|
+
});
|
|
2777
|
+
|
|
2778
|
+
it('should restore the original container position on destroy', async () => {
|
|
2779
|
+
createRootEleForEmbed();
|
|
2780
|
+
const customContainer = document.createElement('div');
|
|
2781
|
+
customContainer.id = 'custom-pre-render-restore';
|
|
2782
|
+
customContainer.style.position = 'static';
|
|
2783
|
+
document.body.appendChild(customContainer);
|
|
2784
|
+
|
|
2785
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2786
|
+
preRenderId: 'container-position-restore',
|
|
2787
|
+
liveboardId: 'myLiveboardId',
|
|
2788
|
+
preRenderContainer: customContainer,
|
|
2789
|
+
});
|
|
2790
|
+
await libEmbed.preRender();
|
|
2791
|
+
expect(customContainer.style.position).toBe('relative');
|
|
2792
|
+
|
|
2793
|
+
libEmbed.destroy();
|
|
2794
|
+
|
|
2795
|
+
// Reverts to the exact value the container had before.
|
|
2796
|
+
expect(customContainer.style.position).toBe('static');
|
|
2797
|
+
// The container reference is dropped so a destroyed embed does
|
|
2798
|
+
// not pin the (possibly detached) element in memory.
|
|
2799
|
+
expect((libEmbed as any).preRenderContainerEl).toBeNull();
|
|
2800
|
+
|
|
2801
|
+
customContainer.remove();
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
it('should not touch the container position when it is already positioned', async () => {
|
|
2805
|
+
createRootEleForEmbed();
|
|
2806
|
+
const customContainer = document.createElement('div');
|
|
2807
|
+
customContainer.id = 'custom-pre-render-positioned';
|
|
2808
|
+
customContainer.style.position = 'absolute';
|
|
2809
|
+
document.body.appendChild(customContainer);
|
|
2810
|
+
|
|
2811
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2812
|
+
preRenderId: 'container-position-positioned',
|
|
2813
|
+
liveboardId: 'myLiveboardId',
|
|
2814
|
+
preRenderContainer: customContainer,
|
|
2815
|
+
});
|
|
2816
|
+
await libEmbed.preRender();
|
|
2817
|
+
expect(customContainer.style.position).toBe('absolute');
|
|
2818
|
+
|
|
2819
|
+
libEmbed.destroy();
|
|
2820
|
+
expect(customContainer.style.position).toBe('absolute');
|
|
2821
|
+
|
|
2822
|
+
customContainer.remove();
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2825
|
+
it('should keep the container positioned on destroy while another preRender wrapper remains', async () => {
|
|
2826
|
+
createRootEleForEmbed();
|
|
2827
|
+
const customContainer = document.createElement('div');
|
|
2828
|
+
customContainer.id = 'custom-pre-render-shared';
|
|
2829
|
+
customContainer.style.position = 'static';
|
|
2830
|
+
document.body.appendChild(customContainer);
|
|
2831
|
+
|
|
2832
|
+
const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2833
|
+
preRenderId: 'container-shared-first',
|
|
2834
|
+
liveboardId: 'myLiveboardId',
|
|
2835
|
+
preRenderContainer: customContainer,
|
|
2836
|
+
});
|
|
2837
|
+
await firstEmbed.preRender();
|
|
2838
|
+
|
|
2839
|
+
const secondEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2840
|
+
preRenderId: 'container-shared-second',
|
|
2841
|
+
liveboardId: 'myLiveboardId',
|
|
2842
|
+
preRenderContainer: customContainer,
|
|
2843
|
+
});
|
|
2844
|
+
await secondEmbed.preRender();
|
|
2845
|
+
|
|
2846
|
+
expect(customContainer.style.position).toBe('relative');
|
|
2847
|
+
|
|
2848
|
+
// Destroying the first embed must not strip the positioning
|
|
2849
|
+
// context the second embed still relies on.
|
|
2850
|
+
firstEmbed.destroy();
|
|
2851
|
+
expect(customContainer.style.position).toBe('relative');
|
|
2852
|
+
|
|
2853
|
+
// Once the last embed is destroyed, the override is reverted.
|
|
2854
|
+
secondEmbed.destroy();
|
|
2855
|
+
expect(customContainer.style.position).toBe('static');
|
|
2856
|
+
|
|
2857
|
+
customContainer.remove();
|
|
2858
|
+
});
|
|
2859
|
+
|
|
2860
|
+
it('should resolve a selector container inside the host shadow root', async () => {
|
|
2861
|
+
const host = document.createElement('div');
|
|
2862
|
+
document.body.appendChild(host);
|
|
2863
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
2864
|
+
const hostEl = document.createElement('div');
|
|
2865
|
+
const shadowContainer = document.createElement('div');
|
|
2866
|
+
shadowContainer.id = 'shadow-pre-render-container';
|
|
2867
|
+
shadow.appendChild(hostEl);
|
|
2868
|
+
shadow.appendChild(shadowContainer);
|
|
2869
|
+
|
|
2870
|
+
const libEmbed = new LiveboardEmbed(hostEl, {
|
|
2871
|
+
preRenderId: 'container-shadow',
|
|
2872
|
+
liveboardId: 'myLiveboardId',
|
|
2873
|
+
preRenderContainer: '#shadow-pre-render-container',
|
|
2874
|
+
});
|
|
2875
|
+
await libEmbed.preRender();
|
|
2876
|
+
|
|
2877
|
+
const preRenderIds = libEmbed.getPreRenderIds();
|
|
2878
|
+
const preRenderWrapper = shadow.querySelector(`#${preRenderIds.wrapper}`);
|
|
2879
|
+
// The wrapper lands in the shadow container, not the body.
|
|
2880
|
+
expect(shadowContainer.contains(preRenderWrapper)).toBe(true);
|
|
2881
|
+
expect(document.getElementById(preRenderIds.wrapper)).toBeNull();
|
|
2882
|
+
|
|
2883
|
+
host.remove();
|
|
2884
|
+
});
|
|
2885
|
+
|
|
2886
|
+
it('should re-attach the wrapper when a selector container is remounted', async () => {
|
|
2887
|
+
createRootEleForEmbed();
|
|
2888
|
+
const oldContainer = document.createElement('div');
|
|
2889
|
+
oldContainer.id = 'remount-container';
|
|
2890
|
+
document.body.appendChild(oldContainer);
|
|
2891
|
+
|
|
2892
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2893
|
+
preRenderId: 'container-remount',
|
|
2894
|
+
liveboardId: 'myLiveboardId',
|
|
2895
|
+
preRenderContainer: '#remount-container',
|
|
2896
|
+
doNotTrackPreRenderSize: true,
|
|
2897
|
+
});
|
|
2898
|
+
libEmbed.preRender();
|
|
2899
|
+
await waitFor(() => !!getIFrameEl());
|
|
2900
|
+
await libEmbed.showPreRender();
|
|
2901
|
+
|
|
2902
|
+
const preRenderIds = libEmbed.getPreRenderIds();
|
|
2903
|
+
const wrapper = document.getElementById(preRenderIds.wrapper);
|
|
2904
|
+
expect(oldContainer.contains(wrapper)).toBe(true);
|
|
2905
|
+
|
|
2906
|
+
// Simulate React remounting the container: the old node (with
|
|
2907
|
+
// our wrapper) is detached and a fresh node takes its place.
|
|
2908
|
+
const removeSpy = jest.spyOn(oldContainer, 'removeEventListener');
|
|
2909
|
+
oldContainer.remove();
|
|
2910
|
+
const newContainer = document.createElement('div');
|
|
2911
|
+
newContainer.id = 'remount-container';
|
|
2912
|
+
document.body.appendChild(newContainer);
|
|
2913
|
+
const addSpy = jest.spyOn(newContainer, 'addEventListener');
|
|
2914
|
+
|
|
2915
|
+
// A reposition (scroll/resize/re-show) heals the stale ref.
|
|
2916
|
+
libEmbed.syncPreRenderStyle();
|
|
2917
|
+
|
|
2918
|
+
expect(newContainer.contains(wrapper)).toBe(true);
|
|
2919
|
+
expect(oldContainer.contains(wrapper)).toBe(false);
|
|
2920
|
+
// The scroll listener is migrated to the live container.
|
|
2921
|
+
expect(removeSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
2922
|
+
expect(addSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
2923
|
+
|
|
2924
|
+
libEmbed.destroy();
|
|
2925
|
+
newContainer.remove();
|
|
2926
|
+
});
|
|
2927
|
+
|
|
2928
|
+
it('should leave an element-passed container untouched when it is detached', async () => {
|
|
2929
|
+
createRootEleForEmbed();
|
|
2930
|
+
const container = document.createElement('div');
|
|
2931
|
+
document.body.appendChild(container);
|
|
2932
|
+
|
|
2933
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2934
|
+
preRenderId: 'container-element-remount',
|
|
2935
|
+
liveboardId: 'myLiveboardId',
|
|
2936
|
+
preRenderContainer: container,
|
|
2937
|
+
doNotTrackPreRenderSize: true,
|
|
2938
|
+
});
|
|
2939
|
+
libEmbed.preRender();
|
|
2940
|
+
await waitFor(() => !!getIFrameEl());
|
|
2941
|
+
await libEmbed.showPreRender();
|
|
2942
|
+
|
|
2943
|
+
const preRenderIds = libEmbed.getPreRenderIds();
|
|
2944
|
+
const wrapper = document.getElementById(preRenderIds.wrapper);
|
|
2945
|
+
expect(container.contains(wrapper)).toBe(true);
|
|
2946
|
+
|
|
2947
|
+
// Detach the element container; it cannot be re-resolved, so
|
|
2948
|
+
// the wrapper stays with it (no crash, no body fallback).
|
|
2949
|
+
container.remove();
|
|
2950
|
+
libEmbed.syncPreRenderStyle();
|
|
2951
|
+
|
|
2952
|
+
expect(container.contains(wrapper)).toBe(true);
|
|
2953
|
+
|
|
2954
|
+
libEmbed.destroy();
|
|
2955
|
+
});
|
|
2956
|
+
});
|
|
2663
2957
|
});
|
|
2664
2958
|
|
|
2665
2959
|
describe('IdleSessionTimeout embedEvent for TrustedAuthTokenCookieless authType with autoLogin true', () => {
|
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,
|
|
@@ -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/index.ts
CHANGED
|
@@ -33,7 +33,7 @@ import { SearchEmbed, SearchViewConfig } from './embed/search';
|
|
|
33
33
|
import { SearchBarEmbed, SearchBarViewConfig } from './embed/search-bar';
|
|
34
34
|
import { SpotterAgentEmbed, SpotterAgentEmbedViewConfig, BodylessConversation, BodylessConversationViewConfig} from './embed/bodyless-conversation';
|
|
35
35
|
import { SpotterEmbed, SpotterEmbedViewConfig, SpotterChatViewConfig, SpotterSidebarViewConfig, ConversationEmbed, ConversationViewConfig } from './embed/conversation';
|
|
36
|
-
import { SpotterVizConfig, SpotterVizStarterPrompt } from './embed/spotter-viz-utils';
|
|
36
|
+
import { SpotterVizConfig, SpotterVizStarterPrompt, SpotterVizLoaderTip } from './embed/spotter-viz-utils';
|
|
37
37
|
import {
|
|
38
38
|
AuthFailureType, AuthStatus, AuthEvent, AuthEventEmitter,
|
|
39
39
|
} from './auth';
|
|
@@ -143,6 +143,7 @@ export {
|
|
|
143
143
|
LiveboardViewConfig,
|
|
144
144
|
SpotterVizConfig,
|
|
145
145
|
SpotterVizStarterPrompt,
|
|
146
|
+
SpotterVizLoaderTip,
|
|
146
147
|
AppViewConfig,
|
|
147
148
|
PrefetchFeatures,
|
|
148
149
|
FrameParams,
|