@xiping/react-components 1.0.52 → 1.0.54
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 +86 -67
- 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 +16 -16
- 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 t=require("react/jsx-runtime"),q=require("clsx"),e=require("react");;/* empty css */const T=M=>{const{originalImage:a,modifiedImage:h,className:E,dividerWidth:c=1,originalLabel:L="原图",modifiedLabel:j="处理过的图"}=M,[s,S]=e.useState(50),[b,y]=e.useState(16/9),p=e.useRef(null),l=e.useRef(!1),r=e.useRef(null),u=e.useRef(null),o=e.useRef(null),w=e.useCallback(n=>{if(!p.current)return;(!u.current||!l.current)&&(u.current=p.current.getBoundingClientRect());const i=u.current,m=(n-i.left)/i.width*100,g=Math.min(100,Math.max(0,m));S(g)},[]),d=e.useCallback(n=>{l.current&&(r.current!==null&&cancelAnimationFrame(r.current),r.current=requestAnimationFrame(()=>{w(n)}))},[w]),x=e.useCallback(n=>{d(n.clientX)},[d]),R=e.useCallback(n=>{n.touches.length>0&&d(n.touches[0].clientX)},[d]),k=e.useCallback(()=>{l.current=!0,u.current=null;const n=v=>x(v),i=v=>R(v),f=()=>{l.current=!1,r.current!==null&&(cancelAnimationFrame(r.current),r.current=null),o.current&&(o.current(),o.current=null)},m=()=>f(),g=()=>f();window.addEventListener("mousemove",n),window.addEventListener("touchmove",i,{passive:!1}),window.addEventListener("mouseup",m),window.addEventListener("touchend",g);const P=()=>{window.removeEventListener("mousemove",n),window.removeEventListener("touchmove",i),window.removeEventListener("mouseup",m),window.removeEventListener("touchend",g)};o.current=P},[x,R]);e.useEffect(()=>()=>{o.current&&(o.current(),o.current=null)},[]),e.useEffect(()=>{const n=new Image;n.onload=()=>{y(n.width/n.height)},n.src=a},[a]);const C=e.useMemo(()=>({backgroundRepeat:"no-repeat",backgroundPosition:"top left",backgroundImage:`url(${a})`,backgroundSize:"contain",aspectRatio:b}),[a,b]),N=e.useMemo(()=>({width:`${s}%`,backgroundImage:`url(${h})`,backgroundRepeat:"no-repeat",backgroundPosition:"top left",backgroundSize:"auto 100%"}),[s,h]),I=e.useMemo(()=>({left:`${s}%`,transform:"translateX(-50%)",width:typeof c=="number"?`${c}px`:c}),[s,c]);return t.jsxs("div",{ref:p,className:q("xiping-image-compare-container",E),style:C,children:[t.jsx("div",{className:"xiping-image-compare-label xiping-image-compare-label-original",children:L}),t.jsx("div",{className:"xiping-image-compare-overlay",style:N,children:t.jsx("div",{className:"xiping-image-compare-label xiping-image-compare-label-modified",children:j})}),t.jsx("div",{className:"xiping-image-compare-divider",style:I,onMouseDown:k,onTouchStart:k,children:t.jsx("div",{className:"xiping-image-compare-divider-button",children:t.jsx("svg",{className:"xiping-image-compare-divider-icon",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:t.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;
|