@xiping/react-components 1.0.51 → 1.0.53

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.
Files changed (27) hide show
  1. package/dist/cjs/components/image-compare/ImageCompare.d.ts +6 -0
  2. package/dist/cjs/components/image-compare/ImageCompare.js +1 -1
  3. package/dist/cjs/components/subtitle-player/CurrentMode.d.ts +2 -0
  4. package/dist/cjs/components/subtitle-player/CurrentMode.js +2 -2
  5. package/dist/cjs/components/subtitle-player/LyricsMode.d.ts +2 -0
  6. package/dist/cjs/components/subtitle-player/LyricsMode.js +2 -2
  7. package/dist/cjs/components/subtitle-player/SubtitlePlayer.d.ts +34 -0
  8. package/dist/cjs/components/subtitle-player/SubtitlePlayer.js +1 -1
  9. package/dist/cjs/components/subtitle-player/utils.d.ts +1 -0
  10. package/dist/cjs/components/subtitle-player/utils.js +1 -1
  11. package/dist/cjs/react-components.css +1 -1
  12. package/dist/es/components/image-compare/ImageCompare.d.ts +6 -0
  13. package/dist/es/components/image-compare/ImageCompare.js +83 -65
  14. package/dist/es/components/subtitle-player/CurrentMode.d.ts +2 -0
  15. package/dist/es/components/subtitle-player/CurrentMode.js +25 -22
  16. package/dist/es/components/subtitle-player/LyricsMode.d.ts +2 -0
  17. package/dist/es/components/subtitle-player/LyricsMode.js +46 -43
  18. package/dist/es/components/subtitle-player/SubtitlePlayer.d.ts +34 -0
  19. package/dist/es/components/subtitle-player/SubtitlePlayer.js +146 -98
  20. package/dist/es/components/subtitle-player/utils.d.ts +1 -0
  21. package/dist/es/components/subtitle-player/utils.js +7 -6
  22. package/dist/es/react-components.css +1 -1
  23. package/package.json +17 -17
  24. package/dist/cjs/components/toast/index.d.ts +0 -4
  25. package/dist/cjs/components/translate/Translate.d.ts +0 -5
  26. package/dist/es/components/toast/index.d.ts +0 -4
  27. package/dist/es/components/translate/Translate.d.ts +0 -5
@@ -2,6 +2,12 @@ interface ImageCompareProps {
2
2
  className?: string;
3
3
  originalImage: string;
4
4
  modifiedImage: string;
5
+ /** 中间分隔线的宽度,可以是数字(px)或字符串(如 '2px', '0.5px'),默认为 1px */
6
+ dividerWidth?: number | string;
7
+ /** 原图标签文本,默认为 "原图" */
8
+ originalLabel?: string;
9
+ /** 处理过的图标签文本,默认为 "处理过的图" */
10
+ modifiedLabel?: string;
5
11
  }
6
12
  declare const ImageCompare: (props: ImageCompareProps) => import("react/jsx-runtime").JSX.Element;
7
13
  export default ImageCompare;
@@ -1 +1 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const o=require("react/jsx-runtime"),x=require("clsx"),t=require("react"),j=(r,n)=>{let s;return function(...c){s||(r.apply(this,c),s=!0,setTimeout(()=>s=!1,n))}},R=r=>{const{originalImage:n,modifiedImage:s,className:c}=r,[d,b]=t.useState(50),[a,h]=t.useState(!1),[v,k]=t.useState(16/9),i=t.useRef(null),g=t.useCallback(e=>{if(!a||!i.current)return;const m=i.current.getBoundingClientRect(),M=(e-m.left)/m.width*100;b(Math.min(100,Math.max(0,M)))},[a]),l=t.useCallback(j(e=>g(e),16),[g]),p=t.useCallback(e=>{l(e.clientX)},[l]),w=t.useCallback(e=>{l(e.touches[0].clientX)},[l]),f=t.useCallback(()=>{h(!0)},[]),u=t.useCallback(()=>{h(!1)},[]);return t.useEffect(()=>{const e=new Image;e.onload=()=>{k(e.width/e.height)},e.src=n},[n]),o.jsxs("div",{ref:i,className:x("relative select-none overflow-hidden w-full",c),style:{backgroundRepeat:"no-repeat",backgroundPosition:"top left",backgroundImage:`url(${n})`,backgroundSize:"contain",aspectRatio:v},onMouseMove:a?p:void 0,onTouchMove:a?w:void 0,onMouseUp:u,onMouseLeave:u,onTouchEnd:u,children:[o.jsx("div",{className:"absolute inset-0 h-full overflow-hidden",style:{width:`${d}%`,backgroundImage:`url(${s})`,backgroundRepeat:"no-repeat",backgroundPosition:"top left",backgroundSize:"auto 100%"}}),o.jsx("div",{className:"absolute top-0 bottom-0 w-1 bg-white cursor-ew-resize",style:{left:`${d}%`,transform:"translateX(-50%)"},onMouseDown:f,onTouchStart:f,children:o.jsx("div",{className:"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-white shadow-lg flex items-center justify-center",children:o.jsx("svg",{className:"w-6 h-6 text-gray-600",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:o.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M8 9l4-4 4 4m0 6l-4 4-4-4"})})})})]})};exports.default=R;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const n=require("react/jsx-runtime"),z=require("clsx"),e=require("react"),T=M=>{const{originalImage:a,modifiedImage:g,className:y,dividerWidth:l=1,originalLabel:j="原图",modifiedLabel:E="处理过的图"}=M,[c,L]=e.useState(50),[w,S]=e.useState(16/9),h=e.useRef(null),u=e.useRef(!1),r=e.useRef(null),i=e.useRef(null),o=e.useRef(null),p=e.useCallback(t=>{if(!h.current)return;(!i.current||!u.current)&&(i.current=h.current.getBoundingClientRect());const s=i.current,m=(t-s.left)/s.width*100,f=Math.min(100,Math.max(0,m));L(f)},[]),d=e.useCallback(t=>{u.current&&(r.current!==null&&cancelAnimationFrame(r.current),r.current=requestAnimationFrame(()=>{p(t)}))},[p]),x=e.useCallback(t=>{d(t.clientX)},[d]),k=e.useCallback(t=>{t.touches.length>0&&d(t.touches[0].clientX)},[d]),R=e.useCallback(()=>{u.current=!0,i.current=null;const t=v=>x(v),s=v=>k(v),b=()=>{u.current=!1,r.current!==null&&(cancelAnimationFrame(r.current),r.current=null),o.current&&(o.current(),o.current=null)},m=()=>b(),f=()=>b();window.addEventListener("mousemove",t),window.addEventListener("touchmove",s,{passive:!1}),window.addEventListener("mouseup",m),window.addEventListener("touchend",f);const P=()=>{window.removeEventListener("mousemove",t),window.removeEventListener("touchmove",s),window.removeEventListener("mouseup",m),window.removeEventListener("touchend",f)};o.current=P},[x,k]);e.useEffect(()=>()=>{o.current&&(o.current(),o.current=null)},[]),e.useEffect(()=>{const t=new Image;t.onload=()=>{S(t.width/t.height)},t.src=a},[a]);const C=e.useMemo(()=>({backgroundRepeat:"no-repeat",backgroundPosition:"top left",backgroundImage:`url(${a})`,backgroundSize:"contain",aspectRatio:w}),[a,w]),N=e.useMemo(()=>({width:`${c}%`,backgroundImage:`url(${g})`,backgroundRepeat:"no-repeat",backgroundPosition:"top left",backgroundSize:"auto 100%"}),[c,g]),I=e.useMemo(()=>({left:`${c}%`,transform:"translateX(-50%)",width:typeof l=="number"?`${l}px`:l}),[c,l]);return n.jsxs("div",{ref:h,className:z("relative select-none overflow-hidden w-full",y),style:C,children:[n.jsx("div",{className:"absolute top-4 left-4 px-3 py-1.5 bg-black/60 text-white text-sm font-medium rounded backdrop-blur-sm z-10",children:j}),n.jsx("div",{className:"absolute inset-0 h-full overflow-hidden",style:N,children:n.jsx("div",{className:"absolute top-4 right-4 px-3 py-1.5 bg-black/60 text-white text-sm font-medium rounded backdrop-blur-sm z-10",children:E})}),n.jsx("div",{className:"absolute top-0 bottom-0 bg-white cursor-ew-resize",style:I,onMouseDown:R,onTouchStart:R,children:n.jsx("div",{className:"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-white shadow-lg flex items-center justify-center",children:n.jsx("svg",{className:"w-6 h-6 text-gray-600",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M8 9l4-4 4 4m0 6l-4 4-4-4"})})})})]})};exports.default=T;
@@ -13,6 +13,8 @@ export interface CurrentModeProps {
13
13
  enableWordHover: boolean;
14
14
  /** 悬停覆盖层节点 */
15
15
  overlayNode: React.ReactNode;
16
+ /** 整条字幕点击处理器 */
17
+ onSubtitleClick?: (event: React.MouseEvent<HTMLDivElement>, entry: SubtitleEntry, label?: string) => void;
16
18
  }
17
19
  /**
18
20
  * 当前模式组件:只显示当前时间对应的字幕
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),p=require("./utils.js"),x=({currentEntries:r,wordHoverFactory:d,enableWordHover:l,overlayNode:o})=>r.some(({entry:e})=>e!==null)?s.jsxs(s.Fragment,{children:[r.map(({entry:e,label:i},c)=>e?s.jsxs("div",{className:"xiping-subtitle-player__item","data-label":i||void 0,children:[i&&s.jsx("span",{className:"xiping-subtitle-player__label",children:i}),s.jsx("div",{className:"xiping-subtitle-player__text",children:(()=>{let n=0;return e.text.split(`
2
- `).map((u,t)=>{if(!e.words||e.words.length===0)return s.jsx("div",{className:"xiping-subtitle-player__line",children:u},t);const a=p.renderTextWithWords(u,e.words,n,t,l?d(e,i):void 0);return n=a.nextWordIndex,s.jsx("div",{className:"xiping-subtitle-player__line",children:a.nodes},t)})})()})]},c):null),l&&s.jsx("div",{className:"xiping-subtitle-player__hover-layer",children:o})]}):null;exports.CurrentMode=x;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),x=require("./utils.js"),m=({currentEntries:n,wordHoverFactory:o,enableWordHover:d,overlayNode:c,onSubtitleClick:t})=>n.some(({entry:s})=>s!==null)?e.jsxs(e.Fragment,{children:[n.map(({entry:s,label:i},p)=>s?e.jsxs("div",{className:"xiping-subtitle-player__item","data-label":i||void 0,onClick:t?r=>t(r,s,i):void 0,style:t?{cursor:"pointer"}:void 0,children:[i&&e.jsx("span",{className:"xiping-subtitle-player__label",children:i}),e.jsx("div",{className:"xiping-subtitle-player__text",children:(()=>{let r=0;return s.text.split(`
2
+ `).map((u,l)=>{if(!s.words||s.words.length===0)return e.jsx("div",{className:"xiping-subtitle-player__line",children:u},l);const a=x.renderTextWithWords(u,s.words,r,l,d?o(s,i):void 0);return r=a.nextWordIndex,e.jsx("div",{className:"xiping-subtitle-player__line",children:a.nodes},l)})})()})]},p):null),d&&e.jsx("div",{className:"xiping-subtitle-player__hover-layer",children:c})]}):null;exports.CurrentMode=m;
@@ -19,6 +19,8 @@ export interface LyricsModeProps {
19
19
  overlayNode: React.ReactNode;
20
20
  /** 容器引用,用于滚动 */
21
21
  containerRef: React.RefObject<HTMLDivElement | null>;
22
+ /** 整条字幕点击处理器 */
23
+ onSubtitleClick?: (event: React.MouseEvent<HTMLDivElement>, entry: SubtitleEntry, label?: string) => void;
22
24
  }
23
25
  /**
24
26
  * 歌词模式组件:显示全部字幕并高亮当前字幕
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),v=require("react"),q=require("clsx"),b=require("./utils.js"),w=({groupedEntriesByTime:j,currentTime:l,wordHoverFactory:y,enableWordHover:u,overlayNode:f,containerRef:n})=>{const c=v.useRef(null);return v.useEffect(()=>{if(c.current&&n.current){const i=n.current,r=c.current,o=r.offsetTop,a=i.clientHeight,e=r.clientHeight,s=o-a/2+e/2;i.scrollTo({top:s,behavior:"smooth"})}},[l,n]),t.jsxs(t.Fragment,{children:[j.map((i,r)=>{const o=i[0]?.startTime??0,a=b.secondsToTimeString(o);return t.jsx("div",{className:"xiping-subtitle-player__group",children:i.map(({entry:e,label:s,startTime:N,endTime:d},m)=>{const x=l>=N&&l<=d,S=l>d,T=m===0;let g=0;return t.jsxs("div",{ref:x?c:null,className:q("xiping-subtitle-player__item",{"xiping-subtitle-player__item--active":x,"xiping-subtitle-player__item--past":S}),"data-label":s||void 0,children:[T&&t.jsx("span",{className:"xiping-subtitle-player__time",children:a}),s&&t.jsx("span",{className:"xiping-subtitle-player__label",children:s}),t.jsx("div",{className:"xiping-subtitle-player__text",children:e.text.split(`
2
- `).map((_,p)=>{if(!e.words||e.words.length===0)return t.jsx("div",{className:"xiping-subtitle-player__line",children:_},p);const h=b.renderTextWithWords(_,e.words,g,p,u?y(e,s):void 0);return g=h.nextWordIndex,t.jsx("div",{className:"xiping-subtitle-player__line",children:h.nodes},p)})})]},`${r}-${m}`)})},r)}),u&&t.jsx("div",{className:"xiping-subtitle-player__hover-layer",children:f})]})};exports.LyricsMode=w;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),j=require("react"),S=require("clsx"),y=require("./utils.js"),H=({groupedEntriesByTime:f,currentTime:c,wordHoverFactory:b,enableWordHover:m,overlayNode:N,containerRef:l,onSubtitleClick:o})=>{const a=j.useRef(null);return j.useEffect(()=>{if(a.current&&l.current){const i=l.current,r=a.current,p=r.offsetTop,u=i.clientHeight,s=r.clientHeight,e=p-u/2+s/2;i.scrollTo({top:e,behavior:"smooth"})}},[c,l]),t.jsxs(t.Fragment,{children:[f.map((i,r)=>{const p=i[0]?.startTime??0,u=y.secondsToTimeString(p);return t.jsx("div",{className:"xiping-subtitle-player__group",children:i.map(({entry:s,label:e,startTime:T,endTime:x},g)=>{const _=c>=T&&c<=x,q=c>x,w=g===0;let h=0;return t.jsxs("div",{ref:_?a:null,className:S("xiping-subtitle-player__item",{"xiping-subtitle-player__item--active":_,"xiping-subtitle-player__item--past":q}),"data-label":e||void 0,onClick:o?n=>o(n,s,e):void 0,style:o?{cursor:"pointer"}:void 0,children:[w&&t.jsx("span",{className:"xiping-subtitle-player__time",children:u}),e&&t.jsx("span",{className:"xiping-subtitle-player__label",children:e}),t.jsx("div",{className:"xiping-subtitle-player__text",children:s.text.split(`
2
+ `).map((n,d)=>{if(!s.words||s.words.length===0)return t.jsx("div",{className:"xiping-subtitle-player__line",children:n},d);const v=y.renderTextWithWords(n,s.words,h,d,m?b(s,e):void 0);return h=v.nextWordIndex,t.jsx("div",{className:"xiping-subtitle-player__line",children:v.nodes},d)})})]},`${r}-${g}`)})},r)}),m&&t.jsx("div",{className:"xiping-subtitle-player__hover-layer",children:N})]})};exports.LyricsMode=H;
@@ -26,6 +26,7 @@ export interface SubtitlePlayerProps {
26
26
  /**
27
27
  * 分词 hover 状态回调,由外部决定如何处理(比如请求第三方接口、展示弹窗等)
28
28
  * 传入 null 表示 hover 结束
29
+ * target.entry 包含完整的字幕条目数据(JSON 格式的数据项)
29
30
  */
30
31
  onWordHoverChange?: (target: HoverTarget | null) => void;
31
32
  /**
@@ -33,16 +34,49 @@ export interface SubtitlePlayerProps {
33
34
  * 建议将数据请求放到父组件,通过该函数返回需要展示的内容
34
35
  */
35
36
  renderWordOverlay?: (target: HoverTarget) => React.ReactNode;
37
+ /**
38
+ * 单词点击回调
39
+ * target.entry 包含完整的字幕条目数据(JSON 格式的数据项)
40
+ */
41
+ onWordClick?: (target: WordClickTarget) => void;
42
+ /**
43
+ * 整条字幕点击回调
44
+ * target.entry 包含完整的字幕条目数据(JSON 格式的数据项)
45
+ */
46
+ onSubtitleClick?: (target: SubtitleClickTarget) => void;
36
47
  }
37
48
  export interface HoverTarget {
38
49
  word: string;
39
50
  wordIndex: number;
40
51
  lineIndex: number;
52
+ /** 完整的字幕条目数据(JSON 格式的数据项) */
53
+ entry: SubtitleEntry;
54
+ label?: string;
55
+ rect: DOMRect;
56
+ element: HTMLElement;
57
+ }
58
+ /** 点击目标类型:单词或整条字幕 */
59
+ export type ClickTargetType = "word" | "subtitle";
60
+ export interface WordClickTarget {
61
+ type: "word";
62
+ word: string;
63
+ wordIndex: number;
64
+ lineIndex: number;
65
+ /** 完整的字幕条目数据(JSON 格式的数据项) */
66
+ entry: SubtitleEntry;
67
+ label?: string;
68
+ rect: DOMRect;
69
+ element: HTMLElement;
70
+ }
71
+ export interface SubtitleClickTarget {
72
+ type: "subtitle";
73
+ /** 完整的字幕条目数据(JSON 格式的数据项) */
41
74
  entry: SubtitleEntry;
42
75
  label?: string;
43
76
  rect: DOMRect;
44
77
  element: HTMLElement;
45
78
  }
79
+ export type ClickTarget = WordClickTarget | SubtitleClickTarget;
46
80
  /**
47
81
  * 字幕播放组件
48
82
  * 支持 SRT 和 WebVTT 格式,可以同时显示多个字幕进行对照
@@ -1 +1 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const h=require("react/jsx-runtime"),c=require("react"),l=require("../../packages/subtitle/lib/src/parser.js"),j=require("../../packages/subtitle/lib/src/json-converter.js"),_=require("clsx"),k=require("./CurrentMode.js"),A=require("./LyricsMode.js"),S=require("./utils.js");;/* empty css */const w=({subtitles:p,currentTime:d,mode:g="current",className:C="",style:P,onWordHoverChange:i,renderWordOverlay:T})=>{const u=c.useMemo(()=>Array.isArray(p)?p:[p],[p]),m=c.useRef(null),[a,M]=c.useState(null),[f,v]=c.useState([]);c.useEffect(()=>{let r=!0;async function o(){try{const t=await Promise.all(u.map(async e=>{try{const n=e.language||"zh",s=e.type==="srt"?await j.srtToJson(e.content,n):await j.vttToJson(e.content,n);return{entries:JSON.parse(s),label:e.label}}catch(n){return console.error("字幕解析失败:",n),{entries:e.type==="srt"?l.parseSRT(e.content):l.parseVTT(e.content),label:e.label}}}));r&&v(t)}catch(t){if(console.error("加载字幕失败:",t),r){const e=u.map(n=>{try{return{entries:n.type==="srt"?l.parseSRT(n.content):l.parseVTT(n.content),label:n.label}}catch(s){return console.error("字幕解析失败:",s),{entries:[],label:n.label}}});v(e)}}}return o(),()=>{r=!1}},[u]);const y=c.useMemo(()=>f.length>0?f:u.map(r=>{try{return{entries:r.type==="srt"?l.parseSRT(r.content):l.parseVTT(r.content),label:r.label}}catch(o){return console.error("字幕解析失败:",o),{entries:[],label:r.label}}}),[f,u]),L=c.useMemo(()=>y.map(({entries:r,label:o})=>({entry:S.getCurrentSubtitleEntry(r,d),label:o,entries:r})),[y,d]),N=c.useMemo(()=>{const r=[];y.forEach(({entries:e,label:n})=>{e.forEach(s=>{r.push({entry:s,label:n,startTime:S.timeStringToSeconds(s.startTime),endTime:S.timeStringToSeconds(s.endTime)})})}),r.sort((e,n)=>e.startTime-n.startTime);const o=[];let t=[];for(const e of r)if(t.length===0)t.push(e);else{const n=t[t.length-1];e.startTime<=n.endTime?t.push(e):(o.push(t),t=[e])}return t.length>0&&o.push(t),o},[y]),R=c.useCallback((r,o)=>{const t=r.currentTarget.getBoundingClientRect(),e=m.current?.getBoundingClientRect(),n=e!==void 0?new DOMRect(t.left-e.left,t.top-e.top,t.width,t.height):t,s={...o,rect:n,element:r.currentTarget};M(s),i?.(s)},[i]),b=c.useCallback(()=>{M(null),i?.(null)},[i]),x=c.useCallback((r,o)=>(t,e,n)=>({onMouseEnter:s=>R(s,{word:t,wordIndex:e,lineIndex:n,entry:r,label:o}),onMouseLeave:b}),[R,b]),q=T&&a?h.jsx("div",{className:"xiping-subtitle-player__hover-overlay",style:{left:a.rect.x+a.rect.width/2,top:a.rect.y+a.rect.height},children:T(a)}):null,E=!!(T||i);return h.jsxs("div",{ref:m,className:_("xiping-subtitle-player",g==="lyrics"?"xiping-subtitle-player--lyrics":"xiping-subtitle-player--current",C),style:P,children:[g==="current"&&h.jsx(k.CurrentMode,{currentEntries:L,wordHoverFactory:x,enableWordHover:E,overlayNode:q}),g==="lyrics"&&h.jsx(A.LyricsMode,{groupedEntriesByTime:N,currentTime:d,wordHoverFactory:x,enableWordHover:E,overlayNode:q,containerRef:m})]})};exports.SubtitlePlayer=w;exports.default=w;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const d=require("react/jsx-runtime"),o=require("react"),l=require("../../packages/subtitle/lib/src/parser.js"),P=require("../../packages/subtitle/lib/src/json-converter.js"),J=require("clsx"),V=require("./CurrentMode.js"),W=require("./LyricsMode.js"),M=require("./utils.js");;/* empty css */const O=({subtitles:g,currentTime:y,mode:T="current",className:L="",style:N,onWordHoverChange:i,renderWordOverlay:f,onWordClick:m,onSubtitleClick:R})=>{const u=o.useMemo(()=>Array.isArray(g)?g:[g],[g]),p=o.useRef(null),[a,b]=o.useState(null),[S,v]=o.useState([]);o.useEffect(()=>{let r=!0;async function c(){try{const t=await Promise.all(u.map(async e=>{try{const n=e.language||"zh",s=e.type==="srt"?await P.srtToJson(e.content,n):await P.vttToJson(e.content,n);return{entries:JSON.parse(s),label:e.label}}catch(n){return console.error("字幕解析失败:",n),{entries:e.type==="srt"?l.parseSRT(e.content):l.parseVTT(e.content),label:e.label}}}));r&&v(t)}catch(t){if(console.error("加载字幕失败:",t),r){const e=u.map(n=>{try{return{entries:n.type==="srt"?l.parseSRT(n.content):l.parseVTT(n.content),label:n.label}}catch(s){return console.error("字幕解析失败:",s),{entries:[],label:n.label}}});v(e)}}}return c(),()=>{r=!1}},[u]);const h=o.useMemo(()=>S.length>0?S:u.map(r=>{try{return{entries:r.type==="srt"?l.parseSRT(r.content):l.parseVTT(r.content),label:r.label}}catch(c){return console.error("字幕解析失败:",c),{entries:[],label:r.label}}}),[S,u]),_=o.useMemo(()=>h.map(({entries:r,label:c})=>({entry:M.getCurrentSubtitleEntry(r,y),label:c,entries:r})),[h,y]),A=o.useMemo(()=>{const r=[];h.forEach(({entries:e,label:n})=>{e.forEach(s=>{r.push({entry:s,label:n,startTime:M.timeStringToSeconds(s.startTime),endTime:M.timeStringToSeconds(s.endTime)})})}),r.sort((e,n)=>e.startTime-n.startTime);const c=[];let t=[];for(const e of r)if(t.length===0)t.push(e);else{const n=t[t.length-1];e.startTime<=n.endTime?t.push(e):(c.push(t),t=[e])}return t.length>0&&c.push(t),c},[h]),C=o.useCallback((r,c)=>{const t=r.currentTarget.getBoundingClientRect(),e=p.current?.getBoundingClientRect(),n=e!==void 0?new DOMRect(t.left-e.left,t.top-e.top,t.width,t.height):t,s={...c,rect:n,element:r.currentTarget};b(s),i?.(s)},[i]),w=o.useCallback(()=>{b(null),i?.(null)},[i]),x=o.useCallback((r,c)=>{if(!m)return;r.stopPropagation();const t=r.currentTarget.getBoundingClientRect(),e=p.current?.getBoundingClientRect(),n=e!==void 0?new DOMRect(t.left-e.left,t.top-e.top,t.width,t.height):t,s={type:"word",...c,rect:n,element:r.currentTarget};m(s)},[m]),q=o.useCallback((r,c,t)=>{if(!R||r.target.closest(".xiping-subtitle-word"))return;const n=r.currentTarget.getBoundingClientRect(),s=p.current?.getBoundingClientRect(),B=s!==void 0?new DOMRect(n.left-s.left,n.top-s.top,n.width,n.height):n,D={type:"subtitle",entry:c,label:t,rect:B,element:r.currentTarget};R(D)},[R]),E=o.useCallback((r,c)=>(t,e,n)=>({onMouseEnter:s=>C(s,{word:t,wordIndex:e,lineIndex:n,entry:r,label:c}),onMouseLeave:w,onClick:s=>x(s,{word:t,wordIndex:e,lineIndex:n,entry:r,label:c})}),[C,w,x]),j=f&&a?d.jsx("div",{className:"xiping-subtitle-player__hover-overlay",style:{left:a.rect.x+a.rect.width/2,top:a.rect.y+a.rect.height},children:f(a)}):null,k=!!(f||i);return d.jsxs("div",{ref:p,className:J("xiping-subtitle-player",T==="lyrics"?"xiping-subtitle-player--lyrics":"xiping-subtitle-player--current",L),style:N,children:[T==="current"&&d.jsx(V.CurrentMode,{currentEntries:_,wordHoverFactory:E,enableWordHover:k,overlayNode:j,onSubtitleClick:q}),T==="lyrics"&&d.jsx(W.LyricsMode,{groupedEntriesByTime:A,currentTime:y,wordHoverFactory:E,enableWordHover:k,overlayNode:j,containerRef:p,onSubtitleClick:q})]})};exports.SubtitlePlayer=O;exports.default=O;
@@ -18,6 +18,7 @@ export declare function getCurrentSubtitleEntry(entries: SubtitleEntry[], curren
18
18
  export type WordHoverHandlerFactory = (word: string, wordIndex: number, lineIndex: number) => {
19
19
  onMouseEnter: React.MouseEventHandler<HTMLSpanElement>;
20
20
  onMouseLeave: React.MouseEventHandler<HTMLSpanElement>;
21
+ onClick?: React.MouseEventHandler<HTMLSpanElement>;
21
22
  } | undefined;
22
23
  /**
23
24
  * 将文本按照分词结果分割并渲染,并返回消耗到的分词下标
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("react/jsx-runtime");function l(t){const n=t.replace(",",".").split(":");if(n.length!==3)return 0;const r=parseInt(n[0],10)||0,a=parseInt(n[1],10)||0,s=parseFloat(n[2])||0;return r*3600+a*60+s}function p(t){const e=Math.floor(t/3600),n=Math.floor(t%3600/60),r=Math.floor(t%60);return e>0?`${e.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`:`${n.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`}function S(t,e){for(const n of t){const r=l(n.startTime),a=l(n.endTime);if(e>=r&&e<=a)return n}return null}function m(t,e,n,r,a){if(!e||e.length===0)return{nodes:t,nextWordIndex:n};const s=[];let i=0;for(let o=n;o<e.length;o++){const u=e[o],g=t.substring(i).indexOf(u);if(g===-1)return{nodes:s.length>0?s:t,nextWordIndex:o};const c=i+g;if(c>i){const h=t.substring(i,c);h&&s.push(d.jsx("span",{className:"xiping-subtitle-word-before",children:h},`before-${r}-${o}`))}const f=a?.(u,o,r);if(s.push(d.jsx("span",{className:"xiping-subtitle-word","data-word":u,onMouseEnter:f?.onMouseEnter,onMouseLeave:f?.onMouseLeave,children:u},`word-${r}-${o}`)),i=c+u.length,i>=t.length)return{nodes:s,nextWordIndex:o+1}}if(i<t.length){const o=t.substring(i);o&&s.push(d.jsx("span",{className:"xiping-subtitle-word-after",children:o},`after-${r}`))}return{nodes:s.length>0?s:t,nextWordIndex:e.length}}exports.getCurrentSubtitleEntry=S;exports.renderTextWithWords=m;exports.secondsToTimeString=p;exports.timeStringToSeconds=l;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime");function g(t){const n=t.replace(",",".").split(":");if(n.length!==3)return 0;const r=parseInt(n[0],10)||0,a=parseInt(n[1],10)||0,s=parseFloat(n[2])||0;return r*3600+a*60+s}function p(t){const e=Math.floor(t/3600),n=Math.floor(t%3600/60),r=Math.floor(t%60);return e>0?`${e.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`:`${n.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`}function S(t,e){for(const n of t){const r=g(n.startTime),a=g(n.endTime);if(e>=r&&e<=a)return n}return null}function m(t,e,n,r,a){if(!e||e.length===0)return{nodes:t,nextWordIndex:n};const s=[];let i=0;for(let o=n;o<e.length;o++){const c=e[o],f=t.substring(i).indexOf(c);if(f===-1)return{nodes:s.length>0?s:t,nextWordIndex:o};const u=i+f;if(u>i){const h=t.substring(i,u);h&&s.push(l.jsx("span",{className:"xiping-subtitle-word-before",children:h},`before-${r}-${o}`))}const d=a?.(c,o,r);if(s.push(l.jsx("span",{className:"xiping-subtitle-word","data-word":c,onMouseEnter:d?.onMouseEnter,onMouseLeave:d?.onMouseLeave,onClick:d?.onClick,children:c},`word-${r}-${o}`)),i=u+c.length,i>=t.length)return{nodes:s,nextWordIndex:o+1}}if(i<t.length){const o=t.substring(i);o&&s.push(l.jsx("span",{className:"xiping-subtitle-word-after",children:o},`after-${r}`))}return{nodes:s.length>0?s:t,nextWordIndex:e.length}}exports.getCurrentSubtitleEntry=S;exports.renderTextWithWords=m;exports.secondsToTimeString=p;exports.timeStringToSeconds=g;