@tombcato/smart-ticker 1.0.0

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/index.js ADDED
@@ -0,0 +1,154 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import React, { useState, useRef, useMemo, useEffect } from "react";
3
+ import { T as TickerUtils, a as TickerCharacterList, b as applyProgress, c as computeColumnActions, A as ACTION_INSERT, s as setTarget, d as createColumn, e as ACTION_SAME, E as EMPTY_CHAR, f as easingFunctions } from "./TickerCore-DDdYkrsq.js";
4
+ import { g } from "./TickerCore-DDdYkrsq.js";
5
+ const Ticker = ({
6
+ value,
7
+ characterLists: charListStrings = [TickerUtils.provideNumberList()],
8
+ duration = 500,
9
+ direction = "ANY",
10
+ easing = "easeInOut",
11
+ // Robinhood 默认: AccelerateDecelerateInterpolator
12
+ className = "",
13
+ charWidth = 1
14
+ }) => {
15
+ const [columns, setColumns] = useState([]);
16
+ const [progress, setProgress] = useState(1);
17
+ const animRef = useRef();
18
+ const colsRef = useRef([]);
19
+ const progressRef = useRef(1);
20
+ const isFirstRef = useRef(true);
21
+ const prevValueRef = useRef("");
22
+ const lists = useMemo(() => charListStrings.map((s) => new TickerCharacterList(s)), [charListStrings]);
23
+ const supported = useMemo(() => {
24
+ const set = /* @__PURE__ */ new Set();
25
+ lists.forEach((l) => l.getSupportedCharacters().forEach((c) => set.add(c)));
26
+ return set;
27
+ }, [lists]);
28
+ const listsRef = useRef(lists);
29
+ const supportedRef = useRef(supported);
30
+ const directionRef = useRef(direction);
31
+ const durationRef = useRef(duration);
32
+ const easingRef = useRef(easing);
33
+ listsRef.current = lists;
34
+ supportedRef.current = supported;
35
+ directionRef.current = direction;
36
+ durationRef.current = duration;
37
+ easingRef.current = easing;
38
+ useEffect(() => {
39
+ if (value === prevValueRef.current) {
40
+ return;
41
+ }
42
+ prevValueRef.current = value;
43
+ if (animRef.current) {
44
+ cancelAnimationFrame(animRef.current);
45
+ animRef.current = void 0;
46
+ }
47
+ let currentCols = colsRef.current;
48
+ if (progressRef.current < 1 && progressRef.current > 0) {
49
+ currentCols = currentCols.map((c) => applyProgress(c, progressRef.current, true).col);
50
+ colsRef.current = currentCols;
51
+ }
52
+ const targetChars = value.split("");
53
+ const sourceChars = currentCols.map((c) => c.currentChar);
54
+ const actions = computeColumnActions(sourceChars, targetChars, supportedRef.current);
55
+ let ci = 0, ti = 0;
56
+ const result = [];
57
+ const validCols = currentCols.filter((c) => c.currentWidth > 0);
58
+ for (const action of actions) {
59
+ if (action === ACTION_INSERT) {
60
+ result.push(setTarget(createColumn(), targetChars[ti++], listsRef.current, directionRef.current));
61
+ } else if (action === ACTION_SAME) {
62
+ const existing = validCols[ci++] || createColumn();
63
+ result.push(setTarget(existing, targetChars[ti++], listsRef.current, directionRef.current));
64
+ } else {
65
+ const existing = validCols[ci++] || createColumn();
66
+ result.push(setTarget(existing, EMPTY_CHAR, listsRef.current, directionRef.current));
67
+ }
68
+ }
69
+ colsRef.current = result;
70
+ setColumns(result);
71
+ if (!isFirstRef.current && result.length > 0) {
72
+ progressRef.current = 0;
73
+ setProgress(0);
74
+ const start = performance.now();
75
+ const dur = durationRef.current;
76
+ const easeFn = easingFunctions[easingRef.current] || easingFunctions.linear;
77
+ let lastUpdate = 0;
78
+ const animate = (now) => {
79
+ const linearP = Math.min((now - start) / dur, 1);
80
+ const p = easeFn(linearP);
81
+ progressRef.current = p;
82
+ if (now - lastUpdate >= 16 || linearP >= 1) {
83
+ lastUpdate = now;
84
+ setProgress(p);
85
+ }
86
+ if (linearP < 1) {
87
+ animRef.current = requestAnimationFrame(animate);
88
+ } else {
89
+ const final = colsRef.current.map((c) => applyProgress(c, 1).col).filter((c) => c.currentWidth > 0);
90
+ colsRef.current = final;
91
+ setColumns(final);
92
+ animRef.current = void 0;
93
+ }
94
+ };
95
+ animRef.current = requestAnimationFrame(animate);
96
+ } else {
97
+ isFirstRef.current = false;
98
+ progressRef.current = 1;
99
+ setProgress(1);
100
+ const final = result.map((c) => applyProgress(c, 1).col).filter((c) => c.currentWidth > 0);
101
+ colsRef.current = final;
102
+ setColumns(final);
103
+ }
104
+ return () => {
105
+ if (animRef.current) cancelAnimationFrame(animRef.current);
106
+ };
107
+ }, [value]);
108
+ const charHeight = 1.2;
109
+ const rendered = useMemo(() => {
110
+ return columns.map((col, i) => {
111
+ const { charIdx, delta } = applyProgress(col, progress);
112
+ const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress;
113
+ if (width <= 0) return null;
114
+ const chars = [];
115
+ const list = col.charList || [];
116
+ const deltaEm = delta * charHeight;
117
+ if (charIdx >= 0 && charIdx < list.length) {
118
+ chars.push(
119
+ /* @__PURE__ */ jsx("div", { className: "ticker-char", style: { transform: `translateY(${deltaEm}em)` }, children: list[charIdx] === EMPTY_CHAR ? " " : list[charIdx] }, `c-${charIdx}`)
120
+ );
121
+ }
122
+ const nextIdx = charIdx + 1;
123
+ if (nextIdx >= 0 && nextIdx < list.length) {
124
+ chars.push(
125
+ /* @__PURE__ */ jsx("div", { className: "ticker-char", style: { transform: `translateY(${deltaEm - charHeight}em)` }, children: list[nextIdx] === EMPTY_CHAR ? " " : list[nextIdx] }, `n-${nextIdx}`)
126
+ );
127
+ }
128
+ const prevIdx = charIdx - 1;
129
+ if (prevIdx >= 0 && prevIdx < list.length) {
130
+ chars.push(
131
+ /* @__PURE__ */ jsx("div", { className: "ticker-char", style: { transform: `translateY(${deltaEm + charHeight}em)` }, children: list[prevIdx] === EMPTY_CHAR ? " " : list[prevIdx] }, `p-${prevIdx}`)
132
+ );
133
+ }
134
+ return /* @__PURE__ */ jsx("div", { className: "ticker-column", style: { width: `${width * 0.8 * charWidth}em` }, children: chars }, i);
135
+ });
136
+ }, [columns, progress, charWidth]);
137
+ return /* @__PURE__ */ jsx("div", { className: `ticker ${className}`.trim(), children: rendered });
138
+ };
139
+ React.memo(Ticker);
140
+ export {
141
+ g as ACTION_DELETE,
142
+ ACTION_INSERT,
143
+ ACTION_SAME,
144
+ EMPTY_CHAR,
145
+ Ticker,
146
+ TickerCharacterList,
147
+ TickerUtils,
148
+ applyProgress,
149
+ computeColumnActions,
150
+ createColumn,
151
+ easingFunctions,
152
+ setTarget
153
+ };
154
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/components/Ticker.tsx"],"sourcesContent":["/**\r\n * Ticker - 直接翻译自 Robinhood Android Ticker\r\n * https://github.com/robinhood/ticker\r\n */\r\nimport React, { useEffect, useRef, useState, useMemo } from 'react';\r\nimport './Ticker.css';\r\nimport {\r\n TickerUtils,\r\n TickerCharacterList,\r\n ScrollingDirection,\r\n ColumnState,\r\n EMPTY_CHAR,\r\n computeColumnActions,\r\n createColumn,\r\n setTarget,\r\n applyProgress,\r\n easingFunctions,\r\n ACTION_INSERT,\r\n ACTION_SAME,\r\n} from '../core/TickerCore';\r\n\r\n// 导出 Utils 供外部使用\r\nexport { TickerUtils };\r\n\r\n// ============================================================================\r\n// Ticker Component\r\n// ============================================================================\r\nexport interface TickerProps {\r\n value: string;\r\n characterLists?: string[];\r\n duration?: number;\r\n direction?: ScrollingDirection;\r\n easing?: string;\r\n className?: string;\r\n charWidth?: number;\r\n}\r\n\r\nexport const Ticker: React.FC<TickerProps> = ({\r\n value,\r\n characterLists: charListStrings = [TickerUtils.provideNumberList()],\r\n duration = 500,\r\n direction = 'ANY',\r\n easing = 'easeInOut', // Robinhood 默认: AccelerateDecelerateInterpolator\r\n className = '',\r\n charWidth = 1,\r\n}) => {\r\n const [columns, setColumns] = useState<ColumnState[]>([]);\r\n const [progress, setProgress] = useState(1);\r\n const animRef = useRef<number>();\r\n const colsRef = useRef<ColumnState[]>([]);\r\n const progressRef = useRef(1);\r\n const isFirstRef = useRef(true);\r\n const prevValueRef = useRef('');\r\n\r\n const lists = useMemo(() => charListStrings.map(s => new TickerCharacterList(s)), [charListStrings]);\r\n const supported = useMemo(() => {\r\n const set = new Set<string>();\r\n lists.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n }, [lists]);\r\n\r\n // 用 ref 存储依赖项,避免 useEffect 因为这些依赖重复触发\r\n const listsRef = useRef(lists);\r\n const supportedRef = useRef(supported);\r\n const directionRef = useRef(direction);\r\n const durationRef = useRef(duration);\r\n const easingRef = useRef(easing);\r\n listsRef.current = lists;\r\n supportedRef.current = supported;\r\n directionRef.current = direction;\r\n durationRef.current = duration;\r\n easingRef.current = easing;\r\n\r\n // 主要逻辑:value 变化时处理\r\n useEffect(() => {\r\n // 防止相同 value 重复触发(React StrictMode)\r\n if (value === prevValueRef.current) {\r\n return;\r\n }\r\n prevValueRef.current = value;\r\n\r\n // 取消正在进行的动画\r\n if (animRef.current) {\r\n cancelAnimationFrame(animRef.current);\r\n animRef.current = undefined;\r\n }\r\n\r\n // 如果动画进行中,先用当前进度更新列状态\r\n let currentCols = colsRef.current;\r\n if (progressRef.current < 1 && progressRef.current > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progressRef.current, true).col);\r\n colsRef.current = currentCols;\r\n }\r\n\r\n const targetChars = value.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supportedRef.current);\r\n\r\n let ci = 0, ti = 0;\r\n const result: ColumnState[] = [];\r\n const validCols = currentCols.filter(c => c.currentWidth > 0);\r\n\r\n for (const action of actions) {\r\n if (action === ACTION_INSERT) {\r\n result.push(setTarget(createColumn(), targetChars[ti++], listsRef.current, directionRef.current));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], listsRef.current, directionRef.current));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, listsRef.current, directionRef.current));\r\n }\r\n }\r\n\r\n colsRef.current = result;\r\n setColumns(result);\r\n\r\n // 动画\r\n if (!isFirstRef.current && result.length > 0) {\r\n progressRef.current = 0;\r\n setProgress(0);\r\n const start = performance.now();\r\n const dur = durationRef.current;\r\n const easeFn = easingFunctions[easingRef.current] || easingFunctions.linear;\r\n let lastUpdate = 0;\r\n\r\n const animate = (now: number) => {\r\n const linearP = Math.min((now - start) / dur, 1);\r\n const p = easeFn(linearP); // 应用 easing 函数\r\n progressRef.current = p;\r\n\r\n // 节流:每 16ms 最多更新一次视图 (约 60fps)\r\n if (now - lastUpdate >= 16 || linearP >= 1) {\r\n lastUpdate = now;\r\n setProgress(p);\r\n }\r\n\r\n if (linearP < 1) {\r\n animRef.current = requestAnimationFrame(animate);\r\n } else {\r\n // 动画结束\r\n const final = colsRef.current\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n colsRef.current = final;\r\n setColumns(final);\r\n animRef.current = undefined;\r\n }\r\n };\r\n animRef.current = requestAnimationFrame(animate);\r\n } else {\r\n // 首次渲染,直接显示\r\n isFirstRef.current = false;\r\n progressRef.current = 1;\r\n setProgress(1);\r\n const final = result.map(c => applyProgress(c, 1).col).filter(c => c.currentWidth > 0);\r\n colsRef.current = final;\r\n setColumns(final);\r\n }\r\n\r\n return () => {\r\n if (animRef.current) cancelAnimationFrame(animRef.current);\r\n };\r\n }, [value]); // 只依赖 value\r\n\r\n // 渲染\r\n const charHeight = 1.2; // 与 CSS line-height 匹配\r\n\r\n const rendered = useMemo(() => {\r\n return columns.map((col, i) => {\r\n const { charIdx, delta } = applyProgress(col, progress);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress;\r\n if (width <= 0) return null;\r\n\r\n const chars: React.ReactNode[] = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // 当前字符\r\n if (charIdx >= 0 && charIdx < list.length) {\r\n chars.push(\r\n <div key={`c-${charIdx}`} className=\"ticker-char\" style={{ transform: `translateY(${deltaEm}em)` }}>\r\n {list[charIdx] === EMPTY_CHAR ? '\\u00A0' : list[charIdx]}\r\n </div>\r\n );\r\n }\r\n // 下一个字符\r\n const nextIdx = charIdx + 1;\r\n if (nextIdx >= 0 && nextIdx < list.length) {\r\n chars.push(\r\n <div key={`n-${nextIdx}`} className=\"ticker-char\" style={{ transform: `translateY(${deltaEm - charHeight}em)` }}>\r\n {list[nextIdx] === EMPTY_CHAR ? '\\u00A0' : list[nextIdx]}\r\n </div>\r\n );\r\n }\r\n // 上一个字符(处理中断)\r\n const prevIdx = charIdx - 1;\r\n if (prevIdx >= 0 && prevIdx < list.length) {\r\n chars.push(\r\n <div key={`p-${prevIdx}`} className=\"ticker-char\" style={{ transform: `translateY(${deltaEm + charHeight}em)` }}>\r\n {list[prevIdx] === EMPTY_CHAR ? '\\u00A0' : list[prevIdx]}\r\n </div>\r\n );\r\n }\r\n\r\n // 基准宽度 0.8em * 倍率\r\n return (\r\n <div key={i} className=\"ticker-column\" style={{ width: `${width * 0.8 * charWidth}em` }}>\r\n {chars}\r\n </div>\r\n );\r\n });\r\n }, [columns, progress, charWidth]);\r\n\r\n return <div className={`ticker ${className}`.trim()}>{rendered}</div>;\r\n};\r\n\r\nexport default React.memo(Ticker);\r\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="6" y="8" width="5" height="16" rx="2.5" fill="#888888" fill-opacity="0.5" />
3
+ <rect x="13.5" y="4" width="5" height="16" rx="2.5" fill="#f0f0f5">
4
+ <animate attributeName="y" values="4; 12; 4" dur="4s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1" />
5
+ </rect>
6
+ <rect x="21" y="10" width="5" height="16" rx="2.5" fill="#888888" fill-opacity="0.8" />
7
+ </svg>
package/dist/logo.svg ADDED
@@ -0,0 +1,7 @@
1
+ <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="6" y="8" width="5" height="16" rx="2.5" fill="#888888" fill-opacity="0.3" />
3
+ <rect x="13.5" y="4" width="5" height="16" rx="2.5" fill="#202022">
4
+ <animate attributeName="y" values="4; 12; 4" dur="4s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1" />
5
+ </rect>
6
+ <rect x="21" y="10" width="5" height="16" rx="2.5" fill="#888888" fill-opacity="0.6" />
7
+ </svg>
package/dist/style.css ADDED
@@ -0,0 +1,53 @@
1
+ .ticker {
2
+ display: inline-flex;
3
+ overflow: hidden;
4
+ font-family: 'JetBrains Mono', monospace;
5
+ font-weight: 600;
6
+ line-height: 1.2;
7
+ }
8
+
9
+ .ticker-column {
10
+ display: inline-block;
11
+ height: 1.2em;
12
+ overflow: hidden;
13
+ position: relative;
14
+ /* 确保字符边缘不会露出 */
15
+ clip-path: inset(0);
16
+ }
17
+
18
+ .ticker-char {
19
+ position: absolute;
20
+ top: 0;
21
+ left: 0;
22
+ right: 0;
23
+ height: 1.2em;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ white-space: pre;
28
+ }
29
+ .ticker[data-v-d7db53e5] {
30
+ display: inline-flex;
31
+ overflow: hidden;
32
+ font-family: 'JetBrains Mono', monospace;
33
+ font-weight: 600;
34
+ line-height: 1.2;
35
+ }
36
+ .ticker-column[data-v-d7db53e5] {
37
+ display: inline-block;
38
+ height: 1.2em;
39
+ overflow: hidden;
40
+ position: relative;
41
+ clip-path: inset(0);
42
+ }
43
+ .ticker-char[data-v-d7db53e5] {
44
+ position: absolute;
45
+ top: 0;
46
+ left: 0;
47
+ right: 0;
48
+ height: 1.2em;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ white-space: pre;
53
+ }