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