@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.
- package/dist/cjs/components/image-compare/ImageCompare.d.ts +6 -0
- package/dist/cjs/components/image-compare/ImageCompare.js +1 -1
- package/dist/cjs/components/subtitle-player/CurrentMode.d.ts +2 -0
- package/dist/cjs/components/subtitle-player/CurrentMode.js +2 -2
- package/dist/cjs/components/subtitle-player/LyricsMode.d.ts +2 -0
- package/dist/cjs/components/subtitle-player/LyricsMode.js +2 -2
- package/dist/cjs/components/subtitle-player/SubtitlePlayer.d.ts +34 -0
- package/dist/cjs/components/subtitle-player/SubtitlePlayer.js +1 -1
- package/dist/cjs/components/subtitle-player/utils.d.ts +1 -0
- package/dist/cjs/components/subtitle-player/utils.js +1 -1
- package/dist/cjs/react-components.css +1 -1
- package/dist/es/components/image-compare/ImageCompare.d.ts +6 -0
- package/dist/es/components/image-compare/ImageCompare.js +83 -65
- package/dist/es/components/subtitle-player/CurrentMode.d.ts +2 -0
- package/dist/es/components/subtitle-player/CurrentMode.js +25 -22
- package/dist/es/components/subtitle-player/LyricsMode.d.ts +2 -0
- package/dist/es/components/subtitle-player/LyricsMode.js +46 -43
- package/dist/es/components/subtitle-player/SubtitlePlayer.d.ts +34 -0
- package/dist/es/components/subtitle-player/SubtitlePlayer.js +146 -98
- package/dist/es/components/subtitle-player/utils.d.ts +1 -0
- package/dist/es/components/subtitle-player/utils.js +7 -6
- package/dist/es/react-components.css +1 -1
- package/package.json +17 -17
- package/dist/cjs/components/toast/index.d.ts +0 -4
- package/dist/cjs/components/translate/Translate.d.ts +0 -5
- package/dist/es/components/toast/index.d.ts +0 -4
- 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
|
|
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
|
|
2
|
-
`).map((u,
|
|
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"),
|
|
2
|
-
`).map((
|
|
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
|
|
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
|
|
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;
|