stream-chat-react
Version:
React components to create chat conversations or livestream style chat
217 lines (216 loc) • 7.96 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const require_useNotificationApi = require("./useNotificationApi.9ffe5761.js");
let react = require("react");
react = require_useNotificationApi.__toESM(react);
let react_jsx_runtime = require("react/jsx-runtime");
let stream_chat = require("stream-chat");
let lodash_mergewith = require("lodash.mergewith");
lodash_mergewith = require_useNotificationApi.__toESM(lodash_mergewith);
let _emoji_mart_react = require("@emoji-mart/react");
_emoji_mart_react = require_useNotificationApi.__toESM(_emoji_mart_react);
//#region src/plugins/Emojis/EmojiPicker.tsx
var Picker = _emoji_mart_react.default.default ?? _emoji_mart_react.default;
var isShadowRoot = (node) => !!node.host;
var defaultButtonClassName = "str-chat__emoji-picker-button";
var classNames = {
pickerContainerClassName: "str-chat__message-textarea-emoji-picker-container",
wrapperClassName: "str-chat__message-textarea-emoji-picker"
};
var EmojiPicker = (props) => {
const { t } = require_useNotificationApi.useTranslationContext("EmojiPicker");
const { textareaRef } = require_useNotificationApi.useMessageComposerContext("EmojiPicker");
const { textComposer } = require_useNotificationApi.useMessageComposerController();
const isCooldownActive = require_useNotificationApi.useIsCooldownActive();
const [displayPicker, setDisplayPicker] = (0, react.useState)(false);
const [referenceElement, setReferenceElement] = (0, react.useState)(null);
const [popperElement, setPopperElement] = (0, react.useState)(null);
const { refs, strategy, x, y } = require_useNotificationApi.usePopoverPosition({
offset: 8,
placement: props.placement ?? "top-end"
});
(0, react.useEffect)(() => {
refs.setReference(referenceElement);
}, [referenceElement, refs]);
(0, react.useEffect)(() => {
refs.setFloating(popperElement);
}, [popperElement, refs]);
const { pickerContainerClassName, wrapperClassName } = classNames;
const { ButtonIconComponent = require_useNotificationApi.IconEmoji } = props;
const pickerStyle = props.pickerProps?.style;
(0, react.useEffect)(() => {
if (!popperElement || !referenceElement) return;
const handlePointerDown = (e) => {
const target = e.target;
const rootNode = target.getRootNode();
if (popperElement.contains(isShadowRoot(rootNode) ? rootNode.host : target) || referenceElement.contains(target)) return;
setDisplayPicker(false);
};
window.addEventListener("pointerdown", handlePointerDown);
return () => window.removeEventListener("pointerdown", handlePointerDown);
}, [referenceElement, popperElement]);
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
className: props.wrapperClassName ?? wrapperClassName,
children: [displayPicker && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: props.pickerContainerClassName ?? pickerContainerClassName,
ref: setPopperElement,
style: {
left: x ?? 0,
position: strategy,
top: y ?? 0
},
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Picker, {
data: async () => (await import("@emoji-mart/data")).default,
onEmojiSelect: (e) => {
const textarea = textareaRef.current;
if (!textarea) return;
textComposer.insertText({ text: e.native });
textarea.focus();
if (props.closeOnEmojiSelect) setDisplayPicker(false);
},
...props.pickerProps,
style: {
...pickerStyle,
"--shadow": "none"
}
})
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.Button, {
appearance: "ghost",
"aria-expanded": displayPicker,
"aria-label": t("aria/Emoji picker"),
circular: true,
className: props.buttonClassName ?? defaultButtonClassName,
disabled: isCooldownActive,
onClick: () => setDisplayPicker((cv) => !cv),
ref: setReferenceElement,
size: "sm",
type: "button",
variant: "secondary",
children: ButtonIconComponent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ButtonIconComponent, {})
})]
});
};
//#endregion
//#region src/plugins/Emojis/middleware/textComposerEmojiMiddleware.ts
var EmojiSearchSource = class extends stream_chat.BaseSearchSource {
constructor(emojiSearchIndex, options) {
super(options);
this.type = "emoji";
this.emojiSearchIndex = emojiSearchIndex;
}
async query(searchQuery) {
if (searchQuery.length === 0) return {
items: [],
next: null
};
return {
items: (await this.emojiSearchIndex.search(searchQuery) ?? []).filter(Boolean).slice(0, 7).map(({ emoticons = [], id, name, native, skins = [] }) => {
const [firstSkin] = skins;
return {
emoticons,
id,
name,
native: native ?? firstSkin.native
};
}),
next: null
};
}
filterQueryResults(items) {
return items.map((item) => ({
...item,
...(0, stream_chat.getTokenizedSuggestionDisplayName)({
displayName: item.id,
searchToken: this.searchQuery
})
}));
}
};
var DEFAULT_OPTIONS = {
minChars: 1,
trigger: ":"
};
/**
* TextComposer middleware for mentions
* Usage:
*
* const textComposer = new TextComposer(options);
*
* textComposer.use(new createTextComposerEmojiMiddleware(emojiSearchIndex, {
* minChars: 2
* }));
*
* @param emojiSearchIndex
* @param {{
* minChars: number;
* trigger: string;
* }} options
* @returns
*/
var createTextComposerEmojiMiddleware = (emojiSearchIndex, options) => {
const finalOptions = (0, lodash_mergewith.default)(DEFAULT_OPTIONS, options ?? {});
const emojiSearchSource = new EmojiSearchSource(emojiSearchIndex);
emojiSearchSource.activate();
return {
id: "stream-io/emoji-middleware",
handlers: {
onChange: async ({ complete, forward, next, state }) => {
if (!state.selection) return forward();
const triggerWithToken = (0, stream_chat.getTriggerCharWithToken)({
acceptTrailingSpaces: false,
text: state.text.slice(0, state.selection.end),
trigger: finalOptions.trigger
});
if (!triggerWithToken || triggerWithToken.length < finalOptions.minChars) {
const hasSuggestionsForTrigger = state.suggestions?.trigger === finalOptions.trigger;
const newState = { ...state };
if (hasSuggestionsForTrigger && newState.suggestions) delete newState.suggestions;
return next(newState);
}
if (triggerWithToken && triggerWithToken === finalOptions.trigger) emojiSearchSource.resetStateAndActivate();
const textWithReplacedWord = await (0, stream_chat.replaceWordWithEntity)({
caretPosition: state.selection.end,
getEntityString: async (word) => {
const { items } = await emojiSearchSource.query(word);
const emoji = items.filter(Boolean).slice(0, 10).find(({ emoticons }) => !!emoticons?.includes(word));
if (!emoji) return null;
const [firstSkin] = emoji.skins ?? [];
return emoji.native ?? firstSkin.native;
},
text: state.text
});
if (textWithReplacedWord !== state.text) return complete({
...state,
suggestions: void 0,
text: textWithReplacedWord
});
return complete({
...state,
suggestions: {
query: triggerWithToken.slice(1),
searchSource: emojiSearchSource,
trigger: finalOptions.trigger
}
});
},
onSuggestionItemSelect: ({ complete, forward, state }) => {
const { selectedSuggestion } = state.change ?? {};
if (!selectedSuggestion || state.suggestions?.trigger !== finalOptions.trigger) return forward();
emojiSearchSource.resetStateAndActivate();
return complete({
...state,
...(0, stream_chat.insertItemWithTrigger)({
insertText: `${"native" in selectedSuggestion ? selectedSuggestion.native : ""} `,
selection: state.selection,
text: state.text,
trigger: finalOptions.trigger
}),
suggestions: void 0
});
}
}
};
};
//#endregion
exports.EmojiPicker = EmojiPicker;
exports.createTextComposerEmojiMiddleware = createTextComposerEmojiMiddleware;
//# sourceMappingURL=emojis.js.map