@cossistant/react 0.0.13 → 0.0.16
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/hooks/index.d.ts +2 -1
- package/hooks/index.js +2 -1
- package/hooks/use-home-page.js +1 -1
- package/hooks/use-home-page.js.map +1 -1
- package/hooks/use-scroll-mask.d.ts +24 -0
- package/hooks/use-scroll-mask.d.ts.map +1 -0
- package/hooks/use-scroll-mask.js +90 -0
- package/hooks/use-scroll-mask.js.map +1 -0
- package/hooks/use-send-message.js +1 -1
- package/hooks/use-send-message.js.map +1 -1
- package/index.d.ts +7 -2
- package/index.js +9 -5
- package/package.json +3 -3
- package/parse.d.ts.map +1 -1
- package/primitives/bubble.d.ts +10 -2
- package/primitives/bubble.d.ts.map +1 -1
- package/primitives/bubble.js +11 -3
- package/primitives/bubble.js.map +1 -1
- package/primitives/conversation-timeline.d.ts.map +1 -1
- package/primitives/conversation-timeline.js +10 -20
- package/primitives/conversation-timeline.js.map +1 -1
- package/primitives/index.d.ts +5 -2
- package/primitives/index.js +11 -3
- package/primitives/index.parts.d.ts +4 -1
- package/primitives/index.parts.js +5 -2
- package/primitives/page-registry.d.ts +30 -0
- package/primitives/page-registry.d.ts.map +1 -0
- package/primitives/page-registry.js +45 -0
- package/primitives/page-registry.js.map +1 -0
- package/primitives/page.d.ts +21 -0
- package/primitives/page.d.ts.map +1 -0
- package/primitives/page.js +18 -0
- package/primitives/page.js.map +1 -0
- package/primitives/router.d.ts +35 -0
- package/primitives/router.d.ts.map +1 -0
- package/primitives/router.js +22 -0
- package/primitives/router.js.map +1 -0
- package/primitives/window.d.ts +8 -3
- package/primitives/window.d.ts.map +1 -1
- package/primitives/window.js +8 -3
- package/primitives/window.js.map +1 -1
- package/realtime/index.js +1 -1
- package/realtime/provider.js +1 -1
- package/realtime/support-provider.js +0 -4
- package/realtime/support-provider.js.map +1 -1
- package/realtime-events.d.ts +1 -1
- package/schemas2.d.ts.map +1 -1
- package/support/components/avatar-stack.js +1 -1
- package/support/components/avatar-stack.js.map +1 -1
- package/support/components/bubble.js +1 -1
- package/support/components/bubble.js.map +1 -1
- package/support/components/button.d.ts +1 -1
- package/support/components/button.js +3 -3
- package/support/components/button.js.map +1 -1
- package/support/components/container.js +1 -1
- package/support/components/container.js.map +1 -1
- package/support/components/conversation-timeline.js +1 -1
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/multimodal-input.js +2 -2
- package/support/components/multimodal-input.js.map +1 -1
- package/support/components/support-content.d.ts +5 -7
- package/support/components/support-content.d.ts.map +1 -1
- package/support/components/support-content.js +9 -11
- package/support/components/support-content.js.map +1 -1
- package/support/components/theme-wrapper.d.ts +15 -0
- package/support/components/theme-wrapper.d.ts.map +1 -0
- package/support/components/theme-wrapper.js +18 -0
- package/support/components/theme-wrapper.js.map +1 -0
- package/support/components/timeline-identification-tool.js +2 -2
- package/support/components/timeline-identification-tool.js.map +1 -1
- package/support/components/watermark.js +2 -2
- package/support/components/watermark.js.map +1 -1
- package/support/index.d.ts +36 -6
- package/support/index.d.ts.map +1 -1
- package/support/index.js +43 -20
- package/support/index.js.map +1 -1
- package/support/pages/articles.d.ts +4 -1
- package/support/pages/articles.d.ts.map +1 -1
- package/support/pages/articles.js +1 -1
- package/support/pages/articles.js.map +1 -1
- package/support/pages/conversation-history.d.ts +5 -10
- package/support/pages/conversation-history.d.ts.map +1 -1
- package/support/pages/conversation-history.js +2 -9
- package/support/pages/conversation-history.js.map +1 -1
- package/support/pages/conversation.d.ts +17 -12
- package/support/pages/conversation.d.ts.map +1 -1
- package/support/pages/conversation.js +5 -2
- package/support/pages/conversation.js.map +1 -1
- package/support/pages/home.d.ts +5 -12
- package/support/pages/home.d.ts.map +1 -1
- package/support/pages/home.js +3 -12
- package/support/pages/home.js.map +1 -1
- package/support/router.d.ts +9 -4
- package/support/router.d.ts.map +1 -1
- package/support/router.js +34 -15
- package/support/router.js.map +1 -1
- package/support/store/support-store.d.ts +17 -14
- package/support/store/support-store.d.ts.map +1 -1
- package/support/store/support-store.js +13 -10
- package/support/store/support-store.js.map +1 -1
- package/support/{support-DfYemt9I.css → support-BQhCt9Za.css} +80 -128
- package/support/support-BQhCt9Za.css.map +1 -0
- package/support/types.d.ts +28 -0
- package/support/types.d.ts.map +1 -0
- package/support/types.js +1 -0
- package/support.css +1 -1
- package/tailwind.css +79 -127
- package/utils/conversation.d.ts.map +1 -1
- package/utils/conversation.js +3 -1
- package/utils/conversation.js.map +1 -1
- package/utils/use-render-element.d.ts.map +1 -1
- package/zod-extensions.d.ts.map +1 -1
- package/index4.d.ts +0 -18
- package/index4.d.ts.map +0 -1
- package/index5.d.ts +0 -999
- package/index5.d.ts.map +0 -1
- package/index6.d.ts +0 -6
- package/react.d.ts +0 -4
- package/support/components/text-effect.d.ts +0 -53
- package/support/components/text-effect.d.ts.map +0 -1
- package/support/components/text-effect.js +0 -225
- package/support/components/text-effect.js.map +0 -1
- package/support/support-DfYemt9I.css.map +0 -1
- package/types.d-BJcRxCew.d.ts +0 -39
- package/types.d-BJcRxCew.d.ts.map +0 -1
package/hooks/index.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ import { CreateConversationVariables, UseCreateConversationOptions, UseCreateCon
|
|
|
19
19
|
import { UseHomePageOptions, UseHomePageReturn, useHomePage } from "./use-home-page.js";
|
|
20
20
|
import { UseMessageComposerOptions, UseMessageComposerReturn, useMessageComposer } from "./use-message-composer.js";
|
|
21
21
|
import { UseRealtimeSupportOptions, UseRealtimeSupportResult, useRealtimeSupport } from "./use-realtime-support.js";
|
|
22
|
+
import { UseScrollMaskOptions, UseScrollMaskReturn, useScrollMask } from "./use-scroll-mask.js";
|
|
22
23
|
import { SendMessageOptions, SendMessageResult, UseSendMessageOptions, UseSendMessageResult, useSendMessage } from "./use-send-message.js";
|
|
23
24
|
import { UseVisitorReturn, useVisitor } from "./use-visitor.js";
|
|
24
25
|
import { WindowVisibilityFocusState, useWindowVisibilityFocus } from "./use-window-visibility-focus.js";
|
|
25
|
-
export { CONVERSATION_AUTO_SEEN_DELAY_MS, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CreateConversationVariables, GroupedMessage, SendMessageOptions, SendMessageResult, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseSendMessageOptions, UseSendMessageResult, UseVisitorReturn, WindowVisibilityFocusState, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useRealtimeSupport, useSendMessage, useVisitor, useWindowVisibilityFocus };
|
|
26
|
+
export { CONVERSATION_AUTO_SEEN_DELAY_MS, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CreateConversationVariables, GroupedMessage, SendMessageOptions, SendMessageResult, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseScrollMaskOptions, UseScrollMaskReturn, UseSendMessageOptions, UseSendMessageResult, UseVisitorReturn, WindowVisibilityFocusState, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useRealtimeSupport, useScrollMask, useSendMessage, useVisitor, useWindowVisibilityFocus };
|
package/hooks/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useClientQuery } from "./private/use-client-query.js";
|
|
2
2
|
import { useClient } from "./private/use-rest-client.js";
|
|
3
|
+
import { useScrollMask } from "./use-scroll-mask.js";
|
|
3
4
|
import { useConversation } from "./use-conversation.js";
|
|
4
5
|
import { useWindowVisibilityFocus } from "./use-window-visibility-focus.js";
|
|
5
6
|
import { CONVERSATION_AUTO_SEEN_DELAY_MS, useConversationAutoSeen } from "./use-conversation-auto-seen.js";
|
|
@@ -23,4 +24,4 @@ import { useDefaultMessages } from "./private/use-default-messages.js";
|
|
|
23
24
|
import { useCreateConversation } from "./use-create-conversation.js";
|
|
24
25
|
import { useRealtimeSupport } from "./use-realtime-support.js";
|
|
25
26
|
|
|
26
|
-
export { CONVERSATION_AUTO_SEEN_DELAY_MS, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useRealtimeSupport, useSendMessage, useVisitor, useWindowVisibilityFocus };
|
|
27
|
+
export { CONVERSATION_AUTO_SEEN_DELAY_MS, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useRealtimeSupport, useScrollMask, useSendMessage, useVisitor, useWindowVisibilityFocus };
|
package/hooks/use-home-page.js
CHANGED
|
@@ -58,7 +58,7 @@ function useHomePage(options = {}) {
|
|
|
58
58
|
});
|
|
59
59
|
const conversations = useMemo(() => allConversations.filter(shouldDisplayConversation), [allConversations]);
|
|
60
60
|
const { lastOpenConversation, availableConversationsCount } = useMemo(() => {
|
|
61
|
-
const openConversation$1 = conversations.find((conv) => conv.status === ConversationStatus.OPEN
|
|
61
|
+
const openConversation$1 = conversations.find((conv) => conv.status === ConversationStatus.OPEN);
|
|
62
62
|
return {
|
|
63
63
|
lastOpenConversation: openConversation$1,
|
|
64
64
|
availableConversationsCount: Math.max(conversations.length - (openConversation$1 ? 1 : 0), 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-home-page.js","names":["openConversation"],"sources":["../../src/hooks/use-home-page.ts"],"sourcesContent":["import { type Conversation, ConversationStatus } from \"@cossistant/types\";\nimport { useCallback, useMemo } from \"react\";\nimport { shouldDisplayConversation } from \"../utils/conversation\";\nimport { useConversations } from \"./use-conversations\";\n\nexport type UseHomePageOptions = {\n\t/**\n\t * Whether to enable conversations fetching.\n\t * Default: true\n\t */\n\tenabled?: boolean;\n\n\t/**\n\t * Callback when user wants to start a new conversation.\n\t */\n\tonStartConversation?: (initialMessage?: string) => void;\n\n\t/**\n\t * Callback when user wants to open an existing conversation.\n\t */\n\tonOpenConversation?: (conversationId: string) => void;\n\n\t/**\n\t * Callback when user wants to view conversation history.\n\t */\n\tonOpenConversationHistory?: () => void;\n};\n\nexport type UseHomePageReturn = {\n\t// Conversations data\n\tconversations: Conversation[];\n\tisLoading: boolean;\n\terror: Error | null;\n\n\t// Derived state\n\tlastOpenConversation: Conversation | undefined;\n\tavailableConversationsCount: number;\n\thasConversations: boolean;\n\n\t// Actions\n\tstartConversation: (initialMessage?: string) => void;\n\topenConversation: (conversationId: string) => void;\n\topenConversationHistory: () => void;\n};\n\n/**\n * Main hook for the home page of the support widget.\n *\n * This hook:\n * - Fetches and manages conversations list\n * - Derives useful state (last open conversation, conversation counts)\n * - Provides navigation actions for the home page\n *\n * It encapsulates all home page logic, making the component\n * purely presentational.\n *\n * @example\n * ```tsx\n * export function HomePage() {\n * const home = useHomePage({\n * onStartConversation: (msg) => {\n * navigate('conversation', { conversationId: PENDING_CONVERSATION_ID, initialMessage: msg });\n * },\n * onOpenConversation: (id) => {\n * navigate('conversation', { conversationId: id });\n * },\n * onOpenConversationHistory: () => {\n * navigate('conversation-history');\n * },\n * });\n *\n * return (\n * <>\n * <h1>How can we help?</h1>\n *\n * {home.lastOpenConversation && (\n * <ConversationCard\n * conversation={home.lastOpenConversation}\n * onClick={() => home.openConversation(home.lastOpenConversation.id)}\n * />\n * )}\n *\n * <Button onClick={() => home.startConversation()}>\n * Ask a question\n * </Button>\n * </>\n * );\n * }\n * ```\n */\nexport function useHomePage(\n\toptions: UseHomePageOptions = {}\n): UseHomePageReturn {\n\tconst {\n\t\tenabled = true,\n\t\tonStartConversation,\n\t\tonOpenConversation,\n\t\tonOpenConversationHistory,\n\t} = options;\n\n\t// Fetch conversations\n\tconst {\n\t\tconversations: allConversations,\n\t\tisLoading,\n\t\terror,\n\t} = useConversations({\n\t\tenabled,\n\t\t// Fetch most recent conversations first\n\t\torderBy: \"updatedAt\",\n\t\torder: \"desc\",\n\t});\n\n\tconst conversations = useMemo(\n\t\t() => allConversations.filter(shouldDisplayConversation),\n\t\t[allConversations]\n\t);\n\n\t// Derive useful state from conversations\n\tconst { lastOpenConversation, availableConversationsCount } = useMemo(() => {\n\t\t// Find the most recent open conversation\n\t\tconst openConversation = conversations.find(\n\t\t\t(conv)
|
|
1
|
+
{"version":3,"file":"use-home-page.js","names":["openConversation"],"sources":["../../src/hooks/use-home-page.ts"],"sourcesContent":["import { type Conversation, ConversationStatus } from \"@cossistant/types\";\nimport { useCallback, useMemo } from \"react\";\nimport { shouldDisplayConversation } from \"../utils/conversation\";\nimport { useConversations } from \"./use-conversations\";\n\nexport type UseHomePageOptions = {\n\t/**\n\t * Whether to enable conversations fetching.\n\t * Default: true\n\t */\n\tenabled?: boolean;\n\n\t/**\n\t * Callback when user wants to start a new conversation.\n\t */\n\tonStartConversation?: (initialMessage?: string) => void;\n\n\t/**\n\t * Callback when user wants to open an existing conversation.\n\t */\n\tonOpenConversation?: (conversationId: string) => void;\n\n\t/**\n\t * Callback when user wants to view conversation history.\n\t */\n\tonOpenConversationHistory?: () => void;\n};\n\nexport type UseHomePageReturn = {\n\t// Conversations data\n\tconversations: Conversation[];\n\tisLoading: boolean;\n\terror: Error | null;\n\n\t// Derived state\n\tlastOpenConversation: Conversation | undefined;\n\tavailableConversationsCount: number;\n\thasConversations: boolean;\n\n\t// Actions\n\tstartConversation: (initialMessage?: string) => void;\n\topenConversation: (conversationId: string) => void;\n\topenConversationHistory: () => void;\n};\n\n/**\n * Main hook for the home page of the support widget.\n *\n * This hook:\n * - Fetches and manages conversations list\n * - Derives useful state (last open conversation, conversation counts)\n * - Provides navigation actions for the home page\n *\n * It encapsulates all home page logic, making the component\n * purely presentational.\n *\n * @example\n * ```tsx\n * export function HomePage() {\n * const home = useHomePage({\n * onStartConversation: (msg) => {\n * navigate('conversation', { conversationId: PENDING_CONVERSATION_ID, initialMessage: msg });\n * },\n * onOpenConversation: (id) => {\n * navigate('conversation', { conversationId: id });\n * },\n * onOpenConversationHistory: () => {\n * navigate('conversation-history');\n * },\n * });\n *\n * return (\n * <>\n * <h1>How can we help?</h1>\n *\n * {home.lastOpenConversation && (\n * <ConversationCard\n * conversation={home.lastOpenConversation}\n * onClick={() => home.openConversation(home.lastOpenConversation.id)}\n * />\n * )}\n *\n * <Button onClick={() => home.startConversation()}>\n * Ask a question\n * </Button>\n * </>\n * );\n * }\n * ```\n */\nexport function useHomePage(\n\toptions: UseHomePageOptions = {}\n): UseHomePageReturn {\n\tconst {\n\t\tenabled = true,\n\t\tonStartConversation,\n\t\tonOpenConversation,\n\t\tonOpenConversationHistory,\n\t} = options;\n\n\t// Fetch conversations\n\tconst {\n\t\tconversations: allConversations,\n\t\tisLoading,\n\t\terror,\n\t} = useConversations({\n\t\tenabled,\n\t\t// Fetch most recent conversations first\n\t\torderBy: \"updatedAt\",\n\t\torder: \"desc\",\n\t});\n\n\tconst conversations = useMemo(\n\t\t() => allConversations.filter(shouldDisplayConversation),\n\t\t[allConversations]\n\t);\n\n\t// Derive useful state from conversations\n\tconst { lastOpenConversation, availableConversationsCount } = useMemo(() => {\n\t\t// Find the most recent open conversation\n\t\tconst openConversation = conversations.find(\n\t\t\t(conv) => conv.status === ConversationStatus.OPEN\n\t\t);\n\n\t\t// Count other conversations (excluding the one we're showing)\n\t\tconst otherCount = Math.max(\n\t\t\tconversations.length - (openConversation ? 1 : 0),\n\t\t\t0\n\t\t);\n\n\t\treturn {\n\t\t\tlastOpenConversation: openConversation,\n\t\t\tavailableConversationsCount: otherCount,\n\t\t};\n\t}, [conversations]);\n\n\t// Navigation actions\n\tconst startConversation = useCallback(\n\t\t(initialMessage?: string) => {\n\t\t\tonStartConversation?.(initialMessage);\n\t\t},\n\t\t[onStartConversation]\n\t);\n\n\tconst openConversation = useCallback(\n\t\t(conversationId: string) => {\n\t\t\tonOpenConversation?.(conversationId);\n\t\t},\n\t\t[onOpenConversation]\n\t);\n\n\tconst openConversationHistory = useCallback(() => {\n\t\tonOpenConversationHistory?.();\n\t}, [onOpenConversationHistory]);\n\n\treturn {\n\t\tconversations,\n\t\tisLoading,\n\t\terror,\n\t\tlastOpenConversation,\n\t\tavailableConversationsCount,\n\t\thasConversations: conversations.length > 0,\n\t\tstartConversation,\n\t\topenConversation,\n\t\topenConversationHistory,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FA,SAAgB,YACf,UAA8B,EAAE,EACZ;CACpB,MAAM,EACL,UAAU,MACV,qBACA,oBACA,8BACG;CAGJ,MAAM,EACL,eAAe,kBACf,WACA,UACG,iBAAiB;EACpB;EAEA,SAAS;EACT,OAAO;EACP,CAAC;CAEF,MAAM,gBAAgB,cACf,iBAAiB,OAAO,0BAA0B,EACxD,CAAC,iBAAiB,CAClB;CAGD,MAAM,EAAE,sBAAsB,gCAAgC,cAAc;EAE3E,MAAMA,qBAAmB,cAAc,MACrC,SAAS,KAAK,WAAW,mBAAmB,KAC7C;AAQD,SAAO;GACN,sBAAsBA;GACtB,6BAPkB,KAAK,IACvB,cAAc,UAAUA,qBAAmB,IAAI,IAC/C,EACA;GAKA;IACC,CAAC,cAAc,CAAC;CAGnB,MAAM,oBAAoB,aACxB,mBAA4B;AAC5B,wBAAsB,eAAe;IAEtC,CAAC,oBAAoB,CACrB;CAED,MAAM,mBAAmB,aACvB,mBAA2B;AAC3B,uBAAqB,eAAe;IAErC,CAAC,mBAAmB,CACpB;CAED,MAAM,0BAA0B,kBAAkB;AACjD,+BAA6B;IAC3B,CAAC,0BAA0B,CAAC;AAE/B,QAAO;EACN;EACA;EACA;EACA;EACA;EACA,kBAAkB,cAAc,SAAS;EACzC;EACA;EACA;EACA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React$1 from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/use-scroll-mask.d.ts
|
|
4
|
+
type UseScrollMaskOptions = {
|
|
5
|
+
maskHeight?: string;
|
|
6
|
+
scrollbarWidth?: string;
|
|
7
|
+
topThreshold?: number;
|
|
8
|
+
bottomThreshold?: number;
|
|
9
|
+
};
|
|
10
|
+
type UseScrollMaskReturn = {
|
|
11
|
+
ref: React$1.RefObject<HTMLDivElement | null>;
|
|
12
|
+
style: React$1.CSSProperties;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Hook that provides dynamic scroll mask styles based on scroll position and scrollability.
|
|
16
|
+
* Only shows gradients when content is scrollable and when not at the edges.
|
|
17
|
+
*
|
|
18
|
+
* @param options - Configuration for mask appearance and scroll thresholds
|
|
19
|
+
* @returns Object with ref to attach to scrollable element and computed style object
|
|
20
|
+
*/
|
|
21
|
+
declare function useScrollMask(options?: UseScrollMaskOptions): UseScrollMaskReturn;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { UseScrollMaskOptions, UseScrollMaskReturn, useScrollMask };
|
|
24
|
+
//# sourceMappingURL=use-scroll-mask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-scroll-mask.d.ts","names":[],"sources":["../../src/hooks/use-scroll-mask.ts"],"sourcesContent":[],"mappings":";;;KAEY,oBAAA;;EAAA,cAAA,CAAA,EAAA,MAAoB;EAOpB,YAAA,CAAA,EAAA,MAAA;EACU,eAAA,CAAA,EAAA,MAAA;CAAhB;AACE,KAFI,mBAAA,GAEE;EAAa,GAAA,EADrB,OAAA,CAAM,SACe,CADL,cACK,GAAA,IAAA,CAAA;EAUX,KAAA,EAVR,OAAA,CAAM,aAUe;;;;;;;;;iBAAb,aAAA,WACN,uBACP"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as React$1 from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/use-scroll-mask.ts
|
|
4
|
+
/**
|
|
5
|
+
* Hook that provides dynamic scroll mask styles based on scroll position and scrollability.
|
|
6
|
+
* Only shows gradients when content is scrollable and when not at the edges.
|
|
7
|
+
*
|
|
8
|
+
* @param options - Configuration for mask appearance and scroll thresholds
|
|
9
|
+
* @returns Object with ref to attach to scrollable element and computed style object
|
|
10
|
+
*/
|
|
11
|
+
function useScrollMask(options = {}) {
|
|
12
|
+
const { maskHeight = "54px", scrollbarWidth = "12px", topThreshold = 2, bottomThreshold = 12 } = options;
|
|
13
|
+
const ref = React$1.useRef(null);
|
|
14
|
+
const [scrollState, setScrollState] = React$1.useState({
|
|
15
|
+
isScrollable: false,
|
|
16
|
+
isAtTop: true,
|
|
17
|
+
isAtBottom: false
|
|
18
|
+
});
|
|
19
|
+
const updateScrollState = React$1.useCallback(() => {
|
|
20
|
+
const element = ref.current;
|
|
21
|
+
if (!element) return;
|
|
22
|
+
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
23
|
+
setScrollState({
|
|
24
|
+
isScrollable: scrollHeight > clientHeight,
|
|
25
|
+
isAtTop: scrollTop <= topThreshold,
|
|
26
|
+
isAtBottom: scrollHeight - scrollTop - clientHeight <= bottomThreshold
|
|
27
|
+
});
|
|
28
|
+
}, [topThreshold, bottomThreshold]);
|
|
29
|
+
React$1.useEffect(() => {
|
|
30
|
+
const element = ref.current;
|
|
31
|
+
if (!element) return;
|
|
32
|
+
element.addEventListener("scroll", updateScrollState, { passive: true });
|
|
33
|
+
return () => {
|
|
34
|
+
element.removeEventListener("scroll", updateScrollState);
|
|
35
|
+
};
|
|
36
|
+
}, [updateScrollState]);
|
|
37
|
+
React$1.useEffect(() => {
|
|
38
|
+
const element = ref.current;
|
|
39
|
+
if (!element) return;
|
|
40
|
+
updateScrollState();
|
|
41
|
+
const resizeObserver = new ResizeObserver(updateScrollState);
|
|
42
|
+
resizeObserver.observe(element);
|
|
43
|
+
const mutationObserver = new MutationObserver(updateScrollState);
|
|
44
|
+
mutationObserver.observe(element, {
|
|
45
|
+
childList: true,
|
|
46
|
+
subtree: true,
|
|
47
|
+
characterData: true
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
resizeObserver.disconnect();
|
|
51
|
+
mutationObserver.disconnect();
|
|
52
|
+
};
|
|
53
|
+
}, [updateScrollState]);
|
|
54
|
+
return {
|
|
55
|
+
ref,
|
|
56
|
+
style: React$1.useMemo(() => {
|
|
57
|
+
const { isScrollable, isAtTop, isAtBottom } = scrollState;
|
|
58
|
+
if (!isScrollable) return {};
|
|
59
|
+
const showTopGradient = !isAtTop;
|
|
60
|
+
const showBottomGradient = !isAtBottom;
|
|
61
|
+
let gradientStops;
|
|
62
|
+
if (showTopGradient && showBottomGradient) gradientStops = `transparent, black ${maskHeight}, black calc(100% - ${maskHeight}), transparent`;
|
|
63
|
+
else if (showTopGradient && !showBottomGradient) gradientStops = `transparent, black ${maskHeight}, black`;
|
|
64
|
+
else if (!showTopGradient && showBottomGradient) gradientStops = `black, black calc(100% - ${maskHeight}), transparent`;
|
|
65
|
+
else return {};
|
|
66
|
+
const maskImage = `linear-gradient(to bottom, ${gradientStops}), linear-gradient(black, black)`;
|
|
67
|
+
const maskSize = `calc(100% - ${scrollbarWidth}) 100%, ${scrollbarWidth} 100%`;
|
|
68
|
+
const maskPosition = "0 0, 100% 0";
|
|
69
|
+
const maskRepeat = "no-repeat, no-repeat";
|
|
70
|
+
return {
|
|
71
|
+
maskImage,
|
|
72
|
+
maskSize,
|
|
73
|
+
maskPosition,
|
|
74
|
+
maskRepeat,
|
|
75
|
+
WebkitMaskImage: maskImage,
|
|
76
|
+
WebkitMaskSize: maskSize,
|
|
77
|
+
WebkitMaskPosition: maskPosition,
|
|
78
|
+
WebkitMaskRepeat: maskRepeat
|
|
79
|
+
};
|
|
80
|
+
}, [
|
|
81
|
+
scrollState,
|
|
82
|
+
maskHeight,
|
|
83
|
+
scrollbarWidth
|
|
84
|
+
])
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
export { useScrollMask };
|
|
90
|
+
//# sourceMappingURL=use-scroll-mask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-scroll-mask.js","names":["React","gradientStops: string"],"sources":["../../src/hooks/use-scroll-mask.ts"],"sourcesContent":["import * as React from \"react\";\n\nexport type UseScrollMaskOptions = {\n\tmaskHeight?: string;\n\tscrollbarWidth?: string;\n\ttopThreshold?: number;\n\tbottomThreshold?: number;\n};\n\nexport type UseScrollMaskReturn = {\n\tref: React.RefObject<HTMLDivElement | null>;\n\tstyle: React.CSSProperties;\n};\n\n/**\n * Hook that provides dynamic scroll mask styles based on scroll position and scrollability.\n * Only shows gradients when content is scrollable and when not at the edges.\n *\n * @param options - Configuration for mask appearance and scroll thresholds\n * @returns Object with ref to attach to scrollable element and computed style object\n */\nexport function useScrollMask(\n\toptions: UseScrollMaskOptions = {}\n): UseScrollMaskReturn {\n\tconst {\n\t\tmaskHeight = \"54px\",\n\t\tscrollbarWidth = \"12px\",\n\t\ttopThreshold = 2,\n\t\tbottomThreshold = 12,\n\t} = options;\n\n\tconst ref = React.useRef<HTMLDivElement>(null);\n\tconst [scrollState, setScrollState] = React.useState({\n\t\tisScrollable: false,\n\t\tisAtTop: true,\n\t\tisAtBottom: false,\n\t});\n\n\t// Check scrollability and position\n\tconst updateScrollState = React.useCallback(() => {\n\t\tconst element = ref.current;\n\t\tif (!element) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { scrollTop, scrollHeight, clientHeight } = element;\n\t\tconst isScrollable = scrollHeight > clientHeight;\n\t\tconst isAtTop = scrollTop <= topThreshold;\n\t\tconst distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n\t\tconst isAtBottom = distanceFromBottom <= bottomThreshold;\n\n\t\tsetScrollState({\n\t\t\tisScrollable,\n\t\t\tisAtTop,\n\t\t\tisAtBottom,\n\t\t});\n\t}, [topThreshold, bottomThreshold]);\n\n\t// Update on scroll\n\tReact.useEffect(() => {\n\t\tconst element = ref.current;\n\t\tif (!element) {\n\t\t\treturn;\n\t\t}\n\n\t\telement.addEventListener(\"scroll\", updateScrollState, { passive: true });\n\t\treturn () => {\n\t\t\telement.removeEventListener(\"scroll\", updateScrollState);\n\t\t};\n\t}, [updateScrollState]);\n\n\t// Update on resize or content changes\n\tReact.useEffect(() => {\n\t\tconst element = ref.current;\n\t\tif (!element) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Initial check\n\t\tupdateScrollState();\n\n\t\tconst resizeObserver = new ResizeObserver(updateScrollState);\n\t\tresizeObserver.observe(element);\n\n\t\t// Also observe children changes (content updates)\n\t\tconst mutationObserver = new MutationObserver(updateScrollState);\n\t\tmutationObserver.observe(element, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t\tcharacterData: true,\n\t\t});\n\n\t\treturn () => {\n\t\t\tresizeObserver.disconnect();\n\t\t\tmutationObserver.disconnect();\n\t\t};\n\t}, [updateScrollState]);\n\n\t// Generate mask styles based on scroll state\n\tconst style = React.useMemo<React.CSSProperties>(() => {\n\t\tconst { isScrollable, isAtTop, isAtBottom } = scrollState;\n\n\t\t// No mask if content isn't scrollable\n\t\tif (!isScrollable) {\n\t\t\treturn {};\n\t\t}\n\n\t\t// Determine which gradients to show\n\t\tconst showTopGradient = !isAtTop;\n\t\tconst showBottomGradient = !isAtBottom;\n\n\t\t// Build gradient stops\n\t\tlet gradientStops: string;\n\t\tif (showTopGradient && showBottomGradient) {\n\t\t\t// Both gradients (middle of scroll)\n\t\t\tgradientStops = `transparent, black ${maskHeight}, black calc(100% - ${maskHeight}), transparent`;\n\t\t} else if (showTopGradient && !showBottomGradient) {\n\t\t\t// Only top gradient (at bottom)\n\t\t\tgradientStops = `transparent, black ${maskHeight}, black`;\n\t\t} else if (!showTopGradient && showBottomGradient) {\n\t\t\t// Only bottom gradient (at top)\n\t\t\tgradientStops = `black, black calc(100% - ${maskHeight}), transparent`;\n\t\t} else {\n\t\t\t// No gradients (shouldn't happen if scrollable, but handle it)\n\t\t\treturn {};\n\t\t}\n\n\t\tconst maskImage = `linear-gradient(to bottom, ${gradientStops}), linear-gradient(black, black)`;\n\t\tconst maskSize = `calc(100% - ${scrollbarWidth}) 100%, ${scrollbarWidth} 100%`;\n\t\tconst maskPosition = \"0 0, 100% 0\";\n\t\tconst maskRepeat = \"no-repeat, no-repeat\";\n\n\t\treturn {\n\t\t\tmaskImage,\n\t\t\tmaskSize,\n\t\t\tmaskPosition,\n\t\t\tmaskRepeat,\n\t\t\tWebkitMaskImage: maskImage,\n\t\t\tWebkitMaskSize: maskSize,\n\t\t\tWebkitMaskPosition: maskPosition,\n\t\t\tWebkitMaskRepeat: maskRepeat,\n\t\t};\n\t}, [scrollState, maskHeight, scrollbarWidth]);\n\n\treturn { ref, style };\n}\n"],"mappings":";;;;;;;;;;AAqBA,SAAgB,cACf,UAAgC,EAAE,EACZ;CACtB,MAAM,EACL,aAAa,QACb,iBAAiB,QACjB,eAAe,GACf,kBAAkB,OACf;CAEJ,MAAM,MAAMA,QAAM,OAAuB,KAAK;CAC9C,MAAM,CAAC,aAAa,kBAAkBA,QAAM,SAAS;EACpD,cAAc;EACd,SAAS;EACT,YAAY;EACZ,CAAC;CAGF,MAAM,oBAAoBA,QAAM,kBAAkB;EACjD,MAAM,UAAU,IAAI;AACpB,MAAI,CAAC,QACJ;EAGD,MAAM,EAAE,WAAW,cAAc,iBAAiB;AAMlD,iBAAe;GACd,cANoB,eAAe;GAOnC,SANe,aAAa;GAO5B,YAN0B,eAAe,YAAY,gBACb;GAMxC,CAAC;IACA,CAAC,cAAc,gBAAgB,CAAC;AAGnC,SAAM,gBAAgB;EACrB,MAAM,UAAU,IAAI;AACpB,MAAI,CAAC,QACJ;AAGD,UAAQ,iBAAiB,UAAU,mBAAmB,EAAE,SAAS,MAAM,CAAC;AACxE,eAAa;AACZ,WAAQ,oBAAoB,UAAU,kBAAkB;;IAEvD,CAAC,kBAAkB,CAAC;AAGvB,SAAM,gBAAgB;EACrB,MAAM,UAAU,IAAI;AACpB,MAAI,CAAC,QACJ;AAID,qBAAmB;EAEnB,MAAM,iBAAiB,IAAI,eAAe,kBAAkB;AAC5D,iBAAe,QAAQ,QAAQ;EAG/B,MAAM,mBAAmB,IAAI,iBAAiB,kBAAkB;AAChE,mBAAiB,QAAQ,SAAS;GACjC,WAAW;GACX,SAAS;GACT,eAAe;GACf,CAAC;AAEF,eAAa;AACZ,kBAAe,YAAY;AAC3B,oBAAiB,YAAY;;IAE5B,CAAC,kBAAkB,CAAC;AAgDvB,QAAO;EAAE;EAAK,OA7CAA,QAAM,cAAmC;GACtD,MAAM,EAAE,cAAc,SAAS,eAAe;AAG9C,OAAI,CAAC,aACJ,QAAO,EAAE;GAIV,MAAM,kBAAkB,CAAC;GACzB,MAAM,qBAAqB,CAAC;GAG5B,IAAIC;AACJ,OAAI,mBAAmB,mBAEtB,iBAAgB,sBAAsB,WAAW,sBAAsB,WAAW;YACxE,mBAAmB,CAAC,mBAE9B,iBAAgB,sBAAsB,WAAW;YACvC,CAAC,mBAAmB,mBAE9B,iBAAgB,4BAA4B,WAAW;OAGvD,QAAO,EAAE;GAGV,MAAM,YAAY,8BAA8B,cAAc;GAC9D,MAAM,WAAW,eAAe,eAAe,UAAU,eAAe;GACxE,MAAM,eAAe;GACrB,MAAM,aAAa;AAEnB,UAAO;IACN;IACA;IACA;IACA;IACA,iBAAiB;IACjB,gBAAgB;IAChB,oBAAoB;IACpB,kBAAkB;IAClB;KACC;GAAC;GAAa;GAAY;GAAe,CAAC;EAExB"}
|
|
@@ -66,7 +66,7 @@ function useSendMessage(options = {}) {
|
|
|
66
66
|
item: {
|
|
67
67
|
id: timelineItemPayload.id,
|
|
68
68
|
text: timelineItemPayload.text ?? "",
|
|
69
|
-
type: timelineItemPayload.type,
|
|
69
|
+
type: timelineItemPayload.type === "identification" ? "message" : timelineItemPayload.type,
|
|
70
70
|
visibility: timelineItemPayload.visibility,
|
|
71
71
|
userId: timelineItemPayload.userId,
|
|
72
72
|
aiAgentId: timelineItemPayload.aiAgentId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-send-message.js","names":["initialConversation:\n\t\t\t\t\t| CreateConversationResponseBody[\"conversation\"]\n\t\t\t\t\t| undefined","result: SendMessageResult"],"sources":["../../src/hooks/use-send-message.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { generateMessageId } from \"@cossistant/core\";\nimport type { CreateConversationResponseBody } from \"@cossistant/types/api/conversation\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useCallback, useState } from \"react\";\n\nimport { useSupport } from \"../provider\";\n\nexport type SendMessageOptions = {\n\tconversationId?: string | null;\n\tmessage: string;\n\tfiles?: File[];\n\tdefaultTimelineItems?: TimelineItem[];\n\tvisitorId?: string;\n\t/**\n\t * Optional message ID to use for the optimistic update and API request.\n\t * When not provided, a ULID will be generated on the client.\n\t */\n\tmessageId?: string;\n\tonSuccess?: (conversationId: string, messageId: string) => void;\n\tonError?: (error: Error) => void;\n};\n\nexport type SendMessageResult = {\n\tconversationId: string;\n\tmessageId: string;\n\tconversation?: CreateConversationResponseBody[\"conversation\"];\n\tinitialTimelineItems?: CreateConversationResponseBody[\"initialTimelineItems\"];\n};\n\nexport type UseSendMessageResult = {\n\tmutate: (options: SendMessageOptions) => void;\n\tmutateAsync: (\n\t\toptions: SendMessageOptions\n\t) => Promise<SendMessageResult | null>;\n\tisPending: boolean;\n\terror: Error | null;\n\treset: () => void;\n};\n\nexport type UseSendMessageOptions = {\n\tclient?: CossistantClient;\n};\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\tif (typeof error === \"string\") {\n\t\treturn new Error(error);\n\t}\n\n\treturn new Error(\"Unknown error\");\n}\n\nfunction buildTimelineItemPayload(\n\tbody: string,\n\tconversationId: string,\n\tvisitorId: string | null,\n\tmessageId?: string\n): TimelineItem {\n\tconst nowIso = new Date().toISOString();\n\tconst id = messageId ?? generateMessageId();\n\n\treturn {\n\t\tid,\n\t\tconversationId,\n\t\torganizationId: \"\", // Will be set by backend\n\t\ttype: \"message\" as const,\n\t\ttext: body,\n\t\tparts: [{ type: \"text\" as const, text: body }],\n\t\tvisibility: \"public\" as const,\n\t\tuserId: null,\n\t\taiAgentId: null,\n\t\tvisitorId: visitorId ?? null,\n\t\tcreatedAt: nowIso,\n\t\tdeletedAt: null,\n\t} satisfies TimelineItem;\n}\n\n/**\n * Sends visitor messages while handling optimistic pending conversations and\n * exposing react-query-like mutation state.\n */\nexport function useSendMessage(\n\toptions: UseSendMessageOptions = {}\n): UseSendMessageResult {\n\tconst { client: contextClient } = useSupport();\n\tconst client = options.client ?? contextClient;\n\n\tconst [isPending, setIsPending] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst mutateAsync = useCallback(\n\t\tasync (payload: SendMessageOptions): Promise<SendMessageResult | null> => {\n\t\t\tconst {\n\t\t\t\tconversationId: providedConversationId,\n\t\t\t\tmessage,\n\t\t\t\tdefaultTimelineItems = [],\n\t\t\t\tvisitorId,\n\t\t\t\tmessageId: providedMessageId,\n\t\t\t\tonSuccess,\n\t\t\t\tonError,\n\t\t\t} = payload;\n\n\t\t\tif (!message.trim()) {\n\t\t\t\tconst emptyMessageError = new Error(\"Message cannot be empty\");\n\t\t\t\tsetError(emptyMessageError);\n\t\t\t\tonError?.(emptyMessageError);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tsetIsPending(true);\n\t\t\tsetError(null);\n\n\t\t\ttry {\n\t\t\t\tlet conversationId = providedConversationId ?? undefined;\n\t\t\t\tlet preparedDefaultTimelineItems = defaultTimelineItems;\n\t\t\t\tlet initialConversation:\n\t\t\t\t\t| CreateConversationResponseBody[\"conversation\"]\n\t\t\t\t\t| undefined;\n\n\t\t\t\tif (!conversationId) {\n\t\t\t\t\tconst initiated = client.initiateConversation({\n\t\t\t\t\t\tdefaultTimelineItems,\n\t\t\t\t\t\tvisitorId: visitorId ?? undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconversationId = initiated.conversationId;\n\t\t\t\t\tpreparedDefaultTimelineItems = initiated.defaultTimelineItems;\n\t\t\t\t\tinitialConversation = initiated.conversation;\n\t\t\t\t}\n\n\t\t\t\tconst timelineItemPayload = buildTimelineItemPayload(\n\t\t\t\t\tmessage,\n\t\t\t\t\tconversationId,\n\t\t\t\t\tvisitorId ?? null,\n\t\t\t\t\tprovidedMessageId\n\t\t\t\t);\n\n\t\t\
|
|
1
|
+
{"version":3,"file":"use-send-message.js","names":["initialConversation:\n\t\t\t\t\t| CreateConversationResponseBody[\"conversation\"]\n\t\t\t\t\t| undefined","result: SendMessageResult"],"sources":["../../src/hooks/use-send-message.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport { generateMessageId } from \"@cossistant/core\";\nimport type { CreateConversationResponseBody } from \"@cossistant/types/api/conversation\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useCallback, useState } from \"react\";\n\nimport { useSupport } from \"../provider\";\n\nexport type SendMessageOptions = {\n\tconversationId?: string | null;\n\tmessage: string;\n\tfiles?: File[];\n\tdefaultTimelineItems?: TimelineItem[];\n\tvisitorId?: string;\n\t/**\n\t * Optional message ID to use for the optimistic update and API request.\n\t * When not provided, a ULID will be generated on the client.\n\t */\n\tmessageId?: string;\n\tonSuccess?: (conversationId: string, messageId: string) => void;\n\tonError?: (error: Error) => void;\n};\n\nexport type SendMessageResult = {\n\tconversationId: string;\n\tmessageId: string;\n\tconversation?: CreateConversationResponseBody[\"conversation\"];\n\tinitialTimelineItems?: CreateConversationResponseBody[\"initialTimelineItems\"];\n};\n\nexport type UseSendMessageResult = {\n\tmutate: (options: SendMessageOptions) => void;\n\tmutateAsync: (\n\t\toptions: SendMessageOptions\n\t) => Promise<SendMessageResult | null>;\n\tisPending: boolean;\n\terror: Error | null;\n\treset: () => void;\n};\n\nexport type UseSendMessageOptions = {\n\tclient?: CossistantClient;\n};\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\n\tif (typeof error === \"string\") {\n\t\treturn new Error(error);\n\t}\n\n\treturn new Error(\"Unknown error\");\n}\n\nfunction buildTimelineItemPayload(\n\tbody: string,\n\tconversationId: string,\n\tvisitorId: string | null,\n\tmessageId?: string\n): TimelineItem {\n\tconst nowIso = new Date().toISOString();\n\tconst id = messageId ?? generateMessageId();\n\n\treturn {\n\t\tid,\n\t\tconversationId,\n\t\torganizationId: \"\", // Will be set by backend\n\t\ttype: \"message\" as const,\n\t\ttext: body,\n\t\tparts: [{ type: \"text\" as const, text: body }],\n\t\tvisibility: \"public\" as const,\n\t\tuserId: null,\n\t\taiAgentId: null,\n\t\tvisitorId: visitorId ?? null,\n\t\tcreatedAt: nowIso,\n\t\tdeletedAt: null,\n\t} satisfies TimelineItem;\n}\n\n/**\n * Sends visitor messages while handling optimistic pending conversations and\n * exposing react-query-like mutation state.\n */\nexport function useSendMessage(\n\toptions: UseSendMessageOptions = {}\n): UseSendMessageResult {\n\tconst { client: contextClient } = useSupport();\n\tconst client = options.client ?? contextClient;\n\n\tconst [isPending, setIsPending] = useState(false);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst mutateAsync = useCallback(\n\t\tasync (payload: SendMessageOptions): Promise<SendMessageResult | null> => {\n\t\t\tconst {\n\t\t\t\tconversationId: providedConversationId,\n\t\t\t\tmessage,\n\t\t\t\tdefaultTimelineItems = [],\n\t\t\t\tvisitorId,\n\t\t\t\tmessageId: providedMessageId,\n\t\t\t\tonSuccess,\n\t\t\t\tonError,\n\t\t\t} = payload;\n\n\t\t\tif (!message.trim()) {\n\t\t\t\tconst emptyMessageError = new Error(\"Message cannot be empty\");\n\t\t\t\tsetError(emptyMessageError);\n\t\t\t\tonError?.(emptyMessageError);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tsetIsPending(true);\n\t\t\tsetError(null);\n\n\t\t\ttry {\n\t\t\t\tlet conversationId = providedConversationId ?? undefined;\n\t\t\t\tlet preparedDefaultTimelineItems = defaultTimelineItems;\n\t\t\t\tlet initialConversation:\n\t\t\t\t\t| CreateConversationResponseBody[\"conversation\"]\n\t\t\t\t\t| undefined;\n\n\t\t\t\tif (!conversationId) {\n\t\t\t\t\tconst initiated = client.initiateConversation({\n\t\t\t\t\t\tdefaultTimelineItems,\n\t\t\t\t\t\tvisitorId: visitorId ?? undefined,\n\t\t\t\t\t});\n\t\t\t\t\tconversationId = initiated.conversationId;\n\t\t\t\t\tpreparedDefaultTimelineItems = initiated.defaultTimelineItems;\n\t\t\t\t\tinitialConversation = initiated.conversation;\n\t\t\t\t}\n\n\t\t\t\tconst timelineItemPayload = buildTimelineItemPayload(\n\t\t\t\t\tmessage,\n\t\t\t\t\tconversationId,\n\t\t\t\t\tvisitorId ?? null,\n\t\t\t\t\tprovidedMessageId\n\t\t\t\t);\n\n\t\t\tconst response = await client.sendMessage({\n\t\t\t\tconversationId,\n\t\t\t\titem: {\n\t\t\t\t\tid: timelineItemPayload.id,\n\t\t\t\t\ttext: timelineItemPayload.text ?? \"\",\n\t\t\t\t\ttype:\n\t\t\t\t\t\ttimelineItemPayload.type === \"identification\"\n\t\t\t\t\t\t\t? \"message\"\n\t\t\t\t\t\t\t: timelineItemPayload.type,\n\t\t\t\t\tvisibility: timelineItemPayload.visibility,\n\t\t\t\t\tuserId: timelineItemPayload.userId,\n\t\t\t\t\taiAgentId: timelineItemPayload.aiAgentId,\n\t\t\t\t\tvisitorId: timelineItemPayload.visitorId,\n\t\t\t\t\tcreatedAt: timelineItemPayload.createdAt,\n\t\t\t\t\tparts: timelineItemPayload.parts,\n\t\t\t\t},\n\t\t\t\tcreateIfPending: true,\n\t\t\t});\n\n\t\t\t\tconst messageId = response.item.id;\n\n\t\t\t\tif (!messageId) {\n\t\t\t\t\tthrow new Error(\"SendMessage response missing item.id\");\n\t\t\t\t}\n\n\t\t\t\tconst result: SendMessageResult = {\n\t\t\t\t\tconversationId,\n\t\t\t\t\tmessageId,\n\t\t\t\t};\n\n\t\t\t\tif (\"conversation\" in response && response.conversation) {\n\t\t\t\t\tresult.conversation = response.conversation;\n\t\t\t\t\tresult.initialTimelineItems = response.initialTimelineItems;\n\t\t\t\t} else if (initialConversation) {\n\t\t\t\t\tresult.conversation = initialConversation;\n\t\t\t\t\tresult.initialTimelineItems = preparedDefaultTimelineItems;\n\t\t\t\t}\n\n\t\t\t\tsetIsPending(false);\n\t\t\t\tsetError(null);\n\t\t\t\tonSuccess?.(result.conversationId, result.messageId);\n\t\t\t\treturn result;\n\t\t\t} catch (raw) {\n\t\t\t\tconst normalised = toError(raw);\n\t\t\t\tsetIsPending(false);\n\t\t\t\tsetError(normalised);\n\t\t\t\tonError?.(normalised);\n\t\t\t\tthrow normalised;\n\t\t\t}\n\t\t},\n\t\t[client]\n\t);\n\n\tconst mutate = useCallback(\n\t\t(opts: SendMessageOptions) => {\n\t\t\tvoid mutateAsync(opts).catch(() => {\n\t\t\t\t// Swallow errors to mimic react-query behaviour for mutate\n\t\t\t});\n\t\t},\n\t\t[mutateAsync]\n\t);\n\n\tconst reset = useCallback(() => {\n\t\tsetError(null);\n\t\tsetIsPending(false);\n\t}, []);\n\n\treturn {\n\t\tmutate,\n\t\tmutateAsync,\n\t\tisPending,\n\t\terror,\n\t\treset,\n\t};\n}\n"],"mappings":";;;;;AA4CA,SAAS,QAAQ,OAAuB;AACvC,KAAI,iBAAiB,MACpB,QAAO;AAGR,KAAI,OAAO,UAAU,SACpB,QAAO,IAAI,MAAM,MAAM;AAGxB,wBAAO,IAAI,MAAM,gBAAgB;;AAGlC,SAAS,yBACR,MACA,gBACA,WACA,WACe;CACf,MAAM,0BAAS,IAAI,MAAM,EAAC,aAAa;AAGvC,QAAO;EACN,IAHU,aAAa,mBAAmB;EAI1C;EACA,gBAAgB;EAChB,MAAM;EACN,MAAM;EACN,OAAO,CAAC;GAAE,MAAM;GAAiB,MAAM;GAAM,CAAC;EAC9C,YAAY;EACZ,QAAQ;EACR,WAAW;EACX,WAAW,aAAa;EACxB,WAAW;EACX,WAAW;EACX;;;;;;AAOF,SAAgB,eACf,UAAiC,EAAE,EACZ;CACvB,MAAM,EAAE,QAAQ,kBAAkB,YAAY;CAC9C,MAAM,SAAS,QAAQ,UAAU;CAEjC,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,CAAC,OAAO,YAAY,SAAuB,KAAK;CAEtD,MAAM,cAAc,YACnB,OAAO,YAAmE;EACzE,MAAM,EACL,gBAAgB,wBAChB,SACA,uBAAuB,EAAE,EACzB,WACA,WAAW,mBACX,WACA,YACG;AAEJ,MAAI,CAAC,QAAQ,MAAM,EAAE;GACpB,MAAM,oCAAoB,IAAI,MAAM,0BAA0B;AAC9D,YAAS,kBAAkB;AAC3B,aAAU,kBAAkB;AAC5B,UAAO;;AAGR,eAAa,KAAK;AAClB,WAAS,KAAK;AAEd,MAAI;GACH,IAAI,iBAAiB,0BAA0B;GAC/C,IAAI,+BAA+B;GACnC,IAAIA;AAIJ,OAAI,CAAC,gBAAgB;IACpB,MAAM,YAAY,OAAO,qBAAqB;KAC7C;KACA,WAAW,aAAa;KACxB,CAAC;AACF,qBAAiB,UAAU;AAC3B,mCAA+B,UAAU;AACzC,0BAAsB,UAAU;;GAGjC,MAAM,sBAAsB,yBAC3B,SACA,gBACA,aAAa,MACb,kBACA;GAEF,MAAM,WAAW,MAAM,OAAO,YAAY;IACzC;IACA,MAAM;KACL,IAAI,oBAAoB;KACxB,MAAM,oBAAoB,QAAQ;KAClC,MACC,oBAAoB,SAAS,mBAC1B,YACA,oBAAoB;KACxB,YAAY,oBAAoB;KAChC,QAAQ,oBAAoB;KAC5B,WAAW,oBAAoB;KAC/B,WAAW,oBAAoB;KAC/B,WAAW,oBAAoB;KAC/B,OAAO,oBAAoB;KAC3B;IACD,iBAAiB;IACjB,CAAC;GAED,MAAM,YAAY,SAAS,KAAK;AAEhC,OAAI,CAAC,UACJ,OAAM,IAAI,MAAM,uCAAuC;GAGxD,MAAMC,SAA4B;IACjC;IACA;IACA;AAED,OAAI,kBAAkB,YAAY,SAAS,cAAc;AACxD,WAAO,eAAe,SAAS;AAC/B,WAAO,uBAAuB,SAAS;cAC7B,qBAAqB;AAC/B,WAAO,eAAe;AACtB,WAAO,uBAAuB;;AAG/B,gBAAa,MAAM;AACnB,YAAS,KAAK;AACd,eAAY,OAAO,gBAAgB,OAAO,UAAU;AACpD,UAAO;WACC,KAAK;GACb,MAAM,aAAa,QAAQ,IAAI;AAC/B,gBAAa,MAAM;AACnB,YAAS,WAAW;AACpB,aAAU,WAAW;AACrB,SAAM;;IAGR,CAAC,OAAO,CACR;AAgBD,QAAO;EACN,QAfc,aACb,SAA6B;AAC7B,GAAK,YAAY,KAAK,CAAC,YAAY,GAEjC;KAEH,CAAC,YAAY,CACb;EASA;EACA;EACA;EACA,OAVa,kBAAkB;AAC/B,YAAS,KAAK;AACd,gBAAa,MAAM;KACjB,EAAE,CAAC;EAQL"}
|
package/index.d.ts
CHANGED
|
@@ -20,12 +20,14 @@ import { CreateConversationVariables, UseCreateConversationOptions, UseCreateCon
|
|
|
20
20
|
import { UseHomePageOptions, UseHomePageReturn, useHomePage } from "./hooks/use-home-page.js";
|
|
21
21
|
import { UseMessageComposerOptions, UseMessageComposerReturn, useMessageComposer } from "./hooks/use-message-composer.js";
|
|
22
22
|
import { UseRealtimeSupportOptions, UseRealtimeSupportResult, useRealtimeSupport } from "./hooks/use-realtime-support.js";
|
|
23
|
+
import { UseScrollMaskOptions, UseScrollMaskReturn, useScrollMask } from "./hooks/use-scroll-mask.js";
|
|
23
24
|
import { SendMessageOptions, SendMessageResult, UseSendMessageOptions, UseSendMessageResult, useSendMessage } from "./hooks/use-send-message.js";
|
|
24
25
|
import { UseVisitorReturn, useVisitor } from "./hooks/use-visitor.js";
|
|
25
26
|
import { WindowVisibilityFocusState, useWindowVisibilityFocus } from "./hooks/use-window-visibility-focus.js";
|
|
26
27
|
import "./hooks/index.js";
|
|
27
28
|
import { IdentifySupportVisitor, IdentifySupportVisitorProps } from "./identify-visitor.js";
|
|
28
29
|
import { SupportConfig, SupportConfigProps } from "./support-config.js";
|
|
30
|
+
import { Page, PageProps } from "./primitives/page.js";
|
|
29
31
|
import { index_d_exports } from "./primitives/index.js";
|
|
30
32
|
import { CossistantContextValue, CossistantProviderProps, SupportContext, SupportProvider, SupportProviderProps, UseSupportValue, useSupport } from "./provider.js";
|
|
31
33
|
import { RealtimeAuthConfig, RealtimeContextValue, RealtimeProvider, RealtimeProviderProps, useRealtimeConnection } from "./realtime/provider.js";
|
|
@@ -35,7 +37,10 @@ import { applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingS
|
|
|
35
37
|
import { RealtimeEventHandler, RealtimeEventHandlerEntry, RealtimeEventHandlersMap, RealtimeEventMeta, useRealtime } from "./realtime/use-realtime.js";
|
|
36
38
|
import "./realtime/index.js";
|
|
37
39
|
import { Text, useSupportText } from "./support/text/index.js";
|
|
40
|
+
import { BubbleSlotProps, ContainerSlotProps, RouterSlotProps } from "./support/types.js";
|
|
41
|
+
import { CoButton } from "./support/components/button.js";
|
|
42
|
+
import { Header } from "./support/components/header.js";
|
|
38
43
|
import { WebSocketContextValue, WebSocketProvider, useWebSocket } from "./support/context/websocket.js";
|
|
39
44
|
import { useSupportConfig, useSupportNavigation, useSupportStore } from "./support/store/support-store.js";
|
|
40
|
-
import { Support, SupportProps } from "./support/index.js";
|
|
41
|
-
export { CONVERSATION_AUTO_SEEN_DELAY_MS, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CossistantContextValue, CossistantProviderProps, CreateConversationVariables, GroupedMessage, IdentifySupportVisitor, IdentifySupportVisitorProps, index_d_exports as Primitives, RealtimeAuthConfig, RealtimeContextValue, RealtimeEventHandler, RealtimeEventHandlerEntry, RealtimeEventHandlersMap, RealtimeEventMeta, RealtimeProvider, RealtimeProviderProps, SendMessageOptions, SendMessageResult, Support, SupportConfig, SupportConfigProps, SupportContext, SupportLocale, SupportProps, SupportProvider, SupportProviderProps, SupportRealtimeProvider, SupportTextContentOverrides, Text, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseSendMessageOptions, UseSendMessageResult, UseSupportValue, UseVisitorReturn, WebSocketContextValue, WebSocketProvider, WindowVisibilityFocusState, applyConversationSeenEvent, applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, hydrateConversationSeen, setTypingState, upsertConversationSeen, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useRealtime, useRealtimeConnection, useRealtimeSupport, useSendMessage, useSupport, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useVisitor, useWebSocket, useWindowVisibilityFocus };
|
|
45
|
+
import { DefaultRoutes, NavigationState, RouteRegistry, Support, SupportPage, SupportProps } from "./support/index.js";
|
|
46
|
+
export { BubbleSlotProps, CoButton as Button, CONVERSATION_AUTO_SEEN_DELAY_MS, ContainerSlotProps, ConversationItem, ConversationLifecycleState, ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, ConversationTimelineTypingParticipant, ConversationTypingParticipant, CossistantContextValue, CossistantProviderProps, CreateConversationVariables, DefaultRoutes, GroupedMessage, Header, IdentifySupportVisitor, IdentifySupportVisitorProps, NavigationState, Page, PageProps, index_d_exports as Primitives, RealtimeAuthConfig, RealtimeContextValue, RealtimeEventHandler, RealtimeEventHandlerEntry, RealtimeEventHandlersMap, RealtimeEventMeta, RealtimeProvider, RealtimeProviderProps, RouteRegistry, RouterSlotProps, SendMessageOptions, SendMessageResult, Support, SupportConfig, SupportConfigProps, SupportContext, SupportLocale, SupportPage, SupportProps, SupportProvider, SupportProviderProps, SupportRealtimeProvider, SupportTextContentOverrides, Text, TimelineEventItem, TimelineToolItem, UseClientResult, UseComposerRefocusOptions, UseComposerRefocusReturn, UseConversationAutoSeenOptions, UseConversationHistoryPageOptions, UseConversationHistoryPageReturn, UseConversationLifecycleOptions, UseConversationLifecycleReturn, UseConversationOptions, UseConversationPageOptions, UseConversationPageReturn, UseConversationPreviewOptions, UseConversationPreviewReturn, UseConversationResult, UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, UseConversationTimelineOptions, UseConversationTimelineReturn, UseConversationsOptions, UseConversationsResult, UseCreateConversationOptions, UseCreateConversationResult, UseGroupedMessagesOptions, UseGroupedMessagesProps, UseHomePageOptions, UseHomePageReturn, UseMessageComposerOptions, UseMessageComposerReturn, UseMultimodalInputOptions, UseMultimodalInputReturn, UseRealtimeSupportOptions, UseRealtimeSupportResult, UseScrollMaskOptions, UseScrollMaskReturn, UseSendMessageOptions, UseSendMessageResult, UseSupportValue, UseVisitorReturn, WebSocketContextValue, WebSocketProvider, WindowVisibilityFocusState, applyConversationSeenEvent, applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, hydrateConversationSeen, setTypingState, upsertConversationSeen, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useRealtime, useRealtimeConnection, useRealtimeSupport, useScrollMask, useSendMessage, useSupport, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useVisitor, useWebSocket, useWindowVisibilityFocus };
|
package/index.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { useClientQuery } from "./hooks/private/use-client-query.js";
|
|
2
2
|
import { useClient } from "./hooks/private/use-rest-client.js";
|
|
3
3
|
import { applyConversationSeenEvent, hydrateConversationSeen, upsertConversationSeen } from "./realtime/seen-store.js";
|
|
4
|
-
import { RealtimeProvider, useRealtimeConnection } from "./realtime/provider.js";
|
|
5
|
-
import { applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, setTypingState } from "./realtime/typing-store.js";
|
|
6
|
-
import { useRealtime } from "./realtime/use-realtime.js";
|
|
7
|
-
import { SupportRealtimeProvider } from "./realtime/support-provider.js";
|
|
8
4
|
import { SupportConfig } from "./support-config.js";
|
|
5
|
+
import { applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, setTypingState } from "./realtime/typing-store.js";
|
|
6
|
+
import { useScrollMask } from "./hooks/use-scroll-mask.js";
|
|
7
|
+
import { Page } from "./primitives/page.js";
|
|
9
8
|
import { useSupportConfig, useSupportNavigation, useSupportStore } from "./support/store/support-store.js";
|
|
10
9
|
import { primitives_exports } from "./primitives/index.js";
|
|
10
|
+
import { RealtimeProvider, useRealtimeConnection } from "./realtime/provider.js";
|
|
11
|
+
import { useRealtime } from "./realtime/use-realtime.js";
|
|
12
|
+
import { SupportRealtimeProvider } from "./realtime/support-provider.js";
|
|
13
|
+
import { CoButton } from "./support/components/button.js";
|
|
14
|
+
import { Header } from "./support/components/header.js";
|
|
11
15
|
import { Text, useSupportText } from "./support/text/index.js";
|
|
12
16
|
import { useConversation } from "./hooks/use-conversation.js";
|
|
13
17
|
import { useWindowVisibilityFocus } from "./hooks/use-window-visibility-focus.js";
|
|
@@ -36,4 +40,4 @@ import { useCreateConversation } from "./hooks/use-create-conversation.js";
|
|
|
36
40
|
import { useRealtimeSupport } from "./hooks/use-realtime-support.js";
|
|
37
41
|
import { IdentifySupportVisitor } from "./identify-visitor.js";
|
|
38
42
|
|
|
39
|
-
export { CONVERSATION_AUTO_SEEN_DELAY_MS, IdentifySupportVisitor, primitives_exports as Primitives, RealtimeProvider, Support, SupportConfig, SupportContext, SupportProvider, SupportRealtimeProvider, Text, WebSocketProvider, applyConversationSeenEvent, applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, hydrateConversationSeen, setTypingState, upsertConversationSeen, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useRealtime, useRealtimeConnection, useRealtimeSupport, useSendMessage, useSupport, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useVisitor, useWebSocket, useWindowVisibilityFocus };
|
|
43
|
+
export { CoButton as Button, CONVERSATION_AUTO_SEEN_DELAY_MS, Header, IdentifySupportVisitor, Page, primitives_exports as Primitives, RealtimeProvider, Support, SupportConfig, SupportContext, SupportProvider, SupportRealtimeProvider, Text, WebSocketProvider, applyConversationSeenEvent, applyConversationTypingEvent, clearTypingFromTimelineItem, clearTypingState, hydrateConversationSeen, setTypingState, upsertConversationSeen, useClient, useClientQuery, useComposerRefocus, useConversation, useConversationAutoSeen, useConversationHistoryPage, useConversationLifecycle, useConversationPage, useConversationPreview, useConversationSeen, useConversationTimeline, useConversationTimelineItems, useConversationTyping, useConversations, useCreateConversation, useDebouncedConversationSeen, useDefaultMessages, useGroupedMessages, useHomePage, useMessageComposer, useMultimodalInput, useRealtime, useRealtimeConnection, useRealtimeSupport, useScrollMask, useSendMessage, useSupport, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useVisitor, useWebSocket, useWindowVisibilityFocus };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cossistant/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.16",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Cossistant team",
|
|
7
7
|
"description": "Headless React SDK for building AI-powered support/chat widgets. Hooks + primitives, WS-driven, TypeScript-first. Next.js-ready, Tailwind optional.",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"*.css"
|
|
89
89
|
],
|
|
90
90
|
"dependencies": {
|
|
91
|
-
"@cossistant/core": "0.0.
|
|
92
|
-
"@cossistant/types": "0.0.
|
|
91
|
+
"@cossistant/core": "0.0.16",
|
|
92
|
+
"@cossistant/types": "0.0.16",
|
|
93
93
|
"class-variance-authority": "^0.7.1",
|
|
94
94
|
"clsx": "^2.1.1",
|
|
95
95
|
"nanoid": "^5.1.5",
|
package/parse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","names":["core","ZodError","ZodSafeParseResult","T","ZodSafeParseSuccess","ZodSafeParseError","parse","$ZodType","$ZodIssue","ParseContext","util","AnyFunc","$ZodErrorClass","output","parseAsync","Promise","safeParse","safeParseAsync","encode","input","decode","encodeAsync","decodeAsync","safeEncode","safeDecode","safeEncodeAsync","safeDecodeAsync"],"sources":["../../../node_modules/.bun/zod@4.1.12/node_modules/zod/v4/classic/parse.d.cts"],"sourcesContent":["import * as core from \"../core/index.cjs\";\nimport { type ZodError } from \"./errors.cjs\";\nexport type ZodSafeParseResult<T> = ZodSafeParseSuccess<T> | ZodSafeParseError<T>;\nexport type ZodSafeParseSuccess<T> = {\n success: true;\n data: T;\n error?: never;\n};\nexport type ZodSafeParseError<T> = {\n success: false;\n data?: never;\n error: ZodError<T>;\n};\nexport declare const parse: <T extends core.$ZodType>(schema: T, value: unknown, _ctx?: core.ParseContext<core.$ZodIssue>, _params?: {\n callee?: core.util.AnyFunc;\n Err?: core.$ZodErrorClass;\n}) => core.output<T>;\nexport declare const parseAsync: <T extends core.$ZodType>(schema: T, value: unknown, _ctx?: core.ParseContext<core.$ZodIssue>, _params?: {\n callee?: core.util.AnyFunc;\n Err?: core.$ZodErrorClass;\n}) => Promise<core.output<T>>;\nexport declare const safeParse: <T extends core.$ZodType>(schema: T, value: unknown, _ctx?: core.ParseContext<core.$ZodIssue>) => ZodSafeParseResult<core.output<T>>;\nexport declare const safeParseAsync: <T extends core.$ZodType>(schema: T, value: unknown, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<ZodSafeParseResult<core.output<T>>>;\nexport declare const encode: <T extends core.$ZodType>(schema: T, value: core.output<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => core.input<T>;\nexport declare const decode: <T extends core.$ZodType>(schema: T, value: core.input<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => core.output<T>;\nexport declare const encodeAsync: <T extends core.$ZodType>(schema: T, value: core.output<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<core.input<T>>;\nexport declare const decodeAsync: <T extends core.$ZodType>(schema: T, value: core.input<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<core.output<T>>;\nexport declare const safeEncode: <T extends core.$ZodType>(schema: T, value: core.output<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => ZodSafeParseResult<core.input<T>>;\nexport declare const safeDecode: <T extends core.$ZodType>(schema: T, value: core.input<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => ZodSafeParseResult<core.output<T>>;\nexport declare const safeEncodeAsync: <T extends core.$ZodType>(schema: T, value: core.output<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<ZodSafeParseResult<core.input<T>>>;\nexport declare const safeDecodeAsync: <T extends core.$ZodType>(schema: T, value: core.input<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<ZodSafeParseResult<core.output<T>>>;\n"],"x_google_ignoreList":[0],"mappings":";;;KAEYE,wBAAwBE,oBAAoBD,KAAKE,kBAAkBF;AAAnED,KACAE,mBADkB,
|
|
1
|
+
{"version":3,"file":"parse.d.ts","names":["core","ZodError","ZodSafeParseResult","T","ZodSafeParseSuccess","ZodSafeParseError","parse","$ZodType","$ZodIssue","ParseContext","util","AnyFunc","$ZodErrorClass","output","parseAsync","Promise","safeParse","safeParseAsync","encode","input","decode","encodeAsync","decodeAsync","safeEncode","safeDecode","safeEncodeAsync","safeDecodeAsync"],"sources":["../../../node_modules/.bun/zod@4.1.12/node_modules/zod/v4/classic/parse.d.cts"],"sourcesContent":["import * as core from \"../core/index.cjs\";\nimport { type ZodError } from \"./errors.cjs\";\nexport type ZodSafeParseResult<T> = ZodSafeParseSuccess<T> | ZodSafeParseError<T>;\nexport type ZodSafeParseSuccess<T> = {\n success: true;\n data: T;\n error?: never;\n};\nexport type ZodSafeParseError<T> = {\n success: false;\n data?: never;\n error: ZodError<T>;\n};\nexport declare const parse: <T extends core.$ZodType>(schema: T, value: unknown, _ctx?: core.ParseContext<core.$ZodIssue>, _params?: {\n callee?: core.util.AnyFunc;\n Err?: core.$ZodErrorClass;\n}) => core.output<T>;\nexport declare const parseAsync: <T extends core.$ZodType>(schema: T, value: unknown, _ctx?: core.ParseContext<core.$ZodIssue>, _params?: {\n callee?: core.util.AnyFunc;\n Err?: core.$ZodErrorClass;\n}) => Promise<core.output<T>>;\nexport declare const safeParse: <T extends core.$ZodType>(schema: T, value: unknown, _ctx?: core.ParseContext<core.$ZodIssue>) => ZodSafeParseResult<core.output<T>>;\nexport declare const safeParseAsync: <T extends core.$ZodType>(schema: T, value: unknown, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<ZodSafeParseResult<core.output<T>>>;\nexport declare const encode: <T extends core.$ZodType>(schema: T, value: core.output<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => core.input<T>;\nexport declare const decode: <T extends core.$ZodType>(schema: T, value: core.input<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => core.output<T>;\nexport declare const encodeAsync: <T extends core.$ZodType>(schema: T, value: core.output<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<core.input<T>>;\nexport declare const decodeAsync: <T extends core.$ZodType>(schema: T, value: core.input<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<core.output<T>>;\nexport declare const safeEncode: <T extends core.$ZodType>(schema: T, value: core.output<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => ZodSafeParseResult<core.input<T>>;\nexport declare const safeDecode: <T extends core.$ZodType>(schema: T, value: core.input<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => ZodSafeParseResult<core.output<T>>;\nexport declare const safeEncodeAsync: <T extends core.$ZodType>(schema: T, value: core.output<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<ZodSafeParseResult<core.input<T>>>;\nexport declare const safeDecodeAsync: <T extends core.$ZodType>(schema: T, value: core.input<T>, _ctx?: core.ParseContext<core.$ZodIssue>) => Promise<ZodSafeParseResult<core.output<T>>>;\n"],"x_google_ignoreList":[0],"mappings":";;;KAEYE,wBAAwBE,oBAAoBD,KAAKE,kBAAkBF;AAAnED,KACAE,mBADkB,CAAAD,CAAAA,CAAAA,GAAAA;EAA0BA,OAAAA,EAAAA,IAAAA;EAApBC,IAAAA,EAG1BD,CAH0BC;EAA2CD,KAAAA,CAAAA,EAAAA,KAAAA;CAAlBE;AAAiB,KAMlEA,iBANkE,CAAA,CAAA,CAAA,GAAA;EAClED,OAAAA,EAAAA,KAAAA;EAKAC,IAAAA,CAAAA,EAAAA,KAAAA;SAGDJ,SAASE"}
|
package/primitives/bubble.d.ts
CHANGED
|
@@ -12,8 +12,16 @@ type SupportBubbleProps = Omit<React$1.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
|
12
12
|
className?: string;
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
|
-
* Floating action button that toggles the support window.
|
|
16
|
-
*
|
|
15
|
+
* Floating action button that toggles the support window.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <Bubble>
|
|
19
|
+
* {({ isOpen, unreadCount, toggle }) => (
|
|
20
|
+
* <button onClick={toggle}>
|
|
21
|
+
* {isOpen ? "×" : "💬"} {unreadCount > 0 && `(${unreadCount})`}
|
|
22
|
+
* </button>
|
|
23
|
+
* )}
|
|
24
|
+
* </Bubble>
|
|
17
25
|
*/
|
|
18
26
|
declare const SupportBubble: React$1.ForwardRefExoticComponent<Omit<React$1.ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
|
|
19
27
|
children?: React$1.ReactNode | ((props: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bubble.d.ts","names":[],"sources":["../../src/primitives/bubble.tsx"],"sourcesContent":[],"mappings":";;;KAMY,kBAAA,GAAqB,KAChC,OAAA,CAAM,qBAAqB;aAIxB,OAAA,CAAM;IALE,MAAA,EAAA,OAAA;IACgB,WAAA,EAAA,MAAA;IAA3B,QAAM,EAAA,OAAA;IAD0B,MAAA,EAAA,GAAA,GAAA,IAAA;EAK7B,CAAA,EAAA,GAMM,OAAA,CAAM,SANN,CAAA;EAMA,OAAM,CAAA,EAAA,OAAA;EAAS,SAAA,CAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"bubble.d.ts","names":[],"sources":["../../src/primitives/bubble.tsx"],"sourcesContent":[],"mappings":";;;KAMY,kBAAA,GAAqB,KAChC,OAAA,CAAM,qBAAqB;aAIxB,OAAA,CAAM;IALE,MAAA,EAAA,OAAA;IACgB,WAAA,EAAA,MAAA;IAA3B,QAAM,EAAA,OAAA;IAD0B,MAAA,EAAA,GAAA,GAAA,IAAA;EAK7B,CAAA,EAAA,GAMM,OAAA,CAAM,SANN,CAAA;EAMA,OAAM,CAAA,EAAA,OAAA;EAAS,SAAA,CAAA,EAAA,MAAA;AAiBzB,CAAA;;;;;;;;;;;;;cAAa,eAAa,OAAA,CAAA,0BAAA,KAAA,OAAA,CAAA,qBAAA;aAvBtB,OAAA,CAAM;;;;;QAMA,OAAA,CAAM"}
|
package/primitives/bubble.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { useTypingStore } from "../realtime/typing-store.js";
|
|
2
1
|
import { useRenderElement } from "../utils/use-render-element.js";
|
|
2
|
+
import { useTypingStore } from "../realtime/typing-store.js";
|
|
3
3
|
import { useSupportConfig } from "../support/store/support-store.js";
|
|
4
4
|
import { useSupport } from "../provider.js";
|
|
5
5
|
import * as React$1 from "react";
|
|
6
6
|
|
|
7
7
|
//#region src/primitives/bubble.tsx
|
|
8
8
|
/**
|
|
9
|
-
* Floating action button that toggles the support window.
|
|
10
|
-
*
|
|
9
|
+
* Floating action button that toggles the support window.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <Bubble>
|
|
13
|
+
* {({ isOpen, unreadCount, toggle }) => (
|
|
14
|
+
* <button onClick={toggle}>
|
|
15
|
+
* {isOpen ? "×" : "💬"} {unreadCount > 0 && `(${unreadCount})`}
|
|
16
|
+
* </button>
|
|
17
|
+
* )}
|
|
18
|
+
* </Bubble>
|
|
11
19
|
*/
|
|
12
20
|
const SupportBubble = (() => {
|
|
13
21
|
const Component = React$1.forwardRef(({ children, className, asChild = false,...props }, ref) => {
|
package/primitives/bubble.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bubble.js","names":["React"],"sources":["../../src/primitives/bubble.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useTypingStore } from \"../realtime/typing-store\";\nimport { useSupportConfig } from \"../support\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\nexport type SupportBubbleProps = Omit<\n\tReact.ButtonHTMLAttributes<HTMLButtonElement>,\n\t\"children\"\n> & {\n\tchildren?:\n\t\t| React.ReactNode\n\t\t| ((props: {\n\t\t\t\tisOpen: boolean;\n\t\t\t\tunreadCount: number;\n\t\t\t\tisTyping: boolean;\n\t\t\t\ttoggle: () => void;\n\t\t }) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Floating action button that toggles the support window
|
|
1
|
+
{"version":3,"file":"bubble.js","names":["React"],"sources":["../../src/primitives/bubble.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useTypingStore } from \"../realtime/typing-store\";\nimport { useSupportConfig } from \"../support\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\nexport type SupportBubbleProps = Omit<\n\tReact.ButtonHTMLAttributes<HTMLButtonElement>,\n\t\"children\"\n> & {\n\tchildren?:\n\t\t| React.ReactNode\n\t\t| ((props: {\n\t\t\t\tisOpen: boolean;\n\t\t\t\tunreadCount: number;\n\t\t\t\tisTyping: boolean;\n\t\t\t\ttoggle: () => void;\n\t\t }) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Floating action button that toggles the support window.\n *\n * @example\n * <Bubble>\n * {({ isOpen, unreadCount, toggle }) => (\n * <button onClick={toggle}>\n * {isOpen ? \"×\" : \"💬\"} {unreadCount > 0 && `(${unreadCount})`}\n * </button>\n * )}\n * </Bubble>\n */\nexport const SupportBubble = (() => {\n\tconst Component = React.forwardRef<HTMLButtonElement, SupportBubbleProps>(\n\t\t({ children, className, asChild = false, ...props }, ref) => {\n\t\t\tconst { isOpen, toggle } = useSupportConfig();\n\t\t\tconst { unreadCount, visitor } = useSupport();\n\t\t\tconst visitorId = visitor?.id ?? null;\n\n\t\t\tconst hasTyping = useTypingStore(\n\t\t\t\tReact.useCallback(\n\t\t\t\t\t(state) =>\n\t\t\t\t\t\tObject.values(state.conversations).some((entries) =>\n\t\t\t\t\t\t\tObject.values(entries).some((entry) => {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tvisitorId &&\n\t\t\t\t\t\t\t\t\tentry.actorType === \"visitor\" &&\n\t\t\t\t\t\t\t\t\tentry.actorId === visitorId\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t),\n\t\t\t\t\t[visitorId]\n\t\t\t\t)\n\t\t\t);\n\n\t\t\tconst renderProps = {\n\t\t\t\tisOpen,\n\t\t\t\tunreadCount,\n\t\t\t\tisTyping: hasTyping,\n\t\t\t\ttoggle,\n\t\t\t};\n\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"button\",\n\t\t\t\t{\n\t\t\t\t\tasChild,\n\t\t\t\t\tclassName,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref,\n\t\t\t\t\tstate: renderProps,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\ttype: \"button\",\n\t\t\t\t\t\t\"aria-haspopup\": \"dialog\",\n\t\t\t\t\t\t\"aria-expanded\": isOpen,\n\t\t\t\t\t\tonClick: toggle,\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"SupportBubble\";\n\treturn Component;\n})();\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkCA,MAAa,uBAAuB;CACnC,MAAM,YAAYA,QAAM,YACtB,EAAE,UAAU,WAAW,UAAU,MAAO,GAAG,SAAS,QAAQ;EAC5D,MAAM,EAAE,QAAQ,WAAW,kBAAkB;EAC7C,MAAM,EAAE,aAAa,YAAY,YAAY;EAC7C,MAAM,YAAY,SAAS,MAAM;EAsBjC,MAAM,cAAc;GACnB;GACA;GACA,UAvBiB,eACjBA,QAAM,aACJ,UACA,OAAO,OAAO,MAAM,cAAc,CAAC,MAAM,YACxC,OAAO,OAAO,QAAQ,CAAC,MAAM,UAAU;AACtC,QACC,aACA,MAAM,cAAc,aACpB,MAAM,YAAY,UAElB,QAAO;AAGR,WAAO;KACN,CACF,EACF,CAAC,UAAU,CACX,CACD;GAMA;GACA;EAED,MAAM,UACL,OAAO,aAAa,aAAa,SAAS,YAAY,GAAG;AAE1D,SAAO,iBACN,UACA;GACC;GACA;GACA,EACD;GACC;GACA,OAAO;GACP,OAAO;IACN,MAAM;IACN,iBAAiB;IACjB,iBAAiB;IACjB,SAAS;IACT,GAAG;IACH,UAAU;IACV;GACD,CACD;GAEF;AAED,WAAU,cAAc;AACxB,QAAO;IACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-timeline.d.ts","names":[],"sources":["../../src/primitives/conversation-timeline.tsx"],"sourcesContent":[],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"conversation-timeline.d.ts","names":[],"sources":["../../src/primitives/conversation-timeline.tsx"],"sourcesContent":[],"mappings":";;;;;;;AASA;AAOA;AACsB,KARV,+BAAA,GAQU;EAArB,SAAM,EAAA,MAAA;EADiC,SAAA,CAAA,EAAA,OAAA;EAKpC,OAAM,CAAA,EAAA,OAAA;EACG,OAAA,EAAA,OAAA;CAAoC;AAGxC,KATG,yBAAA,GAA4B,IAS/B,CARR,OAAA,CAAM,cAQE,CARa,cAQb,CAAA,EAAA,UAAA,CAAA,GAAA;EAAgB,QAAA,CAAA,EAJrB,OAAA,CAAM,SAIe,GAAA,CAAA,CAAA,KAAA,EAHZ,+BAGY,EAAA,GAHwB,OAAA,CAAM,SAG9B,CAAA;EA6BZ,OAAA,CAAA,EAAA,OAAA;EAAoB,SAAA,CAAA,EAAA,MAAA;EAAA,KAAA,CAAA,EA7BxB,YA6BwB,EAAA;EAAA,SAAA,CAAA,EAAA,OAAA;EAjC7B,OAAM,CAAA,EAAA,OAAA;EACG,UAAA,CAAA,EAAA,OAAA;EAAoC,WAAM,CAAA,EAAA,GAAA,GAAA,IAAA;EAG9C,aAAA,CAAA,EAAA,GAAA,GAAA,IAAA;;;;;AAmLT;AACsB,cAvJT,oBAuJS,EAvJW,OAAA,CAAA,yBAuJX,CAvJW,IAuJX,CAvJW,OAAA,CAAA,cAuJX,CAvJW,cAuJX,CAAA,EAAA,UAAA,CAAA,GAAA;EAArB,QAAM,CAAA,EAxLH,OAAA,CAAM,SAwLH,GAAA,CAAA,CAAA,KAAA,EAvLM,+BAuLN,EAAA,GAvL0C,OAAA,CAAM,SAuLhD,CAAA;EAD0C,OAAA,CAAA,EAAA,OAAA;EAIrC,SAAM,CAAA,EAAA,MAAA;EAAS,KAAA,CAAA,EAvLlB,YAuLkB,EAAA;EASd,SAAA,CAAA,EAAA,OAAA;EAA6B,OAAA,CAAA,EAAA,OAAA;EAAA,UAAA,CAAA,EAAA,OAAA;EAAA,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAT9B,aAAM,CAAA,EAAA,GAAA,GAAA,IAAA;;KAJN,kCAAA,GAAqC,KAChD,OAAA,CAAM,eAAe;EAYoB,QAAA,CAAA,EAT9B,OAAA,CAAM,SASwB;EAAA,OAAA,CAAA,EAAA,OAAA;EAyB9B,SAAA,CAAA,EAAA,MAAA;CACU;;;;;AAYT,cAtCA,6BA+DT,EA/DsC,OAAA,CAAA,yBA+DtC,CA/DsC,IA+DtC,CA/DsC,OAAA,CAAA,cA+DtC,CA/DsC,cA+DtC,CAAA,EAAA,UAAA,CAAA,GAAA;EAzBoC,QAAA,CAAA,EA/C5B,OAAA,CAAM,SA+CsB;EAAA,OAAA,CAAA,EAAA,OAAA;EAAA,SAAA,CAAA,EAAA,MAAA;CAT5B,wBAAM,eAAA,CAAA,CAAA;KAJN,gCAAA,GAAmC,KAC9C,OAAA,CAAM,eAAe;aAGV,OAAA,CAAM;EASsB,OAAA,CAAA,EAAA,OAAA;EAAA,SAAA,CAAA,EAAA,MAAA;AA2BxC,CAAA;;;;;AAI2B,cA/Bd,2BA+Bc,EA/Ba,OAAA,CAAA,yBA+Bb,CA/Ba,IA+Bb,CA/Ba,OAAA,CAAA,cA+Bb,CA/Ba,cA+Bb,CAAA,EAAA,UAAA,CAAA,GAAA;EASd,QAAA,CAAA,EAjDD,OAAA,CAAM,SAiDL;EAAyB,OAAA,CAAA,EAAA,OAAA;EAAA,SAAA,CAAA,EAAA,MAAA;CAAA,wBAAA,eAAA,CAAA,CAAA;AAT1B,KAJA,8BAAA,GAAiC,IAI3B,CAHjB,OAAA,CAAM,cAGW,CAHI,cAGJ,CAAA,EAAA,UAAA,CAAA,GAAA;aAAN,OAAA,CAAM;;EASoB,SAAA,CAAA,EAAA,MAAA;CAAA;;;;;cAAzB,2BAAyB,OAAA,CAAA,0BAAA,KAAA,OAAA,CAAA,eAAA;aAT1B,OAAA,CAAM"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useRenderElement } from "../utils/use-render-element.js";
|
|
2
|
+
import { useScrollMask } from "../hooks/use-scroll-mask.js";
|
|
2
3
|
import * as React$1 from "react";
|
|
3
4
|
|
|
4
5
|
//#region src/primitives/conversation-timeline.tsx
|
|
@@ -17,11 +18,18 @@ function getLastItemKey(items) {
|
|
|
17
18
|
const ConversationTimeline = (() => {
|
|
18
19
|
const Component = React$1.forwardRef(({ children, className, asChild = false, items = [], isLoading = false, hasMore = false, autoScroll = true, onScrollEnd, onScrollStart,...props }, ref) => {
|
|
19
20
|
const internalRef = React$1.useRef(null);
|
|
21
|
+
const { ref: scrollMaskRef, style: scrollMaskStyle } = useScrollMask({
|
|
22
|
+
maskHeight: "54px",
|
|
23
|
+
scrollbarWidth: "8px",
|
|
24
|
+
topThreshold: TOP_THRESHOLD_PX,
|
|
25
|
+
bottomThreshold: BOTTOM_THRESHOLD_PX
|
|
26
|
+
});
|
|
20
27
|
const setRefs = React$1.useCallback((node) => {
|
|
21
28
|
internalRef.current = node;
|
|
29
|
+
scrollMaskRef.current = node;
|
|
22
30
|
if (typeof ref === "function") ref(node);
|
|
23
31
|
else if (ref) ref.current = node;
|
|
24
|
-
}, [ref]);
|
|
32
|
+
}, [ref, scrollMaskRef]);
|
|
25
33
|
const isInitialRender = React$1.useRef(true);
|
|
26
34
|
const previousItemCount = React$1.useRef(items.length);
|
|
27
35
|
const previousLastItemKey = React$1.useRef(getLastItemKey(items));
|
|
@@ -69,24 +77,6 @@ const ConversationTimeline = (() => {
|
|
|
69
77
|
if (atTop && !isAtTop.current) onScrollStart?.();
|
|
70
78
|
isAtTop.current = atTop;
|
|
71
79
|
}, [onScrollStart, onScrollEnd]);
|
|
72
|
-
const fadeStyle = React$1.useMemo(() => {
|
|
73
|
-
const maskHeight = "90px";
|
|
74
|
-
const scrollbarWidth = "8px";
|
|
75
|
-
const maskImage = `linear-gradient(to bottom, transparent, black ${maskHeight}, black calc(100% - ${maskHeight}), transparent), linear-gradient(black, black)`;
|
|
76
|
-
const maskSize = `calc(100% - ${scrollbarWidth}) 100%, ${scrollbarWidth} 100%`;
|
|
77
|
-
const maskPosition = "0 0, 100% 0";
|
|
78
|
-
const maskRepeat = "no-repeat, no-repeat";
|
|
79
|
-
return {
|
|
80
|
-
maskImage,
|
|
81
|
-
maskSize,
|
|
82
|
-
maskPosition,
|
|
83
|
-
maskRepeat,
|
|
84
|
-
WebkitMaskImage: maskImage,
|
|
85
|
-
WebkitMaskSize: maskSize,
|
|
86
|
-
WebkitMaskPosition: maskPosition,
|
|
87
|
-
WebkitMaskRepeat: maskRepeat
|
|
88
|
-
};
|
|
89
|
-
}, []);
|
|
90
80
|
return useRenderElement("div", {
|
|
91
81
|
className,
|
|
92
82
|
asChild
|
|
@@ -99,7 +89,7 @@ const ConversationTimeline = (() => {
|
|
|
99
89
|
"aria-live": "polite",
|
|
100
90
|
"aria-relevant": "additions",
|
|
101
91
|
onScroll: handleScroll,
|
|
102
|
-
style:
|
|
92
|
+
style: scrollMaskStyle,
|
|
103
93
|
...props,
|
|
104
94
|
children: content
|
|
105
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-timeline.js","names":["React","renderProps: ConversationTimelineRenderProps"],"sources":["../../src/primitives/conversation-timeline.tsx"],"sourcesContent":["import type { TimelineItem as TimelineItemType } from \"@cossistant/types/api/timeline-item\";\nimport * as React from \"react\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\n/**\n * High-level state of the timeline handed to render-prop children so they can show\n * skeletons, empty states or pagination affordances.\n */\nexport type ConversationTimelineRenderProps = {\n\titemCount: number;\n\tisLoading?: boolean;\n\thasMore?: boolean;\n\tisEmpty: boolean;\n};\n\nexport type ConversationTimelineProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?:\n\t\t| React.ReactNode\n\t\t| ((props: ConversationTimelineRenderProps) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n\titems?: TimelineItemType[];\n\tisLoading?: boolean;\n\thasMore?: boolean;\n\tautoScroll?: boolean;\n\tonScrollEnd?: () => void;\n\tonScrollStart?: () => void;\n};\n\nconst BOTTOM_THRESHOLD_PX = 12;\nconst TOP_THRESHOLD_PX = 2;\n\nfunction getLastItemKey(items: TimelineItemType[]): string | number | null {\n\tif (items.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst lastItem = items.at(-1);\n\n\tif (lastItem?.id) {\n\t\treturn lastItem.id;\n\t}\n\n\treturn lastItem?.createdAt ?? null;\n}\n\n/**\n * Scrollable conversation timeline that wires auto-scroll behaviour, live-region semantics and\n * pagination callbacks for displaying timeline items (messages, events, etc.).\n */\nexport const ConversationTimeline = (() => {\n\tconst Component = React.forwardRef<HTMLDivElement, ConversationTimelineProps>(\n\t\t(\n\t\t\t{\n\t\t\t\tchildren,\n\t\t\t\tclassName,\n\t\t\t\tasChild = false,\n\t\t\t\titems = [],\n\t\t\t\tisLoading = false,\n\t\t\t\thasMore = false,\n\t\t\t\tautoScroll = true,\n\t\t\t\tonScrollEnd,\n\t\t\t\tonScrollStart,\n\t\t\t\t...props\n\t\t\t},\n\t\t\tref\n\t\t) => {\n\t\t\tconst internalRef = React.useRef<HTMLDivElement>(null);\n\t\t\tconst setRefs = React.useCallback(\n\t\t\t\t(node: HTMLDivElement | null) => {\n\t\t\t\t\tinternalRef.current = node;\n\t\t\t\t\tif (typeof ref === \"function\") {\n\t\t\t\t\t\tref(node);\n\t\t\t\t\t} else if (ref) {\n\t\t\t\t\t\t(ref as React.MutableRefObject<HTMLDivElement | null>).current =\n\t\t\t\t\t\t\tnode;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t[ref]\n\t\t\t);\n\n\t\t\tconst isInitialRender = React.useRef(true);\n\t\t\tconst previousItemCount = React.useRef(items.length);\n\t\t\tconst previousLastItemKey = React.useRef<string | number | null>(\n\t\t\t\tgetLastItemKey(items)\n\t\t\t);\n\t\t\tconst isPinnedToBottom = React.useRef(true);\n\t\t\tconst isAtTop = React.useRef(true);\n\n\t\t\tconst renderProps: ConversationTimelineRenderProps = {\n\t\t\t\titemCount: items.length,\n\t\t\t\tisLoading,\n\t\t\t\thasMore,\n\t\t\t\tisEmpty: items.length === 0,\n\t\t\t};\n\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\t\tconst lastItemKey = getLastItemKey(items);\n\n\t\t\t// Auto-scroll to bottom when new timeline items are added\n\t\t\tReact.useEffect(() => {\n\t\t\t\tconst element = internalRef.current;\n\n\t\t\t\tif (!(element && autoScroll)) {\n\t\t\t\t\tpreviousItemCount.current = items.length;\n\t\t\t\t\tpreviousLastItemKey.current = lastItemKey;\n\t\t\t\t\tisInitialRender.current = false;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst hasNewItems = items.length > previousItemCount.current;\n\t\t\t\tconst itemsRemoved = items.length < previousItemCount.current;\n\t\t\t\tconst appendedNewItem =\n\t\t\t\t\thasNewItems &&\n\t\t\t\t\tlastItemKey !== null &&\n\t\t\t\t\tlastItemKey !== previousLastItemKey.current;\n\t\t\t\tconst replacedLastItem =\n\t\t\t\t\t!hasNewItems &&\n\t\t\t\t\tlastItemKey !== null &&\n\t\t\t\t\tlastItemKey !== previousLastItemKey.current;\n\n\t\t\t\tconst shouldSnapToBottom =\n\t\t\t\t\tisInitialRender.current ||\n\t\t\t\t\t(itemsRemoved && isPinnedToBottom.current) ||\n\t\t\t\t\t(appendedNewItem && isPinnedToBottom.current) ||\n\t\t\t\t\t(replacedLastItem && isPinnedToBottom.current);\n\n\t\t\t\tif (shouldSnapToBottom) {\n\t\t\t\t\telement.scrollTop = element.scrollHeight;\n\t\t\t\t\tisPinnedToBottom.current = true;\n\t\t\t\t\tisAtTop.current = false;\n\t\t\t\t}\n\n\t\t\t\tpreviousItemCount.current = items.length;\n\t\t\t\tpreviousLastItemKey.current = lastItemKey;\n\t\t\t\tisInitialRender.current = false;\n\t\t\t}, [autoScroll, items.length, lastItemKey]);\n\n\t\t\t// Handle scroll events for infinite scrolling\n\t\t\tconst handleScroll = React.useCallback(\n\t\t\t\t(e: React.UIEvent<HTMLDivElement>) => {\n\t\t\t\t\tconst element = e.currentTarget;\n\t\t\t\t\tconst { scrollTop, scrollHeight, clientHeight } = element;\n\n\t\t\t\t\tconst distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n\t\t\t\t\tconst pinnedNow = distanceFromBottom <= BOTTOM_THRESHOLD_PX;\n\t\t\t\t\tif (pinnedNow && !isPinnedToBottom.current) {\n\t\t\t\t\t\tonScrollEnd?.();\n\t\t\t\t\t}\n\t\t\t\t\tisPinnedToBottom.current = pinnedNow;\n\n\t\t\t\t\tconst atTop = scrollTop <= TOP_THRESHOLD_PX;\n\t\t\t\t\tif (atTop && !isAtTop.current) {\n\t\t\t\t\t\tonScrollStart?.();\n\t\t\t\t\t}\n\t\t\t\t\tisAtTop.current = atTop;\n\t\t\t\t},\n\t\t\t\t[onScrollStart, onScrollEnd]\n\t\t\t);\n\n\t\t\t// Static fade effect on top and bottom\n\t\t\tconst fadeStyle = React.useMemo(() => {\n\t\t\t\tconst maskHeight = \"90px\";\n\t\t\t\tconst scrollbarWidth = \"8px\";\n\n\t\t\t\tconst maskImage = `linear-gradient(to bottom, transparent, black ${maskHeight}, black calc(100% - ${maskHeight}), transparent), linear-gradient(black, black)`;\n\t\t\t\tconst maskSize = `calc(100% - ${scrollbarWidth}) 100%, ${scrollbarWidth} 100%`;\n\t\t\t\tconst maskPosition = \"0 0, 100% 0\";\n\t\t\t\tconst maskRepeat = \"no-repeat, no-repeat\";\n\n\t\t\t\treturn {\n\t\t\t\t\tmaskImage,\n\t\t\t\t\tmaskSize,\n\t\t\t\t\tmaskPosition,\n\t\t\t\t\tmaskRepeat,\n\t\t\t\t\tWebkitMaskImage: maskImage,\n\t\t\t\t\tWebkitMaskSize: maskSize,\n\t\t\t\t\tWebkitMaskPosition: maskPosition,\n\t\t\t\t\tWebkitMaskRepeat: maskRepeat,\n\t\t\t\t};\n\t\t\t}, []);\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"div\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref: setRefs,\n\t\t\t\t\tstate: renderProps,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\trole: \"log\",\n\t\t\t\t\t\t\"aria-label\": \"Conversation timeline\",\n\t\t\t\t\t\t\"aria-live\": \"polite\",\n\t\t\t\t\t\t\"aria-relevant\": \"additions\",\n\t\t\t\t\t\tonScroll: handleScroll,\n\t\t\t\t\t\tstyle: fadeStyle,\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"ConversationTimeline\";\n\treturn Component;\n})();\n\nexport type ConversationTimelineContainerProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode;\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Wrapper around the scrollable timeline giving consumers an easy hook to add\n * padding, backgrounds or transitions without touching the core timeline logic.\n */\nexport const ConversationTimelineContainer = (() => {\n\tconst Component = React.forwardRef<\n\t\tHTMLDivElement,\n\t\tConversationTimelineContainerProps\n\t>(({ children, className, asChild = false, ...props }, ref) =>\n\t\tuseRenderElement(\n\t\t\t\"div\",\n\t\t\t{\n\t\t\t\tclassName,\n\t\t\t\tasChild,\n\t\t\t},\n\t\t\t{\n\t\t\t\tref,\n\t\t\t\tprops: {\n\t\t\t\t\t...props,\n\t\t\t\t\tchildren,\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\t);\n\n\tComponent.displayName = \"ConversationTimelineContainer\";\n\treturn Component;\n})();\n\nexport type ConversationTimelineLoadingProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode;\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Accessible status region for loading more timeline items. Lets host apps render\n * skeletons or shimmer states without reimplementing ARIA wiring.\n */\nexport const ConversationTimelineLoading = (() => {\n\tconst Component = React.forwardRef<\n\t\tHTMLDivElement,\n\t\tConversationTimelineLoadingProps\n\t>(({ children, className, asChild = false, ...props }, ref) =>\n\t\tuseRenderElement(\n\t\t\t\"div\",\n\t\t\t{\n\t\t\t\tclassName,\n\t\t\t\tasChild,\n\t\t\t},\n\t\t\t{\n\t\t\t\tref,\n\t\t\t\tprops: {\n\t\t\t\t\trole: \"status\",\n\t\t\t\t\t\"aria-label\": \"Loading timeline items\",\n\t\t\t\t\t...props,\n\t\t\t\t\tchildren,\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\t);\n\n\tComponent.displayName = \"ConversationTimelineLoading\";\n\treturn Component;\n})();\n\nexport type ConversationTimelineEmptyProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode;\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Placeholder state rendered when no timeline items are present. Uses a polite status\n * region so screen readers announce the empty state.\n */\nexport const ConversationTimelineEmpty = (() => {\n\tconst Component = React.forwardRef<\n\t\tHTMLDivElement,\n\t\tConversationTimelineEmptyProps\n\t>(({ children, className, asChild = false, ...props }, ref) =>\n\t\tuseRenderElement(\n\t\t\t\"div\",\n\t\t\t{\n\t\t\t\tclassName,\n\t\t\t\tasChild,\n\t\t\t},\n\t\t\t{\n\t\t\t\tref,\n\t\t\t\tprops: {\n\t\t\t\t\trole: \"status\",\n\t\t\t\t\t\"aria-label\": \"No timeline items\",\n\t\t\t\t\t...props,\n\t\t\t\t\tchildren,\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\t);\n\n\tComponent.displayName = \"ConversationTimelineEmpty\";\n\treturn Component;\n})();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"conversation-timeline.js","names":["React","renderProps: ConversationTimelineRenderProps"],"sources":["../../src/primitives/conversation-timeline.tsx"],"sourcesContent":["import type { TimelineItem as TimelineItemType } from \"@cossistant/types/api/timeline-item\";\nimport * as React from \"react\";\nimport { useScrollMask } from \"../hooks/use-scroll-mask\";\nimport { useRenderElement } from \"../utils/use-render-element\";\n\n/**\n * High-level state of the timeline handed to render-prop children so they can show\n * skeletons, empty states or pagination affordances.\n */\nexport type ConversationTimelineRenderProps = {\n\titemCount: number;\n\tisLoading?: boolean;\n\thasMore?: boolean;\n\tisEmpty: boolean;\n};\n\nexport type ConversationTimelineProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?:\n\t\t| React.ReactNode\n\t\t| ((props: ConversationTimelineRenderProps) => React.ReactNode);\n\tasChild?: boolean;\n\tclassName?: string;\n\titems?: TimelineItemType[];\n\tisLoading?: boolean;\n\thasMore?: boolean;\n\tautoScroll?: boolean;\n\tonScrollEnd?: () => void;\n\tonScrollStart?: () => void;\n};\n\nconst BOTTOM_THRESHOLD_PX = 12;\nconst TOP_THRESHOLD_PX = 2;\n\nfunction getLastItemKey(items: TimelineItemType[]): string | number | null {\n\tif (items.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst lastItem = items.at(-1);\n\n\tif (lastItem?.id) {\n\t\treturn lastItem.id;\n\t}\n\n\treturn lastItem?.createdAt ?? null;\n}\n\n/**\n * Scrollable conversation timeline that wires auto-scroll behaviour, live-region semantics and\n * pagination callbacks for displaying timeline items (messages, events, etc.).\n */\nexport const ConversationTimeline = (() => {\n\tconst Component = React.forwardRef<HTMLDivElement, ConversationTimelineProps>(\n\t\t(\n\t\t\t{\n\t\t\t\tchildren,\n\t\t\t\tclassName,\n\t\t\t\tasChild = false,\n\t\t\t\titems = [],\n\t\t\t\tisLoading = false,\n\t\t\t\thasMore = false,\n\t\t\t\tautoScroll = true,\n\t\t\t\tonScrollEnd,\n\t\t\t\tonScrollStart,\n\t\t\t\t...props\n\t\t\t},\n\t\t\tref\n\t\t) => {\n\t\t\tconst internalRef = React.useRef<HTMLDivElement>(null);\n\t\t\tconst { ref: scrollMaskRef, style: scrollMaskStyle } = useScrollMask({\n\t\t\t\tmaskHeight: \"54px\",\n\t\t\t\tscrollbarWidth: \"8px\",\n\t\t\t\ttopThreshold: TOP_THRESHOLD_PX,\n\t\t\t\tbottomThreshold: BOTTOM_THRESHOLD_PX,\n\t\t\t});\n\n\t\t\tconst setRefs = React.useCallback(\n\t\t\t\t(node: HTMLDivElement | null) => {\n\t\t\t\t\tinternalRef.current = node;\n\t\t\t\t\t(\n\t\t\t\t\t\tscrollMaskRef as React.MutableRefObject<HTMLDivElement | null>\n\t\t\t\t\t).current = node;\n\t\t\t\t\tif (typeof ref === \"function\") {\n\t\t\t\t\t\tref(node);\n\t\t\t\t\t} else if (ref) {\n\t\t\t\t\t\t(ref as React.MutableRefObject<HTMLDivElement | null>).current =\n\t\t\t\t\t\t\tnode;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t[ref, scrollMaskRef]\n\t\t\t);\n\n\t\t\tconst isInitialRender = React.useRef(true);\n\t\t\tconst previousItemCount = React.useRef(items.length);\n\t\t\tconst previousLastItemKey = React.useRef<string | number | null>(\n\t\t\t\tgetLastItemKey(items)\n\t\t\t);\n\t\t\tconst isPinnedToBottom = React.useRef(true);\n\t\t\tconst isAtTop = React.useRef(true);\n\n\t\t\tconst renderProps: ConversationTimelineRenderProps = {\n\t\t\t\titemCount: items.length,\n\t\t\t\tisLoading,\n\t\t\t\thasMore,\n\t\t\t\tisEmpty: items.length === 0,\n\t\t\t};\n\n\t\t\tconst content =\n\t\t\t\ttypeof children === \"function\" ? children(renderProps) : children;\n\n\t\t\tconst lastItemKey = getLastItemKey(items);\n\n\t\t\t// Auto-scroll to bottom when new timeline items are added\n\t\t\tReact.useEffect(() => {\n\t\t\t\tconst element = internalRef.current;\n\n\t\t\t\tif (!(element && autoScroll)) {\n\t\t\t\t\tpreviousItemCount.current = items.length;\n\t\t\t\t\tpreviousLastItemKey.current = lastItemKey;\n\t\t\t\t\tisInitialRender.current = false;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst hasNewItems = items.length > previousItemCount.current;\n\t\t\t\tconst itemsRemoved = items.length < previousItemCount.current;\n\t\t\t\tconst appendedNewItem =\n\t\t\t\t\thasNewItems &&\n\t\t\t\t\tlastItemKey !== null &&\n\t\t\t\t\tlastItemKey !== previousLastItemKey.current;\n\t\t\t\tconst replacedLastItem =\n\t\t\t\t\t!hasNewItems &&\n\t\t\t\t\tlastItemKey !== null &&\n\t\t\t\t\tlastItemKey !== previousLastItemKey.current;\n\n\t\t\t\tconst shouldSnapToBottom =\n\t\t\t\t\tisInitialRender.current ||\n\t\t\t\t\t(itemsRemoved && isPinnedToBottom.current) ||\n\t\t\t\t\t(appendedNewItem && isPinnedToBottom.current) ||\n\t\t\t\t\t(replacedLastItem && isPinnedToBottom.current);\n\n\t\t\t\tif (shouldSnapToBottom) {\n\t\t\t\t\telement.scrollTop = element.scrollHeight;\n\t\t\t\t\tisPinnedToBottom.current = true;\n\t\t\t\t\tisAtTop.current = false;\n\t\t\t\t}\n\n\t\t\t\tpreviousItemCount.current = items.length;\n\t\t\t\tpreviousLastItemKey.current = lastItemKey;\n\t\t\t\tisInitialRender.current = false;\n\t\t\t}, [autoScroll, items.length, lastItemKey]);\n\n\t\t\t// Handle scroll events for infinite scrolling\n\t\t\tconst handleScroll = React.useCallback(\n\t\t\t\t(e: React.UIEvent<HTMLDivElement>) => {\n\t\t\t\t\tconst element = e.currentTarget;\n\t\t\t\t\tconst { scrollTop, scrollHeight, clientHeight } = element;\n\n\t\t\t\t\tconst distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n\t\t\t\t\tconst pinnedNow = distanceFromBottom <= BOTTOM_THRESHOLD_PX;\n\t\t\t\t\tif (pinnedNow && !isPinnedToBottom.current) {\n\t\t\t\t\t\tonScrollEnd?.();\n\t\t\t\t\t}\n\t\t\t\t\tisPinnedToBottom.current = pinnedNow;\n\n\t\t\t\t\tconst atTop = scrollTop <= TOP_THRESHOLD_PX;\n\t\t\t\t\tif (atTop && !isAtTop.current) {\n\t\t\t\t\t\tonScrollStart?.();\n\t\t\t\t\t}\n\t\t\t\t\tisAtTop.current = atTop;\n\t\t\t\t},\n\t\t\t\t[onScrollStart, onScrollEnd]\n\t\t\t);\n\n\t\t\treturn useRenderElement(\n\t\t\t\t\"div\",\n\t\t\t\t{\n\t\t\t\t\tclassName,\n\t\t\t\t\tasChild,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tref: setRefs,\n\t\t\t\t\tstate: renderProps,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\trole: \"log\",\n\t\t\t\t\t\t\"aria-label\": \"Conversation timeline\",\n\t\t\t\t\t\t\"aria-live\": \"polite\",\n\t\t\t\t\t\t\"aria-relevant\": \"additions\",\n\t\t\t\t\t\tonScroll: handleScroll,\n\t\t\t\t\t\tstyle: scrollMaskStyle,\n\t\t\t\t\t\t...props,\n\t\t\t\t\t\tchildren: content,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t);\n\n\tComponent.displayName = \"ConversationTimeline\";\n\treturn Component;\n})();\n\nexport type ConversationTimelineContainerProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode;\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Wrapper around the scrollable timeline giving consumers an easy hook to add\n * padding, backgrounds or transitions without touching the core timeline logic.\n */\nexport const ConversationTimelineContainer = (() => {\n\tconst Component = React.forwardRef<\n\t\tHTMLDivElement,\n\t\tConversationTimelineContainerProps\n\t>(({ children, className, asChild = false, ...props }, ref) =>\n\t\tuseRenderElement(\n\t\t\t\"div\",\n\t\t\t{\n\t\t\t\tclassName,\n\t\t\t\tasChild,\n\t\t\t},\n\t\t\t{\n\t\t\t\tref,\n\t\t\t\tprops: {\n\t\t\t\t\t...props,\n\t\t\t\t\tchildren,\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\t);\n\n\tComponent.displayName = \"ConversationTimelineContainer\";\n\treturn Component;\n})();\n\nexport type ConversationTimelineLoadingProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode;\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Accessible status region for loading more timeline items. Lets host apps render\n * skeletons or shimmer states without reimplementing ARIA wiring.\n */\nexport const ConversationTimelineLoading = (() => {\n\tconst Component = React.forwardRef<\n\t\tHTMLDivElement,\n\t\tConversationTimelineLoadingProps\n\t>(({ children, className, asChild = false, ...props }, ref) =>\n\t\tuseRenderElement(\n\t\t\t\"div\",\n\t\t\t{\n\t\t\t\tclassName,\n\t\t\t\tasChild,\n\t\t\t},\n\t\t\t{\n\t\t\t\tref,\n\t\t\t\tprops: {\n\t\t\t\t\trole: \"status\",\n\t\t\t\t\t\"aria-label\": \"Loading timeline items\",\n\t\t\t\t\t...props,\n\t\t\t\t\tchildren,\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\t);\n\n\tComponent.displayName = \"ConversationTimelineLoading\";\n\treturn Component;\n})();\n\nexport type ConversationTimelineEmptyProps = Omit<\n\tReact.HTMLAttributes<HTMLDivElement>,\n\t\"children\"\n> & {\n\tchildren?: React.ReactNode;\n\tasChild?: boolean;\n\tclassName?: string;\n};\n\n/**\n * Placeholder state rendered when no timeline items are present. Uses a polite status\n * region so screen readers announce the empty state.\n */\nexport const ConversationTimelineEmpty = (() => {\n\tconst Component = React.forwardRef<\n\t\tHTMLDivElement,\n\t\tConversationTimelineEmptyProps\n\t>(({ children, className, asChild = false, ...props }, ref) =>\n\t\tuseRenderElement(\n\t\t\t\"div\",\n\t\t\t{\n\t\t\t\tclassName,\n\t\t\t\tasChild,\n\t\t\t},\n\t\t\t{\n\t\t\t\tref,\n\t\t\t\tprops: {\n\t\t\t\t\trole: \"status\",\n\t\t\t\t\t\"aria-label\": \"No timeline items\",\n\t\t\t\t\t...props,\n\t\t\t\t\tchildren,\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\t);\n\n\tComponent.displayName = \"ConversationTimelineEmpty\";\n\treturn Component;\n})();\n"],"mappings":"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"}
|