@thoughtspot/visual-embed-sdk 1.39.2-alpha.1 → 1.39.2-alpha.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.
Files changed (170) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/config.spec.js +9 -0
  3. package/cjs/src/config.spec.js.map +1 -1
  4. package/cjs/src/embed/app.d.ts +75 -15
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +68 -9
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +356 -4
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/bodyless-conversation.d.ts +19 -7
  11. package/cjs/src/embed/bodyless-conversation.d.ts.map +1 -1
  12. package/cjs/src/embed/bodyless-conversation.js +24 -4
  13. package/cjs/src/embed/bodyless-conversation.js.map +1 -1
  14. package/cjs/src/embed/bodyless-conversation.spec.js +8 -190
  15. package/cjs/src/embed/bodyless-conversation.spec.js.map +1 -1
  16. package/cjs/src/embed/conversation.d.ts +2 -60
  17. package/cjs/src/embed/conversation.d.ts.map +1 -1
  18. package/cjs/src/embed/conversation.js +1 -9
  19. package/cjs/src/embed/conversation.js.map +1 -1
  20. package/cjs/src/embed/conversation.spec.js +0 -102
  21. package/cjs/src/embed/conversation.spec.js.map +1 -1
  22. package/cjs/src/embed/liveboard.d.ts +56 -0
  23. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  24. package/cjs/src/embed/liveboard.js +46 -0
  25. package/cjs/src/embed/liveboard.js.map +1 -1
  26. package/cjs/src/embed/liveboard.spec.js +203 -0
  27. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  28. package/cjs/src/errors.d.ts +1 -0
  29. package/cjs/src/errors.d.ts.map +1 -1
  30. package/cjs/src/errors.js +1 -0
  31. package/cjs/src/errors.js.map +1 -1
  32. package/cjs/src/index.d.ts +2 -2
  33. package/cjs/src/index.d.ts.map +1 -1
  34. package/cjs/src/index.js +2 -1
  35. package/cjs/src/index.js.map +1 -1
  36. package/cjs/src/react/all-types-export.d.ts +1 -1
  37. package/cjs/src/react/all-types-export.d.ts.map +1 -1
  38. package/cjs/src/react/all-types-export.js +3 -2
  39. package/cjs/src/react/all-types-export.js.map +1 -1
  40. package/cjs/src/react/index.d.ts +71 -20
  41. package/cjs/src/react/index.d.ts.map +1 -1
  42. package/cjs/src/react/index.js +79 -42
  43. package/cjs/src/react/index.js.map +1 -1
  44. package/cjs/src/react/index.spec.js +436 -100
  45. package/cjs/src/react/index.spec.js.map +1 -1
  46. package/cjs/src/types.d.ts +46 -4
  47. package/cjs/src/types.d.ts.map +1 -1
  48. package/cjs/src/types.js +28 -0
  49. package/cjs/src/types.js.map +1 -1
  50. package/cjs/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  51. package/cjs/src/utils/graphql/nlsService/conversation-service.js +2 -0
  52. package/cjs/src/utils/graphql/nlsService/conversation-service.js.map +1 -1
  53. package/cjs/src/utils/processTrigger.js +2 -1
  54. package/cjs/src/utils/processTrigger.js.map +1 -1
  55. package/cjs/src/utils.d.ts +6 -0
  56. package/cjs/src/utils.d.ts.map +1 -1
  57. package/cjs/src/utils.js +23 -3
  58. package/cjs/src/utils.js.map +1 -1
  59. package/cjs/src/utils.spec.js +237 -1
  60. package/cjs/src/utils.spec.js.map +1 -1
  61. package/dist/index-e3Uw3YFO.js +7371 -0
  62. package/dist/src/embed/app.d.ts +75 -15
  63. package/dist/src/embed/app.d.ts.map +1 -1
  64. package/dist/src/embed/bodyless-conversation.d.ts +19 -7
  65. package/dist/src/embed/bodyless-conversation.d.ts.map +1 -1
  66. package/dist/src/embed/conversation.d.ts +2 -60
  67. package/dist/src/embed/conversation.d.ts.map +1 -1
  68. package/dist/src/embed/liveboard.d.ts +56 -0
  69. package/dist/src/embed/liveboard.d.ts.map +1 -1
  70. package/dist/src/errors.d.ts +1 -0
  71. package/dist/src/errors.d.ts.map +1 -1
  72. package/dist/src/index.d.ts +2 -2
  73. package/dist/src/index.d.ts.map +1 -1
  74. package/dist/src/react/all-types-export.d.ts +1 -1
  75. package/dist/src/react/all-types-export.d.ts.map +1 -1
  76. package/dist/src/react/index.d.ts +71 -20
  77. package/dist/src/react/index.d.ts.map +1 -1
  78. package/dist/src/types.d.ts +46 -4
  79. package/dist/src/types.d.ts.map +1 -1
  80. package/dist/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  81. package/dist/src/utils.d.ts +6 -0
  82. package/dist/src/utils.d.ts.map +1 -1
  83. package/dist/tsembed-react.es.js +276 -74
  84. package/dist/tsembed-react.js +276 -72
  85. package/dist/tsembed.es.js +194 -27
  86. package/dist/tsembed.js +192 -25
  87. package/dist/visual-embed-sdk-react-full.d.ts +246 -105
  88. package/dist/visual-embed-sdk-react.d.ts +246 -105
  89. package/dist/visual-embed-sdk.d.ts +176 -86
  90. package/lib/package.json +1 -1
  91. package/lib/src/config.spec.js +9 -0
  92. package/lib/src/config.spec.js.map +1 -1
  93. package/lib/src/embed/app.d.ts +75 -15
  94. package/lib/src/embed/app.d.ts.map +1 -1
  95. package/lib/src/embed/app.js +68 -9
  96. package/lib/src/embed/app.js.map +1 -1
  97. package/lib/src/embed/app.spec.js +357 -5
  98. package/lib/src/embed/app.spec.js.map +1 -1
  99. package/lib/src/embed/bodyless-conversation.d.ts +19 -7
  100. package/lib/src/embed/bodyless-conversation.d.ts.map +1 -1
  101. package/lib/src/embed/bodyless-conversation.js +23 -4
  102. package/lib/src/embed/bodyless-conversation.js.map +1 -1
  103. package/lib/src/embed/bodyless-conversation.spec.js +9 -191
  104. package/lib/src/embed/bodyless-conversation.spec.js.map +1 -1
  105. package/lib/src/embed/conversation.d.ts +2 -60
  106. package/lib/src/embed/conversation.d.ts.map +1 -1
  107. package/lib/src/embed/conversation.js +2 -10
  108. package/lib/src/embed/conversation.js.map +1 -1
  109. package/lib/src/embed/conversation.spec.js +2 -104
  110. package/lib/src/embed/conversation.spec.js.map +1 -1
  111. package/lib/src/embed/liveboard.d.ts +56 -0
  112. package/lib/src/embed/liveboard.d.ts.map +1 -1
  113. package/lib/src/embed/liveboard.js +47 -1
  114. package/lib/src/embed/liveboard.js.map +1 -1
  115. package/lib/src/embed/liveboard.spec.js +203 -0
  116. package/lib/src/embed/liveboard.spec.js.map +1 -1
  117. package/lib/src/errors.d.ts +1 -0
  118. package/lib/src/errors.d.ts.map +1 -1
  119. package/lib/src/errors.js +1 -0
  120. package/lib/src/errors.js.map +1 -1
  121. package/lib/src/index.d.ts +2 -2
  122. package/lib/src/index.d.ts.map +1 -1
  123. package/lib/src/index.js +2 -2
  124. package/lib/src/index.js.map +1 -1
  125. package/lib/src/react/all-types-export.d.ts +1 -1
  126. package/lib/src/react/all-types-export.d.ts.map +1 -1
  127. package/lib/src/react/all-types-export.js +1 -1
  128. package/lib/src/react/all-types-export.js.map +1 -1
  129. package/lib/src/react/index.d.ts +71 -20
  130. package/lib/src/react/index.d.ts.map +1 -1
  131. package/lib/src/react/index.js +79 -43
  132. package/lib/src/react/index.js.map +1 -1
  133. package/lib/src/react/index.spec.js +439 -103
  134. package/lib/src/react/index.spec.js.map +1 -1
  135. package/lib/src/types.d.ts +46 -4
  136. package/lib/src/types.d.ts.map +1 -1
  137. package/lib/src/types.js +28 -0
  138. package/lib/src/types.js.map +1 -1
  139. package/lib/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  140. package/lib/src/utils/graphql/nlsService/conversation-service.js +2 -0
  141. package/lib/src/utils/graphql/nlsService/conversation-service.js.map +1 -1
  142. package/lib/src/utils/processTrigger.js +2 -1
  143. package/lib/src/utils/processTrigger.js.map +1 -1
  144. package/lib/src/utils.d.ts +6 -0
  145. package/lib/src/utils.d.ts.map +1 -1
  146. package/lib/src/utils.js +21 -2
  147. package/lib/src/utils.js.map +1 -1
  148. package/lib/src/utils.spec.js +238 -2
  149. package/lib/src/utils.spec.js.map +1 -1
  150. package/lib/src/visual-embed-sdk.d.ts +178 -88
  151. package/package.json +1 -1
  152. package/src/config.spec.ts +11 -0
  153. package/src/embed/app.spec.ts +444 -4
  154. package/src/embed/app.ts +131 -27
  155. package/src/embed/bodyless-conversation.spec.ts +9 -203
  156. package/src/embed/bodyless-conversation.ts +24 -10
  157. package/src/embed/conversation.spec.ts +5 -131
  158. package/src/embed/conversation.ts +10 -82
  159. package/src/embed/liveboard.spec.ts +251 -1
  160. package/src/embed/liveboard.ts +97 -5
  161. package/src/errors.ts +1 -0
  162. package/src/index.ts +2 -0
  163. package/src/react/all-types-export.ts +2 -1
  164. package/src/react/index.spec.tsx +556 -157
  165. package/src/react/index.tsx +117 -51
  166. package/src/types.ts +42 -0
  167. package/src/utils/graphql/nlsService/conversation-service.ts +2 -0
  168. package/src/utils/processTrigger.ts +1 -1
  169. package/src/utils.spec.ts +279 -2
  170. package/src/utils.ts +28 -2
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { useRef } from 'react';
3
+ import React, { useRef, useCallback } from 'react';
4
4
  import useDeepCompareEffect from 'use-deep-compare-effect';
5
5
  import { AuthEventEmitter } from '../auth';
6
6
  import { deepMerge } from '../utils';
@@ -10,12 +10,13 @@ import { SearchEmbed as _SearchEmbed, SearchViewConfig } from '../embed/search';
10
10
  import { AppEmbed as _AppEmbed, AppViewConfig } from '../embed/app';
11
11
  import { LiveboardEmbed as _LiveboardEmbed, LiveboardViewConfig } from '../embed/liveboard';
12
12
  import { TsEmbed } from '../embed/ts-embed';
13
- import { SpotterAgentEmbed as _SpotterAgentEmbed, SpotterAgentEmbedViewConfig } from '../embed/bodyless-conversation';
13
+ import { SpotterAgentEmbed as _SpotterAgentEmbed, SpotterAgentEmbedViewConfig, ConversationMessage as _ConversationMessage, SpotterAgentMessageViewConfig } from '../embed/bodyless-conversation';
14
14
 
15
15
  import { EmbedConfig, EmbedEvent, AllEmbedViewConfig } from '../types';
16
16
  import { EmbedProps, getViewPropsAndListeners } from './util';
17
17
  import { SpotterEmbed as _SpotterEmbed, SpotterEmbedViewConfig, ConversationEmbed as _ConversationEmbed, ConversationViewConfig } from '../embed/conversation';
18
18
  import { init } from '../embed/base';
19
+ import { ERROR_MESSAGE } from '../errors';
19
20
 
20
21
  const componentFactory = <T extends typeof TsEmbed, U extends EmbedProps, V extends AllEmbedViewConfig>(
21
22
  EmbedConstructor: T,
@@ -383,62 +384,61 @@ export const ConversationEmbed = componentFactory<
383
384
  ConversationViewConfig
384
385
  >(_ConversationEmbed);
385
386
 
386
- interface SpotterAgentEmbedProps extends EmbedProps, SpotterAgentEmbedViewConfig {}
387
+ /**
388
+ * React component for individual conversation messages from SpotterAgent.
389
+ * This component is used internally by the useSpotterAgent hook.
390
+ * @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl
391
+ */
392
+ interface ConversationMessageProps extends EmbedProps, SpotterAgentMessageViewConfig {}
393
+
394
+ export const ConversationMessage = componentFactory<
395
+ typeof _ConversationMessage,
396
+ ConversationMessageProps,
397
+ SpotterAgentMessageViewConfig
398
+ >(_ConversationMessage);
399
+
400
+ type SpotterMessageProps = {
401
+ message: SpotterAgentMessageViewConfig;
402
+ query?: string;
403
+ } & Omit<EmbedProps, keyof SpotterAgentMessageViewConfig>;
387
404
 
388
405
  /**
389
- * React component for SpotterAgent embed, which can be integrated inside
390
- * chatbots or other conversational interfaces.
406
+ * React component for displaying individual conversation messages from SpotterAgent.
407
+ *
408
+ * This component renders a single message response from your ThoughtSpot conversation,
409
+ * showing charts, visualizations, or text responses based on the user's query.
410
+ *
391
411
  * @example
392
412
  * ```tsx
393
- * function SpotterAgent() {
394
- * const ref = useRef();
395
- *
396
- * const handleSendMessage = async () => {
397
- * const { container, error } = await ref.current.sendMessage('show me sales by region');
398
- * if (container) {
399
- * document.body.appendChild(container);
400
- * }
401
- * };
402
- *
403
- * return (
404
- * <div>
405
- * <SpotterAgentEmbed ref={ref} worksheetId="worksheetId" />
406
- * <button onClick={handleSendMessage}>Send Message</button>
407
- * </div>
408
- * );
413
+ * const { sendMessage } = useSpotterAgent({ worksheetId: 'worksheetId' });
414
+ * const result = await sendMessage('show me sales by region');
415
+ *
416
+ * if (!result.error) {
417
+ * // Simple usage - just pass the message data
418
+ * <SpotterMessage message={result.message} />
419
+ *
420
+ * // With optional query for context
421
+ * <SpotterMessage
422
+ * message={result.message}
423
+ * query={result.query}
424
+ * />
409
425
  * }
410
426
  * ```
427
+ * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl
411
428
  */
412
- export const SpotterAgentEmbed = React.forwardRef<_SpotterAgentEmbed, SpotterAgentEmbedProps>((props, ref) => {
413
- const { className, ...restProps } = props;
414
- const serviceRef = useRef<_SpotterAgentEmbed | null>(null);
415
-
416
- useDeepCompareEffect(() => {
417
- if (serviceRef.current) {
418
- serviceRef.current = null;
419
- }
420
-
421
- const configProps = {
422
- ...restProps,
423
- ...(className ? { containerClassName: className } : {})
424
- };
425
-
426
- serviceRef.current = new _SpotterAgentEmbed(configProps);
429
+ export const SpotterMessage = React.forwardRef<
430
+ React.ComponentRef<typeof ConversationMessage>,
431
+ SpotterMessageProps
432
+ >((props, ref) => {
433
+ const { message, query: _, ...otherProps } = props;
427
434
 
428
- if (ref) {
429
- if (typeof ref === 'function') {
430
- ref(serviceRef.current);
431
- } else {
432
- ref.current = serviceRef.current;
433
- }
434
- }
435
-
436
- return () => {
437
- serviceRef.current = null;
438
- };
439
- }, [props]);
440
-
441
- return null;
435
+ return (
436
+ <ConversationMessage
437
+ ref={ref}
438
+ {...message}
439
+ {...otherProps}
440
+ />
441
+ );
442
442
  });
443
443
 
444
444
  /**
@@ -470,7 +470,8 @@ type EmbedComponent = typeof SearchEmbed
470
470
  | typeof LiveboardEmbed
471
471
  | typeof SearchBarEmbed
472
472
  | typeof SageEmbed
473
- | typeof SpotterAgentEmbed
473
+ | typeof ConversationMessage
474
+ | typeof SpotterMessage
474
475
  | typeof SpotterEmbed
475
476
  | typeof ConversationEmbed;
476
477
 
@@ -518,6 +519,71 @@ export function useInit(config: EmbedConfig) {
518
519
  return ref;
519
520
  }
520
521
 
522
+ /**
523
+ * React hook for interacting with SpotterAgent AI conversations.
524
+ *
525
+ * This hook provides a sendMessage function that allows you to send natural language
526
+ * queries to your data and get back AI-generated responses with visualizations.
527
+ *
528
+ * @param config - Configuration object containing worksheetId and other options
529
+ * @returns Object with sendMessage function that returns conversation results
530
+ * @example
531
+ * ```tsx
532
+ * const { sendMessage } = useSpotterAgent({ worksheetId: 'worksheetId' });
533
+ *
534
+ * const handleQuery = async () => {
535
+ * const result = await sendMessage('show me sales by region');
536
+ *
537
+ * if (!result.error) {
538
+ * // Display the message response
539
+ * <SpotterMessage message={result.message} />
540
+ * } else {
541
+ * console.error('Error:', result.error);
542
+ * }
543
+ * };
544
+ * ```
545
+ * @version SDK: 1.39.0 | ThoughtSpot: 10.11.0.cl
546
+ */
547
+ export function useSpotterAgent(config: SpotterAgentEmbedViewConfig) {
548
+ const serviceRef = useRef<_SpotterAgentEmbed | null>(null);
549
+
550
+ useDeepCompareEffect(() => {
551
+ if (serviceRef.current) {
552
+ serviceRef.current = null;
553
+ }
554
+
555
+ serviceRef.current = new _SpotterAgentEmbed(config);
556
+
557
+ return () => {
558
+ serviceRef.current = null;
559
+ };
560
+ }, [config]);
561
+
562
+ const sendMessage = useCallback(async (query: string) => {
563
+ if (!serviceRef.current) {
564
+ return { error: new Error(ERROR_MESSAGE.SPOTTER_AGENT_NOT_INITIALIZED) };
565
+ }
566
+
567
+ const result = await serviceRef.current.sendMessageData(query);
568
+
569
+ if (result.error) {
570
+ return { error: result.error };
571
+ }
572
+
573
+ return {
574
+ query: query,
575
+ message: {
576
+ ...result.data,
577
+ worksheetId: config.worksheetId,
578
+ },
579
+ };
580
+ }, [config.worksheetId]);
581
+
582
+ return {
583
+ sendMessage,
584
+ };
585
+ }
586
+
521
587
  export {
522
588
  LiveboardViewConfig,
523
589
  SearchViewConfig,
package/src/types.ts CHANGED
@@ -217,6 +217,12 @@ export enum HomeLeftNavItem {
217
217
  * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
218
218
  */
219
219
  Spotter = 'spotter',
220
+ /**
221
+ * Favorites option in the insights left navigation,
222
+ * available when new navigation V3 is enabled.
223
+ * @version SDK: 1.41.0 | ThoughtSpot: 10.12.0.cl
224
+ */
225
+ Favorites = 'favorites',
220
226
  }
221
227
  export type DOMSelector = string | HTMLElement;
222
228
 
@@ -1010,6 +1016,20 @@ export interface BaseViewConfig {
1010
1016
  * @private
1011
1017
  */
1012
1018
  insertInToSlide?: boolean;
1019
+ /**
1020
+ * Show alert messages and toast messages in the embed.
1021
+ * Supported embed in all embed types.
1022
+ *
1023
+ * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1.sw
1024
+ * @example
1025
+ * ```js
1026
+ * const embed = new AppEmbed('#tsEmbed', {
1027
+ * ... // other embed view config
1028
+ * showAlerts:true,
1029
+ * })
1030
+ * ```
1031
+ */
1032
+ showAlerts?: boolean;
1013
1033
  }
1014
1034
 
1015
1035
  /**
@@ -2687,6 +2707,12 @@ export enum EmbedEvent {
2687
2707
  * @version SDK : 1.40.0 | ThoughtSpot: 10.11.0.cl
2688
2708
  */
2689
2709
  ExitPresentMode = 'exitPresentMode',
2710
+ /**
2711
+ * Emitted when a user requests the full height lazy load data.
2712
+ * @version SDK : 1.39.0 | ThoughtSpot : 10.10.0.cl
2713
+ * @hidden
2714
+ */
2715
+ RequestVisibleEmbedCoordinates = 'requestVisibleEmbedCoordinates',
2690
2716
  }
2691
2717
 
2692
2718
  /**
@@ -3771,6 +3797,19 @@ export enum HostEvent {
3771
3797
  * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
3772
3798
  */
3773
3799
  ExitPresentMode = 'exitPresentMode',
3800
+ /**
3801
+ * Triggers the full height lazy load data.
3802
+ * @example
3803
+ * ```js
3804
+ * liveboardEmbed.on(EmbedEvent.RequestVisibleEmbedCoordinates, (payload) => {
3805
+ * console.log(payload);
3806
+ * });
3807
+ * ```
3808
+ * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl
3809
+ *
3810
+ * @hidden
3811
+ */
3812
+ VisibleEmbedCoordinates = 'visibleEmbedCoordinates',
3774
3813
  }
3775
3814
 
3776
3815
  /**
@@ -3876,6 +3915,7 @@ export enum Param {
3876
3915
  Query = 'query',
3877
3916
  HideHomepageLeftNav = 'hideHomepageLeftNav',
3878
3917
  ModularHomeExperienceEnabled = 'modularHomeExperience',
3918
+ ListPageVersion = 'listpageVersion',
3879
3919
  PendoTrackingKey = 'additionalPendoKey',
3880
3920
  LiveboardHeaderSticky = 'isLiveboardHeaderSticky',
3881
3921
  IsProductTour = 'isProductTour',
@@ -3914,6 +3954,8 @@ export enum Param {
3914
3954
  PrimaryAction = 'primaryAction',
3915
3955
  isSpotterAgentEmbed = 'isSpotterAgentEmbed',
3916
3956
  IsLiveboardStylingAndGroupingEnabled = 'isLiveboardStylingAndGroupingEnabled',
3957
+ IsLazyLoadingForEmbedEnabled = 'isLazyLoadingForEmbedEnabled',
3958
+ RootMarginForLazyLoad = 'rootMarginForLazyLoad'
3917
3959
  }
3918
3960
 
3919
3961
  /**
@@ -64,6 +64,8 @@ export class Conversation {
64
64
  );
65
65
  const data = responses[0].data;
66
66
  return {
67
+ convId: this.conversationId,
68
+ messageId: responses[0].msgId,
67
69
  data: data.asstRespData.nlsAnsData.sageQuerySuggestions[0],
68
70
  error: null,
69
71
  };
@@ -31,7 +31,7 @@ function postIframeMessage(
31
31
  thoughtSpotHost: string,
32
32
  channel?: MessageChannel,
33
33
  ) {
34
- return iFrame.contentWindow.postMessage(message, thoughtSpotHost, [channel?.port2]);
34
+ return iFrame.contentWindow?.postMessage(message, thoughtSpotHost, [channel?.port2]);
35
35
  }
36
36
 
37
37
  export const TRIGGER_TIMEOUT = 30000;
package/src/utils.spec.ts CHANGED
@@ -15,6 +15,8 @@ import {
15
15
  getValueFromWindow,
16
16
  handlePresentEvent,
17
17
  handleExitPresentMode,
18
+ getTypeFromValue,
19
+ calculateVisibleElementData,
18
20
  } from './utils';
19
21
  import { RuntimeFilterOp } from './types';
20
22
  import { logger } from './utils/logger';
@@ -284,6 +286,27 @@ describe('unit test for utils', () => {
284
286
  test('isUndefined', () => {
285
287
  expect(isUndefined(undefined)).toBe(true);
286
288
  expect(isUndefined({})).toBe(false);
289
+ expect(isUndefined(null)).toBe(false);
290
+ expect(isUndefined('')).toBe(false);
291
+ expect(isUndefined(0)).toBe(false);
292
+ });
293
+
294
+ test('removeTypename should handle edge cases', () => {
295
+ expect(removeTypename(null)).toBe(null);
296
+ expect(removeTypename(undefined)).toBe(undefined);
297
+ expect(removeTypename('string')).toBe('string');
298
+ expect(removeTypename(123)).toBe(123);
299
+ });
300
+
301
+ test('getTypeFromValue should return correct types', () => {
302
+ expect(getTypeFromValue('test')).toEqual(['char', 'string']);
303
+ expect(getTypeFromValue(123)).toEqual(['double', 'double']);
304
+ expect(getTypeFromValue(true)).toEqual(['boolean', 'boolean']);
305
+ expect(getTypeFromValue(false)).toEqual(['boolean', 'boolean']);
306
+ expect(getTypeFromValue(null)).toEqual(['', '']);
307
+ expect(getTypeFromValue(undefined)).toEqual(['', '']);
308
+ expect(getTypeFromValue({})).toEqual(['', '']);
309
+ expect(getTypeFromValue([])).toEqual(['', '']);
287
310
  });
288
311
 
289
312
  describe('getValueFromWindow and storeValueInWindow', () => {
@@ -293,7 +316,7 @@ describe('unit test for utils', () => {
293
316
  });
294
317
 
295
318
  test('Object should be set if not', () => {
296
- // eslint-disable-next-line no-underscore-dangle
319
+
297
320
  (window as any)._tsEmbedSDK = null;
298
321
 
299
322
  storeValueInWindow('test', 'testValue');
@@ -303,6 +326,13 @@ describe('unit test for utils', () => {
303
326
  test('Return undefined if key is not found', () => {
304
327
  expect(getValueFromWindow('notFound')).toBe(undefined);
305
328
  });
329
+
330
+ test('Store with ignoreIfAlreadyExists option', () => {
331
+ storeValueInWindow('test2', 'firstValue');
332
+ const result = storeValueInWindow('test2', 'secondValue', { ignoreIfAlreadyExists: true });
333
+ expect(result).toBe('firstValue');
334
+ expect(getValueFromWindow('test2')).toBe('firstValue');
335
+ });
306
336
  });
307
337
  });
308
338
 
@@ -312,7 +342,7 @@ describe('Fullscreen Utility Functions', () => {
312
342
 
313
343
  beforeEach(() => {
314
344
  jest.clearAllMocks();
315
-
345
+
316
346
  // Store and mock exitFullscreen
317
347
  originalExitFullscreen = document.exitFullscreen;
318
348
  document.exitFullscreen = jest.fn();
@@ -407,3 +437,250 @@ describe('Fullscreen Utility Functions', () => {
407
437
  });
408
438
  });
409
439
  });
440
+
441
+ describe('calculateVisibleElementData', () => {
442
+ let mockElement: HTMLElement;
443
+ let originalInnerHeight: number;
444
+ let originalInnerWidth: number;
445
+
446
+ beforeEach(() => {
447
+ // Store original window dimensions
448
+ originalInnerHeight = window.innerHeight;
449
+ originalInnerWidth = window.innerWidth;
450
+
451
+ // Mock window dimensions
452
+ Object.defineProperty(window, 'innerHeight', {
453
+ writable: true,
454
+ configurable: true,
455
+ value: 800,
456
+ });
457
+ Object.defineProperty(window, 'innerWidth', {
458
+ writable: true,
459
+ configurable: true,
460
+ value: 1200,
461
+ });
462
+
463
+ // Create mock element
464
+ mockElement = document.createElement('div');
465
+ });
466
+
467
+ afterEach(() => {
468
+ // Restore original window dimensions
469
+ Object.defineProperty(window, 'innerHeight', {
470
+ value: originalInnerHeight,
471
+ });
472
+ Object.defineProperty(window, 'innerWidth', {
473
+ value: originalInnerWidth,
474
+ });
475
+ });
476
+
477
+ it('should calculate data for fully visible element', () => {
478
+ // Mock getBoundingClientRect for element fully within viewport
479
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
480
+ top: 100,
481
+ left: 150,
482
+ bottom: 300,
483
+ right: 400,
484
+ width: 250,
485
+ height: 200,
486
+ } as DOMRect);
487
+
488
+ const result = calculateVisibleElementData(mockElement);
489
+
490
+ expect(result).toEqual({
491
+ top: 0, // Not clipped from top
492
+ height: 200, // Full height visible
493
+ left: 0, // Not clipped from left
494
+ width: 250, // Full width visible
495
+ });
496
+ });
497
+
498
+ it('should calculate data for element clipped from top', () => {
499
+ // Mock getBoundingClientRect for element partially above viewport
500
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
501
+ top: -50,
502
+ left: 100,
503
+ bottom: 150,
504
+ right: 400,
505
+ width: 300,
506
+ height: 200,
507
+ } as DOMRect);
508
+
509
+ const result = calculateVisibleElementData(mockElement);
510
+
511
+ expect(result).toEqual({
512
+ top: 50, // Clipped 50px from top
513
+ height: 150, // 150px visible height (0 to 150)
514
+ left: 0, // Not clipped from left
515
+ width: 300, // Full width visible
516
+ });
517
+ });
518
+
519
+ it('should calculate data for element clipped from left', () => {
520
+ // Mock getBoundingClientRect for element partially left of viewport
521
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
522
+ top: 100,
523
+ left: -80,
524
+ bottom: 300,
525
+ right: 200,
526
+ width: 280,
527
+ height: 200,
528
+ } as DOMRect);
529
+
530
+ const result = calculateVisibleElementData(mockElement);
531
+
532
+ expect(result).toEqual({
533
+ top: 0, // Not clipped from top
534
+ height: 200, // Full height visible
535
+ left: 80, // Clipped 80px from left
536
+ width: 200, // 200px visible width (0 to 200)
537
+ });
538
+ });
539
+
540
+ it('should calculate data for element clipped from bottom', () => {
541
+ // Mock getBoundingClientRect for element extending below viewport
542
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
543
+ top: 600,
544
+ left: 100,
545
+ bottom: 950, // Extends beyond window height of 800
546
+ right: 400,
547
+ width: 300,
548
+ height: 350,
549
+ } as DOMRect);
550
+
551
+ const result = calculateVisibleElementData(mockElement);
552
+
553
+ expect(result).toEqual({
554
+ top: 0, // Not clipped from top
555
+ height: 200, // Only 200px visible (600 to 800)
556
+ left: 0, // Not clipped from left
557
+ width: 300, // Full width visible
558
+ });
559
+ });
560
+
561
+ it('should calculate data for element clipped from right', () => {
562
+ // Mock getBoundingClientRect for element extending beyond right edge
563
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
564
+ top: 100,
565
+ left: 1000,
566
+ bottom: 300,
567
+ right: 1400, // Extends beyond window width of 1200
568
+ width: 400,
569
+ height: 200,
570
+ } as DOMRect);
571
+
572
+ const result = calculateVisibleElementData(mockElement);
573
+
574
+ expect(result).toEqual({
575
+ top: 0, // Not clipped from top
576
+ height: 200, // Full height visible
577
+ left: 0, // Not clipped from left
578
+ width: 200, // Only 200px visible width (1000 to 1200)
579
+ });
580
+ });
581
+
582
+ it('should calculate data for element clipped from multiple sides', () => {
583
+ // Mock getBoundingClientRect for element clipped from top and left
584
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
585
+ top: -100,
586
+ left: -50,
587
+ bottom: 200,
588
+ right: 300,
589
+ width: 350,
590
+ height: 300,
591
+ } as DOMRect);
592
+
593
+ const result = calculateVisibleElementData(mockElement);
594
+
595
+ expect(result).toEqual({
596
+ top: 100, // Clipped 100px from top
597
+ height: 200, // 200px visible height (0 to 200)
598
+ left: 50, // Clipped 50px from left
599
+ width: 300, // 300px visible width (0 to 300)
600
+ });
601
+ });
602
+
603
+ it('should handle element completely outside viewport (above)', () => {
604
+ // Mock getBoundingClientRect for element completely above viewport
605
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
606
+ top: -300,
607
+ left: 100,
608
+ bottom: -100,
609
+ right: 400,
610
+ width: 300,
611
+ height: 200,
612
+ } as DOMRect);
613
+
614
+ const result = calculateVisibleElementData(mockElement);
615
+
616
+ expect(result).toEqual({
617
+ top: 300, // Clipped 300px from top
618
+ height: 0, // No visible height (clamped from negative)
619
+ left: 0, // Not clipped from left
620
+ width: 300, // Full width would be visible if in viewport
621
+ });
622
+ });
623
+
624
+ it('should handle element completely outside viewport (left)', () => {
625
+ // Mock getBoundingClientRect for element completely left of viewport
626
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
627
+ top: 100,
628
+ left: -400,
629
+ bottom: 300,
630
+ right: -100,
631
+ width: 300,
632
+ height: 200,
633
+ } as DOMRect);
634
+
635
+ const result = calculateVisibleElementData(mockElement);
636
+
637
+ expect(result).toEqual({
638
+ top: 0, // Not clipped from top
639
+ height: 200, // Full height would be visible if in viewport
640
+ left: 400, // Clipped 400px from left
641
+ width: 0, // No visible width (min(1200, -100) - max(-400, 0) = -100 - 0 = -100, but clamped)
642
+ });
643
+ });
644
+
645
+ it('should handle element larger than viewport', () => {
646
+ // Mock getBoundingClientRect for element larger than viewport
647
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
648
+ top: -200,
649
+ left: -300,
650
+ bottom: 1000,
651
+ right: 1500,
652
+ width: 1800,
653
+ height: 1200,
654
+ } as DOMRect);
655
+
656
+ const result = calculateVisibleElementData(mockElement);
657
+
658
+ expect(result).toEqual({
659
+ top: 200, // Clipped 200px from top
660
+ height: 800, // Visible height equals window height
661
+ left: 300, // Clipped 300px from left
662
+ width: 1200, // Visible width equals window width
663
+ });
664
+ });
665
+
666
+ it('should handle element exactly at viewport boundaries', () => {
667
+ // Mock getBoundingClientRect for element at exact viewport boundaries
668
+ jest.spyOn(mockElement, 'getBoundingClientRect').mockReturnValue({
669
+ top: 0,
670
+ left: 0,
671
+ bottom: 800,
672
+ right: 1200,
673
+ width: 1200,
674
+ height: 800,
675
+ } as DOMRect);
676
+
677
+ const result = calculateVisibleElementData(mockElement);
678
+
679
+ expect(result).toEqual({
680
+ top: 0, // Not clipped from top
681
+ height: 800, // Full viewport height
682
+ left: 0, // Not clipped from left
683
+ width: 1200, // Full viewport width
684
+ });
685
+ });
686
+ });
package/src/utils.ts CHANGED
@@ -269,7 +269,7 @@ export const getOperationNameFromQuery = (query: string) => {
269
269
  export function removeTypename(obj: any) {
270
270
  if (!obj || typeof obj !== 'object') return obj;
271
271
 
272
- // eslint-disable-next-line no-restricted-syntax
272
+
273
273
  for (const key in obj) {
274
274
  if (key === '__typename') {
275
275
  delete obj[key];
@@ -300,7 +300,11 @@ export const setStyleProperties = (
300
300
  ): void => {
301
301
  if (!element?.style) return;
302
302
  Object.keys(styleProperties).forEach((styleProperty) => {
303
- element.style[styleProperty] = styleProperties[styleProperty].toString();
303
+ const styleKey = styleProperty as keyof CSSStyleDeclaration;
304
+ const value = styleProperties[styleKey];
305
+ if (value !== undefined) {
306
+ (element.style as any)[styleKey] = value.toString();
307
+ }
304
308
  });
305
309
  };
306
310
  /**
@@ -463,3 +467,25 @@ export const handleExitPresentMode = async (): Promise<void> => {
463
467
 
464
468
  logger.warn('Exit fullscreen API is not supported by this browser.');
465
469
  };
470
+
471
+ export const calculateVisibleElementData = (element: HTMLElement) => {
472
+ const rect = element.getBoundingClientRect();
473
+
474
+ const windowHeight = window.innerHeight;
475
+ const windowWidth = window.innerWidth;
476
+
477
+ const frameRelativeTop = Math.max(rect.top, 0);
478
+ const frameRelativeLeft = Math.max(rect.left, 0);
479
+
480
+ const frameRelativeBottom = Math.min(windowHeight, rect.bottom);
481
+ const frameRelativeRight = Math.min(windowWidth, rect.right);
482
+
483
+ const data = {
484
+ top: Math.max(0, rect.top * -1),
485
+ height: Math.max(0, frameRelativeBottom - frameRelativeTop),
486
+ left: Math.max(0, rect.left * -1),
487
+ width: Math.max(0, frameRelativeRight - frameRelativeLeft),
488
+ };
489
+
490
+ return data;
491
+ }