@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 tombcato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ <p align="center">
2
+ <img src="./public/logo.svg#gh-light-mode-only" alt="Smart Ticker" width="120" />
3
+ <img src="./public/logo-dark.svg#gh-dark-mode-only" alt="Smart Ticker" width="120" />
4
+ </p>
5
+
6
+ <h1 align="center">Smart Ticker</h1>
7
+
8
+ <p align="center">
9
+ 高性能智能文本差异滚动组件,基于 Levenshtein diff 算法,支持多字符集,适用于React/Vue
10
+ </p>
11
+
12
+ <p align="center">
13
+ <img src="./smartticker.gif" alt="Demo" width="600" />
14
+ </p>
15
+
16
+ <p align="center">
17
+ <img src="https://img.shields.io/badge/React-18+-61DAFB?logo=react" alt="React" />
18
+ <img src="https://img.shields.io/badge/Vue-3+-4FC08D?logo=vuedotjs" alt="Vue" />
19
+ <img src="https://img.shields.io/badge/TypeScript-5+-3178C6?logo=typescript" alt="TypeScript" />
20
+ <img src="https://img.shields.io/npm/v/@tombcato/smart-ticker?color=cb3837&logo=npm" alt="npm" />
21
+ </p>
22
+
23
+ <p align="center">
24
+ <a href="https://tombcato.github.io/smart-ticker/">📺 官网演示</a> ·
25
+ <a href="https://www.npmjs.com/package/@tombcato/smart-ticker">📥 NPM</a>
26
+ </p>
27
+
28
+ ## ✨ 特性
29
+
30
+ - **智能差异动画** - 只有变化的字符会滚动,相同的字符保持静止
31
+ - **平滑中断** - 动画过程中值变化时,从当前位置无缝衔接到新目标
32
+ - **多种缓动曲线** - 支持 `linear`、`easeInOut`、`bounce` 等多种动画效果
33
+ - **字符宽度可调** - 通过 `charWidth` 属性控制字符间距
34
+ - **多字符集支持** - 支持数字、字母、符号等多种字符集,可混合使用
35
+ - **双框架支持** - 提供 React 组件和 Vue 组件
36
+ - **高性能** - 使用 `requestAnimationFrame` 和 `React.memo` 优化渲染
37
+
38
+ ## 📦 安装
39
+
40
+ ### NPM 安装(推荐)
41
+
42
+ ```bash
43
+ npm install @tombcato/smart-ticker
44
+ ```
45
+
46
+ ### 从源码安装
47
+
48
+ ```bash
49
+ # 克隆仓库
50
+ git clone https://github.com/tombcato/smart-ticker.git
51
+
52
+ # 安装依赖
53
+ cd smart-ticker
54
+ npm install
55
+
56
+ # 启动开发服务器
57
+ npm run dev
58
+ ```
59
+
60
+ ## 🚀 使用方法
61
+
62
+ ### React
63
+
64
+ ```tsx
65
+ import { Ticker } from './components/Ticker';
66
+
67
+ function App() {
68
+ const [price, setPrice] = useState(73.18);
69
+
70
+ return (
71
+ <Ticker
72
+ value={price.toFixed(2)}
73
+ duration={800}
74
+ easing="easeInOut"
75
+ charWidth={1}
76
+ characterLists={['0123456789.,']}
77
+ />
78
+ );
79
+ }
80
+ ```
81
+
82
+ ### Vue
83
+
84
+ ```vue
85
+ <template>
86
+ <Ticker
87
+ :value="price"
88
+ :duration="800"
89
+ easing="easeInOut"
90
+ :char-width="1"
91
+ :character-lists="['0123456789.,']"
92
+ />
93
+ </template>
94
+
95
+ <script setup>
96
+ import Ticker from './components/vue/Ticker.vue';
97
+ import { ref } from 'vue';
98
+
99
+ const price = ref('73.18');
100
+ </script>
101
+ ```
102
+
103
+ ## ⚙️ API
104
+
105
+ ### Props
106
+
107
+ | 属性 | 类型 | 默认值 | 说明 |
108
+ |------|------|--------|------|
109
+ | `value` | `string` | - | 要显示的文本值(必填) |
110
+ | `duration` | `number` | `500` | 动画持续时间(毫秒) |
111
+ | `easing` | `string` | `'easeInOut'` | 缓动函数:`linear`、`easeIn`、`easeOut`、`easeInOut`、`bounce` |
112
+ | `direction` | `string` | `'ANY'` | 滚动方向:`UP`、`DOWN`、`ANY`(自动选择最短路径) |
113
+ | `charWidth` | `number` | `1` | 字符宽度倍率(基准为 0.8em) |
114
+ | `characterLists` | `string[]` | `['0123456789']` | 支持的字符列表 |
115
+ | `className` | `string` | `''` | 自定义 CSS 类名 |
116
+
117
+ ### 内置字符列表
118
+
119
+ ```ts
120
+ import { TickerUtils } from './components/Ticker';
121
+
122
+ TickerUtils.provideNumberList() // '0123456789'
123
+ TickerUtils.provideAlphabeticalList() // 'abcdefghijklmnopqrstuvwxyz'
124
+ TickerUtils.provideHexadecimalList() // '0123456789ABCDEF'
125
+ ```
126
+
127
+ ## 🎨 示例场景
128
+
129
+ - **金融数据** - 股票价格、加密货币行情
130
+ - **计数器** - 访问量、点赞数
131
+ - **比分牌** - 体育比赛实时比分
132
+ - **机场信息牌** - 航班号、登机口
133
+ - **隐私模式** - 余额隐藏/显示切换
134
+
135
+ ## 📁 项目结构
136
+
137
+ ```
138
+ ticker-smart-text-diff/
139
+ ├── src/
140
+ │ ├── components/
141
+ │ │ ├── Ticker.tsx # React 组件
142
+ │ │ ├── Ticker.css # 组件样式
143
+ │ │ └── vue/
144
+ │ │ └── Ticker.vue # Vue 组件
145
+ │ ├── core/
146
+ │ │ └── TickerCore.ts # 核心逻辑(框架无关)
147
+ │ ├── App.tsx # React Demo
148
+ │ └── App.css # Demo 样式
149
+ ├── public/
150
+ │ └── vue-demo.html # Vue CDN Demo
151
+ └── package.json
152
+ ```
153
+
154
+ ## 🔧 技术栈
155
+
156
+ - **构建工具**: Vite
157
+ - **语言**: TypeScript
158
+ - **框架**: React 18 / Vue 3
159
+ - **样式**: CSS Variables + 响应式设计
160
+
161
+ ## 📄 License
162
+
163
+ MIT
164
+
@@ -0,0 +1,244 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ const EMPTY_CHAR = "\0";
5
+ const TickerUtils = {
6
+ provideNumberList: () => "0123456789",
7
+ provideAlphabeticalList: () => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
8
+ };
9
+ class TickerCharacterList {
10
+ constructor(characterList) {
11
+ __publicField(this, "numOriginalCharacters");
12
+ __publicField(this, "characterList");
13
+ __publicField(this, "characterIndicesMap");
14
+ const charsArray = characterList.split("");
15
+ const length = charsArray.length;
16
+ this.numOriginalCharacters = length;
17
+ this.characterIndicesMap = /* @__PURE__ */ new Map();
18
+ for (let i = 0; i < length; i++) {
19
+ this.characterIndicesMap.set(charsArray[i], i);
20
+ }
21
+ this.characterList = new Array(length * 2 + 1);
22
+ this.characterList[0] = EMPTY_CHAR;
23
+ for (let i = 0; i < length; i++) {
24
+ this.characterList[1 + i] = charsArray[i];
25
+ this.characterList[1 + length + i] = charsArray[i];
26
+ }
27
+ }
28
+ getCharacterIndices(start, end, direction) {
29
+ let startIndex = this.getIndexOfChar(start);
30
+ let endIndex = this.getIndexOfChar(end);
31
+ if (startIndex < 0 || endIndex < 0) return null;
32
+ switch (direction) {
33
+ case "DOWN":
34
+ if (end === EMPTY_CHAR) {
35
+ endIndex = this.characterList.length;
36
+ } else if (endIndex < startIndex) {
37
+ endIndex += this.numOriginalCharacters;
38
+ }
39
+ break;
40
+ case "UP":
41
+ if (startIndex < endIndex) {
42
+ startIndex += this.numOriginalCharacters;
43
+ }
44
+ break;
45
+ case "ANY":
46
+ if (start !== EMPTY_CHAR && end !== EMPTY_CHAR) {
47
+ if (endIndex < startIndex) {
48
+ const nonWrap = startIndex - endIndex;
49
+ const wrap = this.numOriginalCharacters - startIndex + endIndex;
50
+ if (wrap < nonWrap) endIndex += this.numOriginalCharacters;
51
+ } else if (startIndex < endIndex) {
52
+ const nonWrap = endIndex - startIndex;
53
+ const wrap = this.numOriginalCharacters - endIndex + startIndex;
54
+ if (wrap < nonWrap) startIndex += this.numOriginalCharacters;
55
+ }
56
+ }
57
+ break;
58
+ }
59
+ return { startIndex, endIndex };
60
+ }
61
+ getSupportedCharacters() {
62
+ return new Set(this.characterIndicesMap.keys());
63
+ }
64
+ getCharacterList() {
65
+ return this.characterList;
66
+ }
67
+ getIndexOfChar(c) {
68
+ if (c === EMPTY_CHAR) return 0;
69
+ if (this.characterIndicesMap.has(c)) return this.characterIndicesMap.get(c) + 1;
70
+ return -1;
71
+ }
72
+ }
73
+ const ACTION_SAME = 0;
74
+ const ACTION_INSERT = 1;
75
+ const ACTION_DELETE = 2;
76
+ function computeColumnActions(source, target, supported) {
77
+ let si = 0, ti = 0;
78
+ const actions = [];
79
+ while (true) {
80
+ const endS = si === source.length;
81
+ const endT = ti === target.length;
82
+ if (endS && endT) break;
83
+ if (endS) {
84
+ for (; ti < target.length; ti++) actions.push(ACTION_INSERT);
85
+ break;
86
+ }
87
+ if (endT) {
88
+ for (; si < source.length; si++) actions.push(ACTION_DELETE);
89
+ break;
90
+ }
91
+ const sSupp = supported.has(source[si]);
92
+ const tSupp = supported.has(target[ti]);
93
+ if (sSupp && tSupp) {
94
+ let se = si + 1, te = ti + 1;
95
+ while (se < source.length && supported.has(source[se])) se++;
96
+ while (te < target.length && supported.has(target[te])) te++;
97
+ const sLen = se - si, tLen = te - ti;
98
+ if (sLen === tLen) {
99
+ for (let i = 0; i < sLen; i++) actions.push(ACTION_SAME);
100
+ } else {
101
+ const matrix = Array(sLen + 1).fill(null).map(() => Array(tLen + 1).fill(0));
102
+ for (let i = 0; i <= sLen; i++) matrix[i][0] = i;
103
+ for (let j = 0; j <= tLen; j++) matrix[0][j] = j;
104
+ for (let r2 = 1; r2 <= sLen; r2++) {
105
+ for (let c2 = 1; c2 <= tLen; c2++) {
106
+ const cost = source[si + r2 - 1] === target[ti + c2 - 1] ? 0 : 1;
107
+ matrix[r2][c2] = Math.min(matrix[r2 - 1][c2] + 1, matrix[r2][c2 - 1] + 1, matrix[r2 - 1][c2 - 1] + cost);
108
+ }
109
+ }
110
+ const result = [];
111
+ let r = sLen, c = tLen;
112
+ while (r > 0 || c > 0) {
113
+ if (r === 0) {
114
+ result.push(ACTION_INSERT);
115
+ c--;
116
+ } else if (c === 0) {
117
+ result.push(ACTION_DELETE);
118
+ r--;
119
+ } else {
120
+ const ins = matrix[r][c - 1], del = matrix[r - 1][c], rep = matrix[r - 1][c - 1];
121
+ if (ins < del && ins < rep) {
122
+ result.push(ACTION_INSERT);
123
+ c--;
124
+ } else if (del < rep) {
125
+ result.push(ACTION_DELETE);
126
+ r--;
127
+ } else {
128
+ result.push(ACTION_SAME);
129
+ r--;
130
+ c--;
131
+ }
132
+ }
133
+ }
134
+ for (let i = result.length - 1; i >= 0; i--) actions.push(result[i]);
135
+ }
136
+ si = se;
137
+ ti = te;
138
+ } else if (sSupp) {
139
+ actions.push(ACTION_INSERT);
140
+ ti++;
141
+ } else if (tSupp) {
142
+ actions.push(ACTION_DELETE);
143
+ si++;
144
+ } else {
145
+ actions.push(ACTION_SAME);
146
+ si++;
147
+ ti++;
148
+ }
149
+ }
150
+ return actions;
151
+ }
152
+ function createColumn() {
153
+ return {
154
+ currentChar: EMPTY_CHAR,
155
+ targetChar: EMPTY_CHAR,
156
+ charList: null,
157
+ startIndex: 0,
158
+ endIndex: 0,
159
+ sourceWidth: 0,
160
+ currentWidth: 0,
161
+ targetWidth: 0,
162
+ directionAdj: 1,
163
+ prevDelta: 0,
164
+ currDelta: 0
165
+ };
166
+ }
167
+ function setTarget(col, target, lists, dir) {
168
+ const c = { ...col };
169
+ c.targetChar = target;
170
+ c.sourceWidth = c.currentWidth;
171
+ c.targetWidth = target === EMPTY_CHAR ? 0 : 1;
172
+ let found = false;
173
+ for (const list of lists) {
174
+ const indices = list.getCharacterIndices(c.currentChar, target, dir);
175
+ if (indices) {
176
+ c.charList = list.getCharacterList();
177
+ c.startIndex = indices.startIndex;
178
+ c.endIndex = indices.endIndex;
179
+ found = true;
180
+ break;
181
+ }
182
+ }
183
+ if (!found) {
184
+ c.charList = c.currentChar === target ? [c.currentChar] : [c.currentChar, target];
185
+ c.startIndex = 0;
186
+ c.endIndex = c.currentChar === target ? 0 : 1;
187
+ }
188
+ c.directionAdj = c.endIndex >= c.startIndex ? 1 : -1;
189
+ c.prevDelta = c.currDelta;
190
+ c.currDelta = 0;
191
+ return c;
192
+ }
193
+ function applyProgress(col, progress, forceUpdate = false) {
194
+ const c = { ...col };
195
+ const total = Math.abs(c.endIndex - c.startIndex);
196
+ const pos = progress * total;
197
+ const offset = pos - Math.floor(pos);
198
+ const additional = c.prevDelta * (1 - progress);
199
+ const delta = offset * c.directionAdj + additional;
200
+ const charIdx = c.startIndex + Math.floor(pos) * c.directionAdj;
201
+ if (progress >= 1) {
202
+ c.currentChar = c.targetChar;
203
+ c.currDelta = 0;
204
+ c.prevDelta = 0;
205
+ } else if (forceUpdate && c.charList && charIdx >= 0 && charIdx < c.charList.length) {
206
+ c.currentChar = c.charList[charIdx];
207
+ c.currDelta = delta;
208
+ }
209
+ c.currentWidth = c.sourceWidth + (c.targetWidth - c.sourceWidth) * progress;
210
+ return { col: c, charIdx, delta };
211
+ }
212
+ const easingFunctions = {
213
+ linear: (t) => t,
214
+ easeIn: (t) => t * t,
215
+ easeOut: (t) => 1 - (1 - t) * (1 - t),
216
+ easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
217
+ bounce: (t) => {
218
+ const n1 = 7.5625;
219
+ const d1 = 2.75;
220
+ if (t < 1 / d1) {
221
+ return n1 * t * t;
222
+ } else if (t < 2 / d1) {
223
+ return n1 * (t -= 1.5 / d1) * t + 0.75;
224
+ } else if (t < 2.5 / d1) {
225
+ return n1 * (t -= 2.25 / d1) * t + 0.9375;
226
+ } else {
227
+ return n1 * (t -= 2.625 / d1) * t + 0.984375;
228
+ }
229
+ }
230
+ };
231
+ export {
232
+ ACTION_INSERT as A,
233
+ EMPTY_CHAR as E,
234
+ TickerUtils as T,
235
+ TickerCharacterList as a,
236
+ applyProgress as b,
237
+ computeColumnActions as c,
238
+ createColumn as d,
239
+ ACTION_SAME as e,
240
+ easingFunctions as f,
241
+ ACTION_DELETE as g,
242
+ setTarget as s
243
+ };
244
+ //# sourceMappingURL=TickerCore-DDdYkrsq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TickerCore-DDdYkrsq.js","sources":["../src/core/TickerCore.ts"],"sourcesContent":["// ============================================================================\r\n// Constants\r\n// ============================================================================\r\nexport const EMPTY_CHAR = '\\0';\r\n\r\nexport const TickerUtils = {\r\n provideNumberList: () => '0123456789',\r\n provideAlphabeticalList: () => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',\r\n};\r\n\r\nexport type ScrollingDirection = 'ANY' | 'UP' | 'DOWN';\r\n\r\n// ============================================================================\r\n// TickerCharacterList\r\n// ============================================================================\r\nexport class TickerCharacterList {\r\n private numOriginalCharacters: number;\r\n private characterList: string[];\r\n private characterIndicesMap: Map<string, number>;\r\n\r\n constructor(characterList: string) {\r\n const charsArray = characterList.split('');\r\n const length = charsArray.length;\r\n this.numOriginalCharacters = length;\r\n this.characterIndicesMap = new Map();\r\n\r\n for (let i = 0; i < length; i++) {\r\n this.characterIndicesMap.set(charsArray[i], i);\r\n }\r\n\r\n this.characterList = new Array(length * 2 + 1);\r\n this.characterList[0] = EMPTY_CHAR;\r\n for (let i = 0; i < length; i++) {\r\n this.characterList[1 + i] = charsArray[i];\r\n this.characterList[1 + length + i] = charsArray[i];\r\n }\r\n }\r\n\r\n getCharacterIndices(\r\n start: string,\r\n end: string,\r\n direction: ScrollingDirection\r\n ): { startIndex: number; endIndex: number } | null {\r\n let startIndex = this.getIndexOfChar(start);\r\n let endIndex = this.getIndexOfChar(end);\r\n\r\n if (startIndex < 0 || endIndex < 0) return null;\r\n\r\n switch (direction) {\r\n case 'DOWN':\r\n if (end === EMPTY_CHAR) {\r\n endIndex = this.characterList.length;\r\n } else if (endIndex < startIndex) {\r\n endIndex += this.numOriginalCharacters;\r\n }\r\n break;\r\n case 'UP':\r\n if (startIndex < endIndex) {\r\n startIndex += this.numOriginalCharacters;\r\n }\r\n break;\r\n case 'ANY':\r\n if (start !== EMPTY_CHAR && end !== EMPTY_CHAR) {\r\n if (endIndex < startIndex) {\r\n const nonWrap = startIndex - endIndex;\r\n const wrap = this.numOriginalCharacters - startIndex + endIndex;\r\n if (wrap < nonWrap) endIndex += this.numOriginalCharacters;\r\n } else if (startIndex < endIndex) {\r\n const nonWrap = endIndex - startIndex;\r\n const wrap = this.numOriginalCharacters - endIndex + startIndex;\r\n if (wrap < nonWrap) startIndex += this.numOriginalCharacters;\r\n }\r\n }\r\n break;\r\n }\r\n return { startIndex, endIndex };\r\n }\r\n\r\n getSupportedCharacters(): Set<string> {\r\n return new Set(this.characterIndicesMap.keys());\r\n }\r\n\r\n getCharacterList(): string[] {\r\n return this.characterList;\r\n }\r\n\r\n private getIndexOfChar(c: string): number {\r\n if (c === EMPTY_CHAR) return 0;\r\n if (this.characterIndicesMap.has(c)) return this.characterIndicesMap.get(c)! + 1;\r\n return -1;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Levenshtein\r\n// ============================================================================\r\nexport const ACTION_SAME = 0;\r\nexport const ACTION_INSERT = 1;\r\nexport const ACTION_DELETE = 2;\r\n\r\nexport function computeColumnActions(source: string[], target: string[], supported: Set<string>): number[] {\r\n let si = 0, ti = 0;\r\n const actions: number[] = [];\r\n\r\n while (true) {\r\n const endS = si === source.length;\r\n const endT = ti === target.length;\r\n if (endS && endT) break;\r\n if (endS) { for (; ti < target.length; ti++) actions.push(ACTION_INSERT); break; }\r\n if (endT) { for (; si < source.length; si++) actions.push(ACTION_DELETE); break; }\r\n\r\n const sSupp = supported.has(source[si]);\r\n const tSupp = supported.has(target[ti]);\r\n\r\n if (sSupp && tSupp) {\r\n let se = si + 1, te = ti + 1;\r\n while (se < source.length && supported.has(source[se])) se++;\r\n while (te < target.length && supported.has(target[te])) te++;\r\n\r\n const sLen = se - si, tLen = te - ti;\r\n if (sLen === tLen) {\r\n for (let i = 0; i < sLen; i++) actions.push(ACTION_SAME);\r\n } else {\r\n const matrix: number[][] = Array(sLen + 1).fill(null).map(() => Array(tLen + 1).fill(0));\r\n for (let i = 0; i <= sLen; i++) matrix[i][0] = i;\r\n for (let j = 0; j <= tLen; j++) matrix[0][j] = j;\r\n for (let r = 1; r <= sLen; r++) {\r\n for (let c = 1; c <= tLen; c++) {\r\n const cost = source[si + r - 1] === target[ti + c - 1] ? 0 : 1;\r\n matrix[r][c] = Math.min(matrix[r - 1][c] + 1, matrix[r][c - 1] + 1, matrix[r - 1][c - 1] + cost);\r\n }\r\n }\r\n const result: number[] = [];\r\n let r = sLen, c = tLen;\r\n while (r > 0 || c > 0) {\r\n if (r === 0) { result.push(ACTION_INSERT); c--; }\r\n else if (c === 0) { result.push(ACTION_DELETE); r--; }\r\n else {\r\n const ins = matrix[r][c - 1], del = matrix[r - 1][c], rep = matrix[r - 1][c - 1];\r\n if (ins < del && ins < rep) { result.push(ACTION_INSERT); c--; }\r\n else if (del < rep) { result.push(ACTION_DELETE); r--; }\r\n else { result.push(ACTION_SAME); r--; c--; }\r\n }\r\n }\r\n for (let i = result.length - 1; i >= 0; i--) actions.push(result[i]);\r\n }\r\n si = se; ti = te;\r\n } else if (sSupp) { actions.push(ACTION_INSERT); ti++; }\r\n else if (tSupp) { actions.push(ACTION_DELETE); si++; }\r\n else { actions.push(ACTION_SAME); si++; ti++; }\r\n }\r\n return actions;\r\n}\r\n\r\n// ============================================================================\r\n// Column State\r\n// ============================================================================\r\nexport interface ColumnState {\r\n currentChar: string;\r\n targetChar: string;\r\n charList: string[] | null;\r\n startIndex: number;\r\n endIndex: number;\r\n sourceWidth: number;\r\n currentWidth: number;\r\n targetWidth: number;\r\n directionAdj: number;\r\n prevDelta: number;\r\n currDelta: number;\r\n}\r\n\r\nexport function createColumn(): ColumnState {\r\n return {\r\n currentChar: EMPTY_CHAR, targetChar: EMPTY_CHAR, charList: null,\r\n startIndex: 0, endIndex: 0, sourceWidth: 0, currentWidth: 0, targetWidth: 0,\r\n directionAdj: 1, prevDelta: 0, currDelta: 0,\r\n };\r\n}\r\n\r\nexport function setTarget(col: ColumnState, target: string, lists: TickerCharacterList[], dir: ScrollingDirection): ColumnState {\r\n const c = { ...col };\r\n c.targetChar = target;\r\n c.sourceWidth = c.currentWidth;\r\n c.targetWidth = target === EMPTY_CHAR ? 0 : 1;\r\n\r\n let found = false;\r\n for (const list of lists) {\r\n const indices = list.getCharacterIndices(c.currentChar, target, dir);\r\n if (indices) {\r\n c.charList = list.getCharacterList();\r\n c.startIndex = indices.startIndex;\r\n c.endIndex = indices.endIndex;\r\n found = true;\r\n break;\r\n }\r\n }\r\n if (!found) {\r\n c.charList = c.currentChar === target ? [c.currentChar] : [c.currentChar, target];\r\n c.startIndex = 0;\r\n c.endIndex = c.currentChar === target ? 0 : 1;\r\n }\r\n\r\n c.directionAdj = c.endIndex >= c.startIndex ? 1 : -1;\r\n c.prevDelta = c.currDelta;\r\n c.currDelta = 0;\r\n return c;\r\n}\r\n\r\nexport function applyProgress(col: ColumnState, progress: number, forceUpdate = false): { col: ColumnState; charIdx: number; delta: number } {\r\n const c = { ...col };\r\n const total = Math.abs(c.endIndex - c.startIndex);\r\n const pos = progress * total;\r\n const offset = pos - Math.floor(pos);\r\n const additional = c.prevDelta * (1 - progress);\r\n const delta = offset * c.directionAdj + additional;\r\n const charIdx = c.startIndex + Math.floor(pos) * c.directionAdj;\r\n\r\n if (progress >= 1) {\r\n c.currentChar = c.targetChar;\r\n c.currDelta = 0;\r\n c.prevDelta = 0;\r\n } else if (forceUpdate && c.charList && charIdx >= 0 && charIdx < c.charList.length) {\r\n c.currentChar = c.charList[charIdx];\r\n c.currDelta = delta;\r\n }\r\n\r\n c.currentWidth = c.sourceWidth + (c.targetWidth - c.sourceWidth) * progress;\r\n return { col: c, charIdx, delta };\r\n}\r\n\r\n// ============================================================================\r\n// Easing Functions\r\n// ============================================================================\r\nexport const easingFunctions: Record<string, (t: number) => number> = {\r\n linear: (t) => t,\r\n easeIn: (t) => t * t,\r\n easeOut: (t) => 1 - (1 - t) * (1 - t),\r\n easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,\r\n bounce: (t) => {\r\n const n1 = 7.5625;\r\n const d1 = 2.75;\r\n if (t < 1 / d1) {\r\n return n1 * t * t;\r\n } else if (t < 2 / d1) {\r\n return n1 * (t -= 1.5 / d1) * t + 0.75;\r\n } else if (t < 2.5 / d1) {\r\n return n1 * (t -= 2.25 / d1) * t + 0.9375;\r\n } else {\r\n return n1 * (t -= 2.625 / d1) * t + 0.984375;\r\n }\r\n },\r\n};\r\n"],"names":["r","c"],"mappings":"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"}