@patternfly/chatbot 6.8.0-prerelease.3 → 6.8.0-prerelease.4

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 (89) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -5
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +3 -3
  3. package/dist/cjs/ChatbotHeader/ChatbotHeaderCloseButton.js +1 -1
  4. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.js +2 -2
  5. package/dist/cjs/MarkdownContent/MarkdownContent.js +15 -1
  6. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +2 -2
  7. package/dist/cjs/Message/LinkMessage/LinkMessage.js +5 -1
  8. package/dist/cjs/Message/Message.test.js +3 -1
  9. package/dist/cjs/Message/QuickStarts/QuickStartTileDescription.js +2 -5
  10. package/dist/cjs/MessageBox/JumpButton.test.js +2 -2
  11. package/dist/cjs/MessageBox/MessageBox.js +79 -28
  12. package/dist/cjs/MessageBox/MessageBox.test.d.ts +1 -1
  13. package/dist/cjs/MessageBox/MessageBox.test.js +59 -0
  14. package/dist/cjs/ResponseActions/ResponseActionButton.test.js +6 -6
  15. package/dist/cjs/ResponseActions/ResponseActions.test.js +6 -7
  16. package/dist/cjs/index.d.ts +0 -2
  17. package/dist/cjs/index.js +1 -4
  18. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  19. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +4 -4
  20. package/dist/esm/ChatbotHeader/ChatbotHeaderCloseButton.js +2 -2
  21. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +2 -2
  22. package/dist/esm/MarkdownContent/MarkdownContent.js +15 -1
  23. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +2 -2
  24. package/dist/esm/Message/LinkMessage/LinkMessage.js +6 -2
  25. package/dist/esm/Message/Message.test.js +3 -1
  26. package/dist/esm/Message/QuickStarts/QuickStartTileDescription.js +2 -2
  27. package/dist/esm/MessageBox/JumpButton.test.js +2 -2
  28. package/dist/esm/MessageBox/MessageBox.js +80 -29
  29. package/dist/esm/MessageBox/MessageBox.test.d.ts +1 -1
  30. package/dist/esm/MessageBox/MessageBox.test.js +59 -0
  31. package/dist/esm/ResponseActions/ResponseActionButton.test.js +7 -7
  32. package/dist/esm/ResponseActions/ResponseActions.test.js +7 -8
  33. package/dist/esm/index.d.ts +0 -2
  34. package/dist/esm/index.js +0 -2
  35. package/dist/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +2 -2
  37. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithCustomResponseActions.tsx +3 -4
  38. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithMarkdownToolResponse.tsx +3 -3
  39. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithQuickResponses.tsx +3 -3
  40. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolResponse.tsx +5 -5
  41. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +1 -1
  42. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +7 -31
  43. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +4 -6
  44. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +3 -3
  45. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerResizable.tsx +2 -2
  46. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +2 -2
  47. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithSearchActions.tsx +3 -3
  48. package/patternfly-docs/content/extensions/chatbot/examples/UI/CompactSettings.tsx +10 -5
  49. package/patternfly-docs/content/extensions/chatbot/examples/UI/Settings.tsx +10 -5
  50. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +1 -15
  51. package/patternfly-docs/content/extensions/chatbot/examples/demos/AttachmentDemos.md +1 -3
  52. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +2 -5
  53. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +4 -7
  54. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotAttachment.tsx +4 -6
  55. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotCompact.tsx +4 -7
  56. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotDisplayMode.tsx +4 -7
  57. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotScrolling.tsx +4 -8
  58. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotTranscripts.tsx +4 -7
  59. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +2 -3
  60. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +4 -4
  61. package/src/ChatbotHeader/ChatbotHeaderCloseButton.tsx +2 -2
  62. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +2 -3
  63. package/src/MarkdownContent/MarkdownContent.tsx +14 -0
  64. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +2 -2
  65. package/src/Message/LinkMessage/LinkMessage.tsx +21 -2
  66. package/src/Message/Message.test.tsx +3 -1
  67. package/src/Message/QuickStarts/QuickStartTileDescription.tsx +2 -2
  68. package/src/MessageBox/JumpButton.test.tsx +1 -1
  69. package/src/MessageBox/MessageBox.test.tsx +91 -0
  70. package/src/MessageBox/MessageBox.tsx +137 -40
  71. package/src/ResponseActions/ResponseActionButton.test.tsx +7 -7
  72. package/src/ResponseActions/ResponseActions.test.tsx +7 -8
  73. package/dist/cjs/LoadingMessage/LoadingMessage.d.ts +0 -6
  74. package/dist/cjs/LoadingMessage/LoadingMessage.js +0 -12
  75. package/dist/cjs/LoadingMessage/LoadingMessage.test.d.ts +0 -1
  76. package/dist/cjs/LoadingMessage/LoadingMessage.test.js +0 -20
  77. package/dist/cjs/LoadingMessage/index.d.ts +0 -2
  78. package/dist/cjs/LoadingMessage/index.js +0 -23
  79. package/dist/dynamic/LoadingMessage/package.json +0 -1
  80. package/dist/esm/LoadingMessage/LoadingMessage.d.ts +0 -6
  81. package/dist/esm/LoadingMessage/LoadingMessage.js +0 -5
  82. package/dist/esm/LoadingMessage/LoadingMessage.test.d.ts +0 -1
  83. package/dist/esm/LoadingMessage/LoadingMessage.test.js +0 -15
  84. package/dist/esm/LoadingMessage/index.d.ts +0 -2
  85. package/dist/esm/LoadingMessage/index.js +0 -2
  86. package/src/LoadingMessage/LoadingMessage.test.tsx +0 -15
  87. package/src/LoadingMessage/LoadingMessage.tsx +0 -27
  88. package/src/LoadingMessage/__snapshots__/LoadingMessage.test.tsx.snap +0 -64
  89. package/src/LoadingMessage/index.ts +0 -3
@@ -1,19 +1,16 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.ChatbotConversationHistoryDropdown = void 0;
7
4
  const jsx_runtime_1 = require("react/jsx-runtime");
8
5
  const react_1 = require("react");
9
6
  // Import PatternFly components
10
7
  const react_core_1 = require("@patternfly/react-core");
11
- const ellipsis_v_icon_1 = __importDefault(require("@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon"));
8
+ const rh_ui_ellipsis_vertical_fill_icon_1 = require("@patternfly/react-icons/dist/esm/icons/rh-ui-ellipsis-vertical-fill-icon");
12
9
  const ChatbotConversationHistoryDropdown = ({ menuItems, menuClassName, onSelect, label = 'Conversation options', 'aria-label': ariaLabel, id }) => {
13
10
  const [isOpen, setIsOpen] = (0, react_1.useState)(false);
14
11
  const toggle = (toggleRef) => ((0, jsx_runtime_1.jsx)(react_core_1.Tooltip, { className: "pf-chatbot__tooltip", content: label, position: "bottom",
15
12
  // prevents VO announcements of both aria label and tooltip
16
- aria: "none", children: (0, jsx_runtime_1.jsx)(react_core_1.MenuToggle, { className: "pf-chatbot__history-actions", variant: "plain", "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : label, ref: toggleRef, isExpanded: isOpen, onClick: () => setIsOpen(!isOpen), id: id, role: "menuitem", children: (0, jsx_runtime_1.jsx)(ellipsis_v_icon_1.default, {}) }) }));
13
+ aria: "none", children: (0, jsx_runtime_1.jsx)(react_core_1.MenuToggle, { className: "pf-chatbot__history-actions", variant: "plain", "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : label, ref: toggleRef, isExpanded: isOpen, onClick: () => setIsOpen(!isOpen), id: id, role: "menuitem", children: (0, jsx_runtime_1.jsx)(rh_ui_ellipsis_vertical_fill_icon_1.RhUiEllipsisVerticalFillIcon, {}) }) }));
17
14
  return ((0, jsx_runtime_1.jsx)(react_core_1.Dropdown, { className: `pf-chatbot__selections ${menuClassName !== null && menuClassName !== void 0 ? menuClassName : ''}`, isOpen: isOpen, onSelect: (props) => {
18
15
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(props);
19
16
  setIsOpen((prev) => !prev);
@@ -25,7 +25,7 @@ const ChatbotConversationHistoryDropdown_1 = __importDefault(require("./ChatbotC
25
25
  const LoadingState_1 = __importDefault(require("./LoadingState"));
26
26
  const EmptyState_1 = __importDefault(require("./EmptyState"));
27
27
  const ChatbotConversationHistoryNav = (_a) => {
28
- var { onDrawerToggle, isDrawerOpen, setIsDrawerOpen, activeItemId, onSelectActiveItem, conversations, menuListProps, newChatButtonText = 'New chat', drawerContent, onNewChat, newChatButtonProps, searchInputPlaceholder = 'Search previous conversations...', searchInputAriaLabel = 'Search previous conversations', searchInputProps, handleTextInputChange, displayMode, reverseButtonOrder = false, drawerActionsTestId = 'chatbot-nav-drawer-actions', drawerPanelContentProps, drawerContentProps, drawerContentBodyProps, drawerHeadProps, drawerActionsProps, drawerCloseButtonProps, drawerPanelBodyProps, hasDrawerHeadDivider, isLoading, loadingState, errorState, emptyState, noResultsState, isCompact, title = 'Chat history', navTitleProps, navTitleIcon = (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedClockIcon, {}), searchInputScreenReaderText, searchActionStart, searchActionEnd, searchToolbar, menuProps, menuGroupProps, menuContentProps } = _a, props = __rest(_a, ["onDrawerToggle", "isDrawerOpen", "setIsDrawerOpen", "activeItemId", "onSelectActiveItem", "conversations", "menuListProps", "newChatButtonText", "drawerContent", "onNewChat", "newChatButtonProps", "searchInputPlaceholder", "searchInputAriaLabel", "searchInputProps", "handleTextInputChange", "displayMode", "reverseButtonOrder", "drawerActionsTestId", "drawerPanelContentProps", "drawerContentProps", "drawerContentBodyProps", "drawerHeadProps", "drawerActionsProps", "drawerCloseButtonProps", "drawerPanelBodyProps", "hasDrawerHeadDivider", "isLoading", "loadingState", "errorState", "emptyState", "noResultsState", "isCompact", "title", "navTitleProps", "navTitleIcon", "searchInputScreenReaderText", "searchActionStart", "searchActionEnd", "searchToolbar", "menuProps", "menuGroupProps", "menuContentProps"]);
28
+ var { onDrawerToggle, isDrawerOpen, setIsDrawerOpen, activeItemId, onSelectActiveItem, conversations, menuListProps, newChatButtonText = 'New chat', drawerContent, onNewChat, newChatButtonProps, searchInputPlaceholder = 'Search previous conversations...', searchInputAriaLabel = 'Search previous conversations', searchInputProps, handleTextInputChange, displayMode, reverseButtonOrder = false, drawerActionsTestId = 'chatbot-nav-drawer-actions', drawerPanelContentProps, drawerContentProps, drawerContentBodyProps, drawerHeadProps, drawerActionsProps, drawerCloseButtonProps, drawerPanelBodyProps, hasDrawerHeadDivider, isLoading, loadingState, errorState, emptyState, noResultsState, isCompact, title = 'Chat history', navTitleProps, navTitleIcon = (0, jsx_runtime_1.jsx)(react_icons_1.RhUiClockIcon, {}), searchInputScreenReaderText, searchActionStart, searchActionEnd, searchToolbar, menuProps, menuGroupProps, menuContentProps } = _a, props = __rest(_a, ["onDrawerToggle", "isDrawerOpen", "setIsDrawerOpen", "activeItemId", "onSelectActiveItem", "conversations", "menuListProps", "newChatButtonText", "drawerContent", "onNewChat", "newChatButtonProps", "searchInputPlaceholder", "searchInputAriaLabel", "searchInputProps", "handleTextInputChange", "displayMode", "reverseButtonOrder", "drawerActionsTestId", "drawerPanelContentProps", "drawerContentProps", "drawerContentBodyProps", "drawerHeadProps", "drawerActionsProps", "drawerCloseButtonProps", "drawerPanelBodyProps", "hasDrawerHeadDivider", "isLoading", "loadingState", "errorState", "emptyState", "noResultsState", "isCompact", "title", "navTitleProps", "navTitleIcon", "searchInputScreenReaderText", "searchActionStart", "searchActionEnd", "searchToolbar", "menuProps", "menuGroupProps", "menuContentProps"]);
29
29
  const drawerRef = (0, react_1.useRef)(null);
30
30
  const onExpand = () => {
31
31
  drawerRef.current && drawerRef.current.focus();
@@ -33,7 +33,7 @@ const ChatbotConversationHistoryNav = (_a) => {
33
33
  const isConversation = (item) => item && typeof item === 'object' && 'id' in item && 'text' in item;
34
34
  const getNavItem = (conversation) => {
35
35
  var _a;
36
- return ((0, jsx_runtime_1.jsx)(react_core_1.MenuItem, Object.assign({ className: `pf-chatbot__menu-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__menu-item--active' : ''}`, itemId: conversation.id }, (conversation.noIcon ? {} : { icon: (_a = conversation.icon) !== null && _a !== void 0 ? _a : (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedCommentAltIcon, {}) }), (conversation.menuItems
36
+ return ((0, jsx_runtime_1.jsx)(react_core_1.MenuItem, Object.assign({ className: `pf-chatbot__menu-item ${activeItemId && activeItemId === conversation.id ? 'pf-chatbot__menu-item--active' : ''}`, itemId: conversation.id }, (conversation.noIcon ? {} : { icon: (_a = conversation.icon) !== null && _a !== void 0 ? _a : (0, jsx_runtime_1.jsx)(react_icons_1.RhUiCommentIcon, {}) }), (conversation.menuItems
37
37
  ? {
38
38
  actions: ((0, jsx_runtime_1.jsx)(ChatbotConversationHistoryDropdown_1.default, { menuClassName: conversation.menuClassName, onSelect: conversation.onSelect, menuItems: conversation.menuItems, label: conversation.label }))
39
39
  }
@@ -78,7 +78,7 @@ const ChatbotConversationHistoryNav = (_a) => {
78
78
  return searchActionStart || searchActionEnd ? ((0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__history-search-actions", children: (0, jsx_runtime_1.jsxs)(react_core_1.InputGroup, { children: [searchActionStart && (0, jsx_runtime_1.jsx)(react_core_1.InputGroupItem, { children: searchActionStart }), searchInputContainer && (0, jsx_runtime_1.jsx)(react_core_1.InputGroupItem, { isFill: true, children: searchInputContainer }), searchActionEnd && (0, jsx_runtime_1.jsx)(react_core_1.InputGroupItem, { children: searchActionEnd })] }) })) : (searchInputContainer);
79
79
  };
80
80
  const renderPanelContent = () => {
81
- const drawer = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_core_1.DrawerHead, Object.assign({}, drawerHeadProps, { children: (0, jsx_runtime_1.jsxs)(react_core_1.DrawerActions, Object.assign({ "data-testid": drawerActionsTestId, className: reverseButtonOrder ? 'pf-v6-c-drawer__actions--reversed' : '' }, drawerActionsProps, { children: [(0, jsx_runtime_1.jsx)(react_core_1.DrawerCloseButton, Object.assign({ onClick: onDrawerToggle }, drawerCloseButtonProps)), onNewChat && ((0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ size: isCompact ? 'sm' : undefined, onClick: onNewChat, icon: (0, jsx_runtime_1.jsx)(react_icons_1.PenToSquareIcon, {}) }, newChatButtonProps, { children: newChatButtonText })))] })) })), hasDrawerHeadDivider && (0, jsx_runtime_1.jsx)(react_core_1.Divider, { className: "pf-chatbot__heading-divider" }), (0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__heading-container", children: [(0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__title-container", children: [(0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: "lg", className: "pf-chatbot__title-icon", children: navTitleIcon }), (0, jsx_runtime_1.jsx)(react_core_1.Title, Object.assign({ className: "pf-chatbot__title", headingLevel: "h2" }, navTitleProps, { children: title }))] }), !isLoading && renderSearchAndActions()] }), isLoading ? (0, jsx_runtime_1.jsx)(LoadingState_1.default, Object.assign({}, loadingState)) : renderDrawerContent()] }));
81
+ const drawer = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_core_1.DrawerHead, Object.assign({}, drawerHeadProps, { children: (0, jsx_runtime_1.jsxs)(react_core_1.DrawerActions, Object.assign({ "data-testid": drawerActionsTestId, className: reverseButtonOrder ? 'pf-v6-c-drawer__actions--reversed' : '' }, drawerActionsProps, { children: [(0, jsx_runtime_1.jsx)(react_core_1.DrawerCloseButton, Object.assign({ onClick: onDrawerToggle }, drawerCloseButtonProps)), onNewChat && ((0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ size: isCompact ? 'sm' : undefined, onClick: onNewChat, icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiEditFillIcon, {}) }, newChatButtonProps, { children: newChatButtonText })))] })) })), hasDrawerHeadDivider && (0, jsx_runtime_1.jsx)(react_core_1.Divider, { className: "pf-chatbot__heading-divider" }), (0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__heading-container", children: [(0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__title-container", children: [(0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: "lg", className: "pf-chatbot__title-icon", children: navTitleIcon }), (0, jsx_runtime_1.jsx)(react_core_1.Title, Object.assign({ className: "pf-chatbot__title", headingLevel: "h2" }, navTitleProps, { children: title }))] }), !isLoading && renderSearchAndActions()] }), isLoading ? (0, jsx_runtime_1.jsx)(LoadingState_1.default, Object.assign({}, loadingState)) : renderDrawerContent()] }));
82
82
  return ((0, jsx_runtime_1.jsx)(react_core_1.DrawerPanelContent, Object.assign({ "aria-live": "polite", focusTrap: { enabled: true }, defaultSize: "384px" }, drawerPanelContentProps, { children: drawer })));
83
83
  };
84
84
  // An onKeyDown property must be passed to the Drawer component to handle closing
@@ -20,6 +20,6 @@ const ChatbotHeaderCloseButtonBase = (_a) => {
20
20
  var { className, onClick, tooltipProps, menuAriaLabel = 'Close', innerRef, tooltipContent = 'Close', isCompact } = _a, props = __rest(_a, ["className", "onClick", "tooltipProps", "menuAriaLabel", "innerRef", "tooltipContent", "isCompact"]);
21
21
  return ((0, jsx_runtime_1.jsx)("div", { className: `pf-chatbot__menu${className ? ` ${className}` : ''}`, children: (0, jsx_runtime_1.jsx)(react_core_1.Tooltip, Object.assign({ content: tooltipContent, position: "bottom",
22
22
  // prevents VO announcements of both aria label and tooltip
23
- aria: "none" }, tooltipProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ className: `pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`, variant: "plain", onClick: onClick, "aria-label": menuAriaLabel, ref: innerRef, icon: (0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: isCompact ? 'lg' : 'xl', isInline: true, children: (0, jsx_runtime_1.jsx)(react_icons_1.CloseIcon, {}) }), size: isCompact ? 'sm' : undefined }, props)) })) }));
23
+ aria: "none" }, tooltipProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ className: `pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`, variant: "plain", onClick: onClick, "aria-label": menuAriaLabel, ref: innerRef, icon: (0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: isCompact ? 'lg' : 'xl', isInline: true, children: (0, jsx_runtime_1.jsx)(react_icons_1.RhMicronsCloseIcon, {}) }), size: isCompact ? 'sm' : undefined }, props)) })) }));
24
24
  };
25
25
  exports.ChatbotHeaderCloseButton = (0, react_1.forwardRef)((props, ref) => ((0, jsx_runtime_1.jsx)(ChatbotHeaderCloseButtonBase, Object.assign({ innerRef: ref }, props))));
@@ -15,11 +15,11 @@ exports.ChatbotHeaderNewChatButton = void 0;
15
15
  const jsx_runtime_1 = require("react/jsx-runtime");
16
16
  const react_1 = require("react");
17
17
  const react_core_1 = require("@patternfly/react-core");
18
- const pen_to_square_icon_1 = require("@patternfly/react-icons/dist/esm/icons/pen-to-square-icon");
18
+ const rh_ui_edit_fill_icon_1 = require("@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon");
19
19
  const ChatbotHeaderNewChatButtonBase = (_a) => {
20
20
  var { className, onClick, tooltipProps, menuAriaLabel = 'New chat', innerRef, tooltipContent = 'New chat', isCompact } = _a, props = __rest(_a, ["className", "onClick", "tooltipProps", "menuAriaLabel", "innerRef", "tooltipContent", "isCompact"]);
21
21
  return ((0, jsx_runtime_1.jsx)("div", { className: `pf-chatbot__menu${className ? ` ${className}` : ''}`, children: (0, jsx_runtime_1.jsx)(react_core_1.Tooltip, Object.assign({ content: tooltipContent, position: "bottom",
22
22
  // prevents VO announcements of both aria label and tooltip
23
- aria: "none" }, tooltipProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ className: `pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`, variant: "plain", onClick: onClick, "aria-label": menuAriaLabel, ref: innerRef, icon: (0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: isCompact ? 'lg' : 'xl', isInline: true, children: (0, jsx_runtime_1.jsx)(pen_to_square_icon_1.PenToSquareIcon, {}) }), size: isCompact ? 'sm' : undefined }, props)) })) }));
23
+ aria: "none" }, tooltipProps, { children: (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ className: `pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`, variant: "plain", onClick: onClick, "aria-label": menuAriaLabel, ref: innerRef, icon: (0, jsx_runtime_1.jsx)(react_core_1.Icon, { size: isCompact ? 'lg' : 'xl', isInline: true, children: (0, jsx_runtime_1.jsx)(rh_ui_edit_fill_icon_1.RhUiEditFillIcon, {}) }), size: isCompact ? 'sm' : undefined }, props)) })) }));
24
24
  };
25
25
  exports.ChatbotHeaderNewChatButton = (0, react_1.forwardRef)((props, ref) => ((0, jsx_runtime_1.jsx)(ChatbotHeaderNewChatButtonBase, Object.assign({ innerRef: ref }, props))));
@@ -175,7 +175,21 @@ const MarkdownContent = ({ content, isMarkdownDisabled, codeBlockProps, tablePro
175
175
  }
176
176
  }, remarkPlugins: [[remark_gfm_1.default, Object.assign({}, remarkGfmProps)], ...additionalRemarkPlugins], rehypePlugins: rehypePlugins }, reactMarkdownProps, { remarkRehypeOptions: Object.assign({
177
177
  // removes sr-only class from footnote labels applied by default
178
- footnoteLabelProperties: { className: [''] } }, reactMarkdownProps === null || reactMarkdownProps === void 0 ? void 0 : reactMarkdownProps.remarkRehypeOptions), disallowedElements: disallowedElements, children: content })));
178
+ footnoteLabelProperties: { className: [''] },
179
+ // omit default ↩ text; backref icon is rendered in LinkMessage
180
+ footnoteBackContent: (_referenceIndex, rereferenceIndex) => {
181
+ if (rereferenceIndex > 1) {
182
+ return [
183
+ {
184
+ type: 'element',
185
+ tagName: 'sup',
186
+ properties: {},
187
+ children: [{ type: 'text', value: String(rereferenceIndex) }]
188
+ }
189
+ ];
190
+ }
191
+ return [];
192
+ } }, reactMarkdownProps === null || reactMarkdownProps === void 0 ? void 0 : reactMarkdownProps.remarkRehypeOptions), disallowedElements: disallowedElements, children: content })));
179
193
  };
180
194
  exports.MarkdownContent = MarkdownContent;
181
195
  exports.default = exports.MarkdownContent;
@@ -19,8 +19,8 @@ const react_1 = require("react");
19
19
  // Import PatternFly components
20
20
  const react_core_1 = require("@patternfly/react-core");
21
21
  const check_icon_1 = require("@patternfly/react-icons/dist/esm/icons/check-icon");
22
- const copy_icon_1 = require("@patternfly/react-icons/dist/esm/icons/copy-icon");
23
22
  const react_styles_1 = require("@patternfly/react-styles");
23
+ const react_icons_1 = require("@patternfly/react-icons");
24
24
  const DEFAULT_EXPANDED_TEXT = 'Show less';
25
25
  const DEFAULT_COLLAPSED_TEXT = 'Show more';
26
26
  const CodeBlockMessage = (_a) => {
@@ -75,7 +75,7 @@ const CodeBlockMessage = (_a) => {
75
75
  return ((0, jsx_runtime_1.jsx)("code", Object.assign({}, props, { className: `pf-chatbot__message-inline-code ${isPrimary ? 'pf-m-primary' : ''}`, children: children })));
76
76
  }
77
77
  // Setup code block header
78
- const actions = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(react_core_1.CodeBlockAction, { className: "pf-chatbot__message-code-block-default-action", children: [language && (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__message-code-block-language", children: language }), (0, jsx_runtime_1.jsx)(react_core_1.Button, { ref: buttonRef, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : 'Copy code', variant: "plain", className: "pf-chatbot__button--copy", onClick: (event) => handleCopy(event, children), children: copied ? (0, jsx_runtime_1.jsx)(check_icon_1.CheckIcon, {}) : (0, jsx_runtime_1.jsx)(copy_icon_1.CopyIcon, {}) }), (0, jsx_runtime_1.jsx)(react_core_1.Tooltip, { id: tooltipID, content: "Copy", position: "top", triggerRef: buttonRef })] }), customActions] }));
78
+ const actions = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(react_core_1.CodeBlockAction, { className: "pf-chatbot__message-code-block-default-action", children: [language && (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__message-code-block-language", children: language }), (0, jsx_runtime_1.jsx)(react_core_1.Button, { ref: buttonRef, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : 'Copy code', variant: "plain", className: "pf-chatbot__button--copy", onClick: (event) => handleCopy(event, children), children: copied ? (0, jsx_runtime_1.jsx)(check_icon_1.CheckIcon, {}) : (0, jsx_runtime_1.jsx)(react_icons_1.RhUiCopyFillIcon, {}) }), (0, jsx_runtime_1.jsx)(react_core_1.Tooltip, { id: tooltipID, content: "Copy", position: "top", triggerRef: buttonRef })] }), customActions] }));
79
79
  return ((0, jsx_runtime_1.jsx)("div", { className: (0, react_styles_1.css)('pf-chatbot__message-code-block', shouldRetainStyles && 'pf-m-markdown'), ref: codeBlockRef, children: (0, jsx_runtime_1.jsxs)(react_core_1.CodeBlock, { actions: actions, children: [(0, jsx_runtime_1.jsx)(react_core_1.CodeBlockCode, { children: (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: isExpandable ? ((0, jsx_runtime_1.jsx)(react_core_1.ExpandableSection, Object.assign({ variant: react_core_1.ExpandableSectionVariant.truncate, isExpanded: isExpanded, isDetached: true, toggleId: toggleId, contentId: contentId }, expandableSectionProps, { children: children }))) : (children) }) }), isExpandable && ((0, jsx_runtime_1.jsx)(react_core_1.ExpandableSectionToggle, Object.assign({ isExpanded: isExpanded, onToggle: onToggle, direction: "up", toggleId: toggleId, contentId: contentId, hasTruncatedContent: true, className: "pf-chatbot__message-code-toggle" }, expandableSectionToggleProps, { children: isExpanded ? finalExpandedText : finalCollapsedText })))] }) }));
80
80
  };
81
81
  exports.default = CodeBlockMessage;
@@ -18,13 +18,17 @@ const jsx_runtime_1 = require("react/jsx-runtime");
18
18
  const react_core_1 = require("@patternfly/react-core");
19
19
  const react_icons_1 = require("@patternfly/react-icons");
20
20
  const react_styles_1 = require("@patternfly/react-styles");
21
+ const isFootnoteBackref = (className) => className === null || className === void 0 ? void 0 : className.includes('data-footnote-backref');
21
22
  const LinkMessage = (_a) => {
22
23
  var { children, target, href, id, shouldRetainStyles } = _a, props = __rest(_a, ["children", "target", "href", "id", "shouldRetainStyles"]);
23
24
  if (target === '_blank') {
24
- return ((0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ component: "a", variant: "link", href: href, icon: (0, jsx_runtime_1.jsx)(react_icons_1.ExternalLinkSquareAltIcon, {}), iconPosition: "end", isInline: true, target: target,
25
+ return ((0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ component: "a", variant: "link", href: href, icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhMicronsExternalLinkIcon, {}), iconPosition: "end", isInline: true, target: target,
25
26
  // need to explicitly call this out or id doesn't seem to get passed - required for footnotes
26
27
  id: id }, props, { className: (0, react_styles_1.css)(shouldRetainStyles && 'pf-m-markdown', props === null || props === void 0 ? void 0 : props.className), children: children })));
27
28
  }
29
+ if (isFootnoteBackref(props.className)) {
30
+ return ((0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ isInline: true, component: "a", href: href, variant: "link", id: id, icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiBackwardsIcon, {}) }, props, { className: (0, react_styles_1.css)(shouldRetainStyles && 'pf-m-markdown', props === null || props === void 0 ? void 0 : props.className), children: children })));
31
+ }
28
32
  return (
29
33
  // need to explicitly call this out or id doesn't seem to get passed - required for footnotes
30
34
  (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ isInline: true, component: "a", href: href, variant: "link", id: id }, props, { className: (0, react_styles_1.css)(shouldRetainStyles && 'pf-m-markdown', props === null || props === void 0 ? void 0 : props.className), children: children })));
@@ -36,10 +36,12 @@ jest.mock('@patternfly/react-icons', () => ({
36
36
  RhUiVolumeUpFillIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhUiVolumeUpFillIcon" }),
37
37
  RhUiExportIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhUiExportIcon" }),
38
38
  RhUiExportFillIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhUiExportFillIcon" }),
39
+ RhMicronsInformationFillIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhMicronsInformationFillIcon" }),
39
40
  CheckIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "CheckIcon" }),
40
41
  CloseIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "CloseIcon" }),
41
42
  ExternalLinkSquareAltIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "ExternalLinkSquareAltIcon" }),
42
- TimesIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "TimesIcon" })
43
+ TimesIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "TimesIcon" }),
44
+ RhUiBackwardsIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhUiBackwardsIcon" })
43
45
  }));
44
46
  const ALL_ACTIONS = [
45
47
  { label: /Good response/i },
@@ -1,13 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.pluralizeWord = pluralizeWord;
7
4
  const jsx_runtime_1 = require("react/jsx-runtime");
8
5
  const react_1 = require("react");
9
6
  const react_core_1 = require("@patternfly/react-core");
10
- const info_circle_icon_1 = __importDefault(require("@patternfly/react-icons/dist/js/icons/info-circle-icon"));
7
+ const react_icons_1 = require("@patternfly/react-icons");
11
8
  /** This function is a helper for pluralizing strings stolen from React.
12
9
  *
13
10
  * @param {number} i The quantity of the string you want to pluralize
@@ -28,7 +25,7 @@ const QuickStartTileDescription = ({ description, prerequisites, prerequisiteWor
28
25
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [description, prereqs && prereqs.length > 0 && ((0, jsx_runtime_1.jsxs)(react_core_1.Flex, { spaceItems: { default: 'spaceItemsSm' }, children: [(0, jsx_runtime_1.jsx)("h5", { children: (0, react_core_1.pluralize)(prereqs.length, prerequisiteWord, prerequisiteWordPlural) }), (0, jsx_runtime_1.jsx)(react_core_1.Button, { variant: "link", isInline: true, "data-testid": "qs-card-prereqs", ref: buttonRef, onClick: (e) => {
29
26
  e.preventDefault();
30
27
  e.stopPropagation();
31
- }, "aria-label": quickStartButtonAriaLabel, children: (0, jsx_runtime_1.jsx)(info_circle_icon_1.default, {}) }), (0, jsx_runtime_1.jsx)(react_core_1.Popover, { "aria-label": pluralizedPrereq, headerContent: pluralizedPrereq, triggerRef: buttonRef, bodyContent: (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("ul", { "aria-label": pluralizedPrereq, children: prereqs.map((prerequisite, index) => (
28
+ }, "aria-label": quickStartButtonAriaLabel, children: (0, jsx_runtime_1.jsx)(react_icons_1.RhMicronsInformationFillIcon, {}) }), (0, jsx_runtime_1.jsx)(react_core_1.Popover, { "aria-label": pluralizedPrereq, headerContent: pluralizedPrereq, triggerRef: buttonRef, bodyContent: (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("ul", { "aria-label": pluralizedPrereq, children: prereqs.map((prerequisite, index) => (
32
29
  // eslint-disable-next-line react/no-array-index-key
33
30
  (0, jsx_runtime_1.jsx)("li", { children: prerequisite }, index))) }) }) })] }))] }));
34
31
  };
@@ -32,8 +32,8 @@ describe('JumpButton', () => {
32
32
  yield user_event_1.default.click(react_1.screen.getByRole('button', { name: /Back to bottom/i }));
33
33
  expect(spy).toHaveBeenCalledTimes(1);
34
34
  }));
35
- it('should be hidden if isHidden prop is used', () => __awaiter(void 0, void 0, void 0, function* () {
35
+ it('should be hidden if isHidden prop is used', () => {
36
36
  (0, react_1.render)((0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "bottom", onClick: jest.fn(), isHidden: true }));
37
37
  expect(react_1.screen.queryByRole('button', { name: /Back to bottom/i })).toBeFalsy();
38
- }));
38
+ });
39
39
  });
@@ -21,48 +21,77 @@ const jsx_runtime_1 = require("react/jsx-runtime");
21
21
  // ============================================================================
22
22
  const react_1 = require("react");
23
23
  const JumpButton_1 = __importDefault(require("./JumpButton"));
24
+ const react_core_1 = require("@patternfly/react-core");
25
+ const SCROLL_EDGE_DELTA = 100;
26
+ const getScrollMetrics = (element) => {
27
+ const { scrollTop, scrollHeight, clientHeight } = element;
28
+ const roundedScrollTop = Math.round(scrollTop);
29
+ const roundedClientHeight = Math.round(clientHeight);
30
+ const roundedScrollHeight = Math.round(scrollHeight);
31
+ const distanceFromBottom = roundedScrollHeight - roundedScrollTop - roundedClientHeight;
32
+ return {
33
+ roundedScrollTop,
34
+ roundedClientHeight,
35
+ roundedScrollHeight,
36
+ distanceFromBottom,
37
+ atTop: roundedScrollTop === 0,
38
+ nearBottom: distanceFromBottom <= SCROLL_EDGE_DELTA,
39
+ isOverflowing: roundedScrollHeight >= roundedClientHeight
40
+ };
41
+ };
42
+ const isAtBottomEdge = (metrics, enableSmartScroll, autoScroll, pauseAutoScroll, programmaticScroll = false) => metrics.nearBottom ||
43
+ programmaticScroll ||
44
+ (enableSmartScroll && autoScroll && !pauseAutoScroll && metrics.roundedScrollTop > 0);
45
+ const shouldFollowContentGrowth = (metrics, enableSmartScroll, autoScroll, pauseAutoScroll, programmaticScroll) => programmaticScroll ||
46
+ metrics.nearBottom ||
47
+ (enableSmartScroll && autoScroll && !pauseAutoScroll && metrics.roundedScrollTop > 0);
24
48
  exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
25
49
  var { announcement, ariaLabel = 'Scrollable message log', children, className, position = 'top', onScrollToTopClick, onScrollToBottomClick, enableSmartScroll = false, jumpButtonTopProps, jumpButtonBottomProps, jumpButtonBottomTooltipProps, jumpButtonTopTooltipProps } = _a, props = __rest(_a, ["announcement", "ariaLabel", "children", "className", "position", "onScrollToTopClick", "onScrollToBottomClick", "enableSmartScroll", "jumpButtonTopProps", "jumpButtonBottomProps", "jumpButtonBottomTooltipProps", "jumpButtonTopTooltipProps"]);
26
- const [atTop, setAtTop] = (0, react_1.useState)(false);
27
- const [atBottom, setAtBottom] = (0, react_1.useState)(true);
50
+ const [atTop, setAtTop] = (0, react_1.useState)(true);
51
+ const [atBottom, setAtBottom] = (0, react_1.useState)(false);
28
52
  const [isOverflowing, setIsOverflowing] = (0, react_1.useState)(false);
53
+ const [hasMeasuredScroll, setHasMeasuredScroll] = (0, react_1.useState)(false);
29
54
  const [autoScroll, setAutoScroll] = (0, react_1.useState)(true);
55
+ const autoScrollRef = (0, react_1.useRef)(autoScroll);
30
56
  const lastScrollTop = (0, react_1.useRef)(0);
31
57
  const animationFrame = (0, react_1.useRef)(null);
32
58
  const debounceTimeout = (0, react_1.useRef)(null);
33
59
  const pauseAutoScrollRef = (0, react_1.useRef)(false);
60
+ const programmaticScrollRef = (0, react_1.useRef)(false);
34
61
  const messageBoxRef = (0, react_1.useRef)(null);
35
62
  const scrollQueued = (0, react_1.useRef)(false);
36
63
  const resetUserScrollIntentTimeout = (0, react_1.useRef)();
64
+ autoScrollRef.current = autoScroll;
37
65
  // Configure handlers
38
66
  const handleScroll = (0, react_1.useCallback)(() => {
39
67
  const element = messageBoxRef.current;
40
68
  if (!element) {
41
69
  return;
42
70
  }
43
- const { scrollTop, scrollHeight, clientHeight } = element;
44
- const roundedScrollTop = Math.round(scrollTop);
45
- const roundedClientHeight = Math.round(clientHeight);
46
- const roundedScrollHeight = Math.round(scrollHeight);
47
- const distanceFromBottom = roundedScrollHeight - roundedScrollTop - roundedClientHeight;
71
+ const metrics = getScrollMetrics(element);
72
+ const { roundedScrollTop, distanceFromBottom } = metrics;
48
73
  const isScrollingDown = roundedScrollTop > lastScrollTop.current;
49
74
  const DELTA_UP = 10;
50
75
  const DELTA_DOWN = 50;
51
76
  const DEBOUNCE_DELAY = 200;
52
77
  const delta = isScrollingDown ? DELTA_DOWN : DELTA_UP;
53
78
  const isAtBottom = distanceFromBottom <= delta;
54
- setAtTop(roundedScrollTop === 0);
55
- setAtBottom(roundedScrollTop + roundedClientHeight >= roundedScrollHeight - 1); // rounding means it could be within a pixel of the bottom
79
+ setAtTop(metrics.atTop);
80
+ setAtBottom(isAtBottomEdge(metrics, enableSmartScroll, autoScrollRef.current, pauseAutoScrollRef.current, programmaticScrollRef.current));
81
+ setIsOverflowing(metrics.isOverflowing);
82
+ if (isAtBottom) {
83
+ programmaticScrollRef.current = false;
84
+ }
56
85
  if (!enableSmartScroll || scrollQueued.current) {
57
86
  return;
58
87
  }
59
- if (roundedScrollTop === 0) {
88
+ if (metrics.roundedScrollTop === 0) {
60
89
  pauseAutoScrollRef.current = false;
61
90
  }
62
91
  if (debounceTimeout.current) {
63
92
  clearTimeout(debounceTimeout.current);
64
93
  }
65
- if (!isAtBottom && !pauseAutoScrollRef.current) {
94
+ if (!isAtBottom && !metrics.nearBottom && !pauseAutoScrollRef.current && !programmaticScrollRef.current) {
66
95
  setAutoScroll(false);
67
96
  }
68
97
  // User is near bottom and scrolling down - debounce re-enabling auto-scroll
@@ -72,14 +101,7 @@ exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
72
101
  }, DEBOUNCE_DELAY);
73
102
  }
74
103
  lastScrollTop.current = roundedScrollTop;
75
- }, [messageBoxRef]);
76
- const checkOverflow = (0, react_1.useCallback)(() => {
77
- const element = messageBoxRef.current;
78
- if (element) {
79
- const { scrollHeight, clientHeight } = element;
80
- setIsOverflowing(scrollHeight >= clientHeight);
81
- }
82
- }, [messageBoxRef]);
104
+ }, [enableSmartScroll]);
83
105
  const resumeAutoScroll = (0, react_1.useCallback)(() => {
84
106
  if (!enableSmartScroll) {
85
107
  return;
@@ -131,6 +153,7 @@ exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
131
153
  return;
132
154
  }
133
155
  scrollQueued.current = true;
156
+ programmaticScrollRef.current = true;
134
157
  if (animationFrame.current) {
135
158
  cancelAnimationFrame(animationFrame.current);
136
159
  }
@@ -138,24 +161,51 @@ exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
138
161
  element.scrollTo({ top: element.scrollHeight, behavior });
139
162
  resumeAutoScroll();
140
163
  scrollQueued.current = false;
164
+ if (behavior === 'auto') {
165
+ programmaticScrollRef.current = false;
166
+ lastScrollTop.current = element.scrollTop;
167
+ }
141
168
  });
142
169
  onScrollToBottomClick && onScrollToBottomClick();
143
170
  }, [messageBoxRef, enableSmartScroll]);
144
- // Detect scroll position
145
- (0, react_1.useEffect)(() => {
171
+ const measureJumpButtonState = (0, react_1.useCallback)(() => {
172
+ const element = messageBoxRef.current;
173
+ if (!element) {
174
+ return;
175
+ }
176
+ const metrics = getScrollMetrics(element);
177
+ setAtTop(metrics.atTop);
178
+ setAtBottom(isAtBottomEdge(metrics, enableSmartScroll, autoScrollRef.current, pauseAutoScrollRef.current, programmaticScrollRef.current));
179
+ setIsOverflowing(metrics.isOverflowing);
180
+ setHasMeasuredScroll(true);
181
+ }, [enableSmartScroll]);
182
+ const handleContentResize = (0, react_1.useCallback)(() => {
183
+ const element = messageBoxRef.current;
184
+ if (element) {
185
+ const metrics = getScrollMetrics(element);
186
+ const shouldFollow = shouldFollowContentGrowth(metrics, enableSmartScroll, autoScrollRef.current, pauseAutoScrollRef.current, programmaticScrollRef.current);
187
+ if (shouldFollow && !scrollQueued.current) {
188
+ programmaticScrollRef.current = true;
189
+ element.scrollTop = element.scrollHeight - element.clientHeight;
190
+ lastScrollTop.current = element.scrollTop;
191
+ }
192
+ }
193
+ measureJumpButtonState();
194
+ }, [enableSmartScroll, measureJumpButtonState]);
195
+ // Detect scroll position before paint to avoid jump button flash on mount
196
+ (0, react_1.useLayoutEffect)(() => {
146
197
  const element = messageBoxRef.current;
147
198
  if (!element) {
148
199
  return;
149
200
  }
150
- // Listen for scroll events
201
+ measureJumpButtonState();
151
202
  element.addEventListener('scroll', handleScroll);
152
- // Check initial position and overflow
153
- handleScroll();
154
- checkOverflow();
203
+ const resizeObserver = (0, react_core_1.getResizeObserver)(element, handleContentResize);
155
204
  return () => {
156
205
  element.removeEventListener('scroll', handleScroll);
206
+ resizeObserver();
157
207
  };
158
- }, [checkOverflow, handleScroll, messageBoxRef]);
208
+ }, [handleContentResize, handleScroll, measureJumpButtonState]);
159
209
  (0, react_1.useImperativeHandle)(ref, () => {
160
210
  const node = messageBoxRef.current;
161
211
  // Attach custom methods to the element
@@ -176,12 +226,13 @@ exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
176
226
  }
177
227
  if (!isScrollingDown) {
178
228
  pauseAutoScrollRef.current = true;
229
+ programmaticScrollRef.current = false;
179
230
  clearTimeout(resetUserScrollIntentTimeout.current);
180
231
  return;
181
232
  }
182
233
  const { scrollTop, scrollHeight, clientHeight } = container;
183
234
  const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
184
- if (distanceFromBottom < 100) {
235
+ if (distanceFromBottom < SCROLL_EDGE_DELTA) {
185
236
  pauseAutoScrollRef.current = false;
186
237
  setAutoScroll(true);
187
238
  }
@@ -206,6 +257,6 @@ exports.MessageBox = (0, react_1.forwardRef)((_a, ref) => {
206
257
  onTouchMove,
207
258
  onTouchEnd
208
259
  };
209
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "top", isHidden: isOverflowing && atTop, onClick: scrollToTop, jumpButtonProps: jumpButtonTopProps, jumpButtonTooltipProps: jumpButtonTopTooltipProps }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ role: "region", tabIndex: 0, "aria-label": ariaLabel, className: `pf-chatbot__messagebox ${position === 'bottom' ? 'pf-chatbot__messagebox--bottom' : ''} ${className !== null && className !== void 0 ? className : ''}`, ref: messageBoxRef }, props, (enableSmartScroll ? Object.assign({}, smartScrollHandlers) : {}), { children: [children, (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__messagebox-announcement pf-chatbot-m-hidden", "aria-live": "polite", children: announcement })] })), (0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "bottom", isHidden: isOverflowing && atBottom, onClick: () => scrollToBottom({ resumeSmartScroll: true }), jumpButtonProps: jumpButtonBottomProps, jumpButtonTooltipProps: jumpButtonBottomTooltipProps })] }));
260
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "top", isHidden: !hasMeasuredScroll || (isOverflowing && atTop), onClick: scrollToTop, jumpButtonProps: jumpButtonTopProps, jumpButtonTooltipProps: jumpButtonTopTooltipProps }), (0, jsx_runtime_1.jsx)(JumpButton_1.default, { position: "bottom", isHidden: !hasMeasuredScroll || (isOverflowing && atBottom), onClick: () => scrollToBottom({ resumeSmartScroll: true }), jumpButtonProps: jumpButtonBottomProps, jumpButtonTooltipProps: jumpButtonBottomTooltipProps }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ role: "region", tabIndex: 0, "aria-label": ariaLabel, className: `pf-chatbot__messagebox ${position === 'bottom' ? 'pf-chatbot__messagebox--bottom' : ''} ${className !== null && className !== void 0 ? className : ''}`, ref: messageBoxRef }, props, (enableSmartScroll ? Object.assign({}, smartScrollHandlers) : {}), { children: [children, (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__messagebox-announcement pf-chatbot-m-hidden", "aria-live": "polite", children: announcement })] }))] }));
210
261
  });
211
262
  exports.default = exports.MessageBox;
@@ -1 +1 @@
1
- export {};
1
+ import '@testing-library/jest-dom';
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const jsx_runtime_1 = require("react/jsx-runtime");
16
16
  const react_1 = require("react");
17
17
  const react_2 = require("@testing-library/react");
18
+ require("@testing-library/jest-dom");
18
19
  const MessageBox_1 = require("./MessageBox");
19
20
  const user_event_1 = __importDefault(require("@testing-library/user-event"));
20
21
  describe('MessageBox', () => {
@@ -232,4 +233,62 @@ describe('MessageBox', () => {
232
233
  });
233
234
  expect((_b = ref.current) === null || _b === void 0 ? void 0 : _b.isSmartScrollActive()).toBe(true);
234
235
  }));
236
+ it('should keep auto-scroll active during programmatic scroll to bottom', () => __awaiter(void 0, void 0, void 0, function* () {
237
+ var _a, _b;
238
+ const ref = (0, react_1.createRef)();
239
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(MessageBox_1.MessageBox, { ref: ref, enableSmartScroll: true, children: (0, jsx_runtime_1.jsx)("div", { children: "Test message content" }) }));
240
+ const element = ref.current;
241
+ Object.defineProperty(element, 'scrollHeight', { configurable: true, value: 1000 });
242
+ Object.defineProperty(element, 'clientHeight', { configurable: true, value: 300 });
243
+ Object.defineProperty(element, 'scrollTop', { configurable: true, value: 0, writable: true });
244
+ (0, react_2.act)(() => {
245
+ var _a;
246
+ (_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollToBottom({ behavior: 'smooth' });
247
+ });
248
+ expect((_a = ref.current) === null || _a === void 0 ? void 0 : _a.isSmartScrollActive()).toBe(true);
249
+ (0, react_2.act)(() => {
250
+ element.scrollTop = 500;
251
+ element.dispatchEvent(new Event('scroll', { bubbles: true }));
252
+ });
253
+ expect((_b = ref.current) === null || _b === void 0 ? void 0 : _b.isSmartScrollActive()).toBe(true);
254
+ }));
255
+ it('should not scroll to bottom on initial layout when the message box is at the top', () => {
256
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(MessageBox_1.MessageBox, { enableSmartScroll: true, children: (0, jsx_runtime_1.jsx)("div", { children: "Tall message content" }) }));
257
+ const region = react_2.screen.getByRole('region');
258
+ Object.defineProperty(region, 'scrollHeight', { configurable: true, value: 1000 });
259
+ Object.defineProperty(region, 'clientHeight', { configurable: true, value: 300 });
260
+ Object.defineProperty(region, 'scrollTop', { configurable: true, value: 0, writable: true });
261
+ (0, react_2.act)(() => {
262
+ region.dispatchEvent(new Event('scroll'));
263
+ });
264
+ expect(region.scrollTop).toBe(0);
265
+ });
266
+ it('should show the bottom jump button at the top on initial load when overflowing', () => __awaiter(void 0, void 0, void 0, function* () {
267
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(MessageBox_1.MessageBox, { enableSmartScroll: true, children: (0, jsx_runtime_1.jsx)("div", { children: "Tall message content" }) }));
268
+ const region = react_2.screen.getByRole('region');
269
+ Object.defineProperty(region, 'scrollHeight', { configurable: true, value: 1000 });
270
+ Object.defineProperty(region, 'clientHeight', { configurable: true, value: 300 });
271
+ Object.defineProperty(region, 'scrollTop', { configurable: true, value: 0, writable: true });
272
+ (0, react_2.act)(() => {
273
+ region.dispatchEvent(new Event('scroll'));
274
+ });
275
+ yield (0, react_2.waitFor)(() => {
276
+ expect(react_2.screen.queryByRole('button', { name: /Back to top/i })).toBeFalsy();
277
+ expect(react_2.screen.getByRole('button', { name: /Back to bottom/i })).toBeTruthy();
278
+ });
279
+ }));
280
+ it('should hide the bottom jump button while smart scroll is actively following content', () => __awaiter(void 0, void 0, void 0, function* () {
281
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(MessageBox_1.MessageBox, { enableSmartScroll: true, children: (0, jsx_runtime_1.jsx)("div", { children: "Test message content" }) }));
282
+ const region = react_2.screen.getByRole('region');
283
+ Object.defineProperty(region, 'scrollHeight', { configurable: true, value: 1000 });
284
+ Object.defineProperty(region, 'clientHeight', { configurable: true, value: 300 });
285
+ Object.defineProperty(region, 'scrollTop', { configurable: true, value: 640, writable: true });
286
+ (0, react_2.act)(() => {
287
+ region.dispatchEvent(new Event('scroll'));
288
+ });
289
+ yield (0, react_2.waitFor)(() => {
290
+ expect(react_2.screen.getByRole('button', { name: /Back to top/i })).toBeTruthy();
291
+ expect(react_2.screen.queryByRole('button', { name: /Back to bottom/i })).toBeFalsy();
292
+ });
293
+ }));
235
294
  });
@@ -20,35 +20,35 @@ const react_icons_1 = require("@patternfly/react-icons");
20
20
  const ResponseActionButton_1 = __importDefault(require("./ResponseActionButton"));
21
21
  describe('ResponseActionButton', () => {
22
22
  it('renders aria-label correctly if not clicked', () => {
23
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {}), ariaLabel: "Download", clickedAriaLabel: "Downloaded" }));
23
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiExportIcon, {}), ariaLabel: "Download", clickedAriaLabel: "Downloaded" }));
24
24
  expect(react_1.screen.getByRole('button', { name: 'Download' })).toBeTruthy();
25
25
  });
26
26
  it('renders aria-label correctly if clicked', () => {
27
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {}), ariaLabel: "Download", clickedAriaLabel: "Downloaded", isClicked: true }));
27
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiExportIcon, {}), ariaLabel: "Download", clickedAriaLabel: "Downloaded", isClicked: true }));
28
28
  expect(react_1.screen.getByRole('button', { name: 'Downloaded' })).toBeTruthy();
29
29
  });
30
30
  it('renders tooltip correctly if not clicked', () => __awaiter(void 0, void 0, void 0, function* () {
31
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {}), tooltipContent: "Download", clickedTooltipContent: "Downloaded" }));
31
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiExportIcon, {}), tooltipContent: "Download", clickedTooltipContent: "Downloaded" }));
32
32
  expect(react_1.screen.getByRole('button', { name: 'Download' })).toBeTruthy();
33
33
  // clicking here just triggers the tooltip; in this button, the logic is divorced from whether it is actually clicked
34
34
  yield user_event_1.default.click(react_1.screen.getByRole('button', { name: 'Download' }));
35
35
  expect(react_1.screen.getByRole('tooltip', { name: 'Download' })).toBeTruthy();
36
36
  }));
37
37
  it('renders tooltip correctly if clicked', () => __awaiter(void 0, void 0, void 0, function* () {
38
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {}), tooltipContent: "Download", clickedTooltipContent: "Downloaded", isClicked: true }));
38
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiExportIcon, {}), tooltipContent: "Download", clickedTooltipContent: "Downloaded", isClicked: true }));
39
39
  expect(react_1.screen.getByRole('button', { name: 'Downloaded' })).toBeTruthy();
40
40
  // clicking here just triggers the tooltip; in this button, the logic is divorced from whether it is actually clicked
41
41
  yield user_event_1.default.click(react_1.screen.getByRole('button', { name: 'Downloaded' }));
42
42
  expect(react_1.screen.getByRole('tooltip', { name: 'Downloaded' })).toBeTruthy();
43
43
  }));
44
44
  it('if clicked variant for tooltip is not supplied, it uses the default', () => __awaiter(void 0, void 0, void 0, function* () {
45
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {}), tooltipContent: "Download", isClicked: true }));
45
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiExportIcon, {}), tooltipContent: "Download", isClicked: true }));
46
46
  // clicking here just triggers the tooltip; in this button, the logic is divorced from whether it is actually clicked
47
47
  yield user_event_1.default.click(react_1.screen.getByRole('button', { name: 'Download' }));
48
48
  expect(react_1.screen.getByRole('button', { name: 'Download' })).toBeTruthy();
49
49
  }));
50
50
  it('if clicked variant for aria label is not supplied, it uses the default', () => __awaiter(void 0, void 0, void 0, function* () {
51
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {}), ariaLabel: "Download", isClicked: true }));
51
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, { icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiExportIcon, {}), ariaLabel: "Download", isClicked: true }));
52
52
  expect(react_1.screen.getByRole('button', { name: 'Download' })).toBeTruthy();
53
53
  }));
54
54
  });
@@ -33,9 +33,8 @@ jest.mock('@patternfly/react-icons', () => ({
33
33
  RhUiVolumeUpFillIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhUiVolumeUpFillIcon" }),
34
34
  RhUiExportIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhUiExportIcon" }),
35
35
  RhUiExportFillIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhUiExportFillIcon" }),
36
- DownloadIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "DownloadIcon" }),
37
- InfoCircleIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "InfoCircleIcon" }),
38
- RedoIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RedoIcon" })
36
+ RhMicronsInformationFillIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhMicronsInformationFillIcon" }),
37
+ RhUiRedoIcon: () => (0, jsx_runtime_1.jsx)("div", { children: "RhUiRedoIcon" })
39
38
  }));
40
39
  const ALL_ACTIONS = [
41
40
  { type: 'positive', label: 'Good response', clickedLabel: 'Good response recorded' },
@@ -53,7 +52,7 @@ const CUSTOM_ACTIONS = [
53
52
  onClick: jest.fn(),
54
53
  tooltipContent: 'Regenerate',
55
54
  clickedTooltipContent: 'Regenerated',
56
- icon: (0, jsx_runtime_1.jsx)(react_icons_1.RedoIcon, {})
55
+ icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiRedoIcon, {})
57
56
  },
58
57
  download: {
59
58
  ariaLabel: 'Download',
@@ -61,13 +60,13 @@ const CUSTOM_ACTIONS = [
61
60
  onClick: jest.fn(),
62
61
  tooltipContent: 'Download',
63
62
  clickedTooltipContent: 'Downloaded',
64
- icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {})
63
+ icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiExportIcon, {})
65
64
  },
66
65
  info: {
67
66
  ariaLabel: 'Info',
68
67
  onClick: jest.fn(),
69
68
  tooltipContent: 'Info',
70
- icon: (0, jsx_runtime_1.jsx)(react_icons_1.InfoCircleIcon, {})
69
+ icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhMicronsInformationFillIcon, {})
71
70
  }
72
71
  }
73
72
  ];
@@ -323,7 +322,7 @@ describe('ResponseActions', () => {
323
322
  onClick: jest.fn(),
324
323
  ariaLabel: 'Custom',
325
324
  tooltipContent: 'Custom action',
326
- icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {})
325
+ icon: (0, jsx_runtime_1.jsx)(react_icons_1.RhUiExportIcon, {})
327
326
  }
328
327
  };
329
328
  (0, react_1.render)((0, jsx_runtime_1.jsx)(ResponseActions_1.default, { actions: actions, persistActionSelection: true }));
@@ -38,8 +38,6 @@ export { default as FilePreview } from './FilePreview';
38
38
  export * from './FilePreview';
39
39
  export { default as ImagePreview } from './ImagePreview';
40
40
  export * from './ImagePreview';
41
- export { default as LoadingMessage } from './LoadingMessage';
42
- export * from './LoadingMessage';
43
41
  export { default as MarkdownContent } from './MarkdownContent';
44
42
  export * from './MarkdownContent';
45
43
  export { default as Message } from './Message';