@fluentui/react-northstar
Version:
A themable React component library.
538 lines (532 loc) • 24.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.chatMessageSlotClassNames = exports.chatMessageClassName = exports.ChatMessage = void 0;
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var _invoke2 = _interopRequireDefault(require("lodash/invoke"));
var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
var _accessibility = require("@fluentui/accessibility");
var _reactBindings = require("@fluentui/react-bindings");
var _reactComponentRef = require("@fluentui/react-component-ref");
var customPropTypes = _interopRequireWildcard(require("@fluentui/react-proptypes"));
var _classnames = _interopRequireDefault(require("classnames"));
var PropTypes = _interopRequireWildcard(require("prop-types"));
var React = _interopRequireWildcard(require("react"));
var _utils = require("../../utils");
var _positioner = require("../../utils/positioner");
var _Box = require("../Box/Box");
var _Flex = require("../Flex/Flex");
var _Label = require("../Label/Label");
var _Menu = require("../Menu/Menu");
var _PortalInner = require("../Portal/PortalInner");
var _Reaction = require("../Reaction/Reaction");
var _Text = require("../Text/Text");
var _chatContext = require("./chatContext");
var _chatItemContext = require("./chatItemContext");
var _ChatMessageDetails = require("./ChatMessageDetails");
var _ChatMessageHeader = require("./ChatMessageHeader");
var _ChatMessageReadStatus = require("./ChatMessageReadStatus");
var _ChatMessageContent = require("./ChatMessageContent");
var _excluded = ["inline", "showActionMenu"];
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
var chatMessageClassName = 'ui-chat__message';
exports.chatMessageClassName = chatMessageClassName;
var chatMessageSlotClassNames = {
actionMenu: chatMessageClassName + "__actions",
author: chatMessageClassName + "__author",
badge: chatMessageClassName + "__badge",
bar: chatMessageClassName + "__bar",
body: chatMessageClassName + "__body",
bubble: chatMessageClassName + "__bubble",
bubbleInset: chatMessageClassName + "__bubble-inset",
compactBody: chatMessageClassName + "__compact-body",
reactionGroup: chatMessageClassName + "__reactions",
timestamp: chatMessageClassName + "__timestamp"
};
exports.chatMessageSlotClassNames = chatMessageSlotClassNames;
function partitionActionMenuPropsFromShorthand(value) {
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
var _ref = value,
inline = _ref.inline,
showActionMenu = _ref.showActionMenu,
props = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
return [props, inline != null ? inline : true, showActionMenu];
}
return [value, true, false];
}
/**
* A ChatMessage represents a single message in chat.
*/
var ChatMessage = /*#__PURE__*/React.forwardRef(function (inputProps, ref) {
var context = (0, _reactBindings.useFluentContext)();
var _useTelemetry = (0, _reactBindings.useTelemetry)(ChatMessage.displayName, context.telemetry),
setStart = _useTelemetry.setStart,
setEnd = _useTelemetry.setEnd;
setStart();
var parentAttached = (0, _reactBindings.useContextSelector)(_chatItemContext.ChatItemContext, function (v) {
return v.attached;
});
var chatProps = (0, _chatContext.useChatContextSelectors)({
density: function density(v) {
return v.density;
},
accessibility: function accessibility(v) {
return v.behaviors.message;
}
});
var props = Object.assign({}, inputProps, {
density: inputProps.density === undefined ? chatProps.density : inputProps.density,
accessibility: inputProps.accessibility === undefined ? chatProps.accessibility || _accessibility.chatMessageBehavior : inputProps.accessibility
});
var accessibility = props.accessibility,
_props$attached = props.attached,
attached = _props$attached === void 0 ? parentAttached : _props$attached,
author = props.author,
badge = props.badge,
badgePosition = props.badgePosition,
children = props.children,
className = props.className,
compactBody = props.compactBody,
content = props.content,
density = props.density,
design = props.design,
details = props.details,
header = props.header,
mine = props.mine,
positionActionMenu = props.positionActionMenu,
reactionGroup = props.reactionGroup,
reactionGroupPosition = props.reactionGroupPosition,
readStatus = props.readStatus,
styles = props.styles,
timestamp = props.timestamp,
overflow = props.unstable_overflow,
_props$unstable_layou = props.unstable_layout,
layout = _props$unstable_layou === void 0 ? 'default' : _props$unstable_layou,
variables = props.variables,
failed = props.failed,
bubble = props.bubble,
body = props.body,
bubbleInset = props.bubbleInset,
bubbleInsetContent = props.bubbleInsetContent,
headerContent = props.headerContent;
var isRefreshComfyLayout = layout === 'refresh' && density === 'comfy';
var _partitionPopperProps = (0, _positioner.partitionPopperPropsFromShorthand)(props.actionMenu),
actionMenuOptions = _partitionPopperProps[0],
positioningProps = _partitionPopperProps[1];
var _partitionActionMenuP = partitionActionMenuPropsFromShorthand(actionMenuOptions),
actionMenu = _partitionActionMenuP[0],
inlineActionMenu = _partitionActionMenuP[1],
controlledShowActionMenu = _partitionActionMenuP[2];
var _useAutoControlled = (0, _reactBindings.useAutoControlled)({
defaultValue: false,
value: controlledShowActionMenu
}),
showActionMenu = _useAutoControlled[0],
setShowActionMenu = _useAutoControlled[1];
var hasActionMenu = !(0, _isNil2.default)(actionMenu);
var hasHeaderReactionGroup = !!reactionGroup && reactionGroupPosition === 'start';
var actionMenuId = React.useRef();
actionMenuId.current = (0, _utils.getOrGenerateIdFromShorthand)(chatMessageClassName + "-", actionMenu, actionMenuId.current);
var modifiers = React.useCallback(function (target, container) {
return positionActionMenu && [
// https://popper.js.org/docs/v2/modifiers/flip/
// Forces to flip only in "top-*" positions
{
name: 'flip',
options: {
fallbackPlacements: ['top']
}
}, overflow && {
name: 'preventOverflow',
options: {
boundary: (0, _positioner.getScrollParent)(container)
}
}];
}, [positionActionMenu, overflow]);
var popperRef = React.useRef();
var _usePopper = (0, _positioner.usePopper)(Object.assign({
align: 'end',
rtl: context.rtl,
position: 'above',
positionFixed: overflow,
enabled: hasActionMenu && positionActionMenu,
modifiers: modifiers
}, positioningProps, {
popperRef: (0, _reactBindings.useMergedRefs)(positioningProps == null ? void 0 : positioningProps.popperRef, popperRef)
})),
actionsMenuTargetRef = _usePopper.targetRef,
actionsMenuRef = _usePopper.containerRef;
// `focused` state is used for show/hide actionMenu
var _React$useState = React.useState(false),
focused = _React$useState[0],
setFocused = _React$useState[1];
var getA11Props = (0, _reactBindings.useAccessibility)(accessibility, {
actionHandlers: {
// prevents default FocusZone behavior, e.g., in ChatMessageBehavior, it prevents FocusZone from using arrow keys
// as navigation (only Tab key should work)
preventDefault: function preventDefault(event) {
// preventDefault only if event coming from inside the message
if (event.currentTarget !== event.target) {
event.preventDefault();
}
},
focus: function focus(event) {
var target = actionsMenuTargetRef.current;
if (target) {
target.focus();
event.stopPropagation();
}
}
},
debugName: ChatMessage.displayName,
mapPropsToBehavior: function mapPropsToBehavior() {
return {
hasActionMenu: hasActionMenu,
inlineActionMenu: inlineActionMenu,
actionMenuId: actionMenuId.current
};
},
rtl: context.rtl
});
var _useStyles = (0, _reactBindings.useStyles)(ChatMessage.displayName, {
className: chatMessageClassName,
mapPropsToStyles: function mapPropsToStyles() {
return {
attached: attached,
badgePosition: badgePosition,
density: density,
focused: focused,
hasActionMenu: hasActionMenu,
hasBadge: !!badge,
hasHeaderReactionGroup: hasHeaderReactionGroup,
mine: mine,
showActionMenu: showActionMenu,
hasReactions: !!reactionGroup,
failed: failed,
layout: layout
};
},
mapPropsToInlineStyles: function mapPropsToInlineStyles() {
return {
className: className,
design: design,
styles: styles,
variables: variables
};
},
rtl: context.rtl
}),
classes = _useStyles.classes,
resolvedStyles = _useStyles.styles;
var handleFocus = function handleFocus(e) {
var _popperRef$current;
(_popperRef$current = popperRef.current) == null ? void 0 : _popperRef$current.updatePosition();
// react onFocus is called even when nested component receives focus (i.e. it bubbles)
// so when focus moves within actionMenu, the `focus` state in chatMessage remains true, and keeps actionMenu visible
setFocused(true);
(0, _invoke2.default)(props, 'onFocus', e, props);
};
var handleBlur = function handleBlur(e) {
// `focused` controls is focused the whole `ChatMessage` or any of its children. When we're navigating
// with keyboard the focused element will be changed and there is no way to use `:focus` selector
var shouldPreserveFocusState = (0, _invoke2.default)(e, 'currentTarget.contains', e.relatedTarget);
setFocused(shouldPreserveFocusState);
setShowActionMenu(false);
(0, _invoke2.default)(props, 'onBlur', e, props);
};
var handleMouseEnter = function handleMouseEnter(e) {
var _popperRef$current2;
(_popperRef$current2 = popperRef.current) == null ? void 0 : _popperRef$current2.updatePosition();
if (hasActionMenu && !inlineActionMenu) {
setShowActionMenu(true);
}
(0, _invoke2.default)(props, 'onMouseEnter', e, props);
};
var handleMouseLeave = function handleMouseLeave(e) {
if (!focused && hasActionMenu && !inlineActionMenu) {
setShowActionMenu(false);
}
(0, _invoke2.default)(props, 'onMouseLeave', e, props);
};
var renderActionMenu = function renderActionMenu() {
var actionMenuElement = _Menu.Menu.create(actionMenu, {
defaultProps: function defaultProps() {
var _ref2;
return _ref2 = {}, _ref2[_accessibility.IS_FOCUSABLE_ATTRIBUTE] = true, _ref2.accessibility = _accessibility.menuAsToolbarBehavior, _ref2.className = chatMessageSlotClassNames.actionMenu, _ref2.styles = resolvedStyles.actionMenu, _ref2;
},
overrideProps: {
id: actionMenuId.current
}
});
var content = actionMenuElement ? /*#__PURE__*/React.createElement(_reactComponentRef.Ref, {
innerRef: actionsMenuRef
}, actionMenuElement) : actionMenuElement;
return inlineActionMenu || !content ? content : /*#__PURE__*/React.createElement(_PortalInner.PortalInner, null, content);
};
var handleKeyDown = function handleKeyDown(e) {
if (hasActionMenu && !inlineActionMenu) {
var _actionsMenuRef$curre, _actionsMenuRef$curre2, _actionsMenuRef$curre3;
// reference: https://github.com/microsoft/fluentui/pull/17329
var toFocusItemInActionMenu = (_actionsMenuRef$curre = (_actionsMenuRef$curre2 = actionsMenuRef.current) == null ? void 0 : _actionsMenuRef$curre2.querySelector('[tabindex="0"]')) != null ? _actionsMenuRef$curre : (_actionsMenuRef$curre3 = actionsMenuRef.current) == null ? void 0 : _actionsMenuRef$curre3.querySelectorAll('[tabindex="-1"]:not([data-is-focusable="false"])')[0];
if (e.keyCode === _accessibility.keyboardKey.Enter) {
toFocusItemInActionMenu == null ? void 0 : toFocusItemInActionMenu.focus();
e.stopPropagation();
e.preventDefault();
}
if (e.keyCode === _accessibility.keyboardKey.Tab) {
// TAB/SHIFT+TAB cycles focus among actionMenu and focusable elements within chat message
var isShift = !!e.shiftKey;
var focusableElementsInsideMessage = e.currentTarget.querySelectorAll('[tabindex="-1"]:not([data-is-focusable="false"])');
var firstFocusableInsideMessage = focusableElementsInsideMessage[0];
var lastFocusableInsideMessage = focusableElementsInsideMessage[focusableElementsInsideMessage.length - 1];
if (e.target === toFocusItemInActionMenu) {
// focus is now inside action menu
// cycle focus into the first/last focusable element inside chat message
if (isShift) {
lastFocusableInsideMessage == null ? void 0 : lastFocusableInsideMessage.focus();
} else {
firstFocusableInsideMessage == null ? void 0 : firstFocusableInsideMessage.focus();
}
e.stopPropagation();
e.preventDefault();
} else {
var boundaryElementInsideMessage = isShift ? firstFocusableInsideMessage : lastFocusableInsideMessage;
if (e.target === boundaryElementInsideMessage) {
// focus is now on the first/last focusable element inside chat message
toFocusItemInActionMenu.focus(); // cycle focus back into action Menu
e.stopPropagation();
e.preventDefault();
}
}
}
}
(0, _invoke2.default)(props, 'onKeyDown', e, props);
};
var childrenPropExists = (0, _utils.childrenExist)(children);
var rootClasses = childrenPropExists ? (0, _classnames.default)(classes.root, classes.content) : classes.root;
var ElementType = (0, _reactBindings.getElementType)(props);
var unhandledProps = (0, _reactBindings.useUnhandledProps)(ChatMessage.handledProps, props);
var badgeElement = _Label.Label.create(badge, {
defaultProps: function defaultProps() {
return {
className: chatMessageSlotClassNames.badge,
styles: resolvedStyles.badge
};
}
});
var reactionGroupElement = _Reaction.Reaction.Group.create(reactionGroup, {
defaultProps: function defaultProps() {
return {
className: chatMessageSlotClassNames.reactionGroup,
styles: resolvedStyles.reactionGroup
};
}
});
var actionMenuElement = renderActionMenu();
var authorElement = _Text.Text.create(author, {
defaultProps: function defaultProps() {
return {
size: density === 'comfy' ? 'small' : undefined,
styles: resolvedStyles.author,
className: chatMessageSlotClassNames.author
};
}
});
var timestampElement = _Text.Text.create(timestamp, {
defaultProps: function defaultProps() {
return {
size: 'small',
styles: resolvedStyles.timestamp,
timestamp: true,
className: chatMessageSlotClassNames.timestamp
};
}
});
var messageContent = (0, _utils.createShorthand)(_ChatMessageContent.ChatMessageContent, content, {
defaultProps: function defaultProps() {
return {
badgePosition: badgePosition,
density: density,
failed: failed,
hasBadge: !!badge,
mine: mine,
unstable_layout: layout
};
},
overrideProps: function overrideProps(predefinedProps) {
return {
variables: (0, _reactBindings.mergeVariablesOverrides)(variables, predefinedProps.variables)
};
}
});
var detailsElement = (0, _utils.createShorthand)(_ChatMessageDetails.ChatMessageDetails, details, {
defaultProps: function defaultProps() {
return {
attached: attached,
density: density,
hasHeaderReactionGroup: hasHeaderReactionGroup,
mine: mine
};
}
});
var readStatusElement = (0, _utils.createShorthand)(_ChatMessageReadStatus.ChatMessageReadStatus, readStatus, {
defaultProps: function defaultProps() {
return {
density: density
};
}
});
var elements = /*#__PURE__*/React.createElement(React.Fragment, null);
if (density === 'compact') {
var headerElement = (0, _utils.createShorthand)(_ChatMessageHeader.ChatMessageHeader, header);
var bodyElement = _Box.Box.create(compactBody || {}, {
defaultProps: function defaultProps() {
return getA11Props('compactBody', {
className: chatMessageSlotClassNames.compactBody,
styles: resolvedStyles.compactBody
});
},
overrideProps: function overrideProps() {
return {
content: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(_Flex.Flex.Item, {
grow: 1
}, /*#__PURE__*/React.createElement("div", null, authorElement, messageContent)), timestampElement, detailsElement, badgeElement)
};
}
});
elements = /*#__PURE__*/React.createElement(React.Fragment, null, actionMenuElement, /*#__PURE__*/React.createElement("div", {
className: chatMessageSlotClassNames.bar
}), headerElement, bodyElement, reactionGroupElement, readStatusElement);
} else if (isRefreshComfyLayout) {
var _headerElement = (0, _utils.createShorthand)(_ChatMessageHeader.ChatMessageHeader, header || {}, {
defaultProps: function defaultProps() {
return {
styles: resolvedStyles.header,
content: /*#__PURE__*/React.createElement(React.Fragment, null, authorElement, headerContent, detailsElement)
};
}
});
var bubbleElement = _Box.Box.create(bubble || {}, {
defaultProps: function defaultProps() {
return getA11Props('bubble', {
className: chatMessageSlotClassNames.bubble,
styles: resolvedStyles.bubble
});
},
overrideProps: function overrideProps() {
return {
ref: actionsMenuTargetRef,
content: /*#__PURE__*/React.createElement(React.Fragment, null, actionMenuElement, messageContent, reactionGroupElement, readStatusElement),
onMouseEnter: function onMouseEnter(e) {
var _popperRef$current3;
(_popperRef$current3 = popperRef.current) == null ? void 0 : _popperRef$current3.updatePosition();
handleMouseEnter(e);
},
onMouseLeave: function onMouseLeave(e) {
handleMouseLeave(e);
}
};
}
});
var bubbleInsetElement = _Box.Box.create(bubbleInset || {}, {
defaultProps: function defaultProps() {
return {
as: 'span',
className: chatMessageSlotClassNames.bubbleInset,
styles: resolvedStyles.bubbleInset
};
},
overrideProps: function overrideProps() {
return {
content: /*#__PURE__*/React.createElement(React.Fragment, null, badgeElement, bubbleInsetContent, timestampElement)
};
}
});
var _bodyElement = _Box.Box.create(body || {}, {
defaultProps: function defaultProps() {
return getA11Props('body', {
className: chatMessageSlotClassNames.body,
styles: resolvedStyles.body
});
},
overrideProps: function overrideProps() {
return {
content: /*#__PURE__*/React.createElement(React.Fragment, null, bubbleElement, bubbleInsetElement)
};
}
});
elements = /*#__PURE__*/React.createElement(React.Fragment, null, _headerElement, _bodyElement);
} else {
var _headerElement2 = (0, _utils.createShorthand)(_ChatMessageHeader.ChatMessageHeader, header || {}, {
overrideProps: function overrideProps() {
return {
content: /*#__PURE__*/React.createElement(React.Fragment, null, authorElement, timestampElement, detailsElement, reactionGroupPosition === 'start' && reactionGroupElement)
};
}
});
elements = /*#__PURE__*/React.createElement(React.Fragment, null, actionMenuElement, /*#__PURE__*/React.createElement("div", {
className: chatMessageSlotClassNames.bar
}), badgePosition === 'start' && badgeElement, _headerElement2, messageContent, reactionGroupPosition === 'end' && reactionGroupElement, badgePosition === 'end' && badgeElement, readStatusElement);
}
var element = /*#__PURE__*/React.createElement(_reactComponentRef.Ref, {
innerRef: !isRefreshComfyLayout && actionsMenuTargetRef
}, getA11Props.unstable_wrapWithFocusZone( /*#__PURE__*/React.createElement(ElementType, getA11Props('root', Object.assign({
className: rootClasses,
onBlur: handleBlur,
onFocus: handleFocus,
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onKeyDown: handleKeyDown,
ref: ref
}, _utils.rtlTextContainer.getAttributes({
forElements: [children]
}), unhandledProps)), childrenPropExists ? children : elements)));
setEnd();
return element;
});
exports.ChatMessage = ChatMessage;
ChatMessage.displayName = 'ChatMessage';
ChatMessage.defaultProps = {
badgePosition: 'end',
positionActionMenu: true,
reactionGroupPosition: 'start'
};
ChatMessage.propTypes = Object.assign({}, _utils.commonPropTypes.createCommon({
content: 'shorthand'
}), {
actionMenu: PropTypes.oneOfType([customPropTypes.itemShorthand, customPropTypes.collectionShorthand]),
attached: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['top', 'bottom'])]),
author: customPropTypes.itemShorthand,
badge: customPropTypes.itemShorthand,
badgePosition: PropTypes.oneOf(['start', 'end']),
compactBody: customPropTypes.itemShorthand,
density: PropTypes.oneOf(['comfy', 'compact']),
details: customPropTypes.itemShorthand,
header: customPropTypes.itemShorthand,
mine: PropTypes.bool,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
onKeyDown: PropTypes.func,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
positionActionMenu: PropTypes.bool,
reactionGroup: PropTypes.oneOfType([customPropTypes.collectionShorthand, customPropTypes.itemShorthand]),
reactionGroupPosition: PropTypes.oneOf(['start', 'end']),
readStatus: customPropTypes.itemShorthand,
timestamp: customPropTypes.itemShorthand,
unstable_overflow: PropTypes.bool,
unstable_layout: PropTypes.oneOf(['default', 'refresh']),
failed: PropTypes.bool,
headerContent: PropTypes.node,
body: customPropTypes.itemShorthand,
bubble: customPropTypes.itemShorthand,
bubbleInset: customPropTypes.itemShorthand,
bubbleInsetContent: PropTypes.node
});
ChatMessage.handledProps = Object.keys(ChatMessage.propTypes);
ChatMessage.create = (0, _utils.createShorthandFactory)({
Component: ChatMessage,
mappedProp: 'content'
});
//# sourceMappingURL=ChatMessage.js.map