@thoughtspot/visual-embed-sdk 1.39.3 → 1.40.1-alpha.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.
Files changed (93) hide show
  1. package/cjs/src/embed/app.d.ts +56 -0
  2. package/cjs/src/embed/app.d.ts.map +1 -1
  3. package/cjs/src/embed/app.js +47 -8
  4. package/cjs/src/embed/app.js.map +1 -1
  5. package/cjs/src/embed/app.spec.js +322 -7
  6. package/cjs/src/embed/app.spec.js.map +1 -1
  7. package/cjs/src/embed/liveboard.d.ts +58 -1
  8. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  9. package/cjs/src/embed/liveboard.js +59 -8
  10. package/cjs/src/embed/liveboard.js.map +1 -1
  11. package/cjs/src/embed/liveboard.spec.js +206 -0
  12. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  13. package/cjs/src/embed/ts-embed.d.ts +7 -0
  14. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  15. package/cjs/src/embed/ts-embed.js +61 -7
  16. package/cjs/src/embed/ts-embed.js.map +1 -1
  17. package/cjs/src/types.d.ts +37 -6
  18. package/cjs/src/types.d.ts.map +1 -1
  19. package/cjs/src/types.js +35 -4
  20. package/cjs/src/types.js.map +1 -1
  21. package/cjs/src/utils/processTrigger.js +2 -1
  22. package/cjs/src/utils/processTrigger.js.map +1 -1
  23. package/cjs/src/utils.d.ts +6 -0
  24. package/cjs/src/utils.d.ts.map +1 -1
  25. package/cjs/src/utils.js +23 -3
  26. package/cjs/src/utils.js.map +1 -1
  27. package/cjs/src/utils.spec.js +212 -1
  28. package/cjs/src/utils.spec.js.map +1 -1
  29. package/dist/{index-ZrE8YYq8.js → index-CmEQfuE3.js} +1 -1
  30. package/dist/index-D1pyb7RG.js +7371 -0
  31. package/dist/index-DeFzsyFF.js +7371 -0
  32. package/dist/index-Dpf0rd6w.js +7371 -0
  33. package/dist/index-UuEbsISo.js +7447 -0
  34. package/dist/index-e3Uw3YFO.js +7371 -0
  35. package/dist/src/embed/app.d.ts +56 -0
  36. package/dist/src/embed/app.d.ts.map +1 -1
  37. package/dist/src/embed/bodyless-conversation.d.ts +0 -4
  38. package/dist/src/embed/bodyless-conversation.d.ts.map +1 -1
  39. package/dist/src/embed/liveboard.d.ts +56 -0
  40. package/dist/src/embed/liveboard.d.ts.map +1 -1
  41. package/dist/src/react/index.d.ts +0 -2
  42. package/dist/src/react/index.d.ts.map +1 -1
  43. package/dist/src/types.d.ts +16 -198
  44. package/dist/src/types.d.ts.map +1 -1
  45. package/dist/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  46. package/dist/src/utils.d.ts +6 -0
  47. package/dist/src/utils.d.ts.map +1 -1
  48. package/dist/tsembed-react.es.js +137 -224
  49. package/dist/tsembed-react.js +136 -223
  50. package/dist/tsembed.es.js +137 -224
  51. package/dist/tsembed.js +136 -223
  52. package/dist/visual-embed-sdk-react-full.d.ts +106 -204
  53. package/dist/visual-embed-sdk-react.d.ts +106 -204
  54. package/dist/visual-embed-sdk.d.ts +106 -202
  55. package/lib/src/embed/app.d.ts +56 -0
  56. package/lib/src/embed/app.d.ts.map +1 -1
  57. package/lib/src/embed/app.js +48 -9
  58. package/lib/src/embed/app.js.map +1 -1
  59. package/lib/src/embed/app.spec.js +322 -7
  60. package/lib/src/embed/app.spec.js.map +1 -1
  61. package/lib/src/embed/liveboard.d.ts +58 -1
  62. package/lib/src/embed/liveboard.d.ts.map +1 -1
  63. package/lib/src/embed/liveboard.js +60 -9
  64. package/lib/src/embed/liveboard.js.map +1 -1
  65. package/lib/src/embed/liveboard.spec.js +206 -0
  66. package/lib/src/embed/liveboard.spec.js.map +1 -1
  67. package/lib/src/embed/ts-embed.d.ts +7 -0
  68. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  69. package/lib/src/embed/ts-embed.js +61 -7
  70. package/lib/src/embed/ts-embed.js.map +1 -1
  71. package/lib/src/types.d.ts +37 -6
  72. package/lib/src/types.d.ts.map +1 -1
  73. package/lib/src/types.js +35 -4
  74. package/lib/src/types.js.map +1 -1
  75. package/lib/src/utils/processTrigger.js +2 -1
  76. package/lib/src/utils/processTrigger.js.map +1 -1
  77. package/lib/src/utils.d.ts +6 -0
  78. package/lib/src/utils.d.ts.map +1 -1
  79. package/lib/src/utils.js +21 -2
  80. package/lib/src/utils.js.map +1 -1
  81. package/lib/src/utils.spec.js +213 -2
  82. package/lib/src/utils.spec.js.map +1 -1
  83. package/lib/src/visual-embed-sdk.d.ts +106 -202
  84. package/package.json +1 -2
  85. package/src/embed/app.spec.ts +397 -8
  86. package/src/embed/app.ts +106 -12
  87. package/src/embed/liveboard.spec.ts +254 -1
  88. package/src/embed/liveboard.ts +109 -11
  89. package/src/embed/ts-embed.ts +84 -21
  90. package/src/types.ts +36 -5
  91. package/src/utils/processTrigger.ts +1 -1
  92. package/src/utils.spec.ts +250 -2
  93. package/src/utils.ts +28 -2
@@ -131,7 +131,7 @@ describe('App embed tests', () => {
131
131
  });
132
132
 
133
133
  describe('should render the correct routes for pages', () => {
134
- /* eslint-disable no-loop-func */
134
+
135
135
  const pageRouteMap = {
136
136
  [Page.Search]: 'answer',
137
137
  [Page.Answers]: 'answers',
@@ -148,7 +148,7 @@ describe('App embed tests', () => {
148
148
  const pageId = pageIds[i];
149
149
 
150
150
  test(`${pageId}`, async () => {
151
- const route = pageRouteMap[pageId];
151
+ const route = pageRouteMap[pageId as keyof typeof pageRouteMap];
152
152
  const appEmbed = new AppEmbed(getRootEl(), {
153
153
  ...defaultViewConfig,
154
154
  pageId: pageId as Page,
@@ -181,7 +181,7 @@ describe('App embed tests', () => {
181
181
  const pageIdsForModularHome = pageIdsForModularHomes[i];
182
182
 
183
183
  test(`${pageIdsForModularHome}`, async () => {
184
- const route = pageRouteMap[pageIdsForModularHome];
184
+ const route = pageRouteMapForModularHome[pageIdsForModularHome as keyof typeof pageRouteMapForModularHome];
185
185
  const appEmbed = new AppEmbed(getRootEl(), {
186
186
  ...defaultViewConfig,
187
187
  modularHomeExperience: true,
@@ -753,7 +753,7 @@ describe('App embed tests', () => {
753
753
  test('Should add dataPanelCustomGroupsAccordionInitialState flag to the iframe src', async () => {
754
754
  const appEmbed = new AppEmbed(getRootEl(), {
755
755
  ...defaultViewConfig,
756
- // eslint-disable-next-line max-len
756
+
757
757
  dataPanelCustomGroupsAccordionInitialState:
758
758
  DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST,
759
759
  } as AppViewConfig);
@@ -768,12 +768,13 @@ describe('App embed tests', () => {
768
768
  });
769
769
 
770
770
  test('should register event handlers to adjust iframe height', async () => {
771
+ let embedHeightCallback: any = () => { };
771
772
  const onSpy = jest.spyOn(AppEmbed.prototype, 'on').mockImplementation((event, callback) => {
772
773
  if (event === EmbedEvent.RouteChange) {
773
774
  callback({ data: { currentPath: '/answers' } }, jest.fn());
774
775
  }
775
776
  if (event === EmbedEvent.EmbedHeight) {
776
- callback({ data: '100%' });
777
+ embedHeightCallback = callback;
777
778
  }
778
779
  if (event === EmbedEvent.EmbedIframeCenter) {
779
780
  callback({}, jest.fn());
@@ -785,16 +786,23 @@ describe('App embed tests', () => {
785
786
  const appEmbed = new AppEmbed(getRootEl(), {
786
787
  ...defaultViewConfig,
787
788
  fullHeight: true,
789
+ lazyLoadingForFullHeight: true,
788
790
  } as AppViewConfig);
789
791
 
790
- appEmbed.render();
792
+ // Set the iframe before render
793
+ (appEmbed as any).iFrame = document.createElement('iframe');
794
+
795
+ // Wait for render to complete
796
+ await appEmbed.render();
797
+ embedHeightCallback({ data: '100%' });
791
798
 
799
+ // Verify event handlers were registered
792
800
  await executeAfterWait(() => {
793
801
  expect(onSpy).toHaveBeenCalledWith(EmbedEvent.EmbedHeight, expect.anything());
794
802
  expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RouteChange, expect.anything());
795
803
  expect(onSpy).toHaveBeenCalledWith(EmbedEvent.EmbedIframeCenter, expect.anything());
796
- });
797
- jest.clearAllMocks();
804
+ expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RequestVisibleEmbedCoordinates, expect.anything());
805
+ }, 100);
798
806
  });
799
807
 
800
808
  describe('Navigate to Page API', () => {
@@ -882,4 +890,385 @@ describe('App embed tests', () => {
882
890
  );
883
891
  });
884
892
  });
893
+
894
+ describe('LazyLoadingForFullHeight functionality', () => {
895
+ let mockIFrame: HTMLIFrameElement;
896
+
897
+ beforeEach(() => {
898
+ mockIFrame = document.createElement('iframe');
899
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
900
+ top: 100,
901
+ left: 150,
902
+ bottom: 600,
903
+ right: 800,
904
+ width: 650,
905
+ height: 500,
906
+ });
907
+ jest.spyOn(document, 'createElement').mockImplementation((tagName) => {
908
+ if (tagName === 'iframe') {
909
+ return mockIFrame;
910
+ }
911
+ return document.createElement(tagName);
912
+ });
913
+ });
914
+
915
+ afterEach(() => {
916
+ jest.restoreAllMocks();
917
+ });
918
+
919
+ test('should set lazyLoadingMargin parameter when provided', async () => {
920
+ const appEmbed = new AppEmbed(getRootEl(), {
921
+ ...defaultViewConfig,
922
+ fullHeight: true,
923
+ lazyLoadingForFullHeight: true,
924
+ lazyLoadingMargin: '100px 0px',
925
+ } as AppViewConfig);
926
+
927
+ await appEmbed.render();
928
+
929
+ await executeAfterWait(() => {
930
+ const iframeSrc = getIFrameSrc();
931
+ expect(iframeSrc).toContain('isLazyLoadingForEmbedEnabled=true');
932
+ expect(iframeSrc).toContain('isFullHeightPinboard=true');
933
+ expect(iframeSrc).toContain('rootMarginForLazyLoad=100px%200px');
934
+ }, 100);
935
+ });
936
+
937
+ test('should set isLazyLoadingForEmbedEnabled=true when both fullHeight and lazyLoadingForFullHeight are enabled', async () => {
938
+ // Mock the iframe element first
939
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
940
+ top: 100,
941
+ left: 150,
942
+ bottom: 600,
943
+ right: 800,
944
+ width: 650,
945
+ height: 500,
946
+ });
947
+ Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
948
+
949
+ // Mock the event handlers
950
+ const onSpy = jest.spyOn(AppEmbed.prototype, 'on').mockImplementation((event, callback) => {
951
+ return null;
952
+ });
953
+ jest.spyOn(TsEmbed.prototype as any, 'getIframeCenter').mockReturnValue({});
954
+ jest.spyOn(TsEmbed.prototype as any, 'setIFrameHeight').mockReturnValue({});
955
+
956
+ // Create the AppEmbed instance
957
+ const appEmbed = new AppEmbed(getRootEl(), {
958
+ ...defaultViewConfig,
959
+ fullHeight: true,
960
+ lazyLoadingForFullHeight: true,
961
+ } as AppViewConfig);
962
+
963
+ // Set the iframe before render
964
+ (appEmbed as any).iFrame = mockIFrame;
965
+
966
+ // Add the iframe to the DOM
967
+ const rootEl = getRootEl();
968
+ rootEl.appendChild(mockIFrame);
969
+
970
+ // Wait for render to complete
971
+ await appEmbed.render();
972
+
973
+ // Wait for iframe initialization and URL parameters to be set
974
+ await executeAfterWait(() => {
975
+ const iframeSrc = appEmbed.getIFrameSrc();
976
+ expect(iframeSrc).toContain('isLazyLoadingForEmbedEnabled=true');
977
+ expect(iframeSrc).toContain('isFullHeightPinboard=true');
978
+ }, 100);
979
+ });
980
+
981
+ test('should not set lazyLoadingForEmbed when lazyLoadingForFullHeight is enabled but fullHeight is false', async () => {
982
+ const appEmbed = new AppEmbed(getRootEl(), {
983
+ ...defaultViewConfig,
984
+ fullHeight: false,
985
+ lazyLoadingForFullHeight: true,
986
+ } as AppViewConfig);
987
+
988
+ // Wait for render to complete
989
+ await appEmbed.render();
990
+
991
+ // Wait for iframe initialization and URL parameters to be set
992
+ await executeAfterWait(() => {
993
+ const iframeSrc = getIFrameSrc();
994
+ expect(iframeSrc).not.toContain('isLazyLoadingForEmbedEnabled=true');
995
+ expect(iframeSrc).not.toContain('isFullHeightPinboard=true');
996
+ }, 100); // 100ms wait time to ensure iframe src is set
997
+ });
998
+
999
+ test('should not set isLazyLoadingForEmbedEnabled when fullHeight is true but lazyLoadingForFullHeight is false', async () => {
1000
+ const appEmbed = new AppEmbed(getRootEl(), {
1001
+ ...defaultViewConfig,
1002
+ fullHeight: true,
1003
+ lazyLoadingForFullHeight: false,
1004
+ } as AppViewConfig);
1005
+
1006
+ // Wait for render to complete
1007
+ await appEmbed.render();
1008
+
1009
+ // Wait for iframe initialization and URL parameters to be set
1010
+ await executeAfterWait(() => {
1011
+ const iframeSrc = getIFrameSrc();
1012
+ expect(iframeSrc).not.toContain('isLazyLoadingForEmbedEnabled=true');
1013
+ expect(iframeSrc).toContain('isFullHeightPinboard=true');
1014
+ }, 100); // 100ms wait time to ensure iframe src is set
1015
+ });
1016
+
1017
+ test('should register RequestFullHeightLazyLoadData event handler when fullHeight is enabled', async () => {
1018
+ const onSpy = jest.spyOn(AppEmbed.prototype, 'on');
1019
+
1020
+ const appEmbed = new AppEmbed(getRootEl(), {
1021
+ ...defaultViewConfig,
1022
+ fullHeight: true,
1023
+ } as AppViewConfig);
1024
+
1025
+ await appEmbed.render();
1026
+
1027
+ expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RequestVisibleEmbedCoordinates, expect.any(Function));
1028
+
1029
+ onSpy.mockRestore();
1030
+ });
1031
+
1032
+ test('should send correct visible data when RequestFullHeightLazyLoadData is triggered', async () => {
1033
+ const appEmbed = new AppEmbed(getRootEl(), {
1034
+ ...defaultViewConfig,
1035
+ fullHeight: true,
1036
+ lazyLoadingForFullHeight: true,
1037
+ } as AppViewConfig);
1038
+
1039
+ const mockTrigger = jest.spyOn(appEmbed, 'trigger');
1040
+
1041
+ await appEmbed.render();
1042
+
1043
+ // Trigger the lazy load data calculation
1044
+ (appEmbed as any).sendFullHeightLazyLoadData();
1045
+
1046
+ expect(mockTrigger).toHaveBeenCalledWith(HostEvent.VisibleEmbedCoordinates, {
1047
+ top: 0,
1048
+ height: 500,
1049
+ left: 0,
1050
+ width: 650,
1051
+ });
1052
+ });
1053
+
1054
+ test('should calculate correct visible data for partially visible full height element', async () => {
1055
+ // Mock iframe partially clipped from top and left
1056
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1057
+ top: -50,
1058
+ left: -30,
1059
+ bottom: 700,
1060
+ right: 1024,
1061
+ width: 1054,
1062
+ height: 750,
1063
+ });
1064
+
1065
+ const appEmbed = new AppEmbed(getRootEl(), {
1066
+ ...defaultViewConfig,
1067
+ fullHeight: true,
1068
+ lazyLoadingForFullHeight: true,
1069
+ } as AppViewConfig);
1070
+
1071
+ const mockTrigger = jest.spyOn(appEmbed, 'trigger');
1072
+
1073
+ await appEmbed.render();
1074
+
1075
+ // Trigger the lazy load data calculation
1076
+ (appEmbed as any).sendFullHeightLazyLoadData();
1077
+
1078
+ expect(mockTrigger).toHaveBeenCalledWith(HostEvent.VisibleEmbedCoordinates, {
1079
+ top: 50, // 50px clipped from top
1080
+ height: 700, // visible height (from 0 to 700)
1081
+ left: 30, // 30px clipped from left
1082
+ width: 1024, // visible width (from 0 to 1024)
1083
+ });
1084
+ });
1085
+
1086
+ test('should add window event listeners for resize and scroll when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
1087
+ const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
1088
+
1089
+ const appEmbed = new AppEmbed(getRootEl(), {
1090
+ ...defaultViewConfig,
1091
+ fullHeight: true,
1092
+ lazyLoadingForFullHeight: true,
1093
+ } as AppViewConfig);
1094
+
1095
+ await appEmbed.render();
1096
+
1097
+ // Wait for the post-render events to be registered
1098
+ await executeAfterWait(() => {
1099
+ expect(addEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function));
1100
+ expect(addEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function), true);
1101
+ }, 100);
1102
+
1103
+ addEventListenerSpy.mockRestore();
1104
+ });
1105
+
1106
+ test('should remove window event listeners on destroy when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
1107
+ const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
1108
+
1109
+ const appEmbed = new AppEmbed(getRootEl(), {
1110
+ ...defaultViewConfig,
1111
+ fullHeight: true,
1112
+ lazyLoadingForFullHeight: true,
1113
+ } as AppViewConfig);
1114
+
1115
+ await appEmbed.render();
1116
+ appEmbed.destroy();
1117
+
1118
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function));
1119
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
1120
+
1121
+ removeEventListenerSpy.mockRestore();
1122
+ });
1123
+
1124
+ test('should handle RequestVisibleEmbedCoordinates event and respond with correct data', async () => {
1125
+ // Mock the iframe element
1126
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1127
+ top: 100,
1128
+ left: 150,
1129
+ bottom: 600,
1130
+ right: 800,
1131
+ width: 650,
1132
+ height: 500,
1133
+ });
1134
+ Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
1135
+
1136
+ const appEmbed = new AppEmbed(getRootEl(), {
1137
+ ...defaultViewConfig,
1138
+ fullHeight: true,
1139
+ lazyLoadingForFullHeight: true,
1140
+ } as AppViewConfig);
1141
+
1142
+ // Set the iframe before render
1143
+ (appEmbed as any).iFrame = mockIFrame;
1144
+
1145
+ await appEmbed.render();
1146
+
1147
+ // Create a mock responder function
1148
+ const mockResponder = jest.fn();
1149
+
1150
+ // Trigger the handler directly
1151
+ (appEmbed as any).requestVisibleEmbedCoordinatesHandler({}, mockResponder);
1152
+
1153
+ // Verify the responder was called with the correct data
1154
+ expect(mockResponder).toHaveBeenCalledWith({
1155
+ type: EmbedEvent.RequestVisibleEmbedCoordinates,
1156
+ data: {
1157
+ top: 0,
1158
+ height: 500,
1159
+ left: 0,
1160
+ width: 650,
1161
+ },
1162
+ });
1163
+ });
1164
+ });
1165
+
1166
+ describe('IFrame height management', () => {
1167
+ let mockIFrame: HTMLIFrameElement;
1168
+
1169
+ beforeEach(() => {
1170
+ mockIFrame = document.createElement('iframe');
1171
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1172
+ top: 100,
1173
+ left: 150,
1174
+ bottom: 600,
1175
+ right: 800,
1176
+ width: 650,
1177
+ height: 500,
1178
+ });
1179
+ Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
1180
+ });
1181
+
1182
+ test('should not call setIFrameHeight if currentPath starts with "/embed/viz/"', () => {
1183
+ const appEmbed = new AppEmbed(getRootEl(), {
1184
+ ...defaultViewConfig,
1185
+ fullHeight: true,
1186
+ } as AppViewConfig) as any;
1187
+ const spySetIFrameHeight = jest.spyOn(appEmbed, 'setIFrameHeight');
1188
+
1189
+ appEmbed.render();
1190
+ appEmbed.setIframeHeightForNonEmbedLiveboard({
1191
+ data: { currentPath: '/embed/viz/' },
1192
+ type: 'Route',
1193
+ });
1194
+
1195
+ expect(spySetIFrameHeight).not.toHaveBeenCalled();
1196
+ });
1197
+
1198
+ test('should not call setIFrameHeight if currentPath starts with "/embed/insights/viz/"', () => {
1199
+ const appEmbed = new AppEmbed(getRootEl(), {
1200
+ ...defaultViewConfig,
1201
+ fullHeight: true,
1202
+ } as AppViewConfig) as any;
1203
+ const spySetIFrameHeight = jest.spyOn(appEmbed, 'setIFrameHeight');
1204
+
1205
+ appEmbed.render();
1206
+ appEmbed.setIframeHeightForNonEmbedLiveboard({
1207
+ data: { currentPath: '/embed/insights/viz/' },
1208
+ type: 'Route',
1209
+ });
1210
+
1211
+ expect(spySetIFrameHeight).not.toHaveBeenCalled();
1212
+ });
1213
+
1214
+ test('should call setIFrameHeight if currentPath starts with "/some/other/path/"', () => {
1215
+ const appEmbed = new AppEmbed(getRootEl(), {
1216
+ ...defaultViewConfig,
1217
+ fullHeight: true,
1218
+ } as AppViewConfig) as any;
1219
+ const spySetIFrameHeight = jest
1220
+ .spyOn(appEmbed, 'setIFrameHeight')
1221
+ .mockImplementation(jest.fn());
1222
+
1223
+ appEmbed.render();
1224
+ appEmbed.setIframeHeightForNonEmbedLiveboard({
1225
+ data: { currentPath: '/some/other/path/' },
1226
+ type: 'Route',
1227
+ });
1228
+
1229
+ expect(spySetIFrameHeight).toHaveBeenCalled();
1230
+ });
1231
+
1232
+ test('should update iframe height correctly', async () => {
1233
+ const appEmbed = new AppEmbed(getRootEl(), {
1234
+ ...defaultViewConfig,
1235
+ fullHeight: true,
1236
+ } as AppViewConfig) as any;
1237
+
1238
+ // Set up the mock iframe
1239
+ appEmbed.iFrame = mockIFrame;
1240
+ document.body.appendChild(mockIFrame);
1241
+
1242
+ await appEmbed.render();
1243
+ const mockEvent = {
1244
+ data: 600,
1245
+ type: EmbedEvent.EmbedHeight,
1246
+ };
1247
+ appEmbed.updateIFrameHeight(mockEvent);
1248
+
1249
+ // Check if the iframe style was updated
1250
+ expect(mockIFrame.style.height).toBe('600px');
1251
+ });
1252
+
1253
+ test('should handle updateIFrameHeight with default height', async () => {
1254
+ const appEmbed = new AppEmbed(getRootEl(), {
1255
+ ...defaultViewConfig,
1256
+ fullHeight: true,
1257
+ } as AppViewConfig) as any;
1258
+
1259
+ // Set up the mock iframe
1260
+ appEmbed.iFrame = mockIFrame;
1261
+ document.body.appendChild(mockIFrame);
1262
+
1263
+ await appEmbed.render();
1264
+ const mockEvent = {
1265
+ data: 0, // This will make it use the scrollHeight
1266
+ type: EmbedEvent.EmbedHeight,
1267
+ };
1268
+ appEmbed.updateIFrameHeight(mockEvent);
1269
+
1270
+ // Should use the scrollHeight
1271
+ expect(mockIFrame.style.height).toBe('500px');
1272
+ });
1273
+ });
885
1274
  });
package/src/embed/app.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { logger } from '../utils/logger';
12
- import { getQueryParamString } from '../utils';
12
+ import { calculateVisibleElementData, getQueryParamString } from '../utils';
13
13
  import {
14
14
  Param,
15
15
  DOMSelector,
@@ -23,7 +23,7 @@ import { V1Embed } from './ts-embed';
23
23
  /**
24
24
  * Pages within the ThoughtSpot app that can be embedded.
25
25
  */
26
- // eslint-disable-next-line no-shadow
26
+
27
27
  export enum Page {
28
28
  /**
29
29
  * Home page
@@ -93,7 +93,7 @@ export enum PrimaryNavbarVersion {
93
93
  * Sliding (v3) introduces a new left-side navigation hub featuring a tab switcher,
94
94
  * along with updates to the top navigation bar.
95
95
  * It serves as the foundational version of the PrimaryNavBar.
96
-  */
96
+ */
97
97
  Sliding = 'v3',
98
98
  }
99
99
 
@@ -103,9 +103,9 @@ export enum PrimaryNavbarVersion {
103
103
  */
104
104
  export enum HomePage {
105
105
  /**
106
-  * Modular (v2) introduces the updated Modular Home Experience.
107
-  * It serves as the foundational version of the home page.
108
-  */
106
+ * Modular (v2) introduces the updated Modular Home Experience.
107
+ * It serves as the foundational version of the home page.
108
+ */
109
109
  Modular = 'v2',
110
110
  }
111
111
 
@@ -531,6 +531,48 @@ export interface AppViewConfig extends AllEmbedViewConfig {
531
531
  * ```
532
532
  */
533
533
  isLiveboardStylingAndGroupingEnabled?: boolean;
534
+
535
+ /**
536
+ * This flag is used to enable the full height lazy load data.
537
+ *
538
+ * @example
539
+ * ```js
540
+ * const embed = new AppEmbed('#embed-container', {
541
+ * // ...other options
542
+ * fullHeight: true,
543
+ * lazyLoadingForFullHeight: true,
544
+ * })
545
+ * ```
546
+ *
547
+ * @type {boolean}
548
+ * @default false
549
+ * @version SDK: 1.40.0 | ThoughtSpot:10.12.0.cl
550
+ */
551
+ lazyLoadingForFullHeight?: boolean;
552
+
553
+ /**
554
+ * The margin to be used for lazy loading.
555
+ *
556
+ * For example, if the margin is set to '10px',
557
+ * the visualization will be loaded 10px before the its top edge is visible in the
558
+ * viewport.
559
+ *
560
+ * The format is similar to CSS margin.
561
+ *
562
+ * @example
563
+ * ```js
564
+ * const embed = new AppEmbed('#embed-container', {
565
+ * // ...other options
566
+ * fullHeight: true,
567
+ * lazyLoadingForFullHeight: true,
568
+ * // Using 0px, the visualization will be only loaded when its visible in the viewport.
569
+ * lazyLoadingMargin: '0px',
570
+ * })
571
+ * ```
572
+ * @type {string}
573
+ * @version SDK: 1.40.0 | ThoughtSpot:10.12.0.cl
574
+ */
575
+ lazyLoadingMargin?: string;
534
576
  }
535
577
 
536
578
  /**
@@ -542,7 +584,7 @@ export class AppEmbed extends V1Embed {
542
584
 
543
585
  private defaultHeight = '100%';
544
586
 
545
- // eslint-disable-next-line no-useless-constructor
587
+
546
588
  constructor(domSelector: DOMSelector, viewConfig: AppViewConfig) {
547
589
  viewConfig.embedComponentType = 'AppEmbed';
548
590
  super(domSelector, viewConfig);
@@ -550,6 +592,7 @@ export class AppEmbed extends V1Embed {
550
592
  this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard);
551
593
  this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight);
552
594
  this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter);
595
+ this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler);
553
596
  }
554
597
  }
555
598
 
@@ -583,7 +626,7 @@ export class AppEmbed extends V1Embed {
583
626
  enable2ColumnLayout,
584
627
  enableCustomColumnGroups = false,
585
628
  isOnBeforeGetVizDataInterceptEnabled = false,
586
- /* eslint-disable-next-line max-len */
629
+
587
630
  dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL,
588
631
  collapseSearchBar = true,
589
632
  isLiveboardCompactHeaderEnabled = false,
@@ -598,7 +641,7 @@ export class AppEmbed extends V1Embed {
598
641
  isLiveboardStylingAndGroupingEnabled,
599
642
  } = this.viewConfig;
600
643
 
601
- let params = {};
644
+ let params: any = {};
602
645
  params[Param.PrimaryNavHidden] = !showPrimaryNavbar;
603
646
  params[Param.HideProfleAndHelp] = !!disableProfileAndHelp;
604
647
  params[Param.HideApplicationSwitcher] = !!hideApplicationSwitcher;
@@ -631,6 +674,10 @@ export class AppEmbed extends V1Embed {
631
674
 
632
675
  if (fullHeight === true) {
633
676
  params[Param.fullHeight] = true;
677
+ if (this.viewConfig.lazyLoadingForFullHeight) {
678
+ params[Param.IsLazyLoadingForEmbedEnabled] = true;
679
+ params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin;
680
+ }
634
681
  }
635
682
 
636
683
  if (tag) {
@@ -656,7 +703,7 @@ export class AppEmbed extends V1Embed {
656
703
  }
657
704
 
658
705
  if (isOnBeforeGetVizDataInterceptEnabled) {
659
- /* eslint-disable-next-line max-len */
706
+
660
707
  params[
661
708
  Param.IsOnBeforeGetVizDataInterceptEnabled
662
709
  ] = isOnBeforeGetVizDataInterceptEnabled;
@@ -684,12 +731,12 @@ export class AppEmbed extends V1Embed {
684
731
  || dataPanelCustomGroupsAccordionInitialState
685
732
  === DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST
686
733
  ) {
687
- /* eslint-disable-next-line max-len */
734
+
688
735
  params[
689
736
  Param.DataPanelCustomGroupsAccordionInitialState
690
737
  ] = dataPanelCustomGroupsAccordionInitialState;
691
738
  } else {
692
- /* eslint-disable-next-line max-len */
739
+
693
740
  params[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL;
694
741
  }
695
742
 
@@ -715,6 +762,23 @@ export class AppEmbed extends V1Embed {
715
762
  return queryParams;
716
763
  }
717
764
 
765
+ private sendFullHeightLazyLoadData = () => {
766
+ const data = calculateVisibleElementData(this.iFrame);
767
+ this.trigger(HostEvent.VisibleEmbedCoordinates, data);
768
+ }
769
+
770
+ /**
771
+ * This is a handler for the RequestVisibleEmbedCoordinates event.
772
+ * It is used to send the visible coordinates data to the host application.
773
+ * @param data The event payload
774
+ * @param responder The responder function
775
+ */
776
+ private requestVisibleEmbedCoordinatesHandler = (data: MessagePayload, responder: any) => {
777
+ logger.info('Sending RequestVisibleEmbedCoordinates', data);
778
+ const visibleCoordinatesData = calculateVisibleElementData(this.iFrame);
779
+ responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData });
780
+ }
781
+
718
782
  /**
719
783
  * Constructs the URL of the ThoughtSpot app page to be rendered.
720
784
  * @param pageId The ID of the page to be embedded.
@@ -737,6 +801,7 @@ export class AppEmbed extends V1Embed {
737
801
  */
738
802
  protected updateIFrameHeight = (data: MessagePayload) => {
739
803
  this.setIFrameHeight(Math.max(data.data, this.iFrame?.scrollHeight));
804
+ this.sendFullHeightLazyLoadData();
740
805
  };
741
806
 
742
807
  private embedIframeCenter = (data: MessagePayload, responder: any) => {
@@ -845,6 +910,34 @@ export class AppEmbed extends V1Embed {
845
910
  }
846
911
  }
847
912
 
913
+ /**
914
+ * Destroys the ThoughtSpot embed, and remove any nodes from the DOM.
915
+ * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl
916
+ */
917
+ public destroy() {
918
+ super.destroy();
919
+ this.unregisterLazyLoadEvents();
920
+ }
921
+
922
+ private postRender() {
923
+ this.registerLazyLoadEvents();
924
+ }
925
+
926
+ private registerLazyLoadEvents() {
927
+ if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
928
+ // TODO: Use passive: true, install modernizr to check for passive
929
+ window.addEventListener('resize', this.sendFullHeightLazyLoadData);
930
+ window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true);
931
+ }
932
+ }
933
+
934
+ private unregisterLazyLoadEvents() {
935
+ if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
936
+ window.removeEventListener('resize', this.sendFullHeightLazyLoadData);
937
+ window.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
938
+ }
939
+ }
940
+
848
941
  /**
849
942
  * Renders the embedded application pages in the ThoughtSpot app.
850
943
  * @param renderOptions An object containing the page ID
@@ -856,6 +949,7 @@ export class AppEmbed extends V1Embed {
856
949
  const src = this.getIFrameSrc();
857
950
  await this.renderV1Embed(src);
858
951
 
952
+ this.postRender();
859
953
  return this;
860
954
  }
861
955
  }