@gravity-ui/aikit 2.25.0 → 2.25.1-beta.c31bfaac02b26694749dd7a5d2532192c8e5fdc2.0
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/build/cjs/components/templates/History/ChatItem.d.ts +4 -1
- package/build/cjs/components/templates/History/ChatItem.js +10 -3
- package/build/cjs/components/templates/History/ChatItem.js.map +1 -1
- package/build/cjs/components/templates/History/HistoryList.d.ts +6 -0
- package/build/cjs/components/templates/History/HistoryList.js +30 -5
- package/build/cjs/components/templates/History/HistoryList.js.map +1 -1
- package/build/cjs/components/templates/History/i18n/en.json +4 -1
- package/build/cjs/components/templates/History/i18n/index.d.ts +4 -4
- package/build/cjs/components/templates/History/i18n/ru.json +4 -1
- package/build/cjs/hooks/index.d.ts +1 -0
- package/build/cjs/hooks/index.js +1 -0
- package/build/cjs/hooks/index.js.map +1 -1
- package/build/cjs/hooks/usePinnedChatsStore.d.ts +9 -0
- package/build/cjs/hooks/usePinnedChatsStore.js +13 -0
- package/build/cjs/hooks/usePinnedChatsStore.js.map +1 -0
- package/build/cjs/package.json +1 -1
- package/build/cjs/utils/index.d.ts +1 -0
- package/build/cjs/utils/index.js +1 -0
- package/build/cjs/utils/index.js.map +1 -1
- package/build/cjs/utils/pinnedChatsStore.d.ts +24 -0
- package/build/cjs/utils/pinnedChatsStore.js +85 -0
- package/build/cjs/utils/pinnedChatsStore.js.map +1 -0
- package/build/docs/components/templates/History.md +108 -29
- package/build/esm/components/templates/History/ChatItem.d.ts +4 -1
- package/build/esm/components/templates/History/ChatItem.js +11 -4
- package/build/esm/components/templates/History/ChatItem.js.map +1 -1
- package/build/esm/components/templates/History/HistoryList.d.ts +6 -0
- package/build/esm/components/templates/History/HistoryList.js +30 -5
- package/build/esm/components/templates/History/HistoryList.js.map +1 -1
- package/build/esm/components/templates/History/i18n/en.json +4 -1
- package/build/esm/components/templates/History/i18n/index.d.ts +4 -4
- package/build/esm/components/templates/History/i18n/ru.json +4 -1
- package/build/esm/hooks/index.d.ts +1 -0
- package/build/esm/hooks/index.js +1 -0
- package/build/esm/hooks/index.js.map +1 -1
- package/build/esm/hooks/usePinnedChatsStore.d.ts +9 -0
- package/build/esm/hooks/usePinnedChatsStore.js +10 -0
- package/build/esm/hooks/usePinnedChatsStore.js.map +1 -0
- package/build/esm/package.json +1 -1
- package/build/esm/utils/index.d.ts +1 -0
- package/build/esm/utils/index.js +1 -0
- package/build/esm/utils/index.js.map +1 -1
- package/build/esm/utils/pinnedChatsStore.d.ts +24 -0
- package/build/esm/utils/pinnedChatsStore.js +81 -0
- package/build/esm/utils/pinnedChatsStore.js.map +1 -0
- package/package.json +1 -1
|
@@ -7,6 +7,9 @@ import { ChatType } from "../../../types/index.js";
|
|
|
7
7
|
export interface ChatItemProps {
|
|
8
8
|
chat: ChatType;
|
|
9
9
|
showActions: boolean;
|
|
10
|
+
pinned?: boolean;
|
|
11
|
+
onPinnedChange?: (chatId: string, pinned: boolean) => void;
|
|
12
|
+
pinningDisabled?: boolean;
|
|
10
13
|
isActive?: boolean;
|
|
11
14
|
onDeleteClick?: (e: React.MouseEvent, chat: ChatType) => Promise<void>;
|
|
12
15
|
/** Size of the row actions (default: `s`, `xl` in mobile mode) */
|
|
@@ -18,4 +21,4 @@ export interface ChatItemProps {
|
|
|
18
21
|
*
|
|
19
22
|
* @returns React element
|
|
20
23
|
*/
|
|
21
|
-
export declare function ChatItem({ chat, showActions, isActive, onDeleteClick, size }: ChatItemProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare function ChatItem({ chat, showActions, isActive, onDeleteClick, size, pinned, onPinnedChange, pinningDisabled, }: ChatItemProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -16,7 +16,7 @@ const b = (0, cn_1.block)('history');
|
|
|
16
16
|
*
|
|
17
17
|
* @returns React element
|
|
18
18
|
*/
|
|
19
|
-
function ChatItem({ chat, showActions, isActive, onDeleteClick, size }) {
|
|
19
|
+
function ChatItem({ chat, showActions, isActive, onDeleteClick, size, pinned, onPinnedChange, pinningDisabled, }) {
|
|
20
20
|
const [isDeleteProccesing, setIsDeleteProcessing] = (0, react_1.useState)(false);
|
|
21
21
|
const [hasOverflow, setHasOverflow] = (0, react_1.useState)(false);
|
|
22
22
|
const labelRef = (0, react_1.useRef)(null);
|
|
@@ -32,7 +32,7 @@ function ChatItem({ chat, showActions, isActive, onDeleteClick, size }) {
|
|
|
32
32
|
(0, react_1.useEffect)(() => {
|
|
33
33
|
const label = labelRef.current;
|
|
34
34
|
setHasOverflow(Boolean(label && label.scrollWidth > label.clientWidth));
|
|
35
|
-
}, [chatLabel]);
|
|
35
|
+
}, [chatLabel, pinned, showActions, onPinnedChange]);
|
|
36
36
|
const handleClick = (e) => {
|
|
37
37
|
if (isDeleteProccesing) {
|
|
38
38
|
e.stopPropagation();
|
|
@@ -53,6 +53,13 @@ function ChatItem({ chat, showActions, isActive, onDeleteClick, size }) {
|
|
|
53
53
|
active: isActive,
|
|
54
54
|
mobile: isMobile,
|
|
55
55
|
['is-delete-processing']: isDeleteProccesing,
|
|
56
|
-
}), onClick: handleClick, children: [hasOverflow ? ((0, jsx_runtime_1.jsx)(uikit_1.Tooltip, { content: chatLabel, openDelay: 300, children: label })) : (label), showDeleteAction ? ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { view: "flat", size: deleteButtonSize, color: "secondary", loading: isDeleteProccesing, className: b('delete-button'), onClick: handleDeleteChat, tooltipTitle: (0, i18n_1.i18n)('tooltip-delete'), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { className: b('icon-button'), data: icons_1.TrashBin, size: deleteIconSize }) })) : null
|
|
56
|
+
}), onClick: handleClick, children: [pinned && ((0, jsx_runtime_1.jsx)("span", { role: "img", "aria-label": (0, i18n_1.i18n)('pinned'), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Pin, size: 16 }) })), hasOverflow ? ((0, jsx_runtime_1.jsx)(uikit_1.Tooltip, { content: chatLabel, openDelay: 300, children: label })) : (label), showDeleteAction ? ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { view: "flat", size: deleteButtonSize, color: "secondary", loading: isDeleteProccesing, className: b('delete-button'), onClick: handleDeleteChat, tooltipTitle: (0, i18n_1.i18n)('tooltip-delete'), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { className: b('icon-button'), data: icons_1.TrashBin, size: deleteIconSize }) })) : null, showActions && onPinnedChange && ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { view: "flat", size: deleteButtonSize, disabled: pinningDisabled || isDeleteProccesing, "aria-label": (0, i18n_1.i18n)(pinned ? 'action-unpin' : 'action-pin'), tooltipTitle: (0, i18n_1.i18n)(pinned ? 'action-unpin' : 'action-pin'), onKeyDown: (event) => {
|
|
57
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
|
58
|
+
event.stopPropagation();
|
|
59
|
+
}
|
|
60
|
+
}, onClick: (event) => {
|
|
61
|
+
event.stopPropagation();
|
|
62
|
+
onPinnedChange(chat.id, !pinned);
|
|
63
|
+
}, children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: pinned ? icons_1.PinSlash : icons_1.Pin, size: deleteIconSize }) }))] }));
|
|
57
64
|
}
|
|
58
65
|
//# sourceMappingURL=ChatItem.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatItem.js","sourceRoot":"../../../../../src","sources":["components/templates/History/ChatItem.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"ChatItem.js","sourceRoot":"../../../../../src","sources":["components/templates/History/ChatItem.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useEffect, useRef, useState} from 'react';\n\nimport {Pin, PinSlash, TrashBin} from '@gravity-ui/icons';\nimport {Icon, InputControlSize, Text, Tooltip, useMobile} from '@gravity-ui/uikit';\n\nimport {getControlIconSize, resolveMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {ChatType} from '../../../types';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\n\nimport {i18n} from './i18n';\n\nconst b = block('history');\n\n/**\n * Props for ChatItem component\n */\nexport interface ChatItemProps {\n chat: ChatType;\n showActions: boolean;\n pinned?: boolean;\n onPinnedChange?: (chatId: string, pinned: boolean) => void;\n pinningDisabled?: boolean;\n isActive?: boolean;\n onDeleteClick?: (e: React.MouseEvent, chat: ChatType) => Promise<void>;\n /** Size of the row actions (default: `s`, `xl` in mobile mode) */\n size?: InputControlSize;\n}\n\n/**\n * Chat item component with hover state for actions.\n * In mobile mode there is no hover, so actions are always visible.\n *\n * @returns React element\n */\nexport function ChatItem({\n chat,\n showActions,\n isActive,\n onDeleteClick,\n size,\n pinned,\n onPinnedChange,\n pinningDisabled,\n}: ChatItemProps) {\n const [isDeleteProccesing, setIsDeleteProcessing] = useState(false);\n const [hasOverflow, setHasOverflow] = useState(false);\n const labelRef = useRef<HTMLDivElement>(null);\n const chatLabel = chat.lastMessage || chat.name;\n const isMobile = useMobile();\n const deleteButtonSize = resolveMobileControlSize({\n size,\n desktopDefault: 's',\n mobileDefault: 'xl',\n isMobile,\n });\n const deleteIconSize = getControlIconSize(deleteButtonSize);\n\n useEffect(() => {\n const label = labelRef.current;\n setHasOverflow(Boolean(label && label.scrollWidth > label.clientWidth));\n }, [chatLabel, pinned, showActions, onPinnedChange]);\n\n const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {\n if (isDeleteProccesing) {\n e.stopPropagation();\n }\n };\n\n const handleDeleteChat = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {\n try {\n setIsDeleteProcessing(true);\n await onDeleteClick?.(e, chat);\n } finally {\n setIsDeleteProcessing(false);\n }\n };\n\n const showDeleteAction = isDeleteProccesing || (showActions && onDeleteClick);\n\n const label = (\n <div className={b('chat-content')}>\n <Text variant=\"body-1\" ref={labelRef}>\n {chatLabel}\n </Text>\n </div>\n );\n\n return (\n <div\n className={b('chat-item', {\n active: isActive,\n mobile: isMobile,\n ['is-delete-processing']: isDeleteProccesing,\n })}\n onClick={handleClick}\n >\n {pinned && (\n <span role=\"img\" aria-label={i18n('pinned')}>\n <Icon data={Pin} size={16} />\n </span>\n )}\n {hasOverflow ? (\n <Tooltip content={chatLabel} openDelay={300}>\n {label}\n </Tooltip>\n ) : (\n label\n )}\n\n {showDeleteAction ? (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n color=\"secondary\"\n loading={isDeleteProccesing}\n className={b('delete-button')}\n onClick={handleDeleteChat}\n tooltipTitle={i18n('tooltip-delete')}\n >\n <Icon className={b('icon-button')} data={TrashBin} size={deleteIconSize} />\n </ActionButton>\n ) : null}\n {showActions && onPinnedChange && (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n disabled={pinningDisabled || isDeleteProccesing}\n aria-label={i18n(pinned ? 'action-unpin' : 'action-pin')}\n tooltipTitle={i18n(pinned ? 'action-unpin' : 'action-pin')}\n onKeyDown={(event) => {\n if (event.key === ' ' || event.key === 'Enter') {\n event.stopPropagation();\n }\n }}\n onClick={(event) => {\n event.stopPropagation();\n onPinnedChange(chat.id, !pinned);\n }}\n >\n <Icon data={pinned ? PinSlash : Pin} size={deleteIconSize} />\n </ActionButton>\n )}\n </div>\n );\n}\n"]}
|
|
@@ -19,6 +19,12 @@ export type HistoryLoadMode = 'full' | 'lazy';
|
|
|
19
19
|
export interface HistoryListProps extends QAProps, DOMProps {
|
|
20
20
|
/** Array of chat items */
|
|
21
21
|
chats: ChatType[];
|
|
22
|
+
/** Controlled pinned identifiers. Omit to disable pinning. */
|
|
23
|
+
pinnedChatIds?: readonly string[];
|
|
24
|
+
/** Requests a pin state change; the application updates pinnedChatIds. */
|
|
25
|
+
onPinnedChange?: (chatId: string, pinned: boolean) => void;
|
|
26
|
+
/** Disable pin actions while persistence is pending. */
|
|
27
|
+
pinningDisabled?: boolean;
|
|
22
28
|
/** Currently selected chat */
|
|
23
29
|
selectedChat?: ChatType | null;
|
|
24
30
|
/** Callback when a chat is selected */
|
|
@@ -22,7 +22,7 @@ const b = (0, cn_1.block)('history');
|
|
|
22
22
|
* @returns Rendered history list (search, grouping, lazy/full load, chat rows).
|
|
23
23
|
*/
|
|
24
24
|
function HistoryList(props) {
|
|
25
|
-
const { chats, selectedChat, onSelectChat, onDeleteChat, onLoadMore, hasMore = false, loadMode = 'full', searchable = true, groupBy = 'date', dateFormat, dateLocale, dateLocaleConfig, showActions = true, emptyPlaceholder, emptyFilteredPlaceholder, searchPlaceholder, className, qa, style, filterFunction = chatUtils_1.defaultChatFilter, loading = false, onChatClick, size: sizeProp, } = props;
|
|
25
|
+
const { chats, selectedChat, pinnedChatIds, onPinnedChange, pinningDisabled, onSelectChat, onDeleteChat, onLoadMore, hasMore = false, loadMode = 'full', searchable = true, groupBy = 'date', dateFormat, dateLocale, dateLocaleConfig, showActions = true, emptyPlaceholder, emptyFilteredPlaceholder, searchPlaceholder, className, qa, style, filterFunction = chatUtils_1.defaultChatFilter, loading = false, onChatClick, size: sizeProp, } = props;
|
|
26
26
|
const size = (0, useMobileControlSize_1.useMobileControlSize)(sizeProp, 'm', 'xl');
|
|
27
27
|
const [filteredItemCount, setFilteredItemCount] = (0, react_1.useState)(null);
|
|
28
28
|
const [loadedChats, setLoadedChats] = (0, react_1.useState)([]);
|
|
@@ -54,6 +54,30 @@ function HistoryList(props) {
|
|
|
54
54
|
}, [displayChats, groupBy]);
|
|
55
55
|
const listItems = (0, react_1.useMemo)(() => {
|
|
56
56
|
const items = [];
|
|
57
|
+
if (pinnedChatIds !== undefined) {
|
|
58
|
+
const pinned = new Set(pinnedChatIds);
|
|
59
|
+
const ordered = [
|
|
60
|
+
...displayChats.filter((chat) => pinned.has(chat.id)),
|
|
61
|
+
...displayChats.filter((chat) => !pinned.has(chat.id)),
|
|
62
|
+
];
|
|
63
|
+
let previousDate;
|
|
64
|
+
ordered.forEach((chat) => {
|
|
65
|
+
const date = !pinned.has(chat.id) && chat.createTime
|
|
66
|
+
? new Date(chat.createTime).toDateString()
|
|
67
|
+
: undefined;
|
|
68
|
+
if (groupBy === 'date' && date && date !== previousDate) {
|
|
69
|
+
items.push({
|
|
70
|
+
type: 'date-header',
|
|
71
|
+
disabled: true,
|
|
72
|
+
date,
|
|
73
|
+
rowKey: `date:${date}:${chat.id}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
previousDate = date;
|
|
77
|
+
items.push(Object.assign(Object.assign({}, chat), { type: 'chat', rowKey: `chat:${chat.id}` }));
|
|
78
|
+
});
|
|
79
|
+
return items;
|
|
80
|
+
}
|
|
57
81
|
const sortedGroups = Array.from(groupedChats.entries()).sort(([dateA], [dateB]) => {
|
|
58
82
|
if (dateA === 'all')
|
|
59
83
|
return 0;
|
|
@@ -68,14 +92,15 @@ function HistoryList(props) {
|
|
|
68
92
|
type: 'date-header',
|
|
69
93
|
disabled: true,
|
|
70
94
|
date: dateKey,
|
|
95
|
+
rowKey: `date:${dateKey}`,
|
|
71
96
|
});
|
|
72
97
|
}
|
|
73
98
|
groupChats.forEach((chat) => {
|
|
74
|
-
items.push(Object.assign({ type: 'chat' }, chat));
|
|
99
|
+
items.push(Object.assign(Object.assign({ type: 'chat' }, chat), { rowKey: `chat:${chat.id}` }));
|
|
75
100
|
});
|
|
76
101
|
});
|
|
77
102
|
return items;
|
|
78
|
-
}, [groupedChats, groupBy]);
|
|
103
|
+
}, [groupedChats, groupBy, displayChats, pinnedChatIds]);
|
|
79
104
|
const selectedItemIndex = (0, react_1.useMemo)(() => {
|
|
80
105
|
return listItems.findIndex((item) => item.type === 'chat' && item.id === (selectedChat === null || selectedChat === void 0 ? void 0 : selectedChat.id));
|
|
81
106
|
}, [listItems, selectedChat]);
|
|
@@ -144,13 +169,13 @@ function HistoryList(props) {
|
|
|
144
169
|
if (item.type === 'date-header') {
|
|
145
170
|
return ((0, jsx_runtime_1.jsx)(DateHeaderItem_1.DateHeaderItem, { date: item.date, format: dateFormat, locale: dateLocale, localeConfig: dateLocaleConfig }, `date-${item.date}`));
|
|
146
171
|
}
|
|
147
|
-
return ((0, jsx_runtime_1.jsx)(ChatItem_1.ChatItem, { chat: item, showActions: showActions, onDeleteClick: onDeleteChat ? handleDeleteClick : undefined, isActive: isActive, size: sizeProp }, item.id));
|
|
172
|
+
return ((0, jsx_runtime_1.jsx)(ChatItem_1.ChatItem, { chat: item, showActions: showActions, pinned: pinnedChatIds === null || pinnedChatIds === void 0 ? void 0 : pinnedChatIds.includes(item.id), onPinnedChange: pinnedChatIds === undefined ? undefined : onPinnedChange, pinningDisabled: pinningDisabled, onDeleteClick: onDeleteChat ? handleDeleteClick : undefined, isActive: isActive, size: sizeProp }, item.id));
|
|
148
173
|
};
|
|
149
174
|
const emptyState = emptyPlaceholder || (0, jsx_runtime_1.jsx)("div", { className: b('empty'), children: (0, i18n_1.i18n)('empty-state') });
|
|
150
175
|
const emptyFilteredState = emptyFilteredPlaceholder || ((0, jsx_runtime_1.jsx)("div", { className: b('empty'), children: (0, i18n_1.i18n)('empty-filtered-state') }));
|
|
151
176
|
const finalEmptyPlaceholder = filteredItemCount === null || filteredItemCount === listItems.length
|
|
152
177
|
? emptyState
|
|
153
178
|
: emptyFilteredState;
|
|
154
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: b('container', className), "data-qa": qa, style: style, children: [(0, jsx_runtime_1.jsx)("div", { className: b('list-wrapper'), children: loading ? ((0, jsx_runtime_1.jsx)("div", { className: b('loader-wrapper'), children: (0, jsx_runtime_1.jsx)(Loader_1.Loader, { view: "loading" }) })) : ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)(uikit_1.List, { size: size, items: listItems, renderItem: renderItem, virtualized: false, filterable: searchable, filterItem: wrappedFilterFunction, filterPlaceholder: searchPlaceholder !== null && searchPlaceholder !== void 0 ? searchPlaceholder : (0, i18n_1.i18n)('search-placeholder'), filterClassName: b('filter'), emptyPlaceholder: finalEmptyPlaceholder, selectedItemIndex: selectedItemIndex, itemsClassName: b('list'), itemClassName: b('list-item'), onFilterEnd: (data) => setFilteredItemCount(data.items.length), onItemClick: handleChatClick }), loadMode === 'lazy' && effectiveHasMore && onLoadMore && ((0, jsx_runtime_1.jsx)(IntersectionContainer_1.IntersectionContainer, { onIntersect: handleLoadMore, className: b('lazy-loader'), children: (0, jsx_runtime_1.jsx)(Loader_1.Loader, { view: "loading" }) }, displayChats.length))] })) }), loadMode === 'full' && hasMore && onLoadMore && ((0, jsx_runtime_1.jsx)(uikit_1.Button, { view: "flat-action", size: "m", width: "max", onClick: handleLoadMore, children: (0, i18n_1.i18n)('action-load-more') }))] }));
|
|
179
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: b('container', className), "data-qa": qa, style: style, children: [(0, jsx_runtime_1.jsx)("div", { className: b('list-wrapper'), children: loading ? ((0, jsx_runtime_1.jsx)("div", { className: b('loader-wrapper'), children: (0, jsx_runtime_1.jsx)(Loader_1.Loader, { view: "loading" }) })) : ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)(uikit_1.List, { size: size, items: listItems, itemKey: (item) => item.rowKey, renderItem: renderItem, virtualized: false, filterable: searchable, filterItem: wrappedFilterFunction, filterPlaceholder: searchPlaceholder !== null && searchPlaceholder !== void 0 ? searchPlaceholder : (0, i18n_1.i18n)('search-placeholder'), filterClassName: b('filter'), emptyPlaceholder: finalEmptyPlaceholder, selectedItemIndex: selectedItemIndex, itemsClassName: b('list'), itemClassName: b('list-item'), onFilterEnd: (data) => setFilteredItemCount(data.items.length), onItemClick: handleChatClick }), loadMode === 'lazy' && effectiveHasMore && onLoadMore && ((0, jsx_runtime_1.jsx)(IntersectionContainer_1.IntersectionContainer, { onIntersect: handleLoadMore, className: b('lazy-loader'), children: (0, jsx_runtime_1.jsx)(Loader_1.Loader, { view: "loading" }) }, displayChats.length))] })) }), loadMode === 'full' && hasMore && onLoadMore && ((0, jsx_runtime_1.jsx)(uikit_1.Button, { view: "flat-action", size: "m", width: "max", onClick: handleLoadMore, children: (0, i18n_1.i18n)('action-load-more') }))] }));
|
|
155
180
|
}
|
|
156
181
|
//# sourceMappingURL=HistoryList.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HistoryList.js","sourceRoot":"../../../../../src","sources":["components/templates/History/HistoryList.tsx"],"names":[],"mappings":";;AA0FA,kCA4PC;;;AAtVD,uDAA+E;AAE/E,6CAAkG;AAGlG,iFAAyE;AAEzE,2DAAiG;AACjG,6CAAwC;AACxC,sFAAwE;AACxE,wDAA0C;AAE1C,4CAAoC;AACpC,wDAAgD;AAChD,0CAA4B;AAE5B,yBAAwB;AAExB,MAAM,CAAC,GAAG,IAAA,UAAK,EAAC,SAAS,CAAC,CAAC;AAkE3B;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,KAAuB;IAC/C,MAAM,EACF,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,OAAO,GAAG,KAAK,EACf,QAAQ,GAAG,MAAM,EACjB,UAAU,GAAG,IAAI,EACjB,OAAO,GAAG,MAAM,EAChB,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,WAAW,GAAG,IAAI,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,iBAAiB,EACjB,SAAS,EACT,EAAE,EACF,KAAK,EACL,cAAc,GAAG,6BAAiB,EAClC,OAAO,GAAG,KAAK,EACf,WAAW,EACX,IAAI,EAAE,QAAQ,GACjB,GAAG,KAAK,CAAC;IAEV,MAAM,IAAI,GAAG,IAAA,2CAAoB,EAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAEvD,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,IAAA,gBAAQ,EAAgB,IAAI,CAAC,CAAC;IAChF,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,IAAA,gBAAQ,EAAa,EAAE,CAAC,CAAC;IAC/D,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,IAAA,gBAAQ,EAAC,OAAO,CAAC,CAAC;IACxD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,IAAA,gBAAQ,EAAc,IAAI,GAAG,EAAE,CAAC,CAAC;IAEzE,MAAM,cAAc,GAAG,IAAA,cAAM,EAAC,KAAK,CAAC,CAAC;IAErC,MAAM,YAAY,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QAC9B,MAAM,UAAU,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAE5E,MAAM,aAAa,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAE9E,OAAO,aAAa,CAAC;IACzB,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IAEjD,MAAM,qBAAqB,GAAG,IAAA,cAAM,EAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAC1D,qBAAqB,CAAC,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC;IAEpD,IAAA,iBAAS,EAAC,GAAG,EAAE;QACX,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YACtB,cAAc,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAExB,IAAA,iBAAS,EAAC,GAAG,EAAE;QACX,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YACtB,cAAc,CAAC,EAAE,CAAC,CAAC;QACvB,CAAC;IACL,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEtB,MAAM,YAAY,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QAC9B,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACrB,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,IAAA,4BAAgB,EAAC,YAAY,CAAC,CAAC;IAC1C,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;IAE5B,MAAM,SAAS,GAAqC,IAAA,eAAO,EAAC,GAAG,EAAE;QAC7D,MAAM,KAAK,GAAqC,EAAE,CAAC;QAEnD,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE;YAC9E,IAAI,KAAK,KAAK,KAAK;gBAAE,OAAO,CAAC,CAAC;YAC9B,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,EAAE;YAC3C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,OAAO;YACX,CAAC;YAED,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;gBAC1C,KAAK,CAAC,IAAI,CAAC;oBACP,IAAI,EAAE,aAAa;oBACnB,QAAQ,EAAE,IAAI;oBACd,IAAI,EAAE,OAAO;iBAChB,CAAC,CAAC;YACP,CAAC;YAED,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;gBACxB,KAAK,CAAC,IAAI,iBACN,IAAI,EAAE,MAAM,IACT,IAAI,EACT,CAAC;YACP,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;QAEH,OAAO,KAAK,CAAC;IACjB,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;IAE5B,MAAM,iBAAiB,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QACnC,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,MAAK,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,EAAE,CAAA,CAAC,CAAC;IAC/F,CAAC,EAAE,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;IAE9B,MAAM,gBAAgB,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC;IAErE;;;;OAIG;IACH,MAAM,cAAc,GAAG,IAAA,mBAAW,EAAC,KAAK,IAAI,EAAE;QAC1C,IAAI,CAAC,UAAU,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;YACxC,OAAO;QACX,CAAC;QAED,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YACrB,UAAyB,EAAE,CAAC;YAC7B,OAAO;QACX,CAAC;QAED,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAAC;YAC7C,MAAM,MAAM,GAAG,MAAO,UAA6B,CAAC,MAAM,CAAC,CAAC;YAC5D,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAC/D,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;YACnF,cAAc,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;YACjD,cAAc,CAAC,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;QACnE,CAAC;gBAAS,CAAC;YACP,cAAc,CAAC,OAAO,GAAG,KAAK,CAAC;QACnC,CAAC;IACL,CAAC,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE3B,MAAM,eAAe,GAAG,CACpB,IAAoC,EACpC,CAAS,EACT,YAAsB,EACtB,KAAwE,EAC1E,EAAE;;QACA,IAAI,YAAY,IAAI,CAAA,MAAC,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,MAA4B,0CAAE,IAAI,MAAK,QAAQ,EAAE,CAAC;YAC1E,OAAO;QACX,CAAC;QACD,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,IAAgB,CAAC,CAAC;QACjC,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAG,IAAgB,CAAC,CAAC;IACpC,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,KAAK,EAAE,CAAmB,EAAE,IAAc,EAAE,EAAE;QACpE,CAAC,CAAC,eAAe,EAAE,CAAC;QACpB,IAAI,CAAC;YACD,MAAM,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,IAAI,CAAC,CAAA,CAAC;YAC3B,eAAe,CAAC,CAAC,gBAAgB,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,gBAAgB,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;QAC/D,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,qBAAqB,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QACvC,OAAO,CAAC,MAAc,EAAE,EAAE;YACtB,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YAC1C,OAAO,CAAC,IAAoC,EAAW,EAAE;gBACrD,IAAI,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;oBACxC,OAAO,KAAK,CAAC;gBACjB,CAAC;gBACD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC,CAAC;QACN,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IAErB,MAAM,UAAU,GAAG,CAAC,IAAoC,EAAE,QAAiB,EAAE,EAAE;QAC3E,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAC9B,OAAO,CACH,uBAAC,+BAAc,IAEX,IAAI,EAAE,IAAI,CAAC,IAAI,EACf,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,gBAAgB,IAJzB,QAAQ,IAAI,CAAC,IAAI,EAAE,CAK1B,CACL,CAAC;QACN,CAAC;QAED,OAAO,CACH,uBAAC,mBAAQ,IAEL,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,EAC3D,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,QAAQ,IALT,IAAI,CAAC,EAAE,CAMd,CACL,CAAC;IACN,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,gBAAgB,IAAI,gCAAK,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,YAAG,IAAA,WAAI,EAAC,aAAa,CAAC,GAAO,CAAC;IAE/F,MAAM,kBAAkB,GAAG,wBAAwB,IAAI,CACnD,gCAAK,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,YAAG,IAAA,WAAI,EAAC,sBAAsB,CAAC,GAAO,CACnE,CAAC;IAEF,MAAM,qBAAqB,GACvB,iBAAiB,KAAK,IAAI,IAAI,iBAAiB,KAAK,SAAS,CAAC,MAAM;QAChE,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,kBAAkB,CAAC;IAE7B,OAAO,CACH,iCAAK,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,SAAS,CAAC,aAAW,EAAE,EAAE,KAAK,EAAE,KAAK,aAChE,gCAAK,SAAS,EAAE,CAAC,CAAC,cAAc,CAAC,YAC5B,OAAO,CAAC,CAAC,CAAC,CACP,gCAAK,SAAS,EAAE,CAAC,CAAC,gBAAgB,CAAC,YAC/B,uBAAC,eAAM,IAAC,IAAI,EAAC,SAAS,GAAG,GACvB,CACT,CAAC,CAAC,CAAC,CACA,wBAAC,eAAK,CAAC,QAAQ,eACX,uBAAC,YAAI,IACD,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,EAChB,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,KAAK,EAClB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,qBAAqB,EACjC,iBAAiB,EAAE,iBAAiB,aAAjB,iBAAiB,cAAjB,iBAAiB,GAAI,IAAA,WAAI,EAAC,oBAAoB,CAAC,EAClE,eAAe,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC5B,gBAAgB,EAAE,qBAAqB,EACvC,iBAAiB,EAAE,iBAAiB,EACpC,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,EACzB,aAAa,EAAE,CAAC,CAAC,WAAW,CAAC,EAC7B,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAC9D,WAAW,EAAE,eAAe,GAC9B,EACD,QAAQ,KAAK,MAAM,IAAI,gBAAgB,IAAI,UAAU,IAAI,CACtD,uBAAC,6CAAqB,IAElB,WAAW,EAAE,cAAc,EAC3B,SAAS,EAAE,CAAC,CAAC,aAAa,CAAC,YAE3B,uBAAC,eAAM,IAAC,IAAI,EAAC,SAAS,GAAG,IAJpB,YAAY,CAAC,MAAM,CAKJ,CAC3B,IACY,CACpB,GACC,EAEL,QAAQ,KAAK,MAAM,IAAI,OAAO,IAAI,UAAU,IAAI,CAC7C,uBAAC,cAAM,IAAC,IAAI,EAAC,aAAa,EAAC,IAAI,EAAC,GAAG,EAAC,KAAK,EAAC,KAAK,EAAC,OAAO,EAAE,cAAc,YAClE,IAAA,WAAI,EAAC,kBAAkB,CAAC,GACpB,CACZ,IACC,CACT,CAAC;AACN,CAAC","sourcesContent":["import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';\n\nimport {Button, DOMProps, InputControlSize, List, ListItemData, QAProps} from '@gravity-ui/uikit';\n\nimport {DateLocaleConfig} from '../../../hooks';\nimport {useMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {ChatType, ListItemChatData} from '../../../types';\nimport {ChatFilterFunction, defaultChatFilter, groupChatsByDate} from '../../../utils/chatUtils';\nimport {block} from '../../../utils/cn';\nimport {IntersectionContainer} from '../../atoms/IntersectionContainer';\nimport {Loader} from '../../atoms/Loader';\n\nimport {ChatItem} from './ChatItem';\nimport {DateHeaderItem} from './DateHeaderItem';\nimport {i18n} from './i18n';\n\nimport './History.scss';\n\nconst b = block('history');\n\n/** Result of lazy load more callback - either array of new chats or object with chats and hasMore flag */\nexport type LoadMoreLazyResult = ChatType[] | {chats: ChatType[]; hasMore?: boolean};\n\n/** Callback for lazy load more - returns only new chats, not previously loaded */\nexport type OnLoadMoreLazy = (offset: number) => Promise<LoadMoreLazyResult>;\n\n/** Load mode: full (button, parent manages all data) or lazy (scroll, component accumulates data) */\nexport type HistoryLoadMode = 'full' | 'lazy';\n\n/**\n * Props for the HistoryList component\n */\nexport interface HistoryListProps extends QAProps, DOMProps {\n /** Array of chat items */\n chats: ChatType[];\n /** Currently selected chat */\n selectedChat?: ChatType | null;\n /** Callback when a chat is selected */\n onSelectChat?: (chat: ChatType) => void;\n /** Callback when a chat is deleted */\n onDeleteChat?: (chat: ChatType) => Promise<void>;\n /**\n * Callback to load more chats.\n * - Full mode: () => void - triggers load, parent updates chats prop\n * - Lazy mode: (offset) => Promise<ChatType[]|{chats,hasMore}> - returns only new chats\n */\n onLoadMore?: (() => void) | OnLoadMoreLazy;\n /** Whether there are more chats to load */\n hasMore?: boolean;\n /**\n * Load mode: 'full' (button click, parent provides all data) or 'lazy' (scroll, callback returns new data only)\n * @default 'full'\n */\n loadMode?: HistoryLoadMode;\n /** Enable search functionality */\n searchable?: boolean;\n /** Group chats by date or none */\n groupBy?: 'date' | 'none';\n /** Custom dayjs format for non-relative date headers */\n dateFormat?: string;\n /** Locale for non-relative date headers */\n dateLocale?: string;\n /** Dayjs locale configuration for non-relative date headers */\n dateLocaleConfig?: DateLocaleConfig;\n /** Show action buttons (delete, etc.) */\n showActions?: boolean;\n /** Empty state placeholder */\n emptyPlaceholder?: React.ReactNode;\n /** Empty filtered state placeholder (when search returns no results) */\n emptyFilteredPlaceholder?: React.ReactNode;\n /** Placeholder text for the search/filter input (defaults to built-in i18n string) */\n searchPlaceholder?: string;\n /** Additional CSS class */\n className?: string;\n /** Custom filter function for search */\n filterFunction?: ChatFilterFunction;\n /** Loading state */\n loading?: boolean;\n /** Callback when chat is clicked (for closing popup in parent) */\n onChatClick?: (chat: ChatType) => void;\n /** Size of the list */\n size?: InputControlSize;\n}\n\n/**\n * HistoryList component - displays a list of chats with search, grouping, and actions\n *\n * @param props - {@link HistoryListProps}\n * @returns Rendered history list (search, grouping, lazy/full load, chat rows).\n */\nexport function HistoryList(props: HistoryListProps) {\n const {\n chats,\n selectedChat,\n onSelectChat,\n onDeleteChat,\n onLoadMore,\n hasMore = false,\n loadMode = 'full',\n searchable = true,\n groupBy = 'date',\n dateFormat,\n dateLocale,\n dateLocaleConfig,\n showActions = true,\n emptyPlaceholder,\n emptyFilteredPlaceholder,\n searchPlaceholder,\n className,\n qa,\n style,\n filterFunction = defaultChatFilter,\n loading = false,\n onChatClick,\n size: sizeProp,\n } = props;\n\n const size = useMobileControlSize(sizeProp, 'm', 'xl');\n\n const [filteredItemCount, setFilteredItemCount] = useState<number | null>(null);\n const [loadedChats, setLoadedChats] = useState<ChatType[]>([]);\n const [lazyHasMore, setLazyHasMore] = useState(hasMore);\n const [deletedChats, setDeletedChats] = useState<Set<string>>(new Set());\n\n const loadingMoreRef = useRef(false);\n\n const displayChats = useMemo(() => {\n const totalChats = loadMode === 'lazy' ? [...chats, ...loadedChats] : chats;\n\n const filteredChats = totalChats.filter((chat) => !deletedChats.has(chat.id));\n\n return filteredChats;\n }, [loadMode, chats, loadedChats, deletedChats]);\n\n const displayChatsLengthRef = useRef(displayChats.length);\n displayChatsLengthRef.current = displayChats.length;\n\n useEffect(() => {\n if (loadMode === 'lazy') {\n setLazyHasMore(hasMore);\n }\n }, [loadMode, hasMore]);\n\n useEffect(() => {\n if (loadMode === 'lazy') {\n setLoadedChats([]);\n }\n }, [chats, loadMode]);\n\n const groupedChats = useMemo(() => {\n if (groupBy === 'none') {\n return new Map([['all', displayChats]]);\n }\n return groupChatsByDate(displayChats);\n }, [displayChats, groupBy]);\n\n const listItems: ListItemData<ListItemChatData>[] = useMemo(() => {\n const items: ListItemData<ListItemChatData>[] = [];\n\n const sortedGroups = Array.from(groupedChats.entries()).sort(([dateA], [dateB]) => {\n if (dateA === 'all') return 0;\n return new Date(dateB).getTime() - new Date(dateA).getTime();\n });\n\n sortedGroups.forEach(([dateKey, groupChats]) => {\n if (groupChats.length === 0) {\n return;\n }\n\n if (groupBy === 'date' && dateKey !== 'all') {\n items.push({\n type: 'date-header',\n disabled: true,\n date: dateKey,\n });\n }\n\n groupChats.forEach((chat) => {\n items.push({\n type: 'chat',\n ...chat,\n });\n });\n });\n\n return items;\n }, [groupedChats, groupBy]);\n\n const selectedItemIndex = useMemo(() => {\n return listItems.findIndex((item) => item.type === 'chat' && item.id === selectedChat?.id);\n }, [listItems, selectedChat]);\n\n const effectiveHasMore = loadMode === 'lazy' ? lazyHasMore : hasMore;\n\n /**\n * Unified load more handler.\n * full: fires onLoadMore(), parent manages data and hasMore\n * lazy: calls onLoadMore(offset), accumulates returned chats internally\n */\n const handleLoadMore = useCallback(async () => {\n if (!onLoadMore || loadingMoreRef.current) {\n return;\n }\n\n if (loadMode === 'full') {\n (onLoadMore as () => void)();\n return;\n }\n\n loadingMoreRef.current = true;\n try {\n const offset = displayChatsLengthRef.current;\n const result = await (onLoadMore as OnLoadMoreLazy)(offset);\n const newChats = Array.isArray(result) ? result : result.chats;\n const resultHasMore = Array.isArray(result) ? newChats.length > 0 : result.hasMore;\n setLoadedChats((prev) => [...prev, ...newChats]);\n setLazyHasMore(resultHasMore ?? newChats.length > 0);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('HistoryList: failed to load more chats', error);\n } finally {\n loadingMoreRef.current = false;\n }\n }, [onLoadMore, loadMode]);\n\n const handleChatClick = (\n chat: ListItemData<ListItemChatData>,\n _: number,\n fromKeyboard?: boolean,\n event?: React.KeyboardEvent<HTMLElement> | React.MouseEvent<HTMLElement>,\n ) => {\n if (fromKeyboard && (event?.target as HTMLButtonElement)?.type === 'button') {\n return;\n }\n onSelectChat?.(chat as ChatType);\n onChatClick?.(chat as ChatType);\n };\n\n const handleDeleteClick = async (e: React.MouseEvent, chat: ChatType) => {\n e.stopPropagation();\n try {\n await onDeleteChat?.(chat);\n setDeletedChats((prevDeletedChats) => new Set([...prevDeletedChats, chat.id]));\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('HistoryList: failed to delete chat', error);\n }\n };\n\n const wrappedFilterFunction = useMemo(() => {\n return (filter: string) => {\n const userFilter = filterFunction(filter);\n return (item: ListItemData<ListItemChatData>): boolean => {\n if (filter && item.type === 'date-header') {\n return false;\n }\n return userFilter(item);\n };\n };\n }, [filterFunction]);\n\n const renderItem = (item: ListItemData<ListItemChatData>, isActive: boolean) => {\n if (item.type === 'date-header') {\n return (\n <DateHeaderItem\n key={`date-${item.date}`}\n date={item.date}\n format={dateFormat}\n locale={dateLocale}\n localeConfig={dateLocaleConfig}\n />\n );\n }\n\n return (\n <ChatItem\n key={item.id}\n chat={item}\n showActions={showActions}\n onDeleteClick={onDeleteChat ? handleDeleteClick : undefined}\n isActive={isActive}\n size={sizeProp}\n />\n );\n };\n\n const emptyState = emptyPlaceholder || <div className={b('empty')}>{i18n('empty-state')}</div>;\n\n const emptyFilteredState = emptyFilteredPlaceholder || (\n <div className={b('empty')}>{i18n('empty-filtered-state')}</div>\n );\n\n const finalEmptyPlaceholder =\n filteredItemCount === null || filteredItemCount === listItems.length\n ? emptyState\n : emptyFilteredState;\n\n return (\n <div className={b('container', className)} data-qa={qa} style={style}>\n <div className={b('list-wrapper')}>\n {loading ? (\n <div className={b('loader-wrapper')}>\n <Loader view=\"loading\" />\n </div>\n ) : (\n <React.Fragment>\n <List\n size={size}\n items={listItems}\n renderItem={renderItem}\n virtualized={false}\n filterable={searchable}\n filterItem={wrappedFilterFunction}\n filterPlaceholder={searchPlaceholder ?? i18n('search-placeholder')}\n filterClassName={b('filter')}\n emptyPlaceholder={finalEmptyPlaceholder}\n selectedItemIndex={selectedItemIndex}\n itemsClassName={b('list')}\n itemClassName={b('list-item')}\n onFilterEnd={(data) => setFilteredItemCount(data.items.length)}\n onItemClick={handleChatClick}\n />\n {loadMode === 'lazy' && effectiveHasMore && onLoadMore && (\n <IntersectionContainer\n key={displayChats.length}\n onIntersect={handleLoadMore}\n className={b('lazy-loader')}\n >\n <Loader view=\"loading\" />\n </IntersectionContainer>\n )}\n </React.Fragment>\n )}\n </div>\n\n {loadMode === 'full' && hasMore && onLoadMore && (\n <Button view=\"flat-action\" size=\"m\" width=\"max\" onClick={handleLoadMore}>\n {i18n('action-load-more')}\n </Button>\n )}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"HistoryList.js","sourceRoot":"../../../../../src","sources":["components/templates/History/HistoryList.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';\n\nimport {Button, DOMProps, InputControlSize, List, ListItemData, QAProps} from '@gravity-ui/uikit';\n\nimport {DateLocaleConfig} from '../../../hooks';\nimport {useMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {ChatType, ListItemChatData} from '../../../types';\nimport {ChatFilterFunction, defaultChatFilter, groupChatsByDate} from '../../../utils/chatUtils';\nimport {block} from '../../../utils/cn';\nimport {IntersectionContainer} from '../../atoms/IntersectionContainer';\nimport {Loader} from '../../atoms/Loader';\n\nimport {ChatItem} from './ChatItem';\nimport {DateHeaderItem} from './DateHeaderItem';\nimport {i18n} from './i18n';\n\nimport './History.scss';\n\nconst b = block('history');\n\ntype HistoryListItem = ListItemData<ListItemChatData> & {rowKey: string};\n\n/** Result of lazy load more callback - either array of new chats or object with chats and hasMore flag */\nexport type LoadMoreLazyResult = ChatType[] | {chats: ChatType[]; hasMore?: boolean};\n\n/** Callback for lazy load more - returns only new chats, not previously loaded */\nexport type OnLoadMoreLazy = (offset: number) => Promise<LoadMoreLazyResult>;\n\n/** Load mode: full (button, parent manages all data) or lazy (scroll, component accumulates data) */\nexport type HistoryLoadMode = 'full' | 'lazy';\n\n/**\n * Props for the HistoryList component\n */\nexport interface HistoryListProps extends QAProps, DOMProps {\n /** Array of chat items */\n chats: ChatType[];\n /** Controlled pinned identifiers. Omit to disable pinning. */\n pinnedChatIds?: readonly string[];\n /** Requests a pin state change; the application updates pinnedChatIds. */\n onPinnedChange?: (chatId: string, pinned: boolean) => void;\n /** Disable pin actions while persistence is pending. */\n pinningDisabled?: boolean;\n /** Currently selected chat */\n selectedChat?: ChatType | null;\n /** Callback when a chat is selected */\n onSelectChat?: (chat: ChatType) => void;\n /** Callback when a chat is deleted */\n onDeleteChat?: (chat: ChatType) => Promise<void>;\n /**\n * Callback to load more chats.\n * - Full mode: () => void - triggers load, parent updates chats prop\n * - Lazy mode: (offset) => Promise<ChatType[]|{chats,hasMore}> - returns only new chats\n */\n onLoadMore?: (() => void) | OnLoadMoreLazy;\n /** Whether there are more chats to load */\n hasMore?: boolean;\n /**\n * Load mode: 'full' (button click, parent provides all data) or 'lazy' (scroll, callback returns new data only)\n * @default 'full'\n */\n loadMode?: HistoryLoadMode;\n /** Enable search functionality */\n searchable?: boolean;\n /** Group chats by date or none */\n groupBy?: 'date' | 'none';\n /** Custom dayjs format for non-relative date headers */\n dateFormat?: string;\n /** Locale for non-relative date headers */\n dateLocale?: string;\n /** Dayjs locale configuration for non-relative date headers */\n dateLocaleConfig?: DateLocaleConfig;\n /** Show action buttons (delete, etc.) */\n showActions?: boolean;\n /** Empty state placeholder */\n emptyPlaceholder?: React.ReactNode;\n /** Empty filtered state placeholder (when search returns no results) */\n emptyFilteredPlaceholder?: React.ReactNode;\n /** Placeholder text for the search/filter input (defaults to built-in i18n string) */\n searchPlaceholder?: string;\n /** Additional CSS class */\n className?: string;\n /** Custom filter function for search */\n filterFunction?: ChatFilterFunction;\n /** Loading state */\n loading?: boolean;\n /** Callback when chat is clicked (for closing popup in parent) */\n onChatClick?: (chat: ChatType) => void;\n /** Size of the list */\n size?: InputControlSize;\n}\n\n/**\n * HistoryList component - displays a list of chats with search, grouping, and actions\n *\n * @param props - {@link HistoryListProps}\n * @returns Rendered history list (search, grouping, lazy/full load, chat rows).\n */\nexport function HistoryList(props: HistoryListProps) {\n const {\n chats,\n selectedChat,\n pinnedChatIds,\n onPinnedChange,\n pinningDisabled,\n onSelectChat,\n onDeleteChat,\n onLoadMore,\n hasMore = false,\n loadMode = 'full',\n searchable = true,\n groupBy = 'date',\n dateFormat,\n dateLocale,\n dateLocaleConfig,\n showActions = true,\n emptyPlaceholder,\n emptyFilteredPlaceholder,\n searchPlaceholder,\n className,\n qa,\n style,\n filterFunction = defaultChatFilter,\n loading = false,\n onChatClick,\n size: sizeProp,\n } = props;\n\n const size = useMobileControlSize(sizeProp, 'm', 'xl');\n\n const [filteredItemCount, setFilteredItemCount] = useState<number | null>(null);\n const [loadedChats, setLoadedChats] = useState<ChatType[]>([]);\n const [lazyHasMore, setLazyHasMore] = useState(hasMore);\n const [deletedChats, setDeletedChats] = useState<Set<string>>(new Set());\n\n const loadingMoreRef = useRef(false);\n\n const displayChats = useMemo(() => {\n const totalChats = loadMode === 'lazy' ? [...chats, ...loadedChats] : chats;\n\n const filteredChats = totalChats.filter((chat) => !deletedChats.has(chat.id));\n\n return filteredChats;\n }, [loadMode, chats, loadedChats, deletedChats]);\n\n const displayChatsLengthRef = useRef(displayChats.length);\n displayChatsLengthRef.current = displayChats.length;\n\n useEffect(() => {\n if (loadMode === 'lazy') {\n setLazyHasMore(hasMore);\n }\n }, [loadMode, hasMore]);\n\n useEffect(() => {\n if (loadMode === 'lazy') {\n setLoadedChats([]);\n }\n }, [chats, loadMode]);\n\n const groupedChats = useMemo(() => {\n if (groupBy === 'none') {\n return new Map([['all', displayChats]]);\n }\n return groupChatsByDate(displayChats);\n }, [displayChats, groupBy]);\n\n const listItems = useMemo(() => {\n const items: HistoryListItem[] = [];\n\n if (pinnedChatIds !== undefined) {\n const pinned = new Set(pinnedChatIds);\n const ordered = [\n ...displayChats.filter((chat) => pinned.has(chat.id)),\n ...displayChats.filter((chat) => !pinned.has(chat.id)),\n ];\n let previousDate: string | undefined;\n ordered.forEach((chat) => {\n const date =\n !pinned.has(chat.id) && chat.createTime\n ? new Date(chat.createTime).toDateString()\n : undefined;\n if (groupBy === 'date' && date && date !== previousDate) {\n items.push({\n type: 'date-header',\n disabled: true,\n date,\n rowKey: `date:${date}:${chat.id}`,\n });\n }\n previousDate = date;\n items.push({...chat, type: 'chat', rowKey: `chat:${chat.id}`});\n });\n return items;\n }\n\n const sortedGroups = Array.from(groupedChats.entries()).sort(([dateA], [dateB]) => {\n if (dateA === 'all') return 0;\n return new Date(dateB).getTime() - new Date(dateA).getTime();\n });\n\n sortedGroups.forEach(([dateKey, groupChats]) => {\n if (groupChats.length === 0) {\n return;\n }\n\n if (groupBy === 'date' && dateKey !== 'all') {\n items.push({\n type: 'date-header',\n disabled: true,\n date: dateKey,\n rowKey: `date:${dateKey}`,\n });\n }\n\n groupChats.forEach((chat) => {\n items.push({\n type: 'chat',\n ...chat,\n rowKey: `chat:${chat.id}`,\n });\n });\n });\n\n return items;\n }, [groupedChats, groupBy, displayChats, pinnedChatIds]);\n\n const selectedItemIndex = useMemo(() => {\n return listItems.findIndex((item) => item.type === 'chat' && item.id === selectedChat?.id);\n }, [listItems, selectedChat]);\n\n const effectiveHasMore = loadMode === 'lazy' ? lazyHasMore : hasMore;\n\n /**\n * Unified load more handler.\n * full: fires onLoadMore(), parent manages data and hasMore\n * lazy: calls onLoadMore(offset), accumulates returned chats internally\n */\n const handleLoadMore = useCallback(async () => {\n if (!onLoadMore || loadingMoreRef.current) {\n return;\n }\n\n if (loadMode === 'full') {\n (onLoadMore as () => void)();\n return;\n }\n\n loadingMoreRef.current = true;\n try {\n const offset = displayChatsLengthRef.current;\n const result = await (onLoadMore as OnLoadMoreLazy)(offset);\n const newChats = Array.isArray(result) ? result : result.chats;\n const resultHasMore = Array.isArray(result) ? newChats.length > 0 : result.hasMore;\n setLoadedChats((prev) => [...prev, ...newChats]);\n setLazyHasMore(resultHasMore ?? newChats.length > 0);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('HistoryList: failed to load more chats', error);\n } finally {\n loadingMoreRef.current = false;\n }\n }, [onLoadMore, loadMode]);\n\n const handleChatClick = (\n chat: ListItemData<ListItemChatData>,\n _: number,\n fromKeyboard?: boolean,\n event?: React.KeyboardEvent<HTMLElement> | React.MouseEvent<HTMLElement>,\n ) => {\n if (fromKeyboard && (event?.target as HTMLButtonElement)?.type === 'button') {\n return;\n }\n onSelectChat?.(chat as ChatType);\n onChatClick?.(chat as ChatType);\n };\n\n const handleDeleteClick = async (e: React.MouseEvent, chat: ChatType) => {\n e.stopPropagation();\n try {\n await onDeleteChat?.(chat);\n setDeletedChats((prevDeletedChats) => new Set([...prevDeletedChats, chat.id]));\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('HistoryList: failed to delete chat', error);\n }\n };\n\n const wrappedFilterFunction = useMemo(() => {\n return (filter: string) => {\n const userFilter = filterFunction(filter);\n return (item: ListItemData<ListItemChatData>): boolean => {\n if (filter && item.type === 'date-header') {\n return false;\n }\n return userFilter(item);\n };\n };\n }, [filterFunction]);\n\n const renderItem = (item: ListItemData<ListItemChatData>, isActive: boolean) => {\n if (item.type === 'date-header') {\n return (\n <DateHeaderItem\n key={`date-${item.date}`}\n date={item.date}\n format={dateFormat}\n locale={dateLocale}\n localeConfig={dateLocaleConfig}\n />\n );\n }\n\n return (\n <ChatItem\n key={item.id}\n chat={item}\n showActions={showActions}\n pinned={pinnedChatIds?.includes(item.id)}\n onPinnedChange={pinnedChatIds === undefined ? undefined : onPinnedChange}\n pinningDisabled={pinningDisabled}\n onDeleteClick={onDeleteChat ? handleDeleteClick : undefined}\n isActive={isActive}\n size={sizeProp}\n />\n );\n };\n\n const emptyState = emptyPlaceholder || <div className={b('empty')}>{i18n('empty-state')}</div>;\n\n const emptyFilteredState = emptyFilteredPlaceholder || (\n <div className={b('empty')}>{i18n('empty-filtered-state')}</div>\n );\n\n const finalEmptyPlaceholder =\n filteredItemCount === null || filteredItemCount === listItems.length\n ? emptyState\n : emptyFilteredState;\n\n return (\n <div className={b('container', className)} data-qa={qa} style={style}>\n <div className={b('list-wrapper')}>\n {loading ? (\n <div className={b('loader-wrapper')}>\n <Loader view=\"loading\" />\n </div>\n ) : (\n <React.Fragment>\n <List<HistoryListItem>\n size={size}\n items={listItems}\n itemKey={(item) => item.rowKey}\n renderItem={renderItem}\n virtualized={false}\n filterable={searchable}\n filterItem={wrappedFilterFunction}\n filterPlaceholder={searchPlaceholder ?? i18n('search-placeholder')}\n filterClassName={b('filter')}\n emptyPlaceholder={finalEmptyPlaceholder}\n selectedItemIndex={selectedItemIndex}\n itemsClassName={b('list')}\n itemClassName={b('list-item')}\n onFilterEnd={(data) => setFilteredItemCount(data.items.length)}\n onItemClick={handleChatClick}\n />\n {loadMode === 'lazy' && effectiveHasMore && onLoadMore && (\n <IntersectionContainer\n key={displayChats.length}\n onIntersect={handleLoadMore}\n className={b('lazy-loader')}\n >\n <Loader view=\"loading\" />\n </IntersectionContainer>\n )}\n </React.Fragment>\n )}\n </div>\n\n {loadMode === 'full' && hasMore && onLoadMore && (\n <Button view=\"flat-action\" size=\"m\" width=\"max\" onClick={handleLoadMore}>\n {i18n('action-load-more')}\n </Button>\n )}\n </div>\n );\n}\n"]}
|
|
@@ -5,5 +5,8 @@
|
|
|
5
5
|
"empty-filtered-state": "No chats match your search",
|
|
6
6
|
"tooltip-history": "Chat history",
|
|
7
7
|
"sheet-title": "Chat history",
|
|
8
|
-
"tooltip-delete": "Delete"
|
|
8
|
+
"tooltip-delete": "Delete",
|
|
9
|
+
"pinned": "Pinned chat",
|
|
10
|
+
"action-pin": "Pin chat",
|
|
11
|
+
"action-unpin": "Unpin chat"
|
|
9
12
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export declare const i18n: ((key: "action-load-more" | "search-placeholder" | "empty-state" | "empty-filtered-state" | "tooltip-history" | "sheet-title" | "tooltip-delete", params?: import("@gravity-ui/i18n").Params) => string) & {
|
|
1
|
+
export declare const i18n: ((key: "action-load-more" | "search-placeholder" | "empty-state" | "empty-filtered-state" | "tooltip-history" | "sheet-title" | "tooltip-delete" | "pinned" | "action-pin" | "action-unpin", params?: import("@gravity-ui/i18n").Params) => string) & {
|
|
2
2
|
Translation: import("react").ComponentType<{
|
|
3
3
|
children: (props: {
|
|
4
|
-
t: (key: "action-load-more" | "search-placeholder" | "empty-state" | "empty-filtered-state" | "tooltip-history" | "sheet-title" | "tooltip-delete", params?: import("@gravity-ui/i18n").Params) => string;
|
|
4
|
+
t: (key: "action-load-more" | "search-placeholder" | "empty-state" | "empty-filtered-state" | "tooltip-history" | "sheet-title" | "tooltip-delete" | "pinned" | "action-pin" | "action-unpin", params?: import("@gravity-ui/i18n").Params) => string;
|
|
5
5
|
}) => React.ReactNode;
|
|
6
6
|
}>;
|
|
7
7
|
useTranslation: () => {
|
|
8
|
-
t: (key: "action-load-more" | "search-placeholder" | "empty-state" | "empty-filtered-state" | "tooltip-history" | "sheet-title" | "tooltip-delete", params?: import("@gravity-ui/i18n").Params) => string;
|
|
8
|
+
t: (key: "action-load-more" | "search-placeholder" | "empty-state" | "empty-filtered-state" | "tooltip-history" | "sheet-title" | "tooltip-delete" | "pinned" | "action-pin" | "action-unpin", params?: import("@gravity-ui/i18n").Params) => string;
|
|
9
9
|
};
|
|
10
10
|
keysetData: {
|
|
11
|
-
"g-aikit-History": Record<"action-load-more" | "search-placeholder" | "empty-state" | "empty-filtered-state" | "tooltip-history" | "sheet-title" | "tooltip-delete", import("@gravity-ui/i18n").KeyData>;
|
|
11
|
+
"g-aikit-History": Record<"action-load-more" | "search-placeholder" | "empty-state" | "empty-filtered-state" | "tooltip-history" | "sheet-title" | "tooltip-delete" | "pinned" | "action-pin" | "action-unpin", import("@gravity-ui/i18n").KeyData>;
|
|
12
12
|
};
|
|
13
13
|
};
|
|
@@ -5,5 +5,8 @@
|
|
|
5
5
|
"empty-filtered-state": "Ничего не найдено по вашему запросу",
|
|
6
6
|
"tooltip-history": "История чатов",
|
|
7
7
|
"sheet-title": "История чатов",
|
|
8
|
-
"tooltip-delete": "Удалить"
|
|
8
|
+
"tooltip-delete": "Удалить",
|
|
9
|
+
"pinned": "Закреплённый чат",
|
|
10
|
+
"action-pin": "Закрепить чат",
|
|
11
|
+
"action-unpin": "Открепить чат"
|
|
9
12
|
}
|
package/build/cjs/hooks/index.js
CHANGED
|
@@ -14,4 +14,5 @@ tslib_1.__exportStar(require("./useToolResultContinuation.js"), exports);
|
|
|
14
14
|
tslib_1.__exportStar(require("./useMascotState.js"), exports);
|
|
15
15
|
tslib_1.__exportStar(require("./useKeyboardViewportFit.js"), exports);
|
|
16
16
|
tslib_1.__exportStar(require("./useSheetKeyboardFit.js"), exports);
|
|
17
|
+
tslib_1.__exportStar(require("./usePinnedChatsStore.js"), exports);
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":";;;AAAA,sEAAmC;AACnC,8DAAiC;AACjC,8DAAiC;AACjC,qEAAwC;AACxC,uEAA0C;AAC1C,wEAA2C;AAC3C,0EAA6C;AAC7C,kEAAqC;AACrC,0DAA6B;AAC7B,yEAA4C;AAC5C,8DAAiC;AACjC,sEAAyC;AACzC,mEAAsC","sourcesContent":["export * from './useDateFormatter';\nexport * from './useToolMessage';\nexport * from './useSmartScroll';\nexport * from './useScrollPreservation';\nexport * from './useVirtualStickToBottom';\nexport * from './useAutoCollapseOnSuccess';\nexport * from './useAutoCollapseOnCancelled';\nexport * from './useFileUploadStore';\nexport * from './useToolset';\nexport * from './useToolResultContinuation';\nexport * from './useMascotState';\nexport * from './useKeyboardViewportFit';\nexport * from './useSheetKeyboardFit';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":";;;AAAA,sEAAmC;AACnC,8DAAiC;AACjC,8DAAiC;AACjC,qEAAwC;AACxC,uEAA0C;AAC1C,wEAA2C;AAC3C,0EAA6C;AAC7C,kEAAqC;AACrC,0DAA6B;AAC7B,yEAA4C;AAC5C,8DAAiC;AACjC,sEAAyC;AACzC,mEAAsC;AACtC,mEAAsC","sourcesContent":["export * from './useDateFormatter';\nexport * from './useToolMessage';\nexport * from './useSmartScroll';\nexport * from './useScrollPreservation';\nexport * from './useVirtualStickToBottom';\nexport * from './useAutoCollapseOnSuccess';\nexport * from './useAutoCollapseOnCancelled';\nexport * from './useFileUploadStore';\nexport * from './useToolset';\nexport * from './useToolResultContinuation';\nexport * from './useMascotState';\nexport * from './useKeyboardViewportFit';\nexport * from './useSheetKeyboardFit';\nexport * from './usePinnedChatsStore';\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PinnedChatsStore } from "../utils/pinnedChatsStore.js";
|
|
2
|
+
/** Loads a stable store and subscribes to its confirmed state and persistence errors. */
|
|
3
|
+
export declare function usePinnedChatsStore(store: PinnedChatsStore): {
|
|
4
|
+
setPinned: (id: string, pinned: boolean) => Promise<void>;
|
|
5
|
+
load: () => Promise<void>;
|
|
6
|
+
pinnedChatIds: readonly string[];
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
error: unknown;
|
|
9
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePinnedChatsStore = usePinnedChatsStore;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
/** Loads a stable store and subscribes to its confirmed state and persistence errors. */
|
|
6
|
+
function usePinnedChatsStore(store) {
|
|
7
|
+
const snapshot = (0, react_1.useSyncExternalStore)(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
8
|
+
(0, react_1.useEffect)(() => {
|
|
9
|
+
store.load();
|
|
10
|
+
}, [store]);
|
|
11
|
+
return Object.assign(Object.assign({}, snapshot), { setPinned: store.setPinned, load: store.load });
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=usePinnedChatsStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePinnedChatsStore.js","sourceRoot":"../../../src","sources":["hooks/usePinnedChatsStore.ts"],"names":[],"mappings":";;AAKA,kDAMC;AAXD,iCAAsD;AAItD,yFAAyF;AACzF,SAAgB,mBAAmB,CAAC,KAAuB;IACvD,MAAM,QAAQ,GAAG,IAAA,4BAAoB,EAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC7F,IAAA,iBAAS,EAAC,GAAG,EAAE;QACX,KAAK,CAAC,IAAI,EAAE,CAAC;IACjB,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,uCAAW,QAAQ,KAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAE;AACvE,CAAC","sourcesContent":["import {useEffect, useSyncExternalStore} from 'react';\n\nimport type {PinnedChatsStore} from '../utils/pinnedChatsStore';\n\n/** Loads a stable store and subscribes to its confirmed state and persistence errors. */\nexport function usePinnedChatsStore(store: PinnedChatsStore) {\n const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);\n useEffect(() => {\n store.load();\n }, [store]);\n return {...snapshot, setPinned: store.setPinned, load: store.load};\n}\n"]}
|
package/build/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.25.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
|
1
|
+
{"version":"2.25.1-beta.c31bfaac02b26694749dd7a5d2532192c8e5fdc2.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
package/build/cjs/utils/index.js
CHANGED
|
@@ -10,4 +10,5 @@ tslib_1.__exportStar(require("./clipboardUtils.js"), exports);
|
|
|
10
10
|
tslib_1.__exportStar(require("./aiAgentContext/index.js"), exports);
|
|
11
11
|
tslib_1.__exportStar(require("./toolset/index.js"), exports);
|
|
12
12
|
tslib_1.__exportStar(require("./mascot.js"), exports);
|
|
13
|
+
tslib_1.__exportStar(require("./pinnedChatsStore.js"), exports);
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["utils/index.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,yDAA4B;AAC5B,4DAA+B;AAC/B,0DAA6B;AAC7B,mEAAsC;AACtC,8DAAiC;AACjC,oEAAiC;AACjC,6DAA0B;AAC1B,sDAAyB","sourcesContent":["// Export utilities\nexport * from './chatUtils';\nexport * from './messageUtils';\nexport * from './validation';\nexport * from './messageTypeRegistry';\nexport * from './clipboardUtils';\nexport * from './aiAgentContext';\nexport * from './toolset';\nexport * from './mascot';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["utils/index.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,yDAA4B;AAC5B,4DAA+B;AAC/B,0DAA6B;AAC7B,mEAAsC;AACtC,8DAAiC;AACjC,oEAAiC;AACjC,6DAA0B;AAC1B,sDAAyB;AACzB,gEAAmC","sourcesContent":["// Export utilities\nexport * from './chatUtils';\nexport * from './messageUtils';\nexport * from './validation';\nexport * from './messageTypeRegistry';\nexport * from './clipboardUtils';\nexport * from './aiAgentContext';\nexport * from './toolset';\nexport * from './mascot';\nexport * from './pinnedChatsStore';\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Persistence belongs to the application; only chat identifiers are stored. */
|
|
2
|
+
export interface PinnedChatsStorage {
|
|
3
|
+
load: () => readonly string[] | Promise<readonly string[]>;
|
|
4
|
+
save: (ids: readonly string[]) => void | Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export interface PinnedChatsSnapshot {
|
|
7
|
+
readonly pinnedChatIds: readonly string[];
|
|
8
|
+
readonly isLoading: boolean;
|
|
9
|
+
readonly error: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface PinnedChatsStore {
|
|
12
|
+
getSnapshot: () => PinnedChatsSnapshot;
|
|
13
|
+
subscribe: (listener: () => void) => () => void;
|
|
14
|
+
load: () => Promise<void>;
|
|
15
|
+
setPinned: (id: string, pinned: boolean) => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a store with application-provided persistence. Operations are serialized and only
|
|
19
|
+
* successful saves update the snapshot. Errors are exposed in the snapshot; operations resolve.
|
|
20
|
+
* Create a separate store for each account and keep its identity stable across renders.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createPinnedChatsStore(storage: PinnedChatsStorage): PinnedChatsStore;
|
|
23
|
+
/** Browser storage is accessed lazily, so creation is safe during server rendering. */
|
|
24
|
+
export declare function createLocalStoragePinnedChatsStore(key: string): PinnedChatsStore;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPinnedChatsStore = createPinnedChatsStore;
|
|
4
|
+
exports.createLocalStoragePinnedChatsStore = createLocalStoragePinnedChatsStore;
|
|
5
|
+
function validateIds(value) {
|
|
6
|
+
if (!Array.isArray(value) || value.some((id) => typeof id !== 'string')) {
|
|
7
|
+
throw new Error('Pinned chats must be an array of string identifiers');
|
|
8
|
+
}
|
|
9
|
+
return Object.freeze([...new Set(value)]);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Creates a store with application-provided persistence. Operations are serialized and only
|
|
13
|
+
* successful saves update the snapshot. Errors are exposed in the snapshot; operations resolve.
|
|
14
|
+
* Create a separate store for each account and keep its identity stable across renders.
|
|
15
|
+
*/
|
|
16
|
+
function createPinnedChatsStore(storage) {
|
|
17
|
+
let snapshot = {
|
|
18
|
+
pinnedChatIds: Object.freeze([]),
|
|
19
|
+
isLoading: false,
|
|
20
|
+
error: undefined,
|
|
21
|
+
};
|
|
22
|
+
let loaded = false;
|
|
23
|
+
let pending = 0;
|
|
24
|
+
let queue = Promise.resolve();
|
|
25
|
+
const listeners = new Set();
|
|
26
|
+
const update = (patch) => {
|
|
27
|
+
snapshot = Object.assign(Object.assign({}, snapshot), patch);
|
|
28
|
+
listeners.forEach((listener) => listener());
|
|
29
|
+
};
|
|
30
|
+
const ensureLoaded = async () => {
|
|
31
|
+
if (!loaded) {
|
|
32
|
+
const pinnedChatIds = validateIds(await storage.load());
|
|
33
|
+
loaded = true;
|
|
34
|
+
update({ pinnedChatIds });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const enqueue = (operation) => {
|
|
38
|
+
pending += 1;
|
|
39
|
+
update({ isLoading: true });
|
|
40
|
+
queue = queue.then(async () => {
|
|
41
|
+
try {
|
|
42
|
+
await operation();
|
|
43
|
+
update({ error: undefined });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
update({ error });
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
pending -= 1;
|
|
50
|
+
update({ isLoading: pending > 0 });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return queue;
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
getSnapshot: () => snapshot,
|
|
57
|
+
subscribe: (listener) => {
|
|
58
|
+
listeners.add(listener);
|
|
59
|
+
return () => {
|
|
60
|
+
listeners.delete(listener);
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
load: () => enqueue(ensureLoaded),
|
|
64
|
+
setPinned: (id, pinned) => enqueue(async () => {
|
|
65
|
+
await ensureLoaded();
|
|
66
|
+
const current = snapshot.pinnedChatIds;
|
|
67
|
+
if (current.includes(id) === pinned)
|
|
68
|
+
return;
|
|
69
|
+
const next = pinned ? [...current, id] : current.filter((item) => item !== id);
|
|
70
|
+
await storage.save([...next]);
|
|
71
|
+
update({ pinnedChatIds: Object.freeze(next) });
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Browser storage is accessed lazily, so creation is safe during server rendering. */
|
|
76
|
+
function createLocalStoragePinnedChatsStore(key) {
|
|
77
|
+
return createPinnedChatsStore({
|
|
78
|
+
load: () => {
|
|
79
|
+
const value = window.localStorage.getItem(key);
|
|
80
|
+
return value === null ? [] : validateIds(JSON.parse(value));
|
|
81
|
+
},
|
|
82
|
+
save: (ids) => window.localStorage.setItem(key, JSON.stringify(ids)),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=pinnedChatsStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinnedChatsStore.js","sourceRoot":"../../../src","sources":["utils/pinnedChatsStore.ts"],"names":[],"mappings":"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","sourcesContent":["/** Persistence belongs to the application; only chat identifiers are stored. */\nexport interface PinnedChatsStorage {\n load: () => readonly string[] | Promise<readonly string[]>;\n save: (ids: readonly string[]) => void | Promise<void>;\n}\n\nexport interface PinnedChatsSnapshot {\n readonly pinnedChatIds: readonly string[];\n readonly isLoading: boolean;\n readonly error: unknown;\n}\n\nexport interface PinnedChatsStore {\n getSnapshot: () => PinnedChatsSnapshot;\n subscribe: (listener: () => void) => () => void;\n load: () => Promise<void>;\n setPinned: (id: string, pinned: boolean) => Promise<void>;\n}\n\nfunction validateIds(value: unknown): readonly string[] {\n if (!Array.isArray(value) || value.some((id) => typeof id !== 'string')) {\n throw new Error('Pinned chats must be an array of string identifiers');\n }\n return Object.freeze([...new Set<string>(value)]);\n}\n\n/**\n * Creates a store with application-provided persistence. Operations are serialized and only\n * successful saves update the snapshot. Errors are exposed in the snapshot; operations resolve.\n * Create a separate store for each account and keep its identity stable across renders.\n */\nexport function createPinnedChatsStore(storage: PinnedChatsStorage): PinnedChatsStore {\n let snapshot: PinnedChatsSnapshot = {\n pinnedChatIds: Object.freeze([]),\n isLoading: false,\n error: undefined,\n };\n let loaded = false;\n let pending = 0;\n let queue = Promise.resolve();\n const listeners = new Set<() => void>();\n const update = (patch: Partial<PinnedChatsSnapshot>) => {\n snapshot = {...snapshot, ...patch};\n listeners.forEach((listener) => listener());\n };\n const ensureLoaded = async () => {\n if (!loaded) {\n const pinnedChatIds = validateIds(await storage.load());\n loaded = true;\n update({pinnedChatIds});\n }\n };\n const enqueue = (operation: () => Promise<void>) => {\n pending += 1;\n update({isLoading: true});\n queue = queue.then(async () => {\n try {\n await operation();\n update({error: undefined});\n } catch (error) {\n update({error});\n } finally {\n pending -= 1;\n update({isLoading: pending > 0});\n }\n });\n return queue;\n };\n return {\n getSnapshot: () => snapshot,\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n load: () => enqueue(ensureLoaded),\n setPinned: (id, pinned) =>\n enqueue(async () => {\n await ensureLoaded();\n const current = snapshot.pinnedChatIds;\n if (current.includes(id) === pinned) return;\n const next = pinned ? [...current, id] : current.filter((item) => item !== id);\n await storage.save([...next]);\n update({pinnedChatIds: Object.freeze(next)});\n }),\n };\n}\n\n/** Browser storage is accessed lazily, so creation is safe during server rendering. */\nexport function createLocalStoragePinnedChatsStore(key: string): PinnedChatsStore {\n return createPinnedChatsStore({\n load: () => {\n const value = window.localStorage.getItem(key);\n return value === null ? [] : validateIds(JSON.parse(value));\n },\n save: (ids) => window.localStorage.setItem(key, JSON.stringify(ids)),\n });\n}\n"]}
|