@tombcato/smart-ticker 1.0.0 → 1.0.4

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/README.md CHANGED
@@ -6,12 +6,19 @@
6
6
  <h1 align="center">Smart Ticker</h1>
7
7
 
8
8
  <p align="center">
9
- 高性能智能文本差异滚动组件,基于 Levenshtein diff 算法,支持多字符集,适用于React/Vue
9
+ 高性能智能文本差异滚动组件,支持中英、数字、字母、符号、Emoji等多种字符集,基于 Levenshtein diff 算法,适用于React/Vue,<a href="https://tombcato.github.io/smart-ticker/">官网演示></a>
10
+ </p>
11
+
12
+ <p align="center">
13
+ <strong>简体中文</strong> | <a href="./README_EN.md">English</a>
10
14
  </p>
11
15
 
12
16
  <p align="center">
13
17
  <img src="./smartticker.gif" alt="Demo" width="600" />
14
18
  </p>
19
+ <p align="center">
20
+ <img src="./smartticker2.gif" alt="Demo" width="600" />
21
+ </p>
15
22
 
16
23
  <p align="center">
17
24
  <img src="https://img.shields.io/badge/React-18+-61DAFB?logo=react" alt="React" />
@@ -20,20 +27,13 @@
20
27
  <img src="https://img.shields.io/npm/v/@tombcato/smart-ticker?color=cb3837&logo=npm" alt="npm" />
21
28
  </p>
22
29
 
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
30
 
28
31
  ## ✨ 特性
29
-
30
- - **智能差异动画** - 只有变化的字符会滚动,相同的字符保持静止
31
- - **平滑中断** - 动画过程中值变化时,从当前位置无缝衔接到新目标
32
- - **多种缓动曲线** - 支持 `linear`、`easeInOut`、`bounce` 等多种动画效果
33
- - **字符宽度可调** - 通过 `charWidth` 属性控制字符间距
34
- - **多字符集支持** - 支持数字、字母、符号等多种字符集,可混合使用
35
- - **双框架支持** - 提供 React 组件和 Vue 组件
36
- - **高性能** - 使用 `requestAnimationFrame` 和 `React.memo` 优化渲染
32
+ | | |
33
+ | :--- | :--- |
34
+ | **🌏 多字符集支持**<br>支持中英、数字、Emoji等混合滚动,基于 Unicode 宽度自动调整间距 | **🧠 智能差异动画**<br>Levenshtein 算法计算最小变更路径,相同的字符保持静止 |
35
+ | **⚡ 平滑中断**<br>动画过程中值突变时,从当前动态位置无缝流向新目标 | **📈 丰富动效**<br>内置 `linear`, `bounce`, `easeInOut` 等缓动,支持 `charWidth` 微调 |
36
+ | **🦄 双框架支持**<br>提供 React (Hooks) 和 Vue 3 (Composition) 组件,API 统一 | **🚀 极致性能**<br>基于 `RAF` 驱动,无多余 DOM 操作,适合高频数据流场景 |
37
37
 
38
38
  ## 📦 安装
39
39
 
@@ -59,10 +59,25 @@ npm run dev
59
59
 
60
60
  ## 🚀 使用方法
61
61
 
62
+ ### 📦 引入样式 (Import Styles)
63
+
64
+ **NPM 安装**时,**必须**显式引入样式文件组件才能正常工作。
65
+
66
+ ```javascript
67
+ import '@tombcato/smart-ticker/style.css'
68
+ ```
69
+
70
+ > **源码集成**:如果您直接复制组件源码,React 版本需确保引入同目录的 `Ticker.css`,Vue 版本样式已内置在单文件组件中。
71
+
62
72
  ### React
63
73
 
64
74
  ```tsx
65
- import { Ticker } from './components/Ticker';
75
+ // NPM 方式
76
+ import { Ticker } from '@tombcato/smart-ticker';
77
+ import '@tombcato/smart-ticker/style.css';
78
+
79
+ // 源码方式
80
+ // import { Ticker } from './components/Ticker';
66
81
 
67
82
  function App() {
68
83
  const [price, setPrice] = useState(73.18);
@@ -82,6 +97,18 @@ function App() {
82
97
  ### Vue
83
98
 
84
99
  ```vue
100
+ <script setup>
101
+ // NPM 方式
102
+ import { Ticker } from '@tombcato/smart-ticker/vue';
103
+ import '@tombcato/smart-ticker/style.css';
104
+
105
+ // 源码方式
106
+ // import Ticker from './components/vue/Ticker.vue';
107
+
108
+ import { ref } from 'vue';
109
+
110
+ const price = ref('73.18');
111
+ </script>
85
112
  <template>
86
113
  <Ticker
87
114
  :value="price"
@@ -91,17 +118,25 @@ function App() {
91
118
  :character-lists="['0123456789.,']"
92
119
  />
93
120
  </template>
121
+ ```
94
122
 
95
- <script setup>
96
- import Ticker from './components/vue/Ticker.vue';
97
- import { ref } from 'vue';
123
+ ### 💅 样式自定义
98
124
 
99
- const price = ref('73.18');
100
- </script>
125
+ #### 自定义字体
126
+
127
+ 组件默认使用系统等宽字体栈。如果需要使用自定义字体(如 `JetBrains Mono`),请确保该字体是**等宽字体**,并使用 CSS 覆盖:
128
+
129
+ ```css
130
+ /* 全局样式或组件样式中 */
131
+ .ticker {
132
+ font-family: 'JetBrains Mono', monospace !important;
133
+ }
101
134
  ```
102
135
 
103
- ## ⚙️ API
136
+ > **注意**:必须使用**等宽字体**,否则字符滚动动画的对齐可能会出现偏差。
104
137
 
138
+
139
+ ## ⚙️ API
105
140
  ### Props
106
141
 
107
142
  | 属性 | 类型 | 默认值 | 说明 |
@@ -120,37 +155,83 @@ const price = ref('73.18');
120
155
  import { TickerUtils } from './components/Ticker';
121
156
 
122
157
  TickerUtils.provideNumberList() // '0123456789'
123
- TickerUtils.provideAlphabeticalList() // 'abcdefghijklmnopqrstuvwxyz'
124
- TickerUtils.provideHexadecimalList() // '0123456789ABCDEF'
158
+ TickerUtils.provideAlphabeticalList() // 'abc...zABC...Z'
159
+
160
+ ### 🧩 字符集配置详解
161
+
162
+ `characterLists` 是控制 Ticker 动画逻辑的核心配置。它接受一个字符串数组,数组的每一项代表一组**“可以互相滚动”**的字符。
163
+
164
+ **基本规则:**
165
+ 1. **同组滚动**:如果旧字符和新字符在同一个字符串中(例如 `0` 变 `9` 在 `'0123456789'` 中),它们会产生滚动动画。
166
+ 2. **跨组替换**:如果它们不在同一组(例如 `a` 变 `1`),或者任何一个字符不在配置列表中(例如汉字),它们会原地切换(Switch),不会产生滚动。
167
+
168
+ **配置技巧:**
169
+
170
+ * **默认全字母**:`TickerUtils.provideAlphabeticalList()` 默认包含 `a-z` 和 `A-Z`。如果你希望大小写之间可以滚动(如 `a` -> `A`),使用它即可。
171
+ * **由于物理隔离**:如果你不希望小写字母滚动变成大写字母(希望它们直接切换),请将它们配置为两个独立的字符串,例如 `['abc...', 'ABC...']`。
172
+
173
+ **示例:**
174
+
175
+ ```javascript
176
+ // 场景:数字、字母(大小写隔离)、符号
177
+ characterLists={[
178
+ 'abcdefghijklmnopqrstuvwxyz', // 小写组
179
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', // 大写组
180
+ '0123456789', // 数字组
181
+ '.,!@#$%^&*' // 符号组
182
+ ]}
183
+ ```
125
184
  ```
126
185
 
127
- ## 🎨 示例场景
186
+ ## 💻 运行演示
128
187
 
129
- - **金融数据** - 股票价格、加密货币行情
130
- - **计数器** - 访问量、点赞数
131
- - **比分牌** - 体育比赛实时比分
132
- - **机场信息牌** - 航班号、登机口
133
- - **隐私模式** - 余额隐藏/显示切换
188
+ 本项目提供了完整基于 NPM React 和 Vue 示例工程,位于 `examples` 目录下。
189
+
190
+ ### 启动 React Demo
191
+
192
+ ```bash
193
+ cd examples/react-demo
194
+ npm install
195
+ npm run dev
196
+ ```
197
+
198
+ ### 启动 Vue Demo
199
+
200
+ ```bash
201
+ cd examples/vue-demo
202
+ npm install
203
+ npm run dev
204
+ ```
134
205
 
135
206
  ## 📁 项目结构
136
207
 
137
208
  ```
138
- ticker-smart-text-diff/
209
+ smart-ticker/
139
210
  ├── src/
140
211
  │ ├── components/
141
- │ │ ├── Ticker.tsx # React 组件
142
- │ │ ├── Ticker.css # 组件样式
212
+ │ │ ├── Ticker.tsx # React 组件源码
213
+ │ │ ├── Ticker.css # 组件核心样式
143
214
  │ │ └── vue/
144
- │ │ └── Ticker.vue # Vue 组件
215
+ │ │ └── Ticker.vue # Vue 组件源码
145
216
  │ ├── core/
146
- │ │ └── TickerCore.ts # 核心逻辑(框架无关)
147
- ├── App.tsx # React Demo
148
- │ └── App.css # Demo 样式
217
+ │ │ └── TickerCore.ts # 核心逻辑(Levenshtein diff 算法)
218
+ └── ...
219
+ ├── examples/ # 独立示例工程
220
+ │ ├── react-demo/ # React Demo (Vite + React + TS)
221
+ │ └── vue-demo/ # Vue Demo (Vite + Vue + TS)
149
222
  ├── public/
150
- │ └── vue-demo.html # Vue CDN Demo
223
+ │ └── vue-demo.html # 单文件 CDN 引用示例
151
224
  └── package.json
152
225
  ```
153
226
 
227
+ ## 🎨 示例场景
228
+
229
+ - **金融数据** - 股票价格、加密货币行情
230
+ - **计数器** - 访问量、点赞数
231
+ - **比分牌** - 体育比赛实时比分
232
+ - **机场信息牌** - 航班号、登机口
233
+ - **隐私模式** - 余额隐藏/显示切换
234
+
154
235
  ## 🔧 技术栈
155
236
 
156
237
  - **构建工具**: Vite
@@ -159,6 +240,4 @@ ticker-smart-text-diff/
159
240
  - **样式**: CSS Variables + 响应式设计
160
241
 
161
242
  ## 📄 License
162
-
163
243
  MIT
164
-
@@ -12,7 +12,7 @@ class TickerCharacterList {
12
12
  __publicField(this, "numOriginalCharacters");
13
13
  __publicField(this, "characterList");
14
14
  __publicField(this, "characterIndicesMap");
15
- const charsArray = characterList.split("");
15
+ const charsArray = [...characterList];
16
16
  const length = charsArray.length;
17
17
  this.numOriginalCharacters = length;
18
18
  this.characterIndicesMap = /* @__PURE__ */ new Map();
@@ -240,4 +240,4 @@ exports.computeColumnActions = computeColumnActions;
240
240
  exports.createColumn = createColumn;
241
241
  exports.easingFunctions = easingFunctions;
242
242
  exports.setTarget = setTarget;
243
- //# sourceMappingURL=TickerCore-DSrG8V7Z.cjs.map
243
+ //# sourceMappingURL=TickerCore-BAsdPrD1.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TickerCore-BAsdPrD1.cjs","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];\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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"}
@@ -11,7 +11,7 @@ class TickerCharacterList {
11
11
  __publicField(this, "numOriginalCharacters");
12
12
  __publicField(this, "characterList");
13
13
  __publicField(this, "characterIndicesMap");
14
- const charsArray = characterList.split("");
14
+ const charsArray = [...characterList];
15
15
  const length = charsArray.length;
16
16
  this.numOriginalCharacters = length;
17
17
  this.characterIndicesMap = /* @__PURE__ */ new Map();
@@ -241,4 +241,4 @@ export {
241
241
  ACTION_DELETE as g,
242
242
  setTarget as s
243
243
  };
244
- //# sourceMappingURL=TickerCore-DDdYkrsq.js.map
244
+ //# sourceMappingURL=TickerCore-C7Ejc9kB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TickerCore-C7Ejc9kB.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];\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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"}
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const React = require("react");
5
- const TickerCore = require("./TickerCore-DSrG8V7Z.cjs");
5
+ const TickerCore = require("./TickerCore-BAsdPrD1.cjs");
6
6
  const Ticker = ({
7
7
  value,
8
8
  characterLists: charListStrings = [TickerCore.TickerUtils.provideNumberList()],
@@ -50,7 +50,7 @@ const Ticker = ({
50
50
  currentCols = currentCols.map((c) => TickerCore.applyProgress(c, progressRef.current, true).col);
51
51
  colsRef.current = currentCols;
52
52
  }
53
- const targetChars = value.split("");
53
+ const targetChars = [...value];
54
54
  const sourceChars = currentCols.map((c) => c.currentChar);
55
55
  const actions = TickerCore.computeColumnActions(sourceChars, targetChars, supportedRef.current);
56
56
  let ci = 0, ti = 0;
@@ -132,7 +132,14 @@ const Ticker = ({
132
132
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ticker-char", style: { transform: `translateY(${deltaEm + charHeight}em)` }, children: list[prevIdx] === TickerCore.EMPTY_CHAR ? " " : list[prevIdx] }, `p-${prevIdx}`)
133
133
  );
134
134
  }
135
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ticker-column", style: { width: `${width * 0.8 * charWidth}em` }, children: chars }, i);
135
+ const isFW = (c) => c && c.length > 0 && c.charCodeAt(0) > 255;
136
+ const getW = (c) => isFW(c) ? 1.25 : 0.8;
137
+ const startChar = list[col.startIndex];
138
+ const endChar = list[col.endIndex];
139
+ const w1 = getW(startChar);
140
+ const w2 = getW(endChar);
141
+ const baseW = w1 + (w2 - w1) * progress;
142
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ticker-column", style: { width: `${width * baseW * charWidth}em` }, children: chars }, i);
136
143
  });
137
144
  }, [columns, progress, charWidth]);
138
145
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `ticker ${className}`.trim(), children: rendered });
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","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":["TickerUtils","useState","useRef","useMemo","TickerCharacterList","useEffect","applyProgress","computeColumnActions","ACTION_INSERT","setTarget","createColumn","ACTION_SAME","EMPTY_CHAR","easingFunctions","jsx"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","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];\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 // 动态计算字符基础宽度:全角字符 1.1em,半角 0.8em\r\n // 使用线性插值 (Lerp) 避免突变跳动\r\n const isFW = (c: string) => c && c.length > 0 && c.charCodeAt(0) > 255;\r\n const getW = (c: string) => isFW(c) ? 1.25 : 0.8;\r\n\r\n // 安全获字符\r\n const startChar = list[col.startIndex];\r\n const endChar = list[col.endIndex];\r\n\r\n const w1 = getW(startChar);\r\n const w2 = getW(endChar);\r\n const baseW = w1 + (w2 - w1) * progress;\r\n\r\n return (\r\n <div key={i} className=\"ticker-column\" style={{ width: `${width * baseW * 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":["TickerUtils","useState","useRef","useMemo","TickerCharacterList","useEffect","applyProgress","computeColumnActions","ACTION_INSERT","setTarget","createColumn","ACTION_SAME","EMPTY_CHAR","easingFunctions","jsx"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
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";
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-C7Ejc9kB.js";
4
+ import { g } from "./TickerCore-C7Ejc9kB.js";
5
5
  const Ticker = ({
6
6
  value,
7
7
  characterLists: charListStrings = [TickerUtils.provideNumberList()],
@@ -49,7 +49,7 @@ const Ticker = ({
49
49
  currentCols = currentCols.map((c) => applyProgress(c, progressRef.current, true).col);
50
50
  colsRef.current = currentCols;
51
51
  }
52
- const targetChars = value.split("");
52
+ const targetChars = [...value];
53
53
  const sourceChars = currentCols.map((c) => c.currentChar);
54
54
  const actions = computeColumnActions(sourceChars, targetChars, supportedRef.current);
55
55
  let ci = 0, ti = 0;
@@ -131,7 +131,14 @@ const Ticker = ({
131
131
  /* @__PURE__ */ jsx("div", { className: "ticker-char", style: { transform: `translateY(${deltaEm + charHeight}em)` }, children: list[prevIdx] === EMPTY_CHAR ? " " : list[prevIdx] }, `p-${prevIdx}`)
132
132
  );
133
133
  }
134
- return /* @__PURE__ */ jsx("div", { className: "ticker-column", style: { width: `${width * 0.8 * charWidth}em` }, children: chars }, i);
134
+ const isFW = (c) => c && c.length > 0 && c.charCodeAt(0) > 255;
135
+ const getW = (c) => isFW(c) ? 1.25 : 0.8;
136
+ const startChar = list[col.startIndex];
137
+ const endChar = list[col.endIndex];
138
+ const w1 = getW(startChar);
139
+ const w2 = getW(endChar);
140
+ const baseW = w1 + (w2 - w1) * progress;
141
+ return /* @__PURE__ */ jsx("div", { className: "ticker-column", style: { width: `${width * baseW * charWidth}em` }, children: chars }, i);
135
142
  });
136
143
  }, [columns, progress, charWidth]);
137
144
  return /* @__PURE__ */ jsx("div", { className: `ticker ${className}`.trim(), children: rendered });
package/dist/index.js.map CHANGED
@@ -1 +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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"}
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];\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 // 动态计算字符基础宽度:全角字符 1.1em,半角 0.8em\r\n // 使用线性插值 (Lerp) 避免突变跳动\r\n const isFW = (c: string) => c && c.length > 0 && c.charCodeAt(0) > 255;\r\n const getW = (c: string) => isFW(c) ? 1.25 : 0.8;\r\n\r\n // 安全获字符\r\n const startChar = list[col.startIndex];\r\n const endChar = list[col.endIndex];\r\n\r\n const w1 = getW(startChar);\r\n const w2 = getW(endChar);\r\n const baseW = w1 + (w2 - w1) * progress;\r\n\r\n return (\r\n <div key={i} className=\"ticker-column\" style={{ width: `${width * baseW * 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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"}
package/dist/style.css CHANGED
@@ -1,7 +1,7 @@
1
1
  .ticker {
2
2
  display: inline-flex;
3
3
  overflow: hidden;
4
- font-family: 'JetBrains Mono', monospace;
4
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace, "PingFang SC", "Microsoft YaHei", sans-serif;
5
5
  font-weight: 600;
6
6
  line-height: 1.2;
7
7
  }
@@ -26,21 +26,21 @@
26
26
  justify-content: center;
27
27
  white-space: pre;
28
28
  }
29
- .ticker[data-v-d7db53e5] {
29
+ .ticker[data-v-7e47ac69] {
30
30
  display: inline-flex;
31
31
  overflow: hidden;
32
- font-family: 'JetBrains Mono', monospace;
32
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
33
33
  font-weight: 600;
34
34
  line-height: 1.2;
35
35
  }
36
- .ticker-column[data-v-d7db53e5] {
36
+ .ticker-column[data-v-7e47ac69] {
37
37
  display: inline-block;
38
38
  height: 1.2em;
39
39
  overflow: hidden;
40
40
  position: relative;
41
41
  clip-path: inset(0);
42
42
  }
43
- .ticker-char[data-v-d7db53e5] {
43
+ .ticker-char[data-v-7e47ac69] {
44
44
  position: absolute;
45
45
  top: 0;
46
46
  left: 0;
@@ -162,11 +162,18 @@
162
162
  <body>
163
163
  <div id="app">
164
164
  <div class="price-hero">
165
- <div class="price-label">价格</div>
165
+ <div class="control-buttons" style="justify-content: center; margin-bottom: 2rem;">
166
+ <button :class="{ active: mode === 'price' }" @click="mode = 'price'" style="padding: 0.5rem 1.5rem;">数字
167
+ / Price</button>
168
+ <button :class="{ active: mode === 'text' }" @click="mode = 'text'" style="padding: 0.5rem 1.5rem;">文本 /
169
+ Text</button>
170
+ </div>
171
+
166
172
  <div class="price-value">
167
- <span class="price-currency">$</span>
168
- <ticker-component :value="heroPrice" :duration="animDuration" :easing="easing" :char-width="charWidth"
169
- :character-lists="['0123456789.,']"></ticker-component>
173
+ <span v-if="mode === 'price'" class="price-currency">$</span>
174
+ <ticker-component :value="heroValue" :duration="animDuration" :easing="easing" :char-width="charWidth"
175
+ :character-lists="currentCharacterLists">
176
+ </ticker-component>
170
177
  </div>
171
178
 
172
179
  <div class="price-controls">
@@ -465,10 +472,23 @@
465
472
  return columns.value.map(col => {
466
473
  const { charIdx, delta } = applyProgress(col, progress.value);
467
474
  const logicalWidth = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;
468
- const width = logicalWidth * props.charWidth * 0.8;
475
+
476
+ // Full-width character interpolation logic
477
+ const list = col.charList || [];
478
+ const isFW = (c) => c && c.length > 0 && c.charCodeAt(0) > 255;
479
+ const getW = (c) => isFW(c) ? 1.1 : 0.8;
480
+
481
+ const startChar = list[col.startIndex] || '';
482
+ const endChar = list[col.endIndex] || '';
483
+ const w1 = getW(startChar);
484
+ const w2 = getW(endChar);
485
+ const baseW = w1 + (w2 - w1) * progress.value;
486
+
487
+ const width = logicalWidth * props.charWidth * baseW;
488
+
469
489
  if (width <= 0) return { width, chars: [] };
470
490
  const chars = [];
471
- const list = col.charList || [];
491
+
472
492
  const deltaEm = delta * charHeight;
473
493
  const add = (idx, offsetMod, keyPrefix) => {
474
494
  if (idx >= 0 && idx < list.length) {
@@ -489,11 +509,21 @@
489
509
  createApp({
490
510
  components: { TickerComponent },
491
511
  setup() {
512
+ const mode = ref('price');
492
513
  const priceSequence = ['73.18', '76.58', '173.50', '9.1'];
493
- const heroPrice = ref(priceSequence[0]);
514
+ const textSequence = [
515
+ 'Smart Ticker',
516
+ 'Small Diff',
517
+ '哈基米 Dif@#$',
518
+ '硅基生命 %@#$',
519
+ '宇宙生命 Smart',
520
+ ];
521
+
522
+ const heroValue = ref(priceSequence[0]);
494
523
  const animDuration = ref(800);
495
524
  const easing = ref('easeInOut');
496
525
  const charWidth = ref(1);
526
+
497
527
  const durationOptions = [400, 800, 1200];
498
528
  const widthOptions = [0.8, 1, 1.2];
499
529
  const easingOptions = [
@@ -502,18 +532,30 @@
502
532
  { name: 'bounce', label: '回弹' },
503
533
  ];
504
534
 
535
+ const currentCharacterLists = computed(() => {
536
+ if (mode.value === 'price') return ['0123456789.,'];
537
+ // Provide alphabet, upcase, numbers, symbols
538
+ const alpha = 'abcdefghijklmnopqrstuvwxyz';
539
+ const symbols = ' .%v-@#$';
540
+ return [alpha, alpha.toUpperCase(), '0123456789', symbols];
541
+ });
542
+
505
543
  let interval;
506
544
 
507
- watch(animDuration, (newDur) => {
508
- if (interval) clearInterval(interval);
545
+ watch([animDuration, mode], () => {
509
546
  startInterval();
510
547
  });
511
548
 
512
549
  const startInterval = () => {
513
- let priceIdx = 0;
550
+ if (interval) clearInterval(interval);
551
+ let idx = 0;
552
+ // Reset to first value
553
+ heroValue.value = mode.value === 'price' ? priceSequence[0] : textSequence[0];
554
+
514
555
  interval = setInterval(() => {
515
- priceIdx = (priceIdx + 1) % priceSequence.length;
516
- heroPrice.value = priceSequence[priceIdx];
556
+ idx++;
557
+ const seq = mode.value === 'price' ? priceSequence : textSequence;
558
+ heroValue.value = seq[idx % seq.length];
517
559
  }, animDuration.value + 1000);
518
560
  };
519
561
 
@@ -526,7 +568,9 @@
526
568
  });
527
569
 
528
570
  return {
529
- heroPrice,
571
+ mode,
572
+ heroValue,
573
+ currentCharacterLists,
530
574
  animDuration,
531
575
  easing,
532
576
  charWidth,
package/dist/vue.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const vue = require("vue");
4
- const TickerCore = require("./TickerCore-DSrG8V7Z.cjs");
4
+ const TickerCore = require("./TickerCore-BAsdPrD1.cjs");
5
5
  const charHeight = 1.2;
6
6
  const _sfc_main = /* @__PURE__ */ vue.defineComponent({
7
7
  __name: "Ticker",
@@ -11,7 +11,8 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
11
11
  duration: { type: Number, default: 500 },
12
12
  direction: { type: String, default: "ANY" },
13
13
  easing: { type: String, default: "easeInOut" },
14
- className: { type: String, default: "" }
14
+ className: { type: String, default: "" },
15
+ charWidth: { type: Number, default: 1 }
15
16
  },
16
17
  setup(__props) {
17
18
  const props = __props;
@@ -34,7 +35,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
34
35
  if (progress.value < 1 && progress.value > 0) {
35
36
  currentCols = currentCols.map((c) => TickerCore.applyProgress(c, progress.value, true).col);
36
37
  }
37
- const targetChars = newValue.split("");
38
+ const targetChars = [...newValue];
38
39
  const sourceChars = currentCols.map((c) => c.currentChar);
39
40
  const actions = TickerCore.computeColumnActions(sourceChars, targetChars, supported.value);
40
41
  let ci = 0, ti = 0;
@@ -99,7 +100,14 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
99
100
  add(charIdx, 0, "c");
100
101
  add(charIdx + 1, -charHeight, "n");
101
102
  add(charIdx - 1, charHeight, "p");
102
- return { width, chars };
103
+ const isFW = (c) => c && c.length > 0 && c.charCodeAt(0) > 255;
104
+ const getW = (c) => isFW(c) ? 1.25 : 0.8;
105
+ const startChar = list[col.startIndex] || "";
106
+ const endChar = list[col.endIndex] || "";
107
+ const w1 = getW(startChar);
108
+ const w2 = getW(endChar);
109
+ const baseW = w1 + (w2 - w1) * progress.value;
110
+ return { width, chars, baseW };
103
111
  }).filter((c) => c.width > 0);
104
112
  });
105
113
  return (_ctx, _cache) => {
@@ -110,7 +118,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
110
118
  return vue.openBlock(), vue.createElementBlock("div", {
111
119
  key: i,
112
120
  class: "ticker-column",
113
- style: vue.normalizeStyle({ width: `${col.width}em` })
121
+ style: vue.normalizeStyle({ width: `${col.width * (col.baseW || 0.8) * __props.charWidth}em` })
114
122
  }, [
115
123
  (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(col.chars, (charObj) => {
116
124
  return vue.openBlock(), vue.createElementBlock("div", {
@@ -132,7 +140,7 @@ const _export_sfc = (sfc, props) => {
132
140
  }
133
141
  return target;
134
142
  };
135
- const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d7db53e5"]]);
143
+ const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-7e47ac69"]]);
136
144
  exports.ACTION_DELETE = TickerCore.ACTION_DELETE;
137
145
  exports.ACTION_INSERT = TickerCore.ACTION_INSERT;
138
146
  exports.ACTION_SAME = TickerCore.ACTION_SAME;
package/dist/vue.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vue.cjs","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\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// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = newValue.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\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++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || 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);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n return { width, chars };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: 'JetBrains Mono', monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["ref","computed","TickerCharacterList","watch","applyProgress","computeColumnActions","ACTION_INSERT","setTarget","createColumn","ACTION_SAME","EMPTY_CHAR","easingFunctions","onUnmounted","_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":";;;;AAyIA,MAAM,aAAa;;;;;;;;;;;;AAjGnB,UAAM,QAAQ;AASd,UAAM,UAAUA,IAAAA,IAAmB,EAAE;AACrC,UAAM,WAAWA,IAAAA,IAAI,CAAC;AACtB,QAAI;AAGJ,UAAM,QAAQC,IAAAA,SAAS,MAAM,MAAM,eAAe,IAAI,CAAA,MAAK,IAAIC,+BAAoB,CAAC,CAAC,CAAC;AACtF,UAAM,YAAYD,IAAAA,SAAS,MAAM;AAC7B,YAAM,0BAAU,IAAA;AAChB,YAAM,MAAM,QAAQ,CAAA,MAAK,EAAE,uBAAA,EAAyB,QAAQ,CAAA,MAAK,IAAI,IAAI,CAAC,CAAC,CAAC;AAC5E,aAAO;AAAA,IACX,CAAC;AAGDE,QAAAA,MAAM,MAAM,MAAM,OAAO,CAAC,UAAU,aAAa;AAC7C,UAAI,aAAa,SAAU;AAE3B,UAAI,QAAQ;AACR,6BAAqB,MAAM;AAC3B,iBAAS;AAAA,MACb;AAGA,UAAI,cAAc,QAAQ;AAC1B,UAAI,SAAS,QAAQ,KAAK,SAAS,QAAQ,GAAG;AAC1C,sBAAc,YAAY,IAAI,CAAA,MAAKC,WAAAA,cAAc,GAAG,SAAS,OAAO,IAAI,EAAE,GAAG;AAAA,MACjF;AAEA,YAAM,cAAc,SAAS,MAAM,EAAE;AACrC,YAAM,cAAc,YAAY,IAAI,CAAA,MAAK,EAAE,WAAW;AACtD,YAAM,UAAUC,WAAAA,qBAAqB,aAAa,aAAa,UAAU,KAAK;AAE9E,UAAI,KAAK,GAAG,KAAK;AACjB,YAAM,SAAwB,CAAA;AAC9B,YAAM,YAAY,YAAY,OAAO,CAAA,MAAK,EAAE,eAAe,CAAC;AAE5D,iBAAW,UAAU,SAAS;AAC1B,YAAI,WAAWC,WAAAA,eAAe;AAC1B,iBAAO,KAAKC,qBAAUC,WAAAA,aAAA,GAAgB,YAAY,IAAI,GAAG,MAAM,OAAO,MAAM,SAAS,CAAC;AAAA,QAC1F,WAAW,WAAWC,wBAAa;AAC/B,gBAAM,WAAW,UAAU,IAAI,KAAKD,WAAAA,aAAA;AACpC,iBAAO,KAAKD,qBAAU,UAAU,YAAY,IAAI,GAAG,MAAM,OAAO,MAAM,SAAS,CAAC;AAAA,QACpF,OAAO;AACH,gBAAM,WAAW,UAAU,IAAI,KAAKC,WAAAA,aAAA;AACpC,iBAAO,KAAKD,WAAAA,UAAU,UAAUG,WAAAA,YAAY,MAAM,OAAO,MAAM,SAAS,CAAC;AAAA,QAC7E;AAAA,MACJ;AAEA,cAAQ,QAAQ;AAGhB,eAAS,QAAQ;AACjB,YAAM,QAAQ,YAAY,IAAA;AAC1B,YAAM,MAAM,MAAM;AAClB,YAAM,SAASC,WAAAA,gBAAgB,MAAM,MAAM,KAAKA,WAAAA,gBAAgB;AAChE,UAAI,aAAa;AAEjB,YAAM,UAAU,CAAC,QAAgB;AAC7B,cAAM,UAAU,KAAK,KAAK,MAAM,SAAS,KAAK,CAAC;AAC/C,cAAM,IAAI,OAAO,OAAO;AAGxB,cAAM,eAAe,MAAM,cAAc,MAAM,WAAW;AAE1D,YAAI,cAAc;AACd,uBAAa;AACb,mBAAS,QAAQ;AAAA,QACrB;AAEA,YAAI,UAAU,GAAG;AACb,mBAAS,sBAAsB,OAAO;AAAA,QAC1C,OAAO;AACH,gBAAM,QAAQ,QAAQ,MACjB,IAAI,OAAKP,WAAAA,cAAc,GAAG,CAAC,EAAE,GAAG,EAChC,OAAO,CAAA,MAAK,EAAE,eAAe,CAAC;AACnC,kBAAQ,QAAQ;AAChB,mBAAS,QAAQ;AACjB,mBAAS;AAAA,QACb;AAAA,MACJ;AACA,eAAS,sBAAsB,OAAO;AAAA,IAE1C,GAAG,EAAE,WAAW,MAAM;AAEtBQ,QAAAA,YAAY,MAAM;AACd,UAAI,6BAA6B,MAAM;AAAA,IAC3C,CAAC;AAKD,UAAM,kBAAkBX,IAAAA,SAAS,MAAM;AACnC,aAAO,QAAQ,MAAM,IAAI,CAAA,QAAO;AAC5B,cAAM,EAAE,SAAS,MAAA,IAAUG,WAAAA,cAAc,KAAK,SAAS,KAAK;AAC5D,cAAM,QAAQ,IAAI,eAAe,IAAI,cAAc,IAAI,eAAe,SAAS;AAC/E,YAAI,SAAS,EAAG,QAAO,EAAE,OAAO,OAAO,CAAA,EAAC;AAExC,cAAM,QAA8D,CAAA;AACpE,cAAM,OAAO,IAAI,YAAY,CAAA;AAC7B,cAAM,UAAU,QAAQ;AAGxB,cAAM,MAAM,CAAC,KAAa,WAAmB,cAAsB;AAC9D,cAAI,OAAO,KAAK,MAAM,KAAK,QAAQ;AAChC,kBAAM,KAAK;AAAA,cACP,KAAK,GAAG,SAAS,IAAI,GAAG;AAAA,cACxB,MAAM,KAAK,GAAG;AAAA,cACd,QAAQ,UAAU;AAAA,YAAA,CACrB;AAAA,UACL;AAAA,QACJ;AAEA,YAAI,SAAS,GAAG,GAAG;AACnB,YAAI,UAAU,GAAG,CAAC,YAAY,GAAG;AACjC,YAAI,UAAU,GAAG,YAAY,GAAG;AAEhC,eAAO,EAAE,OAAO,MAAA;AAAA,MACpB,CAAC,EAAE,OAAO,CAAA,MAAK,EAAE,QAAQ,CAAC;AAAA,IAC9B,CAAC;;8BArKCS,IAAAA,mBAgBM,OAAA;AAAA,QAhBD,OAAKC,IAAAA,eAAA,CAAC,UAAiB,QAAA,SAAS,CAAA;AAAA,MAAA;SACnCC,IAAAA,UAAA,IAAA,GAAAF,IAAAA,mBAcMG,cAAA,MAAAC,IAAAA,WAbe,gBAAA,OAAe,CAA1B,KAAK,MAAC;kCADhBJ,IAAAA,mBAcM,OAAA;AAAA,YAZH,KAAK;AAAA,YACN,OAAM;AAAA,YACL,OAAKK,IAAAA,eAAA,EAAA,OAAA,GAAc,IAAI,KAAK,MAAA;AAAA,UAAA;aAE7BH,cAAA,IAAA,GAAAF,IAAAA,mBAOMG,IAAAA,UAAA,MAAAC,IAAAA,WANc,IAAI,QAAf,YAAO;sCADhBJ,IAAAA,mBAOM,OAAA;AAAA,gBALH,KAAK,QAAQ;AAAA,gBACd,OAAM;AAAA,gBACL,OAAKK,IAAAA,eAAA,EAAA,WAAA,cAA6B,QAAQ,MAAM,OAAA;AAAA,cAAA,GAE9CC,oBAAA,QAAQ,SAASC,mCAAU,MAAc,QAAQ,IAAI,GAAA,CAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"vue.cjs","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width * (col.baseW || 0.8) * charWidth}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\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// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n charWidth: { type: Number, default: 1 },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = [...newValue];\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\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++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || 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);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n // Check for full-width char\r\n const isFW = (c: string) => c && c.length > 0 && c.charCodeAt(0) > 255;\r\n const getW = (c: string) => isFW(c) ? 1.25 : 0.8;\r\n\r\n const startChar = list[col.startIndex] || '';\r\n const endChar = list[col.endIndex] || ''; // endIndex not targetIndex\r\n const w1 = getW(startChar);\r\n const w2 = getW(endChar);\r\n const baseW = w1 + (w2 - w1) * progress.value;\r\n\r\n return { width, chars, baseW };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["ref","computed","TickerCharacterList","watch","applyProgress","computeColumnActions","ACTION_INSERT","setTarget","createColumn","ACTION_SAME","EMPTY_CHAR","easingFunctions","onUnmounted","_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":"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"}
package/dist/vue.d.ts CHANGED
@@ -67,6 +67,10 @@ className: {
67
67
  type: StringConstructor;
68
68
  default: string;
69
69
  };
70
+ charWidth: {
71
+ type: NumberConstructor;
72
+ default: number;
73
+ };
70
74
  }>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<ExtractPropTypes< {
71
75
  value: {
72
76
  type: StringConstructor;
@@ -92,12 +96,17 @@ className: {
92
96
  type: StringConstructor;
93
97
  default: string;
94
98
  };
99
+ charWidth: {
100
+ type: NumberConstructor;
101
+ default: number;
102
+ };
95
103
  }>> & Readonly<{}>, {
96
104
  characterLists: string[];
97
105
  duration: number;
98
106
  direction: ScrollingDirection;
99
107
  easing: string;
100
108
  className: string;
109
+ charWidth: number;
101
110
  }, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
102
111
 
103
112
  export declare class TickerCharacterList {
package/dist/vue.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, ref, computed, watch, onUnmounted, createElementBlock, openBlock, normalizeClass, Fragment, renderList, normalizeStyle, toDisplayString, unref } from "vue";
2
- import { 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";
3
- import { g, T } from "./TickerCore-DDdYkrsq.js";
2
+ import { 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-C7Ejc9kB.js";
3
+ import { g, T } from "./TickerCore-C7Ejc9kB.js";
4
4
  const charHeight = 1.2;
5
5
  const _sfc_main = /* @__PURE__ */ defineComponent({
6
6
  __name: "Ticker",
@@ -10,7 +10,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
10
10
  duration: { type: Number, default: 500 },
11
11
  direction: { type: String, default: "ANY" },
12
12
  easing: { type: String, default: "easeInOut" },
13
- className: { type: String, default: "" }
13
+ className: { type: String, default: "" },
14
+ charWidth: { type: Number, default: 1 }
14
15
  },
15
16
  setup(__props) {
16
17
  const props = __props;
@@ -33,7 +34,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
33
34
  if (progress.value < 1 && progress.value > 0) {
34
35
  currentCols = currentCols.map((c) => applyProgress(c, progress.value, true).col);
35
36
  }
36
- const targetChars = newValue.split("");
37
+ const targetChars = [...newValue];
37
38
  const sourceChars = currentCols.map((c) => c.currentChar);
38
39
  const actions = computeColumnActions(sourceChars, targetChars, supported.value);
39
40
  let ci = 0, ti = 0;
@@ -98,7 +99,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
98
99
  add(charIdx, 0, "c");
99
100
  add(charIdx + 1, -charHeight, "n");
100
101
  add(charIdx - 1, charHeight, "p");
101
- return { width, chars };
102
+ const isFW = (c) => c && c.length > 0 && c.charCodeAt(0) > 255;
103
+ const getW = (c) => isFW(c) ? 1.25 : 0.8;
104
+ const startChar = list[col.startIndex] || "";
105
+ const endChar = list[col.endIndex] || "";
106
+ const w1 = getW(startChar);
107
+ const w2 = getW(endChar);
108
+ const baseW = w1 + (w2 - w1) * progress.value;
109
+ return { width, chars, baseW };
102
110
  }).filter((c) => c.width > 0);
103
111
  });
104
112
  return (_ctx, _cache) => {
@@ -109,7 +117,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
109
117
  return openBlock(), createElementBlock("div", {
110
118
  key: i,
111
119
  class: "ticker-column",
112
- style: normalizeStyle({ width: `${col.width}em` })
120
+ style: normalizeStyle({ width: `${col.width * (col.baseW || 0.8) * __props.charWidth}em` })
113
121
  }, [
114
122
  (openBlock(true), createElementBlock(Fragment, null, renderList(col.chars, (charObj) => {
115
123
  return openBlock(), createElementBlock("div", {
@@ -131,7 +139,7 @@ const _export_sfc = (sfc, props) => {
131
139
  }
132
140
  return target;
133
141
  };
134
- const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d7db53e5"]]);
142
+ const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-7e47ac69"]]);
135
143
  export {
136
144
  g as ACTION_DELETE,
137
145
  ACTION_INSERT,
package/dist/vue.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vue.js","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\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// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = newValue.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\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++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || 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);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n return { width, chars };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: 'JetBrains Mono', monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":"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"}
1
+ {"version":3,"file":"vue.js","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width * (col.baseW || 0.8) * charWidth}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\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// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n charWidth: { type: Number, default: 1 },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = [...newValue];\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\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++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || 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);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n // Check for full-width char\r\n const isFW = (c: string) => c && c.length > 0 && c.charCodeAt(0) > 255;\r\n const getW = (c: string) => isFW(c) ? 1.25 : 0.8;\r\n\r\n const startChar = list[col.startIndex] || '';\r\n const endChar = list[col.endIndex] || ''; // endIndex not targetIndex\r\n const w1 = getW(startChar);\r\n const w2 = getW(endChar);\r\n const baseW = w1 + (w2 - w1) * progress.value;\r\n\r\n return { width, chars, baseW };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tombcato/smart-ticker",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "description": "Smart text animation component with diff-based character scrollin",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -72,4 +72,4 @@
72
72
  "vite": "^5.0.0",
73
73
  "vite-plugin-dts": "^3.9.1"
74
74
  }
75
- }
75
+ }
@@ -1 +0,0 @@
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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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"TickerCore-DSrG8V7Z.cjs","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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"}