@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 +117 -38
- package/dist/{TickerCore-DSrG8V7Z.cjs → TickerCore-BAsdPrD1.cjs} +2 -2
- package/dist/TickerCore-BAsdPrD1.cjs.map +1 -0
- package/dist/{TickerCore-DDdYkrsq.js → TickerCore-C7Ejc9kB.js} +2 -2
- package/dist/TickerCore-C7Ejc9kB.js.map +1 -0
- package/dist/index.cjs +10 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/style.css +5 -5
- package/dist/vue-demo.html +57 -13
- package/dist/vue.cjs +14 -6
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.d.ts +9 -0
- package/dist/vue.js +15 -7
- package/dist/vue.js.map +1 -1
- package/package.json +2 -2
- package/dist/TickerCore-DDdYkrsq.js.map +0 -1
- package/dist/TickerCore-DSrG8V7Z.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -6,12 +6,19 @@
|
|
|
6
6
|
<h1 align="center">Smart Ticker</h1>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
import Ticker from './components/vue/Ticker.vue';
|
|
97
|
-
import { ref } from 'vue';
|
|
123
|
+
### 💅 样式自定义
|
|
98
124
|
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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() // '
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
│
|
|
148
|
-
|
|
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 #
|
|
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
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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 });
|
package/dist/index.cjs.map
CHANGED
|
@@ -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-
|
|
4
|
-
import { g } from "./TickerCore-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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-
|
|
29
|
+
.ticker[data-v-7e47ac69] {
|
|
30
30
|
display: inline-flex;
|
|
31
31
|
overflow: hidden;
|
|
32
|
-
font-family:
|
|
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-
|
|
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-
|
|
43
|
+
.ticker-char[data-v-7e47ac69] {
|
|
44
44
|
position: absolute;
|
|
45
45
|
top: 0;
|
|
46
46
|
left: 0;
|
package/dist/vue-demo.html
CHANGED
|
@@ -162,11 +162,18 @@
|
|
|
162
162
|
<body>
|
|
163
163
|
<div id="app">
|
|
164
164
|
<div class="price-hero">
|
|
165
|
-
<div class="
|
|
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="
|
|
169
|
-
:character-lists="
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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, (
|
|
508
|
-
if (interval) clearInterval(interval);
|
|
545
|
+
watch([animDuration, mode], () => {
|
|
509
546
|
startInterval();
|
|
510
547
|
});
|
|
511
548
|
|
|
512
549
|
const startInterval = () => {
|
|
513
|
-
|
|
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
|
-
|
|
516
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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":"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"}
|
|
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-
|
|
3
|
-
import { g, T } from "./TickerCore-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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.
|
|
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":";;;AAGO,MAAM,aAAa;AAEnB,MAAM,cAAc;AAAA,EACvB,mBAAmB,MAAM;AAAA,EACzB,yBAAyB,MAAM;AACnC;AAOO,MAAM,oBAAoB;AAAA,EAK7B,YAAY,eAAuB;AAJ3B;AACA;AACA;AAGJ,UAAM,aAAa,cAAc,MAAM,EAAE;AACzC,UAAM,SAAS,WAAW;AAC1B,SAAK,wBAAwB;AAC7B,SAAK,0CAA0B,IAAA;AAE/B,aAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC7B,WAAK,oBAAoB,IAAI,WAAW,CAAC,GAAG,CAAC;AAAA,IACjD;AAEA,SAAK,gBAAgB,IAAI,MAAM,SAAS,IAAI,CAAC;AAC7C,SAAK,cAAc,CAAC,IAAI;AACxB,aAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC7B,WAAK,cAAc,IAAI,CAAC,IAAI,WAAW,CAAC;AACxC,WAAK,cAAc,IAAI,SAAS,CAAC,IAAI,WAAW,CAAC;AAAA,IACrD;AAAA,EACJ;AAAA,EAEA,oBACI,OACA,KACA,WAC+C;AAC/C,QAAI,aAAa,KAAK,eAAe,KAAK;AAC1C,QAAI,WAAW,KAAK,eAAe,GAAG;AAEtC,QAAI,aAAa,KAAK,WAAW,EAAG,QAAO;AAE3C,YAAQ,WAAA;AAAA,MACJ,KAAK;AACD,YAAI,QAAQ,YAAY;AACpB,qBAAW,KAAK,cAAc;AAAA,QAClC,WAAW,WAAW,YAAY;AAC9B,sBAAY,KAAK;AAAA,QACrB;AACA;AAAA,MACJ,KAAK;AACD,YAAI,aAAa,UAAU;AACvB,wBAAc,KAAK;AAAA,QACvB;AACA;AAAA,MACJ,KAAK;AACD,YAAI,UAAU,cAAc,QAAQ,YAAY;AAC5C,cAAI,WAAW,YAAY;AACvB,kBAAM,UAAU,aAAa;AAC7B,kBAAM,OAAO,KAAK,wBAAwB,aAAa;AACvD,gBAAI,OAAO,QAAS,aAAY,KAAK;AAAA,UACzC,WAAW,aAAa,UAAU;AAC9B,kBAAM,UAAU,WAAW;AAC3B,kBAAM,OAAO,KAAK,wBAAwB,WAAW;AACrD,gBAAI,OAAO,QAAS,eAAc,KAAK;AAAA,UAC3C;AAAA,QACJ;AACA;AAAA,IAAA;AAER,WAAO,EAAE,YAAY,SAAA;AAAA,EACzB;AAAA,EAEA,yBAAsC;AAClC,WAAO,IAAI,IAAI,KAAK,oBAAoB,MAAM;AAAA,EAClD;AAAA,EAEA,mBAA6B;AACzB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEQ,eAAe,GAAmB;AACtC,QAAI,MAAM,WAAY,QAAO;AAC7B,QAAI,KAAK,oBAAoB,IAAI,CAAC,UAAU,KAAK,oBAAoB,IAAI,CAAC,IAAK;AAC/E,WAAO;AAAA,EACX;AACJ;AAKO,MAAM,cAAc;AACpB,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AAEtB,SAAS,qBAAqB,QAAkB,QAAkB,WAAkC;AACvG,MAAI,KAAK,GAAG,KAAK;AACjB,QAAM,UAAoB,CAAA;AAE1B,SAAO,MAAM;AACT,UAAM,OAAO,OAAO,OAAO;AAC3B,UAAM,OAAO,OAAO,OAAO;AAC3B,QAAI,QAAQ,KAAM;AAClB,QAAI,MAAM;AAAE,aAAO,KAAK,OAAO,QAAQ,KAAM,SAAQ,KAAK,aAAa;AAAG;AAAA,IAAO;AACjF,QAAI,MAAM;AAAE,aAAO,KAAK,OAAO,QAAQ,KAAM,SAAQ,KAAK,aAAa;AAAG;AAAA,IAAO;AAEjF,UAAM,QAAQ,UAAU,IAAI,OAAO,EAAE,CAAC;AACtC,UAAM,QAAQ,UAAU,IAAI,OAAO,EAAE,CAAC;AAEtC,QAAI,SAAS,OAAO;AAChB,UAAI,KAAK,KAAK,GAAG,KAAK,KAAK;AAC3B,aAAO,KAAK,OAAO,UAAU,UAAU,IAAI,OAAO,EAAE,CAAC,EAAG;AACxD,aAAO,KAAK,OAAO,UAAU,UAAU,IAAI,OAAO,EAAE,CAAC,EAAG;AAExD,YAAM,OAAO,KAAK,IAAI,OAAO,KAAK;AAClC,UAAI,SAAS,MAAM;AACf,iBAAS,IAAI,GAAG,IAAI,MAAM,IAAK,SAAQ,KAAK,WAAW;AAAA,MAC3D,OAAO;AACH,cAAM,SAAqB,MAAM,OAAO,CAAC,EAAE,KAAK,IAAI,EAAE,IAAI,MAAM,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;AACvF,iBAAS,IAAI,GAAG,KAAK,MAAM,IAAK,QAAO,CAAC,EAAE,CAAC,IAAI;AAC/C,iBAAS,IAAI,GAAG,KAAK,MAAM,IAAK,QAAO,CAAC,EAAE,CAAC,IAAI;AAC/C,iBAASA,KAAI,GAAGA,MAAK,MAAMA,MAAK;AAC5B,mBAASC,KAAI,GAAGA,MAAK,MAAMA,MAAK;AAC5B,kBAAM,OAAO,OAAO,KAAKD,KAAI,CAAC,MAAM,OAAO,KAAKC,KAAI,CAAC,IAAI,IAAI;AAC7D,mBAAOD,EAAC,EAAEC,EAAC,IAAI,KAAK,IAAI,OAAOD,KAAI,CAAC,EAAEC,EAAC,IAAI,GAAG,OAAOD,EAAC,EAAEC,KAAI,CAAC,IAAI,GAAG,OAAOD,KAAI,CAAC,EAAEC,KAAI,CAAC,IAAI,IAAI;AAAA,UACnG;AAAA,QACJ;AACA,cAAM,SAAmB,CAAA;AACzB,YAAI,IAAI,MAAM,IAAI;AAClB,eAAO,IAAI,KAAK,IAAI,GAAG;AACnB,cAAI,MAAM,GAAG;AAAE,mBAAO,KAAK,aAAa;AAAG;AAAA,UAAK,WACvC,MAAM,GAAG;AAAE,mBAAO,KAAK,aAAa;AAAG;AAAA,UAAK,OAChD;AACD,kBAAM,MAAM,OAAO,CAAC,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,IAAI,CAAC,EAAE,CAAC,GAAG,MAAM,OAAO,IAAI,CAAC,EAAE,IAAI,CAAC;AAC/E,gBAAI,MAAM,OAAO,MAAM,KAAK;AAAE,qBAAO,KAAK,aAAa;AAAG;AAAA,YAAK,WACtD,MAAM,KAAK;AAAE,qBAAO,KAAK,aAAa;AAAG;AAAA,YAAK,OAClD;AAAE,qBAAO,KAAK,WAAW;AAAG;AAAK;AAAA,YAAK;AAAA,UAC/C;AAAA,QACJ;AACA,iBAAS,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,IAAK,SAAQ,KAAK,OAAO,CAAC,CAAC;AAAA,MACvE;AACA,WAAK;AAAI,WAAK;AAAA,IAClB,WAAW,OAAO;AAAE,cAAQ,KAAK,aAAa;AAAG;AAAA,IAAM,WAC9C,OAAO;AAAE,cAAQ,KAAK,aAAa;AAAG;AAAA,IAAM,OAChD;AAAE,cAAQ,KAAK,WAAW;AAAG;AAAM;AAAA,IAAM;AAAA,EAClD;AACA,SAAO;AACX;AAmBO,SAAS,eAA4B;AACxC,SAAO;AAAA,IACH,aAAa;AAAA,IAAY,YAAY;AAAA,IAAY,UAAU;AAAA,IAC3D,YAAY;AAAA,IAAG,UAAU;AAAA,IAAG,aAAa;AAAA,IAAG,cAAc;AAAA,IAAG,aAAa;AAAA,IAC1E,cAAc;AAAA,IAAG,WAAW;AAAA,IAAG,WAAW;AAAA,EAAA;AAElD;AAEO,SAAS,UAAU,KAAkB,QAAgB,OAA8B,KAAsC;AAC5H,QAAM,IAAI,EAAE,GAAG,IAAA;AACf,IAAE,aAAa;AACf,IAAE,cAAc,EAAE;AAClB,IAAE,cAAc,WAAW,aAAa,IAAI;AAE5C,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO;AACtB,UAAM,UAAU,KAAK,oBAAoB,EAAE,aAAa,QAAQ,GAAG;AACnE,QAAI,SAAS;AACT,QAAE,WAAW,KAAK,iBAAA;AAClB,QAAE,aAAa,QAAQ;AACvB,QAAE,WAAW,QAAQ;AACrB,cAAQ;AACR;AAAA,IACJ;AAAA,EACJ;AACA,MAAI,CAAC,OAAO;AACR,MAAE,WAAW,EAAE,gBAAgB,SAAS,CAAC,EAAE,WAAW,IAAI,CAAC,EAAE,aAAa,MAAM;AAChF,MAAE,aAAa;AACf,MAAE,WAAW,EAAE,gBAAgB,SAAS,IAAI;AAAA,EAChD;AAEA,IAAE,eAAe,EAAE,YAAY,EAAE,aAAa,IAAI;AAClD,IAAE,YAAY,EAAE;AAChB,IAAE,YAAY;AACd,SAAO;AACX;AAEO,SAAS,cAAc,KAAkB,UAAkB,cAAc,OAA6D;AACzI,QAAM,IAAI,EAAE,GAAG,IAAA;AACf,QAAM,QAAQ,KAAK,IAAI,EAAE,WAAW,EAAE,UAAU;AAChD,QAAM,MAAM,WAAW;AACvB,QAAM,SAAS,MAAM,KAAK,MAAM,GAAG;AACnC,QAAM,aAAa,EAAE,aAAa,IAAI;AACtC,QAAM,QAAQ,SAAS,EAAE,eAAe;AACxC,QAAM,UAAU,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,EAAE;AAEnD,MAAI,YAAY,GAAG;AACf,MAAE,cAAc,EAAE;AAClB,MAAE,YAAY;AACd,MAAE,YAAY;AAAA,EAClB,WAAW,eAAe,EAAE,YAAY,WAAW,KAAK,UAAU,EAAE,SAAS,QAAQ;AACjF,MAAE,cAAc,EAAE,SAAS,OAAO;AAClC,MAAE,YAAY;AAAA,EAClB;AAEA,IAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe;AACnE,SAAO,EAAE,KAAK,GAAG,SAAS,MAAA;AAC9B;AAKO,MAAM,kBAAyD;AAAA,EAClE,QAAQ,CAAC,MAAM;AAAA,EACf,QAAQ,CAAC,MAAM,IAAI;AAAA,EACnB,SAAS,CAAC,MAAM,KAAK,IAAI,MAAM,IAAI;AAAA,EACnC,WAAW,CAAC,MAAM,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,IAAI;AAAA,EACtE,QAAQ,CAAC,MAAM;AACX,UAAM,KAAK;AACX,UAAM,KAAK;AACX,QAAI,IAAI,IAAI,IAAI;AACZ,aAAO,KAAK,IAAI;AAAA,IACpB,WAAW,IAAI,IAAI,IAAI;AACnB,aAAO,MAAM,KAAK,MAAM,MAAM,IAAI;AAAA,IACtC,WAAW,IAAI,MAAM,IAAI;AACrB,aAAO,MAAM,KAAK,OAAO,MAAM,IAAI;AAAA,IACvC,OAAO;AACH,aAAO,MAAM,KAAK,QAAQ,MAAM,IAAI;AAAA,IACxC;AAAA,EACJ;AACJ;"}
|
|
@@ -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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"}
|