@envive-ai/react-widgets-v3 0.3.62 → 0.3.63

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 (47) hide show
  1. package/dist/hocs/withBaseWidget/types.d.cts +7 -0
  2. package/dist/hocs/withBaseWidget/types.d.ts +7 -0
  3. package/dist/hocs/withBaseWidget/withBaseWidget.cjs +2 -1
  4. package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
  5. package/dist/hocs/withBaseWidget/withBaseWidget.d.ts +2 -2
  6. package/dist/hocs/withBaseWidget/withBaseWidget.js +2 -1
  7. package/dist/packages/widgets/dist/SearchResults/SearchResultsWidget.d.ts +2 -2
  8. package/dist/packages/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts +2 -2
  9. package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +2 -2
  10. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
  11. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts +3 -3
  12. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
  13. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts +3 -3
  14. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
  15. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
  16. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
  17. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.ts +2 -2
  18. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.cts +2 -2
  19. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts +2 -2
  20. package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +6 -16
  21. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
  22. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
  23. package/dist/widgets/ProductCardWidget/ProductCardWidget.js +6 -16
  24. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
  25. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
  26. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
  27. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
  28. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.cts +2 -2
  29. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
  30. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
  31. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
  32. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
  33. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
  34. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
  35. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
  36. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
  37. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
  38. package/dist/widgets/dist/SearchResults/SearchResultsWidget.d.cts +2 -2
  39. package/dist/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.cts +2 -2
  40. package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +2 -2
  41. package/package.json +1 -1
  42. package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +36 -0
  43. package/src/hocs/withBaseWidget/types.ts +7 -0
  44. package/src/hocs/withBaseWidget/withBaseWidget.tsx +10 -4
  45. package/src/stories/FullPageSalesAgentWidget.stories.tsx +64 -0
  46. package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +13 -18
  47. package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +69 -2
@@ -12,6 +12,13 @@ interface BaseWidgetProps {
12
12
  uiConfig?: OrgUIConfigV3;
13
13
  isLoading?: boolean;
14
14
  isHardcopyLoading?: boolean;
15
+ /**
16
+ * True once THIS widget's own hardcopy request has settled (resolved or errored),
17
+ * regardless of whether it produced content. Lets a widget tell "hardcopy not received
18
+ * yet" apart from "hardcopy finished with no content" so it can leave a loading placeholder
19
+ * after a failed fetch instead of showing it forever.
20
+ */
21
+ isHardcopySettled?: boolean;
15
22
  isPageLoading?: boolean;
16
23
  isWidgetConfigLoading?: boolean;
17
24
  isUiConfigLoading?: boolean;
@@ -12,6 +12,13 @@ interface BaseWidgetProps {
12
12
  uiConfig?: OrgUIConfigV3;
13
13
  isLoading?: boolean;
14
14
  isHardcopyLoading?: boolean;
15
+ /**
16
+ * True once THIS widget's own hardcopy request has settled (resolved or errored),
17
+ * regardless of whether it produced content. Lets a widget tell "hardcopy not received
18
+ * yet" apart from "hardcopy finished with no content" so it can leave a loading placeholder
19
+ * after a failed fetch instead of showing it forever.
20
+ */
21
+ isHardcopySettled?: boolean;
15
22
  isPageLoading?: boolean;
16
23
  isWidgetConfigLoading?: boolean;
17
24
  isUiConfigLoading?: boolean;
@@ -30,7 +30,6 @@ function withBaseWidget(Widget, options) {
30
30
  const [widgetConfigSettled, setWidgetConfigSettled] = (0, react.useState)(false);
31
31
  const [uiConfigSettled, setUiConfigSettled] = (0, react.useState)(false);
32
32
  const ownRequestsSettled = hardcopySettled && widgetConfigSettled && uiConfigSettled;
33
- const isLoading = !ownRequestsSettled && (isHardcopyLoading || isPageLoading || isWidgetConfigLoading || isUiConfigLoading);
34
33
  const { isVisible } = (0, __envive_ai_react_hooks_hooks_TrackComponentVisibleEvent.useTrackComponentVisibleEvent)(widgetRef, {
35
34
  widget_config_id: widgetConfigId,
36
35
  widget_type: widgetType,
@@ -44,6 +43,7 @@ function withBaseWidget(Widget, options) {
44
43
  widgetConfigId,
45
44
  widgetType
46
45
  ]);
46
+ const isLoading = startLoading && !ownRequestsSettled;
47
47
  const settledStateRef = (0, react.useRef)({
48
48
  hardcopy: false,
49
49
  widgetConfig: false,
@@ -119,6 +119,7 @@ function withBaseWidget(Widget, options) {
119
119
  ...props,
120
120
  isLoading,
121
121
  isHardcopyLoading,
122
+ isHardcopySettled: hardcopySettled,
122
123
  isPageLoading,
123
124
  isWidgetConfigLoading,
124
125
  isUiConfigLoading,
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps, WithBaseWidgetOptions } from "./types.cjs";
2
- import * as react_jsx_runtime1 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime11 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/hocs/withBaseWidget/withBaseWidget.d.ts
5
5
  declare function withBaseWidget<P extends BaseWidgetProps>(Widget: React.ComponentType<P>, options?: WithBaseWidgetOptions): {
6
- (props: P): react_jsx_runtime1.JSX.Element;
6
+ (props: P): react_jsx_runtime11.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps, WithBaseWidgetOptions } from "./types.js";
2
- import * as react_jsx_runtime17 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime19 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/hocs/withBaseWidget/withBaseWidget.d.ts
5
5
  declare function withBaseWidget<P extends BaseWidgetProps>(Widget: React.ComponentType<P>, options?: WithBaseWidgetOptions): {
6
- (props: P): react_jsx_runtime17.JSX.Element;
6
+ (props: P): react_jsx_runtime19.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  //#endregion
@@ -28,7 +28,6 @@ function withBaseWidget(Widget, options) {
28
28
  const [widgetConfigSettled, setWidgetConfigSettled] = useState(false);
29
29
  const [uiConfigSettled, setUiConfigSettled] = useState(false);
30
30
  const ownRequestsSettled = hardcopySettled && widgetConfigSettled && uiConfigSettled;
31
- const isLoading = !ownRequestsSettled && (isHardcopyLoading || isPageLoading || isWidgetConfigLoading || isUiConfigLoading);
32
31
  const { isVisible } = useTrackComponentVisibleEvent(widgetRef, {
33
32
  widget_config_id: widgetConfigId,
34
33
  widget_type: widgetType,
@@ -42,6 +41,7 @@ function withBaseWidget(Widget, options) {
42
41
  widgetConfigId,
43
42
  widgetType
44
43
  ]);
44
+ const isLoading = startLoading && !ownRequestsSettled;
45
45
  const settledStateRef = useRef({
46
46
  hardcopy: false,
47
47
  widgetConfig: false,
@@ -117,6 +117,7 @@ function withBaseWidget(Widget, options) {
117
117
  ...props,
118
118
  isLoading,
119
119
  isHardcopyLoading,
120
+ isHardcopySettled: hardcopySettled,
120
121
  isPageLoading,
121
122
  isWidgetConfigLoading,
122
123
  isUiConfigLoading,
@@ -1,8 +1,8 @@
1
- import * as react_jsx_runtime1 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
2
2
 
3
3
  //#region ../widgets/dist/SearchResults/SearchResultsWidget.d.ts
4
4
  //#region src/SearchResults/SearchResultsWidget.d.ts
5
- declare const SearchResultsWidget: () => react_jsx_runtime1.JSX.Element;
5
+ declare const SearchResultsWidget: () => react_jsx_runtime2.JSX.Element;
6
6
  //#endregion
7
7
 
8
8
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime2 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime3 from "react/jsx-runtime";
2
2
 
3
3
  //#region ../widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts
4
4
  //#region src/SearchZeroState/SearchZeroStateWidget.d.ts
@@ -11,7 +11,7 @@ declare const SearchZeroStateWidget: ({
11
11
  initialIsOpen,
12
12
  widgetConfigId,
13
13
  entryPointRef
14
- }: SearchZeroStateWidgetProps) => react_jsx_runtime2.JSX.Element;
14
+ }: SearchZeroStateWidgetProps) => react_jsx_runtime3.JSX.Element;
15
15
  //#endregion
16
16
 
17
17
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { SuggestionBarLocationForMetrics } from "./types.js";
2
- import * as react_jsx_runtime3 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
  import { SuggestionButtonVariant } from "@envive-ai/react-hooks/contexts/types";
4
4
  import { Message } from "postcss";
5
5
 
@@ -30,7 +30,7 @@ declare function SuggestionBar({
30
30
  buttonBorderRadius,
31
31
  handleReply,
32
32
  dataTestId
33
- }: Readonly<SuggestionBarProps>): react_jsx_runtime3.JSX.Element;
33
+ }: Readonly<SuggestionBarProps>): react_jsx_runtime0.JSX.Element;
34
34
  //#endregion
35
35
  //#endregion
36
36
  export { SuggestionBar };
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.cjs";
2
- import * as react_jsx_runtime12 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime9 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts
5
5
  declare const ChatPreviewComparisonWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime12.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime9.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface ChatPreviewComparisonWidgetProps {
@@ -12,7 +12,7 @@ interface ChatPreviewComparisonWidgetProps {
12
12
  declare const ChatPreviewComparisonWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: ChatPreviewComparisonWidgetProps): react_jsx_runtime12.JSX.Element;
15
+ }: ChatPreviewComparisonWidgetProps): react_jsx_runtime9.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.js";
2
- import * as react_jsx_runtime18 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime17 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts
5
5
  declare const ChatPreviewComparisonWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime18.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime17.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface ChatPreviewComparisonWidgetProps {
@@ -12,7 +12,7 @@ interface ChatPreviewComparisonWidgetProps {
12
12
  declare const ChatPreviewComparisonWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: ChatPreviewComparisonWidgetProps): react_jsx_runtime18.JSX.Element;
15
+ }: ChatPreviewComparisonWidgetProps): react_jsx_runtime17.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.cjs";
2
- import * as react_jsx_runtime5 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime7 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts
5
5
  declare const ChatPreviewLoadingWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime5.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime7.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface ChatPreviewLoadingWidgetProps {
@@ -11,6 +11,6 @@ interface ChatPreviewLoadingWidgetProps {
11
11
  }
12
12
  declare const ChatPreviewLoadingWidget: ({
13
13
  widgetConfigId
14
- }: ChatPreviewLoadingWidgetProps) => react_jsx_runtime5.JSX.Element;
14
+ }: ChatPreviewLoadingWidgetProps) => react_jsx_runtime7.JSX.Element;
15
15
  //#endregion
16
16
  export { ChatPreviewLoadingWidget, ChatPreviewLoadingWidgetProps, ChatPreviewLoadingWidgetWithBaseWidget };
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.js";
2
- import * as react_jsx_runtime15 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime10 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts
5
5
  declare const ChatPreviewLoadingWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime15.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime10.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface ChatPreviewLoadingWidgetProps {
@@ -11,6 +11,6 @@ interface ChatPreviewLoadingWidgetProps {
11
11
  }
12
12
  declare const ChatPreviewLoadingWidget: ({
13
13
  widgetConfigId
14
- }: ChatPreviewLoadingWidgetProps) => react_jsx_runtime15.JSX.Element;
14
+ }: ChatPreviewLoadingWidgetProps) => react_jsx_runtime10.JSX.Element;
15
15
  //#endregion
16
16
  export { ChatPreviewLoadingWidget, ChatPreviewLoadingWidgetProps, ChatPreviewLoadingWidgetWithBaseWidget };
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.cjs";
2
- import * as react_jsx_runtime9 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime4 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts
5
5
  declare const ChatPreviewWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime9.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime4.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface ChatPreviewWidgetProps {
@@ -12,7 +12,7 @@ interface ChatPreviewWidgetProps {
12
12
  declare const ChatPreviewWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: ChatPreviewWidgetProps): react_jsx_runtime9.JSX.Element;
15
+ }: ChatPreviewWidgetProps): react_jsx_runtime4.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.js";
2
- import * as react_jsx_runtime9 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime6 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts
5
5
  declare const ChatPreviewWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime9.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime6.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface ChatPreviewWidgetProps {
@@ -12,7 +12,7 @@ interface ChatPreviewWidgetProps {
12
12
  declare const ChatPreviewWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: ChatPreviewWidgetProps): react_jsx_runtime9.JSX.Element;
15
+ }: ChatPreviewWidgetProps): react_jsx_runtime6.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime2 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime3 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/FloatingChatWidget/FloatingChatWidget.d.ts
4
4
  interface FloatingChatWidgetProps {
@@ -13,7 +13,7 @@ declare const FloatingChatWidget: {
13
13
  previewChatAlwaysOpen,
14
14
  previewCXButtonsEnabled,
15
15
  previewDisclaimer
16
- }?: FloatingChatWidgetProps): react_jsx_runtime2.JSX.Element;
16
+ }?: FloatingChatWidgetProps): react_jsx_runtime3.JSX.Element;
17
17
  displayName: string;
18
18
  };
19
19
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime8 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime3 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/FloatingChatWidget/FloatingChatWidget.d.ts
4
4
  interface FloatingChatWidgetProps {
@@ -13,7 +13,7 @@ declare const FloatingChatWidget: {
13
13
  previewChatAlwaysOpen,
14
14
  previewCXButtonsEnabled,
15
15
  previewDisclaimer
16
- }?: FloatingChatWidgetProps): react_jsx_runtime8.JSX.Element;
16
+ }?: FloatingChatWidgetProps): react_jsx_runtime3.JSX.Element;
17
17
  displayName: string;
18
18
  };
19
19
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime7 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime16 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts
4
4
 
@@ -8,7 +8,7 @@ interface FullPageSalesAgentBaseWidgetProps {
8
8
  declare const FullPageSalesAgentWidget: {
9
9
  ({
10
10
  widgetConfigId
11
- }: FullPageSalesAgentBaseWidgetProps): react_jsx_runtime7.JSX.Element;
11
+ }: FullPageSalesAgentBaseWidgetProps): react_jsx_runtime16.JSX.Element;
12
12
  displayName: string;
13
13
  };
14
14
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime6 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime15 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts
4
4
 
@@ -8,7 +8,7 @@ interface FullPageSalesAgentBaseWidgetProps {
8
8
  declare const FullPageSalesAgentWidget: {
9
9
  ({
10
10
  widgetConfigId
11
- }: FullPageSalesAgentBaseWidgetProps): react_jsx_runtime6.JSX.Element;
11
+ }: FullPageSalesAgentBaseWidgetProps): react_jsx_runtime15.JSX.Element;
12
12
  displayName: string;
13
13
  };
14
14
  //#endregion
@@ -18,25 +18,14 @@ let __envive_ai_react_toolkit_v3_ProductCard = require("@envive-ai/react-toolkit
18
18
  let __envive_ai_react_toolkit_v3_PromptButton_types = require("@envive-ai/react-toolkit-v3/PromptButton/types");
19
19
 
20
20
  //#region src/widgets/ProductCardWidget/ProductCardWidget.tsx
21
- const mockPrompts = [
22
- "Loading prompt 1",
23
- "Loading prompt 2",
24
- "Loading prompt 3",
25
- "Loading prompt 4",
26
- "Loading prompt 5"
27
- ];
28
- const mockHeadline = "Loading...";
29
- const mockAnimatedText = ["Loading..."];
30
21
  const ProductCardWidgetHandler = (props) => {
31
22
  const { onTypedMessageSubmitted } = (0, __envive_ai_react_hooks_contexts_salesAgentContext.useSalesAgent)();
32
23
  const { openChat } = (0, __envive_ai_react_hooks_hooks_ChatToggle.useChatToggle)();
33
- const { hardcopyContent, widgetConfig, uiConfig, isLoading } = props;
34
- const hardCopyHeadline = hardcopyContent?.values?.headline;
35
- const headline = isLoading ? mockHeadline : hardCopyHeadline || "";
36
- const hardCopyAnimatedText = hardcopyContent?.values?.animatedText || [];
37
- const animatedText = isLoading ? mockAnimatedText : hardCopyAnimatedText;
38
- const hardCopyPrompts = hardcopyContent?.values?.prompts || [];
39
- const prompts = isLoading ? mockPrompts : hardCopyPrompts;
24
+ const { hardcopyContent, widgetConfig, uiConfig, isLoading, isHardcopySettled } = props;
25
+ const showSkeleton = isLoading || !hardcopyContent && !isHardcopySettled;
26
+ const headline = hardcopyContent?.values?.headline || "";
27
+ const animatedText = hardcopyContent?.values?.animatedText || [];
28
+ const prompts = hardcopyContent?.values?.prompts || [];
40
29
  const placeholder = hardcopyContent?.values?.placeholder;
41
30
  const productCardWidgetConfig = widgetConfig;
42
31
  const carouselId = productCardWidgetConfig?.contentId || "";
@@ -110,6 +99,7 @@ const ProductCardWidgetHandler = (props) => {
110
99
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__envive_ai_react_toolkit_v3_ProductCard.ProductCard, {
111
100
  theme: __envive_ai_react_toolkit_v3_Tokens.Theme.GLOBAL_CUSTOM,
112
101
  variant,
102
+ isLoading: showSkeleton,
113
103
  imageSrc,
114
104
  fallbackColor,
115
105
  headline,
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime19 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime15 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/ProductCardWidget/ProductCardWidget.d.ts
4
4
  interface ProductCardWidgetProps {
@@ -7,7 +7,7 @@ interface ProductCardWidgetProps {
7
7
  declare const ProductCardWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: ProductCardWidgetProps): react_jsx_runtime19.JSX.Element;
10
+ }: ProductCardWidgetProps): react_jsx_runtime15.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime5 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime9 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/ProductCardWidget/ProductCardWidget.d.ts
4
4
  interface ProductCardWidgetProps {
@@ -7,7 +7,7 @@ interface ProductCardWidgetProps {
7
7
  declare const ProductCardWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: ProductCardWidgetProps): react_jsx_runtime5.JSX.Element;
10
+ }: ProductCardWidgetProps): react_jsx_runtime9.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -17,25 +17,14 @@ import { ProductCard, ProductCardVariant } from "@envive-ai/react-toolkit-v3/Pro
17
17
  import { PromptButtonVariant } from "@envive-ai/react-toolkit-v3/PromptButton/types";
18
18
 
19
19
  //#region src/widgets/ProductCardWidget/ProductCardWidget.tsx
20
- const mockPrompts = [
21
- "Loading prompt 1",
22
- "Loading prompt 2",
23
- "Loading prompt 3",
24
- "Loading prompt 4",
25
- "Loading prompt 5"
26
- ];
27
- const mockHeadline = "Loading...";
28
- const mockAnimatedText = ["Loading..."];
29
20
  const ProductCardWidgetHandler = (props) => {
30
21
  const { onTypedMessageSubmitted } = useSalesAgent();
31
22
  const { openChat } = useChatToggle();
32
- const { hardcopyContent, widgetConfig, uiConfig, isLoading } = props;
33
- const hardCopyHeadline = hardcopyContent?.values?.headline;
34
- const headline = isLoading ? mockHeadline : hardCopyHeadline || "";
35
- const hardCopyAnimatedText = hardcopyContent?.values?.animatedText || [];
36
- const animatedText = isLoading ? mockAnimatedText : hardCopyAnimatedText;
37
- const hardCopyPrompts = hardcopyContent?.values?.prompts || [];
38
- const prompts = isLoading ? mockPrompts : hardCopyPrompts;
23
+ const { hardcopyContent, widgetConfig, uiConfig, isLoading, isHardcopySettled } = props;
24
+ const showSkeleton = isLoading || !hardcopyContent && !isHardcopySettled;
25
+ const headline = hardcopyContent?.values?.headline || "";
26
+ const animatedText = hardcopyContent?.values?.animatedText || [];
27
+ const prompts = hardcopyContent?.values?.prompts || [];
39
28
  const placeholder = hardcopyContent?.values?.placeholder;
40
29
  const productCardWidgetConfig = widgetConfig;
41
30
  const carouselId = productCardWidgetConfig?.contentId || "";
@@ -109,6 +98,7 @@ const ProductCardWidgetHandler = (props) => {
109
98
  return /* @__PURE__ */ jsx(ProductCard, {
110
99
  theme: Theme.GLOBAL_CUSTOM,
111
100
  variant,
101
+ isLoading: showSkeleton,
112
102
  imageSrc,
113
103
  fallbackColor,
114
104
  headline,
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.cjs";
2
- import * as react_jsx_runtime3 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime18 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts
5
5
  declare const PromptButtonCarouselWithImageWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime3.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime18.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface PromptButtonCarouselWithImageWidgetProps {
@@ -12,7 +12,7 @@ interface PromptButtonCarouselWithImageWidgetProps {
12
12
  declare const PromptButtonCarouselWithImageWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: PromptButtonCarouselWithImageWidgetProps): react_jsx_runtime3.JSX.Element;
15
+ }: PromptButtonCarouselWithImageWidgetProps): react_jsx_runtime18.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.js";
2
- import * as react_jsx_runtime11 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime13 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts
5
5
  declare const PromptButtonCarouselWithImageWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime11.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime13.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface PromptButtonCarouselWithImageWidgetProps {
@@ -12,7 +12,7 @@ interface PromptButtonCarouselWithImageWidgetProps {
12
12
  declare const PromptButtonCarouselWithImageWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: PromptButtonCarouselWithImageWidgetProps): react_jsx_runtime11.JSX.Element;
15
+ }: PromptButtonCarouselWithImageWidgetProps): react_jsx_runtime13.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime11 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts
4
4
  interface PromptCarouselWidgetProps {
@@ -7,7 +7,7 @@ interface PromptCarouselWidgetProps {
7
7
  declare const PromptCarouselWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: PromptCarouselWidgetProps): react_jsx_runtime11.JSX.Element;
10
+ }: PromptCarouselWidgetProps): react_jsx_runtime2.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime14 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime8 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts
4
4
  interface PromptCarouselWidgetProps {
@@ -7,7 +7,7 @@ interface PromptCarouselWidgetProps {
7
7
  declare const PromptCarouselWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: PromptCarouselWidgetProps): react_jsx_runtime14.JSX.Element;
10
+ }: PromptCarouselWidgetProps): react_jsx_runtime8.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime0 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime14 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts
4
4
  interface SocialProofFlowWidgetProps {
@@ -7,7 +7,7 @@ interface SocialProofFlowWidgetProps {
7
7
  declare const SocialProofFlowWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: SocialProofFlowWidgetProps): react_jsx_runtime0.JSX.Element;
10
+ }: SocialProofFlowWidgetProps): react_jsx_runtime14.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime13 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts
4
4
  interface SocialProofFlowWidgetProps {
@@ -7,7 +7,7 @@ interface SocialProofFlowWidgetProps {
7
7
  declare const SocialProofFlowWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: SocialProofFlowWidgetProps): react_jsx_runtime13.JSX.Element;
10
+ }: SocialProofFlowWidgetProps): react_jsx_runtime2.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.cjs";
2
- import * as react_jsx_runtime15 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime12 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/SocialProofWidget/SocialProofWidget.d.ts
5
5
  declare const SocialProofWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime15.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime12.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface SocialProofWidgetProps {
@@ -12,7 +12,7 @@ interface SocialProofWidgetProps {
12
12
  declare const SocialProofWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: SocialProofWidgetProps): react_jsx_runtime15.JSX.Element;
15
+ }: SocialProofWidgetProps): react_jsx_runtime12.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.js";
2
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime4 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/SocialProofWidget/SocialProofWidget.d.ts
5
5
  declare const SocialProofWidgetWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime0.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime4.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface SocialProofWidgetProps {
@@ -12,7 +12,7 @@ interface SocialProofWidgetProps {
12
12
  declare const SocialProofWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: SocialProofWidgetProps): react_jsx_runtime0.JSX.Element;
15
+ }: SocialProofWidgetProps): react_jsx_runtime4.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime14 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime1 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts
4
4
  interface TitledPromptCarouselWidgetProps {
@@ -7,7 +7,7 @@ interface TitledPromptCarouselWidgetProps {
7
7
  declare const TitledPromptCarouselWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: TitledPromptCarouselWidgetProps): react_jsx_runtime14.JSX.Element;
10
+ }: TitledPromptCarouselWidgetProps): react_jsx_runtime1.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime3 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime12 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts
4
4
  interface TitledPromptCarouselWidgetProps {
@@ -7,7 +7,7 @@ interface TitledPromptCarouselWidgetProps {
7
7
  declare const TitledPromptCarouselWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: TitledPromptCarouselWidgetProps): react_jsx_runtime3.JSX.Element;
10
+ }: TitledPromptCarouselWidgetProps): react_jsx_runtime12.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime0 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime6 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts
4
4
  interface TypingAnimationFlowWidgetProps {
@@ -7,7 +7,7 @@ interface TypingAnimationFlowWidgetProps {
7
7
  declare const TypingAnimationFlowWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: TypingAnimationFlowWidgetProps): react_jsx_runtime0.JSX.Element;
10
+ }: TypingAnimationFlowWidgetProps): react_jsx_runtime6.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime4 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts
4
4
  interface TypingAnimationFlowWidgetProps {
@@ -7,7 +7,7 @@ interface TypingAnimationFlowWidgetProps {
7
7
  declare const TypingAnimationFlowWidget: {
8
8
  ({
9
9
  widgetConfigId
10
- }: TypingAnimationFlowWidgetProps): react_jsx_runtime4.JSX.Element;
10
+ }: TypingAnimationFlowWidgetProps): react_jsx_runtime0.JSX.Element;
11
11
  displayName: string;
12
12
  };
13
13
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.cjs";
2
- import * as react_jsx_runtime17 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts
5
5
  declare const TypingAnimationWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime17.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime0.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface TypingAnimationWidgetProps {
@@ -12,7 +12,7 @@ interface TypingAnimationWidgetProps {
12
12
  declare const TypingAnimationWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: TypingAnimationWidgetProps): react_jsx_runtime17.JSX.Element;
15
+ }: TypingAnimationWidgetProps): react_jsx_runtime0.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { BaseWidgetProps } from "../../hocs/withBaseWidget/types.js";
2
- import * as react_jsx_runtime1 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts
5
5
  declare const TypingAnimationWithBaseWidget: {
6
- (props: BaseWidgetProps): react_jsx_runtime1.JSX.Element;
6
+ (props: BaseWidgetProps): react_jsx_runtime0.JSX.Element;
7
7
  displayName: string;
8
8
  };
9
9
  interface TypingAnimationWidgetProps {
@@ -12,7 +12,7 @@ interface TypingAnimationWidgetProps {
12
12
  declare const TypingAnimationWidget: {
13
13
  ({
14
14
  widgetConfigId
15
- }: TypingAnimationWidgetProps): react_jsx_runtime1.JSX.Element;
15
+ }: TypingAnimationWidgetProps): react_jsx_runtime0.JSX.Element;
16
16
  displayName: string;
17
17
  };
18
18
  //#endregion
@@ -1,8 +1,8 @@
1
- import * as react_jsx_runtime1 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
2
2
 
3
3
  //#region ../widgets/dist/SearchResults/SearchResultsWidget.d.ts
4
4
  //#region src/SearchResults/SearchResultsWidget.d.ts
5
- declare const SearchResultsWidget: () => react_jsx_runtime1.JSX.Element;
5
+ declare const SearchResultsWidget: () => react_jsx_runtime2.JSX.Element;
6
6
  //#endregion
7
7
 
8
8
  //#endregion
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime2 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime3 from "react/jsx-runtime";
2
2
 
3
3
  //#region ../widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts
4
4
  //#region src/SearchZeroState/SearchZeroStateWidget.d.ts
@@ -11,7 +11,7 @@ declare const SearchZeroStateWidget: ({
11
11
  initialIsOpen,
12
12
  widgetConfigId,
13
13
  entryPointRef
14
- }: SearchZeroStateWidgetProps) => react_jsx_runtime2.JSX.Element;
14
+ }: SearchZeroStateWidgetProps) => react_jsx_runtime3.JSX.Element;
15
15
  //#endregion
16
16
 
17
17
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { SuggestionBarLocationForMetrics } from "./types.cjs";
2
- import * as react_jsx_runtime3 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
  import { SuggestionButtonVariant } from "@envive-ai/react-hooks/contexts/types";
4
4
  import { Message } from "postcss";
5
5
 
@@ -30,7 +30,7 @@ declare function SuggestionBar({
30
30
  buttonBorderRadius,
31
31
  handleReply,
32
32
  dataTestId
33
- }: Readonly<SuggestionBarProps>): react_jsx_runtime3.JSX.Element;
33
+ }: Readonly<SuggestionBarProps>): react_jsx_runtime0.JSX.Element;
34
34
  //#endregion
35
35
  //#endregion
36
36
  export { SuggestionBar };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envive-ai/react-widgets-v3",
3
- "version": "0.3.62",
3
+ "version": "0.3.63",
4
4
  "description": "React widget library v3 for Envive services.",
5
5
  "keywords": [
6
6
  "react",
@@ -408,6 +408,42 @@ describe('withBaseWidget', () => {
408
408
  });
409
409
  });
410
410
 
411
+ it('stays loading until its OWN hardcopy settles even after the shared flag clears', async () => {
412
+ // Complements INTERRUPT-424: that fix stops a settled widget being reverted when a
413
+ // shared flag flips TRUE. This is the mirror case — another widget's hardcopy finishing
414
+ // flips the single shared HardcopyProvider boolean to FALSE while THIS widget's own
415
+ // hardcopy is still in flight. isLoading must track this widget's own requests, not
416
+ // follow the shared flag down and paint empty content early.
417
+ mockHardcopyLoading = false;
418
+ mockPageLoading = false;
419
+ mockWidgetConfigLoading = false;
420
+ mockUiConfigLoading = false;
421
+ let resolveHardcopy!: (value: HardcopyResponse) => void;
422
+ mockGetHardcopy.mockReturnValue(
423
+ new Promise<HardcopyResponse>(resolve => {
424
+ resolveHardcopy = resolve;
425
+ }),
426
+ );
427
+
428
+ const WrappedWidget = withBaseWidget(TestWidget);
429
+ render(
430
+ <WrappedWidget
431
+ widgetConfigId="test-config-own-hardcopy"
432
+ widgetType={WidgetTypeV3.PromptCarouselV3}
433
+ />,
434
+ );
435
+
436
+ await waitFor(() => {
437
+ expect(screen.getByTestId('is-loading')).toHaveTextContent('true');
438
+ });
439
+
440
+ resolveHardcopy({ responseId: 'r', language: 'en', values: {} });
441
+
442
+ await waitFor(() => {
443
+ expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
444
+ });
445
+ });
446
+
411
447
  it.each([
412
448
  [
413
449
  'widgetConfig',
@@ -15,6 +15,13 @@ export interface BaseWidgetProps {
15
15
  uiConfig?: OrgUIConfigV3;
16
16
  isLoading?: boolean;
17
17
  isHardcopyLoading?: boolean;
18
+ /**
19
+ * True once THIS widget's own hardcopy request has settled (resolved or errored),
20
+ * regardless of whether it produced content. Lets a widget tell "hardcopy not received
21
+ * yet" apart from "hardcopy finished with no content" so it can leave a loading placeholder
22
+ * after a failed fetch instead of showing it forever.
23
+ */
24
+ isHardcopySettled?: boolean;
18
25
  isPageLoading?: boolean;
19
26
  isWidgetConfigLoading?: boolean;
20
27
  isUiConfigLoading?: boolean;
@@ -51,10 +51,6 @@ export function withBaseWidget<P extends BaseWidgetProps>(
51
51
  const [uiConfigSettled, setUiConfigSettled] = useState(false);
52
52
  const ownRequestsSettled = hardcopySettled && widgetConfigSettled && uiConfigSettled;
53
53
 
54
- const isLoading =
55
- !ownRequestsSettled &&
56
- (isHardcopyLoading || isPageLoading || isWidgetConfigLoading || isUiConfigLoading);
57
-
58
54
  const visibilityEventProps = {
59
55
  widget_config_id: widgetConfigId,
60
56
  widget_type: widgetType,
@@ -74,6 +70,15 @@ export function withBaseWidget<P extends BaseWidgetProps>(
74
70
  return isVisible && !!widgetConfigId && !!widgetType;
75
71
  }, [isVisible, widgetConfigId, widgetType]);
76
72
 
73
+ // A widget is loading only while its OWN requests are in flight. Deliberately NOT
74
+ // derived from the shared provider isLoading flags (isHardcopyLoading, isPageLoading,
75
+ // …): each of those is a single boolean shared by every widget, so any widget issuing a
76
+ // fetch flips them for all of them — which let another widget's activity pin this one in
77
+ // (or stuck in) its loading state even after its own requests had settled. `startLoading`
78
+ // gates the leading edge so a not-yet-started widget doesn't report loading off the
79
+ // still-false latches.
80
+ const isLoading = startLoading && !ownRequestsSettled;
81
+
77
82
  // Watchdog: a widget that is still in its loading state this long after it started
78
83
  // fetching is stuck (or on a very slow connection). Log a console error so session
79
84
  // tools that capture the console (Quantum Metric, Datadog RUM) can count and segment
@@ -163,6 +168,7 @@ export function withBaseWidget<P extends BaseWidgetProps>(
163
168
  {...props}
164
169
  isLoading={isLoading}
165
170
  isHardcopyLoading={isHardcopyLoading}
171
+ isHardcopySettled={hardcopySettled}
166
172
  isPageLoading={isPageLoading}
167
173
  isWidgetConfigLoading={isWidgetConfigLoading}
168
174
  isUiConfigLoading={isUiConfigLoading}
@@ -1,13 +1,43 @@
1
+ import {
2
+ HardcopyProvider,
3
+ HardcopyResponse,
4
+ } from '@envive-ai/react-hooks/contexts/hardcopyContext';
1
5
  import {
2
6
  PageVisitCategory,
3
7
  useSalesAgent,
4
8
  } from '@envive-ai/react-hooks/contexts/salesAgentContext';
9
+ import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
10
+ import { MockHardcopyService } from '@envive-ai/react-hooks/services/hardcopyService';
5
11
  import { UserEventCategory } from '@spiffy-ai/commerce-api-client';
6
12
  import type { Meta, StoryObj } from '@storybook/react-vite';
7
13
  import { useEffect } from 'react';
8
14
  import { v4 as uuid } from 'uuid';
9
15
  import { FullPageSalesAgentWidget } from '../widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget';
10
16
 
17
+ // Overlay content for the PartialView story below — needs welcomeOverlayTitle
18
+ // set for FullPageSalesAgent's own overlayEnabled check to pass, since
19
+ // partialViewActive is `partialViewEnabled && overlayEnabled`.
20
+ const partialViewHardcopy: HardcopyResponse = {
21
+ responseId: 'mock-fpsa-partial-view',
22
+ language: 'en',
23
+ values: {
24
+ welcomeMessageTitle: "Hi! I'm your personal shopping assistant",
25
+ welcomeMessageText: 'How can I help you today?',
26
+ chatFooterTextFieldPlaceholderText: 'Ask me anything...',
27
+ welcomeOverlayTitle: 'How can I help you find the right product?',
28
+ welcomeOverlayPromptSuggestions: [
29
+ 'Help me find my size',
30
+ 'What are your best sellers?',
31
+ 'Gift ideas for a friend',
32
+ ],
33
+ welcomeOverlayProductCarouselTitle: 'Fan favorites',
34
+ welcomeOverlayProductIds: [
35
+ 'mix-match-nursing-pumping-bra-bundle',
36
+ 'mothers-day-morning-bundle',
37
+ ],
38
+ },
39
+ };
40
+
11
41
  const meta = {
12
42
  title: 'Widgets V3/FullPageSalesAgentWidget',
13
43
  component: FullPageSalesAgentWidget,
@@ -66,3 +96,37 @@ export const Playground: Story = {
66
96
  },
67
97
  },
68
98
  };
99
+
100
+ export const PartialView: Story = {
101
+ name: 'Partial View (default)',
102
+ // 'fake-widget-config-id' short-circuits withBaseWidget's real widget-config
103
+ // fetch (see WidgetConfigProvider.getWidgetConfig) and resolves to `{}` —
104
+ // partialViewEnabled then comes out `undefined`, which now correctly
105
+ // defaults to enabled regardless of which merchant is selected in the
106
+ // toolbar or what that merchant's own backend config has configured.
107
+ args: {
108
+ widgetConfigId: 'fake-widget-config-id',
109
+ },
110
+ decorators: [
111
+ Story => (
112
+ <HardcopyProvider
113
+ hardcopyService={
114
+ new MockHardcopyService({ [WidgetTypeV3.FullPageSalesAgentV3]: partialViewHardcopy })
115
+ }
116
+ >
117
+ <Story />
118
+ </HardcopyProvider>
119
+ ),
120
+ ],
121
+ parameters: {
122
+ docs: {
123
+ description: {
124
+ story:
125
+ 'Partial view is enabled by default — submitting a question opens the conversation in a ' +
126
+ "closed/partial/full bottom sheet instead of taking over the whole screen, and the widget's " +
127
+ 'own product grid stays visible above it. Self-contained (a fake widgetConfigId plus mocked ' +
128
+ "hardcopy) so it renders the same regardless of the toolbar's selected merchant.",
129
+ },
130
+ },
131
+ },
132
+ };
@@ -23,30 +23,24 @@ import { useCallback } from 'react';
23
23
  import { BaseWidgetProps, withBaseWidget } from '../../hocs/withBaseWidget';
24
24
  import { RawValues, getStringIdForText } from '../utils/functions';
25
25
 
26
- const mockPrompts = [
27
- 'Loading prompt 1',
28
- 'Loading prompt 2',
29
- 'Loading prompt 3',
30
- 'Loading prompt 4',
31
- 'Loading prompt 5',
32
- ];
33
-
34
- const mockHeadline = 'Loading...';
35
- const mockAnimatedText = ['Loading...'];
36
-
37
26
  const ProductCardWidgetHandler = (props: BaseWidgetProps) => {
38
27
  const { onTypedMessageSubmitted } = useSalesAgent();
39
28
  const { openChat } = useChatToggle();
40
- const { hardcopyContent, widgetConfig, uiConfig, isLoading } = props;
29
+ const { hardcopyContent, widgetConfig, uiConfig, isLoading, isHardcopySettled } = props;
30
+
31
+ // Show the skeleton while the card is loading, or while its hardcopy hasn't arrived yet (a
32
+ // widget deferred until visible has none). Gate the "no content yet" case on the hardcopy
33
+ // NOT having settled — otherwise a request that finishes without content (a failed/timed-out
34
+ // fetch) would leave `hardcopyContent` undefined and pin the skeleton on forever. Once the
35
+ // hardcopy settles empty, fall through to the normal render (static-copy fallback / empty
36
+ // state) instead of an endless skeleton.
37
+ const showSkeleton = isLoading || (!hardcopyContent && !isHardcopySettled);
41
38
 
42
- const hardCopyHeadline = hardcopyContent?.values?.headline as string | undefined;
43
- const headline = isLoading ? mockHeadline : hardCopyHeadline || '';
39
+ const headline = (hardcopyContent?.values?.headline as string | undefined) || '';
44
40
 
45
- const hardCopyAnimatedText = (hardcopyContent?.values?.animatedText as string[]) || [];
46
- const animatedText = isLoading ? mockAnimatedText : hardCopyAnimatedText;
41
+ const animatedText = (hardcopyContent?.values?.animatedText as string[]) || [];
47
42
 
48
- const hardCopyPrompts = (hardcopyContent?.values?.prompts as string[]) || [];
49
- const prompts = isLoading ? mockPrompts : hardCopyPrompts;
43
+ const prompts = (hardcopyContent?.values?.prompts as string[]) || [];
50
44
 
51
45
  const hardCopyPlaceholder = hardcopyContent?.values?.placeholder as string | undefined;
52
46
  const placeholder = hardCopyPlaceholder;
@@ -141,6 +135,7 @@ const ProductCardWidgetHandler = (props: BaseWidgetProps) => {
141
135
  <ProductCard
142
136
  theme={Theme.GLOBAL_CUSTOM}
143
137
  variant={variant}
138
+ isLoading={showSkeleton}
144
139
  imageSrc={imageSrc}
145
140
  fallbackColor={fallbackColor}
146
141
  headline={headline}
@@ -10,8 +10,21 @@ const mockUseTrackComponentVisibleEvent = vi.fn(() => ({ isVisible: true }));
10
10
 
11
11
  vi.mock('@envive-ai/react-toolkit-v3/ProductCard', () => ({
12
12
  ProductCardVariant: { DARK: 'dark', LIGHT: 'light' },
13
- ProductCard: ({ prompts, onSelect }: { prompts: string[]; onSelect: (text: string) => void }) => (
14
- <div data-testid="product-card-mock">
13
+ // Expose isLoading (the widget's showSkeleton decision) so tests can assert the loading
14
+ // placeholder vs. rendered content across the deferred / resolved / failed fetch paths.
15
+ ProductCard: ({
16
+ prompts,
17
+ onSelect,
18
+ isLoading,
19
+ }: {
20
+ prompts: string[];
21
+ onSelect: (text: string) => void;
22
+ isLoading?: boolean;
23
+ }) => (
24
+ <div
25
+ data-testid="product-card-mock"
26
+ data-loading={String(Boolean(isLoading))}
27
+ >
15
28
  {prompts.map(text => (
16
29
  <button
17
30
  key={text}
@@ -151,3 +164,57 @@ describe('ProductCardWidget analytics', () => {
151
164
  });
152
165
  }, 5000);
153
166
  });
167
+
168
+ describe('ProductCardWidget loading state', () => {
169
+ beforeEach(() => {
170
+ vi.clearAllMocks();
171
+ });
172
+
173
+ const hardcopy = {
174
+ responseId: 'test-response-id',
175
+ language: 'en',
176
+ values: { prompts: ['Prompt 1', 'Prompt 2'] },
177
+ rawValues: { prompts: [{ id: 'id-1', value: 'Prompt 1' }] },
178
+ };
179
+
180
+ const loadingState = () => screen.getByTestId('product-card-mock').dataset.loading;
181
+
182
+ it('shows the skeleton while the hardcopy is pending, then renders content once it resolves', async () => {
183
+ let resolveHardcopy: (value: typeof hardcopy) => void = () => {};
184
+ mockGetHardcopy.mockReturnValue(
185
+ new Promise<typeof hardcopy>(resolve => {
186
+ resolveHardcopy = resolve;
187
+ }),
188
+ );
189
+
190
+ render(<ProductCardWidget widgetConfigId="loading-1" />);
191
+
192
+ // Hardcopy not received yet → skeleton.
193
+ await waitFor(() => expect(loadingState()).toBe('true'), { timeout: 3000 });
194
+
195
+ resolveHardcopy(hardcopy);
196
+
197
+ // Content arrived → skeleton gone.
198
+ await waitFor(() => expect(loadingState()).toBe('false'), { timeout: 3000 });
199
+ expect(screen.getByText('Prompt 1')).toBeInTheDocument();
200
+ }, 5000);
201
+
202
+ it('renders content and drops the skeleton after a successful fetch', async () => {
203
+ mockGetHardcopy.mockResolvedValue(hardcopy);
204
+
205
+ render(<ProductCardWidget widgetConfigId="loading-2" />);
206
+
207
+ await waitFor(() => expect(loadingState()).toBe('false'), { timeout: 3000 });
208
+ expect(screen.getByText('Prompt 1')).toBeInTheDocument();
209
+ }, 5000);
210
+
211
+ it('drops the skeleton after the hardcopy fetch fails instead of hanging on it forever', async () => {
212
+ // A rejected (or timed-out) hardcopy settles without content. The widget must fall through
213
+ // to the normal render rather than pinning the skeleton on indefinitely.
214
+ mockGetHardcopy.mockRejectedValue(new Error('hardcopy failed'));
215
+
216
+ render(<ProductCardWidget widgetConfigId="loading-3" />);
217
+
218
+ await waitFor(() => expect(loadingState()).toBe('false'), { timeout: 3000 });
219
+ }, 5000);
220
+ });