@ant-design/x
Version:
Craft AI-driven interfaces effortlessly
196 lines (190 loc) • 8.17 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _pickAttrs = _interopRequireDefault(require("@rc-component/util/lib/pickAttrs"));
var _clsx = require("clsx");
var _react = _interopRequireDefault(require("react"));
var _useXComponentConfig = _interopRequireDefault(require("../_util/hooks/use-x-component-config"));
var _xProvider = require("../x-provider");
var _context = require("./context");
var _EditableContent = require("./EditableContent");
var _loading = _interopRequireDefault(require("./loading"));
var _style = _interopRequireDefault(require("./style"));
var _TypingContent = require("./TypingContent");
const Bubble = ({
prefixCls: customizePrefixCls,
rootClassName,
style,
className,
styles = {},
classNames = {},
placement = 'start',
content,
contentRender,
editable = false,
typing,
streaming = false,
variant = 'filled',
shape = 'default',
header,
footer,
avatar,
extra,
footerPlacement,
loading,
loadingRender,
onTyping,
onTypingComplete,
onEditConfirm,
onEditCancel,
...restProps
}, ref) => {
// ======================== Ref ==========================
const rootDiv = _react.default.useRef(null);
_react.default.useImperativeHandle(ref, () => ({
nativeElement: rootDiv.current
}));
// ===================== Component Config =========================
const contextConfig = (0, _useXComponentConfig.default)('bubble');
// ============================ Prefix ============================
const {
direction,
getPrefixCls
} = (0, _xProvider.useXProviderContext)();
const prefixCls = getPrefixCls('bubble', customizePrefixCls);
// ============================= Bubble context ==============================
const context = _react.default.useContext(_context.BubbleContext);
// ============================ Styles ============================
const [hashId, cssVarCls] = (0, _style.default)(prefixCls);
const rootMergedStyle = {
...contextConfig.style,
...contextConfig.styles.root,
...styles.root,
...style
};
const rootMergedCls = (0, _clsx.clsx)(prefixCls, contextConfig.className, contextConfig.classNames.root, classNames.root, rootClassName, className, hashId, cssVarCls, `${prefixCls}-${placement}`, {
[`${prefixCls}-${context.status}`]: context.status,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-loading`]: loading
});
const domProps = (0, _pickAttrs.default)(restProps, {
attr: true,
aria: true,
data: true
});
const info = {
key: context?.key,
status: context?.status,
extraInfo: context?.extraInfo
};
// ============================= process content ==============================
const memoedContent = _react.default.useMemo(() => contentRender ? contentRender(content, info) : content, [content, contentRender, info.key, info.status, info.extraInfo]);
const mergeTyping = typeof typing === 'function' ? typing(content, info) : typing;
const usingInnerAnimation = !!mergeTyping && typeof memoedContent === 'string';
/**
* 1、启用内置动画的情况下,由 TypingContent 来负责通知。
* 2、不启用内置动画的情况下,也应当有一个回调来反映 content 的变化。
* 没有动画,则 content 的变化、渲染是全量的,等同于动画是瞬时完成的,合该用 onTypingComplete 来通知变化。
* 3、流式输入 content 的场景下,应当在流式结束时(streaming === false)才执行 onTypingComplete,
* 保证一次流式传输归属于一个动画周期。
**/
_react.default.useEffect(() => {
if (usingInnerAnimation) return;
if (streaming) return;
content && onTypingComplete?.(content);
}, [memoedContent, usingInnerAnimation, streaming]);
// ============================= render ==============================
const _footerPlacement = footerPlacement || (placement === 'start' ? 'outer-start' : 'outer-end');
const isEditing = typeof editable === 'boolean' ? editable : editable.editing;
const renderContent = () => {
if (loading) return loadingRender ? loadingRender() : /*#__PURE__*/_react.default.createElement(_loading.default, {
prefixCls: prefixCls
});
const _content = /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, usingInnerAnimation ? /*#__PURE__*/_react.default.createElement(_TypingContent.TypingContent, {
prefixCls: prefixCls,
streaming: streaming,
typing: mergeTyping,
content: memoedContent,
onTyping: onTyping,
onTypingComplete: onTypingComplete
}) : memoedContent);
const isFooterIn = _footerPlacement.includes('inner');
return /*#__PURE__*/_react.default.createElement("div", {
className: getSlotClassName('body'),
style: getSlotStyle('body')
}, renderHeader(), /*#__PURE__*/_react.default.createElement("div", {
style: {
...contextConfig.styles.content,
...styles.content
},
className: (0, _clsx.clsx)(`${prefixCls}-content`, `${prefixCls}-content-${variant}`, contextConfig.classNames.content, classNames.content, {
[`${prefixCls}-content-${context?.status}`]: context?.status,
[`${prefixCls}-content-${shape}`]: variant !== 'borderless',
[`${prefixCls}-content-editing`]: isEditing,
[`${prefixCls}-content-string`]: typeof memoedContent === 'string'
})
}, isEditing ? /*#__PURE__*/_react.default.createElement(_EditableContent.EditableContent, {
prefixCls: prefixCls,
content: content,
okText: editable?.okText,
cancelText: editable?.cancelText,
onEditConfirm: onEditConfirm,
onEditCancel: onEditCancel
}) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, isFooterIn ? /*#__PURE__*/_react.default.createElement("div", {
className: (0, _clsx.clsx)(`${prefixCls}-content-with-footer`)
}, _content) : _content, isFooterIn && renderFooter())), !isEditing && !isFooterIn && renderFooter());
};
const getSlotClassName = slotType => (0, _clsx.clsx)(`${prefixCls}-${slotType}`, contextConfig.classNames[slotType], classNames[slotType]);
const getSlotStyle = slotType => ({
...contextConfig.styles[slotType],
...styles[slotType]
});
const renderSlot = slot => typeof slot === 'function' ? slot(content, info) : slot;
const renderAvatar = () => {
if (!avatar) return null;
return /*#__PURE__*/_react.default.createElement("div", {
className: getSlotClassName('avatar'),
style: getSlotStyle('avatar')
}, renderSlot(avatar));
};
const renderExtra = () => {
if (!extra) return null;
return /*#__PURE__*/_react.default.createElement("div", {
className: getSlotClassName('extra'),
style: getSlotStyle('extra')
}, renderSlot(extra));
};
const renderHeader = () => {
if (!header) return null;
return /*#__PURE__*/_react.default.createElement("div", {
className: getSlotClassName('header'),
style: getSlotStyle('header')
}, renderSlot(header));
};
const renderFooter = () => {
if (!footer) return null;
const cls = (0, _clsx.clsx)(getSlotClassName('footer'), {
[`${prefixCls}-footer-start`]: _footerPlacement.includes('start'),
[`${prefixCls}-footer-end`]: _footerPlacement.includes('end')
});
return /*#__PURE__*/_react.default.createElement("div", {
className: cls,
style: getSlotStyle('footer')
}, renderSlot(footer));
};
return /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({
className: rootMergedCls,
style: rootMergedStyle
}, restProps, domProps, {
ref: rootDiv
}), renderAvatar(), renderContent(), !isEditing && !loading && renderExtra());
};
const ForwardBubble = /*#__PURE__*/_react.default.forwardRef(Bubble);
if (process.env.NODE_ENV !== 'production') {
ForwardBubble.displayName = 'Bubble';
}
var _default = exports.default = ForwardBubble;