@anker-in/campaign-ui 0.4.5-beta.18 → 0.4.5-beta.19

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.
@@ -1,5 +1,5 @@
1
- "use strict";var f=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var k=(r,l)=>{for(var a in l)f(r,a,{get:l[a],enumerable:!0})},H=(r,l,a,n)=>{if(l&&typeof l=="object"||typeof l=="function")for(let i of C(l))!E.call(r,i)&&i!==a&&f(r,i,{get:()=>l[i],enumerable:!(n=w(l,i))||n.enumerable});return r};var B=r=>H(f({},"__esModule",{value:!0}),r);var $={};k($,{MessageList:()=>S});module.exports=B($);var e=require("react/jsx-runtime"),o=require("react"),p=require("./ChatMessage"),h=require("./ScrollAnchor");const D=()=>(0,e.jsx)("div",{className:"flex h-full items-center justify-center text-gray-400",children:(0,e.jsx)("p",{className:"text-sm",children:"No messages yet"})}),P=()=>(0,e.jsx)("div",{className:"flex justify-center py-4",children:(0,e.jsxs)("div",{className:"flex gap-1",children:[(0,e.jsx)("div",{className:"size-2 animate-bounce rounded-full bg-gray-400"}),(0,e.jsx)("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.1s"}}),(0,e.jsx)("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.2s"}})]})}),S=({messages:r,rendererRegistry:l,defaultRenderer:a,showTimestamp:n=!0,autoScroll:i=!0,isLoadingHistory:v=!1,emptyPlaceholder:y,className:d="",onAddToCart:R})=>{const[t,b]=(0,o.useState)(null),N=(0,o.useCallback)(s=>{b(s)},[]),[g,u]=(0,o.useState)(!1),m=(0,o.useRef)(!1),c=(0,o.useCallback)((s=100)=>{if(!t)return!0;const{scrollTop:T,scrollHeight:L,clientHeight:M}=t;return L-T-M<s},[t]),x=(0,o.useCallback)(()=>{t&&t.scrollTo({top:t.scrollHeight,behavior:"smooth"})},[t]);return(0,o.useEffect)(()=>{if(!t)return;const s=()=>{u(!c())};return s(),t.addEventListener("scroll",s,{passive:!0}),()=>t.removeEventListener("scroll",s)},[t,c]),(0,o.useEffect)(()=>{if(i)return;const s=setTimeout(()=>{u(!c())},150);return()=>clearTimeout(s)},[r,i,c]),(0,o.useEffect)(()=>{if(!i||!t)return;const s=setTimeout(()=>{t&&(t.scrollTop=t.scrollHeight,u(!1))},100);return()=>clearTimeout(s)},[r,i,t]),v&&(m.current=!0),r.length===0?v||m.current?(0,e.jsx)("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${d}`,children:(0,e.jsx)(P,{})}):(0,e.jsx)("div",{className:`flex-1 overflow-hidden ${d}`,children:y||(0,e.jsx)(D,{})}):(m.current=!1,(0,e.jsxs)("div",{className:"relative flex-1 overflow-hidden",children:[(0,e.jsxs)("div",{ref:N,className:`
1
+ "use strict";var f=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var B=Object.prototype.hasOwnProperty;var D=(o,l)=>{for(var n in l)f(o,n,{get:l[n],enumerable:!0})},H=(o,l,n,a)=>{if(l&&typeof l=="object"||typeof l=="function")for(let i of k(l))!B.call(o,i)&&i!==n&&f(o,i,{get:()=>l[i],enumerable:!(a=C(l,i))||a.enumerable});return o};var S=o=>H(f({},"__esModule",{value:!0}),o);var P={};D(P,{MessageList:()=>j});module.exports=S(P);var e=require("react/jsx-runtime"),s=require("react"),g=require("./ChatMessage"),y=require("./ScrollAnchor");const $=()=>(0,e.jsx)("div",{className:"flex h-full items-center justify-center text-gray-400",children:(0,e.jsx)("p",{className:"text-sm",children:"No messages yet"})}),p=()=>(0,e.jsx)("div",{className:"flex justify-center py-4",children:(0,e.jsxs)("div",{className:"flex gap-1",children:[(0,e.jsx)("div",{className:"size-2 animate-bounce rounded-full bg-gray-400"}),(0,e.jsx)("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.1s"}}),(0,e.jsx)("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.2s"}})]})}),j=({messages:o,rendererRegistry:l,defaultRenderer:n,showTimestamp:a=!0,autoScroll:i=!0,isLoadingHistory:u=!1,emptyPlaceholder:b,className:c="",onAddToCart:R})=>{const[t,x]=(0,s.useState)(null),N=(0,s.useCallback)(r=>{x(r)},[]),[v,m]=(0,s.useState)(!1),[T,h]=(0,s.useState)(!1),d=(0,s.useCallback)((r=100)=>{if(!t)return!0;const{scrollTop:M,scrollHeight:E,clientHeight:L}=t;return E-M-L<r},[t]),w=(0,s.useCallback)(()=>{t&&t.scrollTo({top:t.scrollHeight,behavior:"smooth"})},[t]);return(0,s.useEffect)(()=>{if(!t)return;const r=()=>{m(!d())};return r(),t.addEventListener("scroll",r,{passive:!0}),()=>t.removeEventListener("scroll",r)},[t,d]),(0,s.useEffect)(()=>{if(i)return;const r=setTimeout(()=>{m(!d())},150);return()=>clearTimeout(r)},[o,i,d]),(0,s.useEffect)(()=>{if(!i||!t)return;const r=setTimeout(()=>{t&&(t.scrollTop=t.scrollHeight,m(!1))},100);return()=>clearTimeout(r)},[o,i,t]),(0,s.useEffect)(()=>{if(!u&&o.length===0){const r=setTimeout(()=>h(!0),150);return()=>clearTimeout(r)}else h(!1)},[u,o.length]),u?(0,e.jsx)("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${c}`,children:(0,e.jsx)(p,{})}):o.length===0?T?(0,e.jsx)("div",{className:`flex-1 overflow-hidden ${c}`,children:b||(0,e.jsx)($,{})}):(0,e.jsx)("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${c}`,children:(0,e.jsx)(p,{})}):(0,e.jsxs)("div",{className:"relative flex-1 overflow-hidden",children:[(0,e.jsxs)("div",{ref:N,className:`
2
2
  livechat-message-list absolute inset-0 overflow-y-auto p-4
3
- ${d}
4
- `,children:[(0,e.jsx)("div",{className:"flex flex-col gap-1",children:r.map(s=>(0,e.jsx)(p.ChatMessage,{message:s,rendererRegistry:l,defaultRenderer:a,showTimestamp:n,onAddToCart:R},s.id))}),i&&(0,e.jsx)(h.ScrollAnchor,{dependencies:[r]})]}),(0,e.jsx)("button",{onClick:x,className:`livechat-scroll-to-bottom ${g?"visible":"hidden"}`,"aria-label":"Scroll to bottom","aria-hidden":!g,children:(0,e.jsx)("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-gray-700",children:(0,e.jsx)("polyline",{points:"6 9 12 15 18 9"})})})]}))};
3
+ ${c}
4
+ `,children:[(0,e.jsx)("div",{className:"flex flex-col gap-1",children:o.map(r=>(0,e.jsx)(g.ChatMessage,{message:r,rendererRegistry:l,defaultRenderer:n,showTimestamp:a,onAddToCart:R},r.id))}),i&&(0,e.jsx)(y.ScrollAnchor,{dependencies:[o]})]}),(0,e.jsx)("button",{onClick:w,className:`livechat-scroll-to-bottom ${v?"visible":"hidden"}`,"aria-label":"Scroll to bottom","aria-hidden":!v,children:(0,e.jsx)("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-gray-700",children:(0,e.jsx)("polyline",{points:"6 9 12 15 18 9"})})})]})};
5
5
  //# sourceMappingURL=MessageList.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/MessageList.tsx"],
4
- "sourcesContent": ["/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n * \u663E\u793A\u6240\u6709\u804A\u5929\u6D88\u606F\uFF0C\u652F\u6301\u6EDA\u52A8\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u6D88\u606F\u5217\u8868\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatMessage } from './ChatMessage'\nimport { ScrollAnchor } from './ScrollAnchor'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface MessageListProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n */\n rendererRegistry?: MessageRendererRegistry\n\n /**\n * \u9ED8\u8BA4\u6E32\u67D3\u5668\n */\n defaultRenderer?: MessageRenderer\n\n /**\n * \u662F\u5426\u663E\u793A\u65F6\u95F4\u6233\n * @default true\n */\n showTimestamp?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\n * @default true\n */\n autoScroll?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\n\n /**\n * \u7A7A\u72B6\u6001\u5360\u4F4D\u5185\u5BB9\n */\n emptyPlaceholder?: React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n}\n\n/**\n * \u9ED8\u8BA4\u7A7A\u72B6\u6001\u5360\u4F4D\n */\nconst DefaultEmptyPlaceholder: React.FC = () => (\n <div className=\"flex h-full items-center justify-center text-gray-400\">\n <p className=\"text-sm\">No messages yet</p>\n </div>\n)\n\n/**\n * \u52A0\u8F7D\u6307\u793A\u5668\n */\nconst LoadingIndicator: React.FC = () => (\n <div className=\"flex justify-center py-4\">\n <div className=\"flex gap-1\">\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.1s' }} />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.2s' }} />\n </div>\n </div>\n)\n\n/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6240\u6709\u6D88\u606F\uFF08\u7528\u6237\u3001\u52A9\u624B\u3001\u7CFB\u7EDF\uFF09\n * - \u81EA\u52A8\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F\n * - \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u65F6\u663E\u793A\u6307\u793A\u5668\n * - \u7A7A\u72B6\u6001\u5360\u4F4D\n *\n * \u6837\u5F0F\uFF1A\n * - \u4F7F\u7528\u81EA\u5B9A\u4E49\u6EDA\u52A8\u6761\u6837\u5F0F\uFF08livechat.css\uFF09\n * - \u5185\u5BB9\u95F4\u8DDD\u5408\u7406\n * - \u652F\u6301\u54CD\u5E94\u5F0F\u5E03\u5C40\n *\n * @example\n * ```tsx\n * <MessageList\n * messages={messages}\n * rendererRegistry={customRegistry}\n * autoScroll={true}\n * isLoadingHistory={isLoading}\n * />\n * ```\n */\nexport const MessageList: React.FC<MessageListProps> = ({\n messages,\n rendererRegistry,\n defaultRenderer,\n showTimestamp = true,\n autoScroll = true,\n isLoadingHistory = false,\n emptyPlaceholder,\n className = '',\n onAddToCart,\n}) => {\n // \u4F7F\u7528 callback ref + state \u786E\u4FDD DOM \u6302\u8F7D\u540E\u89E6\u53D1\u91CD\u65B0\u6E32\u67D3\n const [listElement, setListElement] = useState<HTMLDivElement | null>(null)\n const listRef = useCallback((node: HTMLDivElement | null) => {\n setListElement(node)\n }, [])\n const [showScrollButton, setShowScrollButton] = useState(false)\n\n // \u8DDF\u8E2A\u662F\u5426\u66FE\u8FDB\u5165\u8FC7 loading \u72B6\u6001\uFF0C\u9632\u6B62 loading\u2192\u7A7A\u6001\u2192\u6D88\u606F\u7684\u77AC\u95F4\u95EA\u70C1\n const wasLoadingRef = useRef(false)\n\n // \u68C0\u67E5\u662F\u5426\u63A5\u8FD1\u5E95\u90E8\n const isNearBottom = useCallback(\n (threshold = 100) => {\n if (!listElement) return true\n\n const { scrollTop, scrollHeight, clientHeight } = listElement\n return scrollHeight - scrollTop - clientHeight < threshold\n },\n [listElement]\n )\n\n // \u5E73\u6ED1\u6EDA\u52A8\u5230\u5E95\u90E8\n const scrollToBottom = useCallback(() => {\n if (!listElement) return\n\n listElement.scrollTo({\n top: listElement.scrollHeight,\n behavior: 'smooth',\n })\n }, [listElement])\n\n // \u76D1\u542C\u6EDA\u52A8\u4E8B\u4EF6\uFF0C\u63A7\u5236\u6309\u94AE\u663E\u793A\n useEffect(() => {\n if (!listElement) return\n\n const handleScroll = () => {\n setShowScrollButton(!isNearBottom())\n }\n\n // \u521D\u59CB\u68C0\u67E5\n handleScroll()\n\n listElement.addEventListener('scroll', handleScroll, { passive: true })\n return () => listElement.removeEventListener('scroll', handleScroll)\n }, [listElement, isNearBottom])\n\n // \u5F53\u6D88\u606F\u5217\u8868\u53D8\u5316\u65F6\uFF0C\u91CD\u65B0\u68C0\u67E5\u6309\u94AE\u72B6\u6001\uFF08\u4F46\u4E0D\u5728\u81EA\u52A8\u6EDA\u52A8\u65F6\uFF09\n useEffect(() => {\n if (autoScroll) return // \u81EA\u52A8\u6EDA\u52A8\u4F1A\u5728\u53E6\u4E00\u4E2A effect \u4E2D\u5904\u7406\n\n const timeoutId = setTimeout(() => {\n setShowScrollButton(!isNearBottom())\n }, 150)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, isNearBottom])\n\n // \u76D1\u542C\u6D88\u606F\u5217\u8868\u53D8\u5316\uFF0C\u81EA\u52A8\u6EDA\u52A8\n useEffect(() => {\n if (!autoScroll || !listElement) return\n\n // \u5EF6\u8FDF\u6EDA\u52A8\u4EE5\u786E\u4FDD DOM \u5DF2\u66F4\u65B0\n const timeoutId = setTimeout(() => {\n if (listElement) {\n listElement.scrollTop = listElement.scrollHeight\n // \u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\u540E\uFF0C\u9690\u85CF\u6309\u94AE\n setShowScrollButton(false)\n }\n }, 100)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, listElement])\n\n if (isLoadingHistory) {\n wasLoadingRef.current = true\n }\n\n if (messages.length === 0) {\n if (isLoadingHistory || wasLoadingRef.current) {\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n return (\n <div className={`flex-1 overflow-hidden ${className}`}>{emptyPlaceholder || <DefaultEmptyPlaceholder />}</div>\n )\n }\n\n wasLoadingRef.current = false\n\n return (\n <div className=\"relative flex-1 overflow-hidden\">\n <div\n ref={listRef}\n className={`\n livechat-message-list absolute inset-0 overflow-y-auto p-4\n ${className}\n `}\n >\n {/* \u6D88\u606F\u5217\u8868 */}\n <div className=\"flex flex-col gap-1\">\n {messages.map(message => (\n <ChatMessage\n key={message.id}\n message={message}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n onAddToCart={onAddToCart}\n />\n ))}\n </div>\n\n {/* \u6EDA\u52A8\u951A\u70B9 */}\n {autoScroll && <ScrollAnchor dependencies={[messages]} />}\n </div>\n\n {/* \u56DE\u5230\u5E95\u90E8\u6309\u94AE */}\n <button\n onClick={scrollToBottom}\n className={`livechat-scroll-to-bottom ${showScrollButton ? 'visible' : 'hidden'}`}\n aria-label=\"Scroll to bottom\"\n aria-hidden={!showScrollButton}\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-gray-700\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n </div>\n )\n}\n"],
5
- "mappings": "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",
6
- "names": ["MessageList_exports", "__export", "MessageList", "__toCommonJS", "import_jsx_runtime", "import_react", "import_ChatMessage", "import_ScrollAnchor", "DefaultEmptyPlaceholder", "LoadingIndicator", "messages", "rendererRegistry", "defaultRenderer", "showTimestamp", "autoScroll", "isLoadingHistory", "emptyPlaceholder", "className", "onAddToCart", "listElement", "setListElement", "listRef", "node", "showScrollButton", "setShowScrollButton", "wasLoadingRef", "isNearBottom", "threshold", "scrollTop", "scrollHeight", "clientHeight", "scrollToBottom", "handleScroll", "timeoutId", "message"]
4
+ "sourcesContent": ["/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n * \u663E\u793A\u6240\u6709\u804A\u5929\u6D88\u606F\uFF0C\u652F\u6301\u6EDA\u52A8\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u6D88\u606F\u5217\u8868\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatMessage } from './ChatMessage'\nimport { ScrollAnchor } from './ScrollAnchor'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface MessageListProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n */\n rendererRegistry?: MessageRendererRegistry\n\n /**\n * \u9ED8\u8BA4\u6E32\u67D3\u5668\n */\n defaultRenderer?: MessageRenderer\n\n /**\n * \u662F\u5426\u663E\u793A\u65F6\u95F4\u6233\n * @default true\n */\n showTimestamp?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\n * @default true\n */\n autoScroll?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\n\n /**\n * \u7A7A\u72B6\u6001\u5360\u4F4D\u5185\u5BB9\n */\n emptyPlaceholder?: React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n}\n\n/**\n * \u9ED8\u8BA4\u7A7A\u72B6\u6001\u5360\u4F4D\n */\nconst DefaultEmptyPlaceholder: React.FC = () => (\n <div className=\"flex h-full items-center justify-center text-gray-400\">\n <p className=\"text-sm\">No messages yet</p>\n </div>\n)\n\n/**\n * \u52A0\u8F7D\u6307\u793A\u5668\n */\nconst LoadingIndicator: React.FC = () => (\n <div className=\"flex justify-center py-4\">\n <div className=\"flex gap-1\">\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.1s' }} />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.2s' }} />\n </div>\n </div>\n)\n\n/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6240\u6709\u6D88\u606F\uFF08\u7528\u6237\u3001\u52A9\u624B\u3001\u7CFB\u7EDF\uFF09\n * - \u81EA\u52A8\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F\n * - \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u65F6\u663E\u793A\u6307\u793A\u5668\n * - \u7A7A\u72B6\u6001\u5360\u4F4D\n *\n * \u6837\u5F0F\uFF1A\n * - \u4F7F\u7528\u81EA\u5B9A\u4E49\u6EDA\u52A8\u6761\u6837\u5F0F\uFF08livechat.css\uFF09\n * - \u5185\u5BB9\u95F4\u8DDD\u5408\u7406\n * - \u652F\u6301\u54CD\u5E94\u5F0F\u5E03\u5C40\n *\n * @example\n * ```tsx\n * <MessageList\n * messages={messages}\n * rendererRegistry={customRegistry}\n * autoScroll={true}\n * isLoadingHistory={isLoading}\n * />\n * ```\n */\nexport const MessageList: React.FC<MessageListProps> = ({\n messages,\n rendererRegistry,\n defaultRenderer,\n showTimestamp = true,\n autoScroll = true,\n isLoadingHistory = false,\n emptyPlaceholder,\n className = '',\n onAddToCart,\n}) => {\n // \u4F7F\u7528 callback ref + state \u786E\u4FDD DOM \u6302\u8F7D\u540E\u89E6\u53D1\u91CD\u65B0\u6E32\u67D3\n const [listElement, setListElement] = useState<HTMLDivElement | null>(null)\n const listRef = useCallback((node: HTMLDivElement | null) => {\n setListElement(node)\n }, [])\n const [showScrollButton, setShowScrollButton] = useState(false)\n\n const [showEmpty, setShowEmpty] = useState(false)\n\n // \u68C0\u67E5\u662F\u5426\u63A5\u8FD1\u5E95\u90E8\n const isNearBottom = useCallback(\n (threshold = 100) => {\n if (!listElement) return true\n\n const { scrollTop, scrollHeight, clientHeight } = listElement\n return scrollHeight - scrollTop - clientHeight < threshold\n },\n [listElement]\n )\n\n // \u5E73\u6ED1\u6EDA\u52A8\u5230\u5E95\u90E8\n const scrollToBottom = useCallback(() => {\n if (!listElement) return\n\n listElement.scrollTo({\n top: listElement.scrollHeight,\n behavior: 'smooth',\n })\n }, [listElement])\n\n // \u76D1\u542C\u6EDA\u52A8\u4E8B\u4EF6\uFF0C\u63A7\u5236\u6309\u94AE\u663E\u793A\n useEffect(() => {\n if (!listElement) return\n\n const handleScroll = () => {\n setShowScrollButton(!isNearBottom())\n }\n\n // \u521D\u59CB\u68C0\u67E5\n handleScroll()\n\n listElement.addEventListener('scroll', handleScroll, { passive: true })\n return () => listElement.removeEventListener('scroll', handleScroll)\n }, [listElement, isNearBottom])\n\n // \u5F53\u6D88\u606F\u5217\u8868\u53D8\u5316\u65F6\uFF0C\u91CD\u65B0\u68C0\u67E5\u6309\u94AE\u72B6\u6001\uFF08\u4F46\u4E0D\u5728\u81EA\u52A8\u6EDA\u52A8\u65F6\uFF09\n useEffect(() => {\n if (autoScroll) return // \u81EA\u52A8\u6EDA\u52A8\u4F1A\u5728\u53E6\u4E00\u4E2A effect \u4E2D\u5904\u7406\n\n const timeoutId = setTimeout(() => {\n setShowScrollButton(!isNearBottom())\n }, 150)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, isNearBottom])\n\n // \u76D1\u542C\u6D88\u606F\u5217\u8868\u53D8\u5316\uFF0C\u81EA\u52A8\u6EDA\u52A8\n useEffect(() => {\n if (!autoScroll || !listElement) return\n\n // \u5EF6\u8FDF\u6EDA\u52A8\u4EE5\u786E\u4FDD DOM \u5DF2\u66F4\u65B0\n const timeoutId = setTimeout(() => {\n if (listElement) {\n listElement.scrollTop = listElement.scrollHeight\n // \u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\u540E\uFF0C\u9690\u85CF\u6309\u94AE\n setShowScrollButton(false)\n }\n }, 100)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, listElement])\n\n useEffect(() => {\n if (!isLoadingHistory && messages.length === 0) {\n const timer = setTimeout(() => setShowEmpty(true), 150)\n return () => clearTimeout(timer)\n } else {\n setShowEmpty(false)\n }\n }, [isLoadingHistory, messages.length])\n\n if (isLoadingHistory) {\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n\n if (messages.length === 0) {\n if (!showEmpty) {\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n return (\n <div className={`flex-1 overflow-hidden ${className}`}>{emptyPlaceholder || <DefaultEmptyPlaceholder />}</div>\n )\n }\n\n return (\n <div className=\"relative flex-1 overflow-hidden\">\n <div\n ref={listRef}\n className={`\n livechat-message-list absolute inset-0 overflow-y-auto p-4\n ${className}\n `}\n >\n {/* \u6D88\u606F\u5217\u8868 */}\n <div className=\"flex flex-col gap-1\">\n {messages.map(message => (\n <ChatMessage\n key={message.id}\n message={message}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n onAddToCart={onAddToCart}\n />\n ))}\n </div>\n\n {/* \u6EDA\u52A8\u951A\u70B9 */}\n {autoScroll && <ScrollAnchor dependencies={[messages]} />}\n </div>\n\n {/* \u56DE\u5230\u5E95\u90E8\u6309\u94AE */}\n <button\n onClick={scrollToBottom}\n className={`livechat-scroll-to-bottom ${showScrollButton ? 'visible' : 'hidden'}`}\n aria-label=\"Scroll to bottom\"\n aria-hidden={!showScrollButton}\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-gray-700\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["MessageList_exports", "__export", "MessageList", "__toCommonJS", "import_jsx_runtime", "import_react", "import_ChatMessage", "import_ScrollAnchor", "DefaultEmptyPlaceholder", "LoadingIndicator", "messages", "rendererRegistry", "defaultRenderer", "showTimestamp", "autoScroll", "isLoadingHistory", "emptyPlaceholder", "className", "onAddToCart", "listElement", "setListElement", "listRef", "node", "showScrollButton", "setShowScrollButton", "showEmpty", "setShowEmpty", "isNearBottom", "threshold", "scrollTop", "scrollHeight", "clientHeight", "scrollToBottom", "handleScroll", "timeoutId", "timer", "message"]
7
7
  }
@@ -1,2 +1,2 @@
1
- "use strict";var d=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var y=(t,e)=>{for(var r in e)d(t,r,{get:e[r],enumerable:!0})},m=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of b(e))!w.call(t,i)&&i!==r&&d(t,i,{get:()=>e[i],enumerable:!(n=T(e,i))||n.enumerable});return t};var C=t=>m(d({},"__esModule",{value:!0}),t);var A={};y(A,{fetcher:()=>k});module.exports=C(A);const v=async(t,e)=>{if(typeof window>"u")return console.warn("[LiveChat Fetcher] reCAPTCHA not available in non-browser environment"),!1;const r=window.grecaptcha?.enterprise?.execute??window.grecaptcha?.execute;if(!r)return console.warn("[LiveChat Fetcher] reCAPTCHA not loaded (neither enterprise nor standard v3)"),!1;try{return await r(e,{action:t})}catch(n){return console.error("[LiveChat Fetcher] reCAPTCHA execution failed:",n),!1}};async function P(t,e,r="X-Recaptcha-Token"){const n=await v(t,e);return n?{[r]:n}:{}}const k=async({url:t,method:e="POST",headers:r={},body:n,timeout:i=9e4,needRecaptcha:p=!1,recaptchaSitekey:l,recaptchaAction:u="",recaptchaHeaderKey:f="X-Recaptcha-Token",signal:c})=>{let h={};p&&(l?h=await P(u,l,f):console.warn("[LiveChat Fetcher] needRecaptcha=true but recaptchaSitekey is missing"));const g=n?JSON.stringify(n):void 0,a=new AbortController;let o;i&&(o=setTimeout(()=>a.abort(),i)),c&&(c.aborted?a.abort():c.addEventListener("abort",()=>a.abort(),{once:!0}));try{const s=await fetch(t,{method:e,mode:"cors",headers:{"Content-Type":"application/json",...r,...h},signal:a.signal,...e!=="GET"&&g&&{body:g}});return o&&clearTimeout(o),s}catch(s){throw o&&clearTimeout(o),s}};
1
+ "use strict";var d=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var m=(t,e)=>{for(var r in e)d(t,r,{get:e[r],enumerable:!0})},v=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of y(e))!w.call(t,o)&&o!==r&&d(t,o,{get:()=>e[o],enumerable:!(n=T(e,o))||n.enumerable});return t};var C=t=>v(d({},"__esModule",{value:!0}),t);var R={};m(R,{fetcher:()=>k});module.exports=C(R);const P=async(t,e)=>{if(typeof window>"u")return console.warn("[LiveChat Fetcher] reCAPTCHA not available in non-browser environment"),!1;const r=window.grecaptcha?.enterprise?.execute??window.grecaptcha?.execute;if(!r)return console.warn("[LiveChat Fetcher] reCAPTCHA not loaded (neither enterprise nor standard v3)"),!1;try{return await r(e,{action:t})}catch(n){return console.error("[LiveChat Fetcher] reCAPTCHA execution failed:",n),!1}};async function A(t,e,r="X-Recaptcha-Token"){const n=await P(t,e);return n?{[r]:n}:{}}const k=async({url:t,method:e="POST",headers:r={},body:n,timeout:o=9e4,needRecaptcha:u=!1,recaptchaSitekey:l,recaptchaAction:f="",recaptchaHeaderKey:b="X-Recaptcha-Token",signal:a})=>{let h={};u&&(l?h=await A(f,l,b):console.warn("[LiveChat Fetcher] needRecaptcha=true but recaptchaSitekey is missing"));const g=n?JSON.stringify(n):void 0,c=new AbortController;let i;o&&(i=setTimeout(()=>c.abort(),o));const p=()=>c.abort();a&&(a.aborted?c.abort():a.addEventListener("abort",p,{once:!0}));try{const s=await fetch(t,{method:e,mode:"cors",headers:{"Content-Type":"application/json",...r,...h},signal:c.signal,...e!=="GET"&&g&&{body:g}});return i&&clearTimeout(i),s}catch(s){throw i&&clearTimeout(i),s}finally{a?.removeEventListener("abort",p)}};
2
2
  //# sourceMappingURL=fetcher.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/utils/fetcher.ts"],
4
- "sourcesContent": ["/**\n * LiveChat Fetcher\n * \u53C2\u7167 storefront \u7684\u5B9E\u73B0\uFF0C\u652F\u6301 reCAPTCHA\n */\n\n/**\n * \u6267\u884C Google reCAPTCHA \u9A8C\u8BC1\n * \u517C\u5BB9 Enterprise \u7248 (recaptcha/enterprise.js) \u548C\u6807\u51C6\u7248 v3 (recaptcha/api.js)\n * \u4F18\u5148\u4F7F\u7528 Enterprise \u7248\uFF0C\u56DE\u9000\u5230\u6807\u51C6\u7248\n */\nconst executeRecaptcha = async (action: string, sitekey: string): Promise<string | false> => {\n if (typeof window === 'undefined') {\n console.warn('[LiveChat Fetcher] reCAPTCHA not available in non-browser environment')\n return false\n }\n\n const executor = window.grecaptcha?.enterprise?.execute ?? window.grecaptcha?.execute\n\n if (!executor) {\n console.warn('[LiveChat Fetcher] reCAPTCHA not loaded (neither enterprise nor standard v3)')\n return false\n }\n\n try {\n const token = await executor(sitekey, { action })\n return token\n } catch (error) {\n console.error('[LiveChat Fetcher] reCAPTCHA execution failed:', error)\n return false\n }\n}\n\n/**\n * \u83B7\u53D6 reCAPTCHA headers\n */\nasync function getRecaptchaHeaders(\n action: string,\n sitekey: string,\n headerKey = 'X-Recaptcha-Token'\n): Promise<Record<string, string>> {\n const recaptchaToken = await executeRecaptcha(action, sitekey)\n if (!recaptchaToken) {\n return {}\n }\n return {\n [headerKey]: recaptchaToken,\n }\n}\n\n/**\n * Fetcher \u53C2\u6570\n */\nexport interface FetcherOptions {\n url: string\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n headers?: Record<string, string>\n body?: any\n timeout?: number\n needRecaptcha?: boolean\n recaptchaSitekey?: string\n recaptchaAction?: string\n recaptchaHeaderKey?: string\n /** \u5916\u90E8\u4F20\u5165\u7684 AbortSignal\uFF0C\u7528\u4E8E\u63D0\u524D\u4E2D\u6B62\u8BF7\u6C42 */\n signal?: AbortSignal\n}\n\n/**\n * Fetcher \u51FD\u6570\n * \u652F\u6301 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n */\nexport const fetcher = async ({\n url,\n method = 'POST',\n headers = {},\n body,\n timeout = 90000,\n needRecaptcha = false,\n recaptchaSitekey,\n recaptchaAction = '',\n recaptchaHeaderKey = 'X-Recaptcha-Token',\n signal: externalSignal,\n}: FetcherOptions): Promise<Response> => {\n let recaptchaHeaders: Record<string, string> = {}\n if (needRecaptcha) {\n if (!recaptchaSitekey) {\n console.warn('[LiveChat Fetcher] needRecaptcha=true but recaptchaSitekey is missing')\n } else {\n recaptchaHeaders = await getRecaptchaHeaders(recaptchaAction, recaptchaSitekey, recaptchaHeaderKey)\n }\n }\n\n const bodyData = body ? JSON.stringify(body) : undefined\n\n // \u5408\u5E76\u5916\u90E8 signal\uFF08\u7528\u4E8E\u624B\u52A8\u4E2D\u6B62\uFF09\u548C\u8D85\u65F6 signal\n const controller = new AbortController()\n let timeoutTimer: NodeJS.Timeout | undefined\n if (timeout) {\n timeoutTimer = setTimeout(() => controller.abort(), timeout)\n }\n // \u5982\u679C\u5916\u90E8 signal \u5DF2\u4E2D\u6B62\u6216\u5728\u8BF7\u6C42\u671F\u95F4\u4E2D\u6B62\uFF0C\u540C\u6B65\u5230\u5185\u90E8 controller\n if (externalSignal) {\n if (externalSignal.aborted) {\n controller.abort()\n } else {\n externalSignal.addEventListener('abort', () => controller.abort(), { once: true })\n }\n }\n\n try {\n const response = await fetch(url, {\n method,\n mode: 'cors',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n ...recaptchaHeaders,\n },\n signal: controller.signal,\n ...(method !== 'GET' && bodyData && { body: bodyData }),\n })\n\n if (timeoutTimer) {\n clearTimeout(timeoutTimer)\n }\n\n return response\n } catch (error) {\n if (timeoutTimer) {\n clearTimeout(timeoutTimer)\n }\n throw error\n }\n}\n\n/**\n * \u6269\u5C55 Window \u63A5\u53E3\u4EE5\u652F\u6301 grecaptcha\n */\n declare global {\n interface Window {\n grecaptcha?: {\n execute: (sitekey: string, options: { action: string }) => Promise<string>\n ready: (callback: () => void) => void\n enterprise?: {\n execute: (sitekey: string, options: { action: string }) =>\n Promise<string>\n ready: (callback: () => void) => void\n }\n }\n }\n }\n"],
5
- "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,eAAAC,EAAAH,GAUA,MAAMI,EAAmB,MAAOC,EAAgBC,IAA6C,CAC3F,GAAI,OAAO,OAAW,IACpB,eAAQ,KAAK,uEAAuE,EAC7E,GAGT,MAAMC,EAAW,OAAO,YAAY,YAAY,SAAW,OAAO,YAAY,QAE9E,GAAI,CAACA,EACH,eAAQ,KAAK,8EAA8E,EACpF,GAGT,GAAI,CAEF,OADc,MAAMA,EAASD,EAAS,CAAE,OAAAD,CAAO,CAAC,CAElD,OAASG,EAAO,CACd,eAAQ,MAAM,iDAAkDA,CAAK,EAC9D,EACT,CACF,EAKA,eAAeC,EACbJ,EACAC,EACAI,EAAY,oBACqB,CACjC,MAAMC,EAAiB,MAAMP,EAAiBC,EAAQC,CAAO,EAC7D,OAAKK,EAGE,CACL,CAACD,CAAS,EAAGC,CACf,EAJS,CAAC,CAKZ,CAuBO,MAAMT,EAAU,MAAO,CAC5B,IAAAU,EACA,OAAAC,EAAS,OACT,QAAAC,EAAU,CAAC,EACX,KAAAC,EACA,QAAAC,EAAU,IACV,cAAAC,EAAgB,GAChB,iBAAAC,EACA,gBAAAC,EAAkB,GAClB,mBAAAC,EAAqB,oBACrB,OAAQC,CACV,IAAyC,CACvC,IAAIC,EAA2C,CAAC,EAC5CL,IACGC,EAGHI,EAAmB,MAAMb,EAAoBU,EAAiBD,EAAkBE,CAAkB,EAFlG,QAAQ,KAAK,uEAAuE,GAMxF,MAAMG,EAAWR,EAAO,KAAK,UAAUA,CAAI,EAAI,OAGzCS,EAAa,IAAI,gBACvB,IAAIC,EACAT,IACFS,EAAe,WAAW,IAAMD,EAAW,MAAM,EAAGR,CAAO,GAGzDK,IACEA,EAAe,QACjBG,EAAW,MAAM,EAEjBH,EAAe,iBAAiB,QAAS,IAAMG,EAAW,MAAM,EAAG,CAAE,KAAM,EAAK,CAAC,GAIrF,GAAI,CACF,MAAME,EAAW,MAAM,MAAMd,EAAK,CAChC,OAAAC,EACA,KAAM,OACN,QAAS,CACP,eAAgB,mBAChB,GAAGC,EACH,GAAGQ,CACL,EACA,OAAQE,EAAW,OACnB,GAAIX,IAAW,OAASU,GAAY,CAAE,KAAMA,CAAS,CACvD,CAAC,EAED,OAAIE,GACF,aAAaA,CAAY,EAGpBC,CACT,OAASlB,EAAO,CACd,MAAIiB,GACF,aAAaA,CAAY,EAErBjB,CACR,CACF",
6
- "names": ["fetcher_exports", "__export", "fetcher", "__toCommonJS", "executeRecaptcha", "action", "sitekey", "executor", "error", "getRecaptchaHeaders", "headerKey", "recaptchaToken", "url", "method", "headers", "body", "timeout", "needRecaptcha", "recaptchaSitekey", "recaptchaAction", "recaptchaHeaderKey", "externalSignal", "recaptchaHeaders", "bodyData", "controller", "timeoutTimer", "response"]
4
+ "sourcesContent": ["/**\n * LiveChat Fetcher\n * \u53C2\u7167 storefront \u7684\u5B9E\u73B0\uFF0C\u652F\u6301 reCAPTCHA\n */\n\n/**\n * \u6267\u884C Google reCAPTCHA \u9A8C\u8BC1\n * \u517C\u5BB9 Enterprise \u7248 (recaptcha/enterprise.js) \u548C\u6807\u51C6\u7248 v3 (recaptcha/api.js)\n * \u4F18\u5148\u4F7F\u7528 Enterprise \u7248\uFF0C\u56DE\u9000\u5230\u6807\u51C6\u7248\n */\nconst executeRecaptcha = async (action: string, sitekey: string): Promise<string | false> => {\n if (typeof window === 'undefined') {\n console.warn('[LiveChat Fetcher] reCAPTCHA not available in non-browser environment')\n return false\n }\n\n const executor = window.grecaptcha?.enterprise?.execute ?? window.grecaptcha?.execute\n\n if (!executor) {\n console.warn('[LiveChat Fetcher] reCAPTCHA not loaded (neither enterprise nor standard v3)')\n return false\n }\n\n try {\n const token = await executor(sitekey, { action })\n return token\n } catch (error) {\n console.error('[LiveChat Fetcher] reCAPTCHA execution failed:', error)\n return false\n }\n}\n\n/**\n * \u83B7\u53D6 reCAPTCHA headers\n */\nasync function getRecaptchaHeaders(\n action: string,\n sitekey: string,\n headerKey = 'X-Recaptcha-Token'\n): Promise<Record<string, string>> {\n const recaptchaToken = await executeRecaptcha(action, sitekey)\n if (!recaptchaToken) {\n return {}\n }\n return {\n [headerKey]: recaptchaToken,\n }\n}\n\n/**\n * Fetcher \u53C2\u6570\n */\nexport interface FetcherOptions {\n url: string\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n headers?: Record<string, string>\n body?: any\n timeout?: number\n needRecaptcha?: boolean\n recaptchaSitekey?: string\n recaptchaAction?: string\n recaptchaHeaderKey?: string\n /** \u5916\u90E8\u4F20\u5165\u7684 AbortSignal\uFF0C\u7528\u4E8E\u63D0\u524D\u4E2D\u6B62\u8BF7\u6C42 */\n signal?: AbortSignal\n}\n\n/**\n * Fetcher \u51FD\u6570\n * \u652F\u6301 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n */\nexport const fetcher = async ({\n url,\n method = 'POST',\n headers = {},\n body,\n timeout = 90000,\n needRecaptcha = false,\n recaptchaSitekey,\n recaptchaAction = '',\n recaptchaHeaderKey = 'X-Recaptcha-Token',\n signal: externalSignal,\n}: FetcherOptions): Promise<Response> => {\n let recaptchaHeaders: Record<string, string> = {}\n if (needRecaptcha) {\n if (!recaptchaSitekey) {\n console.warn('[LiveChat Fetcher] needRecaptcha=true but recaptchaSitekey is missing')\n } else {\n recaptchaHeaders = await getRecaptchaHeaders(recaptchaAction, recaptchaSitekey, recaptchaHeaderKey)\n }\n }\n\n const bodyData = body ? JSON.stringify(body) : undefined\n\n // \u5408\u5E76\u5916\u90E8 signal\uFF08\u7528\u4E8E\u624B\u52A8\u4E2D\u6B62\uFF09\u548C\u8D85\u65F6 signal\n const controller = new AbortController()\n let timeoutTimer: NodeJS.Timeout | undefined\n if (timeout) {\n timeoutTimer = setTimeout(() => controller.abort(), timeout)\n }\n // \u5982\u679C\u5916\u90E8 signal \u5DF2\u4E2D\u6B62\u6216\u5728\u8BF7\u6C42\u671F\u95F4\u4E2D\u6B62\uFF0C\u540C\u6B65\u5230\u5185\u90E8 controller\n const onExternalAbort = () => controller.abort()\n if (externalSignal) {\n if (externalSignal.aborted) {\n controller.abort()\n } else {\n externalSignal.addEventListener('abort', onExternalAbort, { once: true })\n }\n }\n\n try {\n const response = await fetch(url, {\n method,\n mode: 'cors',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n ...recaptchaHeaders,\n },\n signal: controller.signal,\n ...(method !== 'GET' && bodyData && { body: bodyData }),\n })\n\n if (timeoutTimer) {\n clearTimeout(timeoutTimer)\n }\n\n return response\n } catch (error) {\n if (timeoutTimer) {\n clearTimeout(timeoutTimer)\n }\n throw error\n } finally {\n externalSignal?.removeEventListener('abort', onExternalAbort)\n }\n}\n\n/**\n * \u6269\u5C55 Window \u63A5\u53E3\u4EE5\u652F\u6301 grecaptcha\n */\n declare global {\n interface Window {\n grecaptcha?: {\n execute: (sitekey: string, options: { action: string }) => Promise<string>\n ready: (callback: () => void) => void\n enterprise?: {\n execute: (sitekey: string, options: { action: string }) =>\n Promise<string>\n ready: (callback: () => void) => void\n }\n }\n }\n }\n"],
5
+ "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,eAAAC,EAAAH,GAUA,MAAMI,EAAmB,MAAOC,EAAgBC,IAA6C,CAC3F,GAAI,OAAO,OAAW,IACpB,eAAQ,KAAK,uEAAuE,EAC7E,GAGT,MAAMC,EAAW,OAAO,YAAY,YAAY,SAAW,OAAO,YAAY,QAE9E,GAAI,CAACA,EACH,eAAQ,KAAK,8EAA8E,EACpF,GAGT,GAAI,CAEF,OADc,MAAMA,EAASD,EAAS,CAAE,OAAAD,CAAO,CAAC,CAElD,OAASG,EAAO,CACd,eAAQ,MAAM,iDAAkDA,CAAK,EAC9D,EACT,CACF,EAKA,eAAeC,EACbJ,EACAC,EACAI,EAAY,oBACqB,CACjC,MAAMC,EAAiB,MAAMP,EAAiBC,EAAQC,CAAO,EAC7D,OAAKK,EAGE,CACL,CAACD,CAAS,EAAGC,CACf,EAJS,CAAC,CAKZ,CAuBO,MAAMT,EAAU,MAAO,CAC5B,IAAAU,EACA,OAAAC,EAAS,OACT,QAAAC,EAAU,CAAC,EACX,KAAAC,EACA,QAAAC,EAAU,IACV,cAAAC,EAAgB,GAChB,iBAAAC,EACA,gBAAAC,EAAkB,GAClB,mBAAAC,EAAqB,oBACrB,OAAQC,CACV,IAAyC,CACvC,IAAIC,EAA2C,CAAC,EAC5CL,IACGC,EAGHI,EAAmB,MAAMb,EAAoBU,EAAiBD,EAAkBE,CAAkB,EAFlG,QAAQ,KAAK,uEAAuE,GAMxF,MAAMG,EAAWR,EAAO,KAAK,UAAUA,CAAI,EAAI,OAGzCS,EAAa,IAAI,gBACvB,IAAIC,EACAT,IACFS,EAAe,WAAW,IAAMD,EAAW,MAAM,EAAGR,CAAO,GAG7D,MAAMU,EAAkB,IAAMF,EAAW,MAAM,EAC3CH,IACEA,EAAe,QACjBG,EAAW,MAAM,EAEjBH,EAAe,iBAAiB,QAASK,EAAiB,CAAE,KAAM,EAAK,CAAC,GAI5E,GAAI,CACF,MAAMC,EAAW,MAAM,MAAMf,EAAK,CAChC,OAAAC,EACA,KAAM,OACN,QAAS,CACP,eAAgB,mBAChB,GAAGC,EACH,GAAGQ,CACL,EACA,OAAQE,EAAW,OACnB,GAAIX,IAAW,OAASU,GAAY,CAAE,KAAMA,CAAS,CACvD,CAAC,EAED,OAAIE,GACF,aAAaA,CAAY,EAGpBE,CACT,OAASnB,EAAO,CACd,MAAIiB,GACF,aAAaA,CAAY,EAErBjB,CACR,QAAE,CACAa,GAAgB,oBAAoB,QAASK,CAAe,CAC9D,CACF",
6
+ "names": ["fetcher_exports", "__export", "fetcher", "__toCommonJS", "executeRecaptcha", "action", "sitekey", "executor", "error", "getRecaptchaHeaders", "headerKey", "recaptchaToken", "url", "method", "headers", "body", "timeout", "needRecaptcha", "recaptchaSitekey", "recaptchaAction", "recaptchaHeaderKey", "externalSignal", "recaptchaHeaders", "bodyData", "controller", "timeoutTimer", "onExternalAbort", "response"]
7
7
  }
@@ -1,5 +1,5 @@
1
- import{jsx as t,jsxs as u}from"react/jsx-runtime";import{useRef as w,useEffect as c,useState as v,useCallback as d}from"react";import{ChatMessage as C}from"./ChatMessage";import{ScrollAnchor as E}from"./ScrollAnchor";const k=()=>t("div",{className:"flex h-full items-center justify-center text-gray-400",children:t("p",{className:"text-sm",children:"No messages yet"})}),H=()=>t("div",{className:"flex justify-center py-4",children:u("div",{className:"flex gap-1",children:[t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400"}),t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.1s"}}),t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.2s"}})]})}),z=({messages:r,rendererRegistry:g,defaultRenderer:p,showTimestamp:h=!0,autoScroll:o=!0,isLoadingHistory:m=!1,emptyPlaceholder:y,className:i="",onAddToCart:R})=>{const[e,b]=v(null),N=d(s=>{b(s)},[]),[f,a]=v(!1),n=w(!1),l=d((s=100)=>{if(!e)return!0;const{scrollTop:T,scrollHeight:L,clientHeight:M}=e;return L-T-M<s},[e]),x=d(()=>{e&&e.scrollTo({top:e.scrollHeight,behavior:"smooth"})},[e]);return c(()=>{if(!e)return;const s=()=>{a(!l())};return s(),e.addEventListener("scroll",s,{passive:!0}),()=>e.removeEventListener("scroll",s)},[e,l]),c(()=>{if(o)return;const s=setTimeout(()=>{a(!l())},150);return()=>clearTimeout(s)},[r,o,l]),c(()=>{if(!o||!e)return;const s=setTimeout(()=>{e&&(e.scrollTop=e.scrollHeight,a(!1))},100);return()=>clearTimeout(s)},[r,o,e]),m&&(n.current=!0),r.length===0?m||n.current?t("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${i}`,children:t(H,{})}):t("div",{className:`flex-1 overflow-hidden ${i}`,children:y||t(k,{})}):(n.current=!1,u("div",{className:"relative flex-1 overflow-hidden",children:[u("div",{ref:N,className:`
1
+ import{jsx as e,jsxs as m}from"react/jsx-runtime";import{useEffect as n,useState as d,useCallback as u}from"react";import{ChatMessage as C}from"./ChatMessage";import{ScrollAnchor as k}from"./ScrollAnchor";const B=()=>e("div",{className:"flex h-full items-center justify-center text-gray-400",children:e("p",{className:"text-sm",children:"No messages yet"})}),h=()=>e("div",{className:"flex justify-center py-4",children:m("div",{className:"flex gap-1",children:[e("div",{className:"size-2 animate-bounce rounded-full bg-gray-400"}),e("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.1s"}}),e("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.2s"}})]})}),A=({messages:o,rendererRegistry:p,defaultRenderer:g,showTimestamp:y=!0,autoScroll:s=!0,isLoadingHistory:a=!1,emptyPlaceholder:b,className:l="",onAddToCart:R})=>{const[t,x]=d(null),N=u(r=>{x(r)},[]),[f,c]=d(!1),[T,v]=d(!1),i=u((r=100)=>{if(!t)return!0;const{scrollTop:M,scrollHeight:E,clientHeight:L}=t;return E-M-L<r},[t]),w=u(()=>{t&&t.scrollTo({top:t.scrollHeight,behavior:"smooth"})},[t]);return n(()=>{if(!t)return;const r=()=>{c(!i())};return r(),t.addEventListener("scroll",r,{passive:!0}),()=>t.removeEventListener("scroll",r)},[t,i]),n(()=>{if(s)return;const r=setTimeout(()=>{c(!i())},150);return()=>clearTimeout(r)},[o,s,i]),n(()=>{if(!s||!t)return;const r=setTimeout(()=>{t&&(t.scrollTop=t.scrollHeight,c(!1))},100);return()=>clearTimeout(r)},[o,s,t]),n(()=>{if(!a&&o.length===0){const r=setTimeout(()=>v(!0),150);return()=>clearTimeout(r)}else v(!1)},[a,o.length]),a?e("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${l}`,children:e(h,{})}):o.length===0?T?e("div",{className:`flex-1 overflow-hidden ${l}`,children:b||e(B,{})}):e("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${l}`,children:e(h,{})}):m("div",{className:"relative flex-1 overflow-hidden",children:[m("div",{ref:N,className:`
2
2
  livechat-message-list absolute inset-0 overflow-y-auto p-4
3
- ${i}
4
- `,children:[t("div",{className:"flex flex-col gap-1",children:r.map(s=>t(C,{message:s,rendererRegistry:g,defaultRenderer:p,showTimestamp:h,onAddToCart:R},s.id))}),o&&t(E,{dependencies:[r]})]}),t("button",{onClick:x,className:`livechat-scroll-to-bottom ${f?"visible":"hidden"}`,"aria-label":"Scroll to bottom","aria-hidden":!f,children:t("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-gray-700",children:t("polyline",{points:"6 9 12 15 18 9"})})})]}))};export{z as MessageList};
3
+ ${l}
4
+ `,children:[e("div",{className:"flex flex-col gap-1",children:o.map(r=>e(C,{message:r,rendererRegistry:p,defaultRenderer:g,showTimestamp:y,onAddToCart:R},r.id))}),s&&e(k,{dependencies:[o]})]}),e("button",{onClick:w,className:`livechat-scroll-to-bottom ${f?"visible":"hidden"}`,"aria-label":"Scroll to bottom","aria-hidden":!f,children:e("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-gray-700",children:e("polyline",{points:"6 9 12 15 18 9"})})})]})};export{A as MessageList};
5
5
  //# sourceMappingURL=MessageList.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/MessageList.tsx"],
4
- "sourcesContent": ["/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n * \u663E\u793A\u6240\u6709\u804A\u5929\u6D88\u606F\uFF0C\u652F\u6301\u6EDA\u52A8\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u6D88\u606F\u5217\u8868\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatMessage } from './ChatMessage'\nimport { ScrollAnchor } from './ScrollAnchor'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface MessageListProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n */\n rendererRegistry?: MessageRendererRegistry\n\n /**\n * \u9ED8\u8BA4\u6E32\u67D3\u5668\n */\n defaultRenderer?: MessageRenderer\n\n /**\n * \u662F\u5426\u663E\u793A\u65F6\u95F4\u6233\n * @default true\n */\n showTimestamp?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\n * @default true\n */\n autoScroll?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\n\n /**\n * \u7A7A\u72B6\u6001\u5360\u4F4D\u5185\u5BB9\n */\n emptyPlaceholder?: React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n}\n\n/**\n * \u9ED8\u8BA4\u7A7A\u72B6\u6001\u5360\u4F4D\n */\nconst DefaultEmptyPlaceholder: React.FC = () => (\n <div className=\"flex h-full items-center justify-center text-gray-400\">\n <p className=\"text-sm\">No messages yet</p>\n </div>\n)\n\n/**\n * \u52A0\u8F7D\u6307\u793A\u5668\n */\nconst LoadingIndicator: React.FC = () => (\n <div className=\"flex justify-center py-4\">\n <div className=\"flex gap-1\">\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.1s' }} />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.2s' }} />\n </div>\n </div>\n)\n\n/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6240\u6709\u6D88\u606F\uFF08\u7528\u6237\u3001\u52A9\u624B\u3001\u7CFB\u7EDF\uFF09\n * - \u81EA\u52A8\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F\n * - \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u65F6\u663E\u793A\u6307\u793A\u5668\n * - \u7A7A\u72B6\u6001\u5360\u4F4D\n *\n * \u6837\u5F0F\uFF1A\n * - \u4F7F\u7528\u81EA\u5B9A\u4E49\u6EDA\u52A8\u6761\u6837\u5F0F\uFF08livechat.css\uFF09\n * - \u5185\u5BB9\u95F4\u8DDD\u5408\u7406\n * - \u652F\u6301\u54CD\u5E94\u5F0F\u5E03\u5C40\n *\n * @example\n * ```tsx\n * <MessageList\n * messages={messages}\n * rendererRegistry={customRegistry}\n * autoScroll={true}\n * isLoadingHistory={isLoading}\n * />\n * ```\n */\nexport const MessageList: React.FC<MessageListProps> = ({\n messages,\n rendererRegistry,\n defaultRenderer,\n showTimestamp = true,\n autoScroll = true,\n isLoadingHistory = false,\n emptyPlaceholder,\n className = '',\n onAddToCart,\n}) => {\n // \u4F7F\u7528 callback ref + state \u786E\u4FDD DOM \u6302\u8F7D\u540E\u89E6\u53D1\u91CD\u65B0\u6E32\u67D3\n const [listElement, setListElement] = useState<HTMLDivElement | null>(null)\n const listRef = useCallback((node: HTMLDivElement | null) => {\n setListElement(node)\n }, [])\n const [showScrollButton, setShowScrollButton] = useState(false)\n\n // \u8DDF\u8E2A\u662F\u5426\u66FE\u8FDB\u5165\u8FC7 loading \u72B6\u6001\uFF0C\u9632\u6B62 loading\u2192\u7A7A\u6001\u2192\u6D88\u606F\u7684\u77AC\u95F4\u95EA\u70C1\n const wasLoadingRef = useRef(false)\n\n // \u68C0\u67E5\u662F\u5426\u63A5\u8FD1\u5E95\u90E8\n const isNearBottom = useCallback(\n (threshold = 100) => {\n if (!listElement) return true\n\n const { scrollTop, scrollHeight, clientHeight } = listElement\n return scrollHeight - scrollTop - clientHeight < threshold\n },\n [listElement]\n )\n\n // \u5E73\u6ED1\u6EDA\u52A8\u5230\u5E95\u90E8\n const scrollToBottom = useCallback(() => {\n if (!listElement) return\n\n listElement.scrollTo({\n top: listElement.scrollHeight,\n behavior: 'smooth',\n })\n }, [listElement])\n\n // \u76D1\u542C\u6EDA\u52A8\u4E8B\u4EF6\uFF0C\u63A7\u5236\u6309\u94AE\u663E\u793A\n useEffect(() => {\n if (!listElement) return\n\n const handleScroll = () => {\n setShowScrollButton(!isNearBottom())\n }\n\n // \u521D\u59CB\u68C0\u67E5\n handleScroll()\n\n listElement.addEventListener('scroll', handleScroll, { passive: true })\n return () => listElement.removeEventListener('scroll', handleScroll)\n }, [listElement, isNearBottom])\n\n // \u5F53\u6D88\u606F\u5217\u8868\u53D8\u5316\u65F6\uFF0C\u91CD\u65B0\u68C0\u67E5\u6309\u94AE\u72B6\u6001\uFF08\u4F46\u4E0D\u5728\u81EA\u52A8\u6EDA\u52A8\u65F6\uFF09\n useEffect(() => {\n if (autoScroll) return // \u81EA\u52A8\u6EDA\u52A8\u4F1A\u5728\u53E6\u4E00\u4E2A effect \u4E2D\u5904\u7406\n\n const timeoutId = setTimeout(() => {\n setShowScrollButton(!isNearBottom())\n }, 150)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, isNearBottom])\n\n // \u76D1\u542C\u6D88\u606F\u5217\u8868\u53D8\u5316\uFF0C\u81EA\u52A8\u6EDA\u52A8\n useEffect(() => {\n if (!autoScroll || !listElement) return\n\n // \u5EF6\u8FDF\u6EDA\u52A8\u4EE5\u786E\u4FDD DOM \u5DF2\u66F4\u65B0\n const timeoutId = setTimeout(() => {\n if (listElement) {\n listElement.scrollTop = listElement.scrollHeight\n // \u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\u540E\uFF0C\u9690\u85CF\u6309\u94AE\n setShowScrollButton(false)\n }\n }, 100)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, listElement])\n\n if (isLoadingHistory) {\n wasLoadingRef.current = true\n }\n\n if (messages.length === 0) {\n if (isLoadingHistory || wasLoadingRef.current) {\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n return (\n <div className={`flex-1 overflow-hidden ${className}`}>{emptyPlaceholder || <DefaultEmptyPlaceholder />}</div>\n )\n }\n\n wasLoadingRef.current = false\n\n return (\n <div className=\"relative flex-1 overflow-hidden\">\n <div\n ref={listRef}\n className={`\n livechat-message-list absolute inset-0 overflow-y-auto p-4\n ${className}\n `}\n >\n {/* \u6D88\u606F\u5217\u8868 */}\n <div className=\"flex flex-col gap-1\">\n {messages.map(message => (\n <ChatMessage\n key={message.id}\n message={message}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n onAddToCart={onAddToCart}\n />\n ))}\n </div>\n\n {/* \u6EDA\u52A8\u951A\u70B9 */}\n {autoScroll && <ScrollAnchor dependencies={[messages]} />}\n </div>\n\n {/* \u56DE\u5230\u5E95\u90E8\u6309\u94AE */}\n <button\n onClick={scrollToBottom}\n className={`livechat-scroll-to-bottom ${showScrollButton ? 'visible' : 'hidden'}`}\n aria-label=\"Scroll to bottom\"\n aria-hidden={!showScrollButton}\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-gray-700\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n </div>\n )\n}\n"],
5
- "mappings": "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",
6
- "names": ["jsx", "jsxs", "useRef", "useEffect", "useState", "useCallback", "ChatMessage", "ScrollAnchor", "DefaultEmptyPlaceholder", "LoadingIndicator", "MessageList", "messages", "rendererRegistry", "defaultRenderer", "showTimestamp", "autoScroll", "isLoadingHistory", "emptyPlaceholder", "className", "onAddToCart", "listElement", "setListElement", "listRef", "node", "showScrollButton", "setShowScrollButton", "wasLoadingRef", "isNearBottom", "threshold", "scrollTop", "scrollHeight", "clientHeight", "scrollToBottom", "handleScroll", "timeoutId", "message"]
4
+ "sourcesContent": ["/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n * \u663E\u793A\u6240\u6709\u804A\u5929\u6D88\u606F\uFF0C\u652F\u6301\u6EDA\u52A8\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u6D88\u606F\u5217\u8868\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatMessage } from './ChatMessage'\nimport { ScrollAnchor } from './ScrollAnchor'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface MessageListProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n */\n rendererRegistry?: MessageRendererRegistry\n\n /**\n * \u9ED8\u8BA4\u6E32\u67D3\u5668\n */\n defaultRenderer?: MessageRenderer\n\n /**\n * \u662F\u5426\u663E\u793A\u65F6\u95F4\u6233\n * @default true\n */\n showTimestamp?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\n * @default true\n */\n autoScroll?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\n\n /**\n * \u7A7A\u72B6\u6001\u5360\u4F4D\u5185\u5BB9\n */\n emptyPlaceholder?: React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n}\n\n/**\n * \u9ED8\u8BA4\u7A7A\u72B6\u6001\u5360\u4F4D\n */\nconst DefaultEmptyPlaceholder: React.FC = () => (\n <div className=\"flex h-full items-center justify-center text-gray-400\">\n <p className=\"text-sm\">No messages yet</p>\n </div>\n)\n\n/**\n * \u52A0\u8F7D\u6307\u793A\u5668\n */\nconst LoadingIndicator: React.FC = () => (\n <div className=\"flex justify-center py-4\">\n <div className=\"flex gap-1\">\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.1s' }} />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.2s' }} />\n </div>\n </div>\n)\n\n/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6240\u6709\u6D88\u606F\uFF08\u7528\u6237\u3001\u52A9\u624B\u3001\u7CFB\u7EDF\uFF09\n * - \u81EA\u52A8\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F\n * - \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u65F6\u663E\u793A\u6307\u793A\u5668\n * - \u7A7A\u72B6\u6001\u5360\u4F4D\n *\n * \u6837\u5F0F\uFF1A\n * - \u4F7F\u7528\u81EA\u5B9A\u4E49\u6EDA\u52A8\u6761\u6837\u5F0F\uFF08livechat.css\uFF09\n * - \u5185\u5BB9\u95F4\u8DDD\u5408\u7406\n * - \u652F\u6301\u54CD\u5E94\u5F0F\u5E03\u5C40\n *\n * @example\n * ```tsx\n * <MessageList\n * messages={messages}\n * rendererRegistry={customRegistry}\n * autoScroll={true}\n * isLoadingHistory={isLoading}\n * />\n * ```\n */\nexport const MessageList: React.FC<MessageListProps> = ({\n messages,\n rendererRegistry,\n defaultRenderer,\n showTimestamp = true,\n autoScroll = true,\n isLoadingHistory = false,\n emptyPlaceholder,\n className = '',\n onAddToCart,\n}) => {\n // \u4F7F\u7528 callback ref + state \u786E\u4FDD DOM \u6302\u8F7D\u540E\u89E6\u53D1\u91CD\u65B0\u6E32\u67D3\n const [listElement, setListElement] = useState<HTMLDivElement | null>(null)\n const listRef = useCallback((node: HTMLDivElement | null) => {\n setListElement(node)\n }, [])\n const [showScrollButton, setShowScrollButton] = useState(false)\n\n const [showEmpty, setShowEmpty] = useState(false)\n\n // \u68C0\u67E5\u662F\u5426\u63A5\u8FD1\u5E95\u90E8\n const isNearBottom = useCallback(\n (threshold = 100) => {\n if (!listElement) return true\n\n const { scrollTop, scrollHeight, clientHeight } = listElement\n return scrollHeight - scrollTop - clientHeight < threshold\n },\n [listElement]\n )\n\n // \u5E73\u6ED1\u6EDA\u52A8\u5230\u5E95\u90E8\n const scrollToBottom = useCallback(() => {\n if (!listElement) return\n\n listElement.scrollTo({\n top: listElement.scrollHeight,\n behavior: 'smooth',\n })\n }, [listElement])\n\n // \u76D1\u542C\u6EDA\u52A8\u4E8B\u4EF6\uFF0C\u63A7\u5236\u6309\u94AE\u663E\u793A\n useEffect(() => {\n if (!listElement) return\n\n const handleScroll = () => {\n setShowScrollButton(!isNearBottom())\n }\n\n // \u521D\u59CB\u68C0\u67E5\n handleScroll()\n\n listElement.addEventListener('scroll', handleScroll, { passive: true })\n return () => listElement.removeEventListener('scroll', handleScroll)\n }, [listElement, isNearBottom])\n\n // \u5F53\u6D88\u606F\u5217\u8868\u53D8\u5316\u65F6\uFF0C\u91CD\u65B0\u68C0\u67E5\u6309\u94AE\u72B6\u6001\uFF08\u4F46\u4E0D\u5728\u81EA\u52A8\u6EDA\u52A8\u65F6\uFF09\n useEffect(() => {\n if (autoScroll) return // \u81EA\u52A8\u6EDA\u52A8\u4F1A\u5728\u53E6\u4E00\u4E2A effect \u4E2D\u5904\u7406\n\n const timeoutId = setTimeout(() => {\n setShowScrollButton(!isNearBottom())\n }, 150)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, isNearBottom])\n\n // \u76D1\u542C\u6D88\u606F\u5217\u8868\u53D8\u5316\uFF0C\u81EA\u52A8\u6EDA\u52A8\n useEffect(() => {\n if (!autoScroll || !listElement) return\n\n // \u5EF6\u8FDF\u6EDA\u52A8\u4EE5\u786E\u4FDD DOM \u5DF2\u66F4\u65B0\n const timeoutId = setTimeout(() => {\n if (listElement) {\n listElement.scrollTop = listElement.scrollHeight\n // \u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\u540E\uFF0C\u9690\u85CF\u6309\u94AE\n setShowScrollButton(false)\n }\n }, 100)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, listElement])\n\n useEffect(() => {\n if (!isLoadingHistory && messages.length === 0) {\n const timer = setTimeout(() => setShowEmpty(true), 150)\n return () => clearTimeout(timer)\n } else {\n setShowEmpty(false)\n }\n }, [isLoadingHistory, messages.length])\n\n if (isLoadingHistory) {\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n\n if (messages.length === 0) {\n if (!showEmpty) {\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n return (\n <div className={`flex-1 overflow-hidden ${className}`}>{emptyPlaceholder || <DefaultEmptyPlaceholder />}</div>\n )\n }\n\n return (\n <div className=\"relative flex-1 overflow-hidden\">\n <div\n ref={listRef}\n className={`\n livechat-message-list absolute inset-0 overflow-y-auto p-4\n ${className}\n `}\n >\n {/* \u6D88\u606F\u5217\u8868 */}\n <div className=\"flex flex-col gap-1\">\n {messages.map(message => (\n <ChatMessage\n key={message.id}\n message={message}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n onAddToCart={onAddToCart}\n />\n ))}\n </div>\n\n {/* \u6EDA\u52A8\u951A\u70B9 */}\n {autoScroll && <ScrollAnchor dependencies={[messages]} />}\n </div>\n\n {/* \u56DE\u5230\u5E95\u90E8\u6309\u94AE */}\n <button\n onClick={scrollToBottom}\n className={`livechat-scroll-to-bottom ${showScrollButton ? 'visible' : 'hidden'}`}\n aria-label=\"Scroll to bottom\"\n aria-hidden={!showScrollButton}\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-gray-700\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["jsx", "jsxs", "useEffect", "useState", "useCallback", "ChatMessage", "ScrollAnchor", "DefaultEmptyPlaceholder", "LoadingIndicator", "MessageList", "messages", "rendererRegistry", "defaultRenderer", "showTimestamp", "autoScroll", "isLoadingHistory", "emptyPlaceholder", "className", "onAddToCart", "listElement", "setListElement", "listRef", "node", "showScrollButton", "setShowScrollButton", "showEmpty", "setShowEmpty", "isNearBottom", "threshold", "scrollTop", "scrollHeight", "clientHeight", "scrollToBottom", "handleScroll", "timeoutId", "timer", "message"]
7
7
  }
@@ -1,2 +1,2 @@
1
- const f=async(n,t)=>{if(typeof window>"u")return console.warn("[LiveChat Fetcher] reCAPTCHA not available in non-browser environment"),!1;const r=window.grecaptcha?.enterprise?.execute??window.grecaptcha?.execute;if(!r)return console.warn("[LiveChat Fetcher] reCAPTCHA not loaded (neither enterprise nor standard v3)"),!1;try{return await r(t,{action:n})}catch(e){return console.error("[LiveChat Fetcher] reCAPTCHA execution failed:",e),!1}};async function T(n,t,r="X-Recaptcha-Token"){const e=await f(n,t);return e?{[r]:e}:{}}const b=async({url:n,method:t="POST",headers:r={},body:e,timeout:s=9e4,needRecaptcha:g=!1,recaptchaSitekey:d,recaptchaAction:p="",recaptchaHeaderKey:u="X-Recaptcha-Token",signal:a})=>{let l={};g&&(d?l=await T(p,d,u):console.warn("[LiveChat Fetcher] needRecaptcha=true but recaptchaSitekey is missing"));const h=e?JSON.stringify(e):void 0,o=new AbortController;let i;s&&(i=setTimeout(()=>o.abort(),s)),a&&(a.aborted?o.abort():a.addEventListener("abort",()=>o.abort(),{once:!0}));try{const c=await fetch(n,{method:t,mode:"cors",headers:{"Content-Type":"application/json",...r,...l},signal:o.signal,...t!=="GET"&&h&&{body:h}});return i&&clearTimeout(i),c}catch(c){throw i&&clearTimeout(i),c}};export{b as fetcher};
1
+ const b=async(n,t)=>{if(typeof window>"u")return console.warn("[LiveChat Fetcher] reCAPTCHA not available in non-browser environment"),!1;const r=window.grecaptcha?.enterprise?.execute??window.grecaptcha?.execute;if(!r)return console.warn("[LiveChat Fetcher] reCAPTCHA not loaded (neither enterprise nor standard v3)"),!1;try{return await r(t,{action:n})}catch(e){return console.error("[LiveChat Fetcher] reCAPTCHA execution failed:",e),!1}};async function T(n,t,r="X-Recaptcha-Token"){const e=await b(n,t);return e?{[r]:e}:{}}const y=async({url:n,method:t="POST",headers:r={},body:e,timeout:s=9e4,needRecaptcha:p=!1,recaptchaSitekey:d,recaptchaAction:u="",recaptchaHeaderKey:f="X-Recaptcha-Token",signal:i})=>{let l={};p&&(d?l=await T(u,d,f):console.warn("[LiveChat Fetcher] needRecaptcha=true but recaptchaSitekey is missing"));const h=e?JSON.stringify(e):void 0,a=new AbortController;let o;s&&(o=setTimeout(()=>a.abort(),s));const g=()=>a.abort();i&&(i.aborted?a.abort():i.addEventListener("abort",g,{once:!0}));try{const c=await fetch(n,{method:t,mode:"cors",headers:{"Content-Type":"application/json",...r,...l},signal:a.signal,...t!=="GET"&&h&&{body:h}});return o&&clearTimeout(o),c}catch(c){throw o&&clearTimeout(o),c}finally{i?.removeEventListener("abort",g)}};export{y as fetcher};
2
2
  //# sourceMappingURL=fetcher.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/utils/fetcher.ts"],
4
- "sourcesContent": ["/**\n * LiveChat Fetcher\n * \u53C2\u7167 storefront \u7684\u5B9E\u73B0\uFF0C\u652F\u6301 reCAPTCHA\n */\n\n/**\n * \u6267\u884C Google reCAPTCHA \u9A8C\u8BC1\n * \u517C\u5BB9 Enterprise \u7248 (recaptcha/enterprise.js) \u548C\u6807\u51C6\u7248 v3 (recaptcha/api.js)\n * \u4F18\u5148\u4F7F\u7528 Enterprise \u7248\uFF0C\u56DE\u9000\u5230\u6807\u51C6\u7248\n */\nconst executeRecaptcha = async (action: string, sitekey: string): Promise<string | false> => {\n if (typeof window === 'undefined') {\n console.warn('[LiveChat Fetcher] reCAPTCHA not available in non-browser environment')\n return false\n }\n\n const executor = window.grecaptcha?.enterprise?.execute ?? window.grecaptcha?.execute\n\n if (!executor) {\n console.warn('[LiveChat Fetcher] reCAPTCHA not loaded (neither enterprise nor standard v3)')\n return false\n }\n\n try {\n const token = await executor(sitekey, { action })\n return token\n } catch (error) {\n console.error('[LiveChat Fetcher] reCAPTCHA execution failed:', error)\n return false\n }\n}\n\n/**\n * \u83B7\u53D6 reCAPTCHA headers\n */\nasync function getRecaptchaHeaders(\n action: string,\n sitekey: string,\n headerKey = 'X-Recaptcha-Token'\n): Promise<Record<string, string>> {\n const recaptchaToken = await executeRecaptcha(action, sitekey)\n if (!recaptchaToken) {\n return {}\n }\n return {\n [headerKey]: recaptchaToken,\n }\n}\n\n/**\n * Fetcher \u53C2\u6570\n */\nexport interface FetcherOptions {\n url: string\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n headers?: Record<string, string>\n body?: any\n timeout?: number\n needRecaptcha?: boolean\n recaptchaSitekey?: string\n recaptchaAction?: string\n recaptchaHeaderKey?: string\n /** \u5916\u90E8\u4F20\u5165\u7684 AbortSignal\uFF0C\u7528\u4E8E\u63D0\u524D\u4E2D\u6B62\u8BF7\u6C42 */\n signal?: AbortSignal\n}\n\n/**\n * Fetcher \u51FD\u6570\n * \u652F\u6301 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n */\nexport const fetcher = async ({\n url,\n method = 'POST',\n headers = {},\n body,\n timeout = 90000,\n needRecaptcha = false,\n recaptchaSitekey,\n recaptchaAction = '',\n recaptchaHeaderKey = 'X-Recaptcha-Token',\n signal: externalSignal,\n}: FetcherOptions): Promise<Response> => {\n let recaptchaHeaders: Record<string, string> = {}\n if (needRecaptcha) {\n if (!recaptchaSitekey) {\n console.warn('[LiveChat Fetcher] needRecaptcha=true but recaptchaSitekey is missing')\n } else {\n recaptchaHeaders = await getRecaptchaHeaders(recaptchaAction, recaptchaSitekey, recaptchaHeaderKey)\n }\n }\n\n const bodyData = body ? JSON.stringify(body) : undefined\n\n // \u5408\u5E76\u5916\u90E8 signal\uFF08\u7528\u4E8E\u624B\u52A8\u4E2D\u6B62\uFF09\u548C\u8D85\u65F6 signal\n const controller = new AbortController()\n let timeoutTimer: NodeJS.Timeout | undefined\n if (timeout) {\n timeoutTimer = setTimeout(() => controller.abort(), timeout)\n }\n // \u5982\u679C\u5916\u90E8 signal \u5DF2\u4E2D\u6B62\u6216\u5728\u8BF7\u6C42\u671F\u95F4\u4E2D\u6B62\uFF0C\u540C\u6B65\u5230\u5185\u90E8 controller\n if (externalSignal) {\n if (externalSignal.aborted) {\n controller.abort()\n } else {\n externalSignal.addEventListener('abort', () => controller.abort(), { once: true })\n }\n }\n\n try {\n const response = await fetch(url, {\n method,\n mode: 'cors',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n ...recaptchaHeaders,\n },\n signal: controller.signal,\n ...(method !== 'GET' && bodyData && { body: bodyData }),\n })\n\n if (timeoutTimer) {\n clearTimeout(timeoutTimer)\n }\n\n return response\n } catch (error) {\n if (timeoutTimer) {\n clearTimeout(timeoutTimer)\n }\n throw error\n }\n}\n\n/**\n * \u6269\u5C55 Window \u63A5\u53E3\u4EE5\u652F\u6301 grecaptcha\n */\n declare global {\n interface Window {\n grecaptcha?: {\n execute: (sitekey: string, options: { action: string }) => Promise<string>\n ready: (callback: () => void) => void\n enterprise?: {\n execute: (sitekey: string, options: { action: string }) =>\n Promise<string>\n ready: (callback: () => void) => void\n }\n }\n }\n }\n"],
5
- "mappings": "AAUA,MAAMA,EAAmB,MAAOC,EAAgBC,IAA6C,CAC3F,GAAI,OAAO,OAAW,IACpB,eAAQ,KAAK,uEAAuE,EAC7E,GAGT,MAAMC,EAAW,OAAO,YAAY,YAAY,SAAW,OAAO,YAAY,QAE9E,GAAI,CAACA,EACH,eAAQ,KAAK,8EAA8E,EACpF,GAGT,GAAI,CAEF,OADc,MAAMA,EAASD,EAAS,CAAE,OAAAD,CAAO,CAAC,CAElD,OAASG,EAAO,CACd,eAAQ,MAAM,iDAAkDA,CAAK,EAC9D,EACT,CACF,EAKA,eAAeC,EACbJ,EACAC,EACAI,EAAY,oBACqB,CACjC,MAAMC,EAAiB,MAAMP,EAAiBC,EAAQC,CAAO,EAC7D,OAAKK,EAGE,CACL,CAACD,CAAS,EAAGC,CACf,EAJS,CAAC,CAKZ,CAuBO,MAAMC,EAAU,MAAO,CAC5B,IAAAC,EACA,OAAAC,EAAS,OACT,QAAAC,EAAU,CAAC,EACX,KAAAC,EACA,QAAAC,EAAU,IACV,cAAAC,EAAgB,GAChB,iBAAAC,EACA,gBAAAC,EAAkB,GAClB,mBAAAC,EAAqB,oBACrB,OAAQC,CACV,IAAyC,CACvC,IAAIC,EAA2C,CAAC,EAC5CL,IACGC,EAGHI,EAAmB,MAAMd,EAAoBW,EAAiBD,EAAkBE,CAAkB,EAFlG,QAAQ,KAAK,uEAAuE,GAMxF,MAAMG,EAAWR,EAAO,KAAK,UAAUA,CAAI,EAAI,OAGzCS,EAAa,IAAI,gBACvB,IAAIC,EACAT,IACFS,EAAe,WAAW,IAAMD,EAAW,MAAM,EAAGR,CAAO,GAGzDK,IACEA,EAAe,QACjBG,EAAW,MAAM,EAEjBH,EAAe,iBAAiB,QAAS,IAAMG,EAAW,MAAM,EAAG,CAAE,KAAM,EAAK,CAAC,GAIrF,GAAI,CACF,MAAME,EAAW,MAAM,MAAMd,EAAK,CAChC,OAAAC,EACA,KAAM,OACN,QAAS,CACP,eAAgB,mBAChB,GAAGC,EACH,GAAGQ,CACL,EACA,OAAQE,EAAW,OACnB,GAAIX,IAAW,OAASU,GAAY,CAAE,KAAMA,CAAS,CACvD,CAAC,EAED,OAAIE,GACF,aAAaA,CAAY,EAGpBC,CACT,OAASnB,EAAO,CACd,MAAIkB,GACF,aAAaA,CAAY,EAErBlB,CACR,CACF",
6
- "names": ["executeRecaptcha", "action", "sitekey", "executor", "error", "getRecaptchaHeaders", "headerKey", "recaptchaToken", "fetcher", "url", "method", "headers", "body", "timeout", "needRecaptcha", "recaptchaSitekey", "recaptchaAction", "recaptchaHeaderKey", "externalSignal", "recaptchaHeaders", "bodyData", "controller", "timeoutTimer", "response"]
4
+ "sourcesContent": ["/**\n * LiveChat Fetcher\n * \u53C2\u7167 storefront \u7684\u5B9E\u73B0\uFF0C\u652F\u6301 reCAPTCHA\n */\n\n/**\n * \u6267\u884C Google reCAPTCHA \u9A8C\u8BC1\n * \u517C\u5BB9 Enterprise \u7248 (recaptcha/enterprise.js) \u548C\u6807\u51C6\u7248 v3 (recaptcha/api.js)\n * \u4F18\u5148\u4F7F\u7528 Enterprise \u7248\uFF0C\u56DE\u9000\u5230\u6807\u51C6\u7248\n */\nconst executeRecaptcha = async (action: string, sitekey: string): Promise<string | false> => {\n if (typeof window === 'undefined') {\n console.warn('[LiveChat Fetcher] reCAPTCHA not available in non-browser environment')\n return false\n }\n\n const executor = window.grecaptcha?.enterprise?.execute ?? window.grecaptcha?.execute\n\n if (!executor) {\n console.warn('[LiveChat Fetcher] reCAPTCHA not loaded (neither enterprise nor standard v3)')\n return false\n }\n\n try {\n const token = await executor(sitekey, { action })\n return token\n } catch (error) {\n console.error('[LiveChat Fetcher] reCAPTCHA execution failed:', error)\n return false\n }\n}\n\n/**\n * \u83B7\u53D6 reCAPTCHA headers\n */\nasync function getRecaptchaHeaders(\n action: string,\n sitekey: string,\n headerKey = 'X-Recaptcha-Token'\n): Promise<Record<string, string>> {\n const recaptchaToken = await executeRecaptcha(action, sitekey)\n if (!recaptchaToken) {\n return {}\n }\n return {\n [headerKey]: recaptchaToken,\n }\n}\n\n/**\n * Fetcher \u53C2\u6570\n */\nexport interface FetcherOptions {\n url: string\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n headers?: Record<string, string>\n body?: any\n timeout?: number\n needRecaptcha?: boolean\n recaptchaSitekey?: string\n recaptchaAction?: string\n recaptchaHeaderKey?: string\n /** \u5916\u90E8\u4F20\u5165\u7684 AbortSignal\uFF0C\u7528\u4E8E\u63D0\u524D\u4E2D\u6B62\u8BF7\u6C42 */\n signal?: AbortSignal\n}\n\n/**\n * Fetcher \u51FD\u6570\n * \u652F\u6301 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n */\nexport const fetcher = async ({\n url,\n method = 'POST',\n headers = {},\n body,\n timeout = 90000,\n needRecaptcha = false,\n recaptchaSitekey,\n recaptchaAction = '',\n recaptchaHeaderKey = 'X-Recaptcha-Token',\n signal: externalSignal,\n}: FetcherOptions): Promise<Response> => {\n let recaptchaHeaders: Record<string, string> = {}\n if (needRecaptcha) {\n if (!recaptchaSitekey) {\n console.warn('[LiveChat Fetcher] needRecaptcha=true but recaptchaSitekey is missing')\n } else {\n recaptchaHeaders = await getRecaptchaHeaders(recaptchaAction, recaptchaSitekey, recaptchaHeaderKey)\n }\n }\n\n const bodyData = body ? JSON.stringify(body) : undefined\n\n // \u5408\u5E76\u5916\u90E8 signal\uFF08\u7528\u4E8E\u624B\u52A8\u4E2D\u6B62\uFF09\u548C\u8D85\u65F6 signal\n const controller = new AbortController()\n let timeoutTimer: NodeJS.Timeout | undefined\n if (timeout) {\n timeoutTimer = setTimeout(() => controller.abort(), timeout)\n }\n // \u5982\u679C\u5916\u90E8 signal \u5DF2\u4E2D\u6B62\u6216\u5728\u8BF7\u6C42\u671F\u95F4\u4E2D\u6B62\uFF0C\u540C\u6B65\u5230\u5185\u90E8 controller\n const onExternalAbort = () => controller.abort()\n if (externalSignal) {\n if (externalSignal.aborted) {\n controller.abort()\n } else {\n externalSignal.addEventListener('abort', onExternalAbort, { once: true })\n }\n }\n\n try {\n const response = await fetch(url, {\n method,\n mode: 'cors',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n ...recaptchaHeaders,\n },\n signal: controller.signal,\n ...(method !== 'GET' && bodyData && { body: bodyData }),\n })\n\n if (timeoutTimer) {\n clearTimeout(timeoutTimer)\n }\n\n return response\n } catch (error) {\n if (timeoutTimer) {\n clearTimeout(timeoutTimer)\n }\n throw error\n } finally {\n externalSignal?.removeEventListener('abort', onExternalAbort)\n }\n}\n\n/**\n * \u6269\u5C55 Window \u63A5\u53E3\u4EE5\u652F\u6301 grecaptcha\n */\n declare global {\n interface Window {\n grecaptcha?: {\n execute: (sitekey: string, options: { action: string }) => Promise<string>\n ready: (callback: () => void) => void\n enterprise?: {\n execute: (sitekey: string, options: { action: string }) =>\n Promise<string>\n ready: (callback: () => void) => void\n }\n }\n }\n }\n"],
5
+ "mappings": "AAUA,MAAMA,EAAmB,MAAOC,EAAgBC,IAA6C,CAC3F,GAAI,OAAO,OAAW,IACpB,eAAQ,KAAK,uEAAuE,EAC7E,GAGT,MAAMC,EAAW,OAAO,YAAY,YAAY,SAAW,OAAO,YAAY,QAE9E,GAAI,CAACA,EACH,eAAQ,KAAK,8EAA8E,EACpF,GAGT,GAAI,CAEF,OADc,MAAMA,EAASD,EAAS,CAAE,OAAAD,CAAO,CAAC,CAElD,OAASG,EAAO,CACd,eAAQ,MAAM,iDAAkDA,CAAK,EAC9D,EACT,CACF,EAKA,eAAeC,EACbJ,EACAC,EACAI,EAAY,oBACqB,CACjC,MAAMC,EAAiB,MAAMP,EAAiBC,EAAQC,CAAO,EAC7D,OAAKK,EAGE,CACL,CAACD,CAAS,EAAGC,CACf,EAJS,CAAC,CAKZ,CAuBO,MAAMC,EAAU,MAAO,CAC5B,IAAAC,EACA,OAAAC,EAAS,OACT,QAAAC,EAAU,CAAC,EACX,KAAAC,EACA,QAAAC,EAAU,IACV,cAAAC,EAAgB,GAChB,iBAAAC,EACA,gBAAAC,EAAkB,GAClB,mBAAAC,EAAqB,oBACrB,OAAQC,CACV,IAAyC,CACvC,IAAIC,EAA2C,CAAC,EAC5CL,IACGC,EAGHI,EAAmB,MAAMd,EAAoBW,EAAiBD,EAAkBE,CAAkB,EAFlG,QAAQ,KAAK,uEAAuE,GAMxF,MAAMG,EAAWR,EAAO,KAAK,UAAUA,CAAI,EAAI,OAGzCS,EAAa,IAAI,gBACvB,IAAIC,EACAT,IACFS,EAAe,WAAW,IAAMD,EAAW,MAAM,EAAGR,CAAO,GAG7D,MAAMU,EAAkB,IAAMF,EAAW,MAAM,EAC3CH,IACEA,EAAe,QACjBG,EAAW,MAAM,EAEjBH,EAAe,iBAAiB,QAASK,EAAiB,CAAE,KAAM,EAAK,CAAC,GAI5E,GAAI,CACF,MAAMC,EAAW,MAAM,MAAMf,EAAK,CAChC,OAAAC,EACA,KAAM,OACN,QAAS,CACP,eAAgB,mBAChB,GAAGC,EACH,GAAGQ,CACL,EACA,OAAQE,EAAW,OACnB,GAAIX,IAAW,OAASU,GAAY,CAAE,KAAMA,CAAS,CACvD,CAAC,EAED,OAAIE,GACF,aAAaA,CAAY,EAGpBE,CACT,OAASpB,EAAO,CACd,MAAIkB,GACF,aAAaA,CAAY,EAErBlB,CACR,QAAE,CACAc,GAAgB,oBAAoB,QAASK,CAAe,CAC9D,CACF",
6
+ "names": ["executeRecaptcha", "action", "sitekey", "executor", "error", "getRecaptchaHeaders", "headerKey", "recaptchaToken", "fetcher", "url", "method", "headers", "body", "timeout", "needRecaptcha", "recaptchaSitekey", "recaptchaAction", "recaptchaHeaderKey", "externalSignal", "recaptchaHeaders", "bodyData", "controller", "timeoutTimer", "onExternalAbort", "response"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anker-in/campaign-ui",
3
- "version": "0.4.5-beta.18",
3
+ "version": "0.4.5-beta.19",
4
4
  "description": "Campaign UI components and utilities for Anker projects",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -124,8 +124,7 @@ export const MessageList: React.FC<MessageListProps> = ({
124
124
  }, [])
125
125
  const [showScrollButton, setShowScrollButton] = useState(false)
126
126
 
127
- // 跟踪是否曾进入过 loading 状态,防止 loading→空态→消息的瞬间闪烁
128
- const wasLoadingRef = useRef(false)
127
+ const [showEmpty, setShowEmpty] = useState(false)
129
128
 
130
129
  // 检查是否接近底部
131
130
  const isNearBottom = useCallback(
@@ -190,12 +189,25 @@ export const MessageList: React.FC<MessageListProps> = ({
190
189
  return () => clearTimeout(timeoutId)
191
190
  }, [messages, autoScroll, listElement])
192
191
 
192
+ useEffect(() => {
193
+ if (!isLoadingHistory && messages.length === 0) {
194
+ const timer = setTimeout(() => setShowEmpty(true), 150)
195
+ return () => clearTimeout(timer)
196
+ } else {
197
+ setShowEmpty(false)
198
+ }
199
+ }, [isLoadingHistory, messages.length])
200
+
193
201
  if (isLoadingHistory) {
194
- wasLoadingRef.current = true
202
+ return (
203
+ <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>
204
+ <LoadingIndicator />
205
+ </div>
206
+ )
195
207
  }
196
208
 
197
209
  if (messages.length === 0) {
198
- if (isLoadingHistory || wasLoadingRef.current) {
210
+ if (!showEmpty) {
199
211
  return (
200
212
  <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>
201
213
  <LoadingIndicator />
@@ -207,8 +219,6 @@ export const MessageList: React.FC<MessageListProps> = ({
207
219
  )
208
220
  }
209
221
 
210
- wasLoadingRef.current = false
211
-
212
222
  return (
213
223
  <div className="relative flex-1 overflow-hidden">
214
224
  <div
@@ -98,11 +98,12 @@ export const fetcher = async ({
98
98
  timeoutTimer = setTimeout(() => controller.abort(), timeout)
99
99
  }
100
100
  // 如果外部 signal 已中止或在请求期间中止,同步到内部 controller
101
+ const onExternalAbort = () => controller.abort()
101
102
  if (externalSignal) {
102
103
  if (externalSignal.aborted) {
103
104
  controller.abort()
104
105
  } else {
105
- externalSignal.addEventListener('abort', () => controller.abort(), { once: true })
106
+ externalSignal.addEventListener('abort', onExternalAbort, { once: true })
106
107
  }
107
108
  }
108
109
 
@@ -129,6 +130,8 @@ export const fetcher = async ({
129
130
  clearTimeout(timeoutTimer)
130
131
  }
131
132
  throw error
133
+ } finally {
134
+ externalSignal?.removeEventListener('abort', onExternalAbort)
132
135
  }
133
136
  }
134
137