@tdh-keyboard/core 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/README.md +542 -0
  2. package/package.json +3 -2
package/README.md ADDED
@@ -0,0 +1,542 @@
1
+ # @tdh-keyboard/core
2
+
3
+ `@tdh-keyboard/core` 是中文虚拟键盘组件库的底层能力包,提供全局配置、输入框写入、键盘定位、长按连发、手写画布,以及拼音引擎和手写识别器的通用接口。
4
+
5
+ 它本身不包含 UI,更适合以下场景:
6
+
7
+ - 给 `@tdh-keyboard/vue`、`@tdh-keyboard/react` 提供底层能力
8
+ - 在你自己的组件中复用输入框写入和长按逻辑
9
+ - 接入自定义拼音引擎或手写识别服务
10
+
11
+ ## 安装
12
+
13
+ ```bash
14
+ npm install @tdh-keyboard/core
15
+ ```
16
+
17
+ ## 导出总览
18
+
19
+ | 导出 | 类型 | 作用 |
20
+ | --- | --- | --- |
21
+ | `setKeyboardConfig` | function | 设置全局键盘默认配置 |
22
+ | `getKeyboardConfig` | function | 读取当前全局键盘配置 |
23
+ | `registerPinyinEngine` | function | 注册全局拼音引擎实例 |
24
+ | `getPinyinEngine` | function | 获取已注册的拼音引擎 |
25
+ | `registerHandwritingRecognizer` | function | 注册全局手写识别器实例 |
26
+ | `getHandwritingRecognizer` | function | 获取已注册的手写识别器 |
27
+ | `isInputElement` | function | 判断元素是否为可写入的输入框 |
28
+ | `getInputElement` | function | 获取当前激活的输入框 |
29
+ | `writeToInputElement` | function | 在输入框光标位置写入文本 |
30
+ | `delToInputElement` | function | 删除输入框选区或光标前字符 |
31
+ | `moveCursor` | function | 移动输入框光标 |
32
+ | `calculateKeyboardPosition` | function | 计算键盘浮动/底部定位坐标 |
33
+ | `createKeyRepeater` | function | 创建长按连发控制器 |
34
+ | `CanvasDrawer` | class | 管理手写画布、笔迹和自动清屏 |
35
+ | `KeyboardConfig` | interface | 键盘全局配置类型 |
36
+ | `PinyinEngine` | interface | 拼音引擎通用接口 |
37
+ | `PinyinState` | interface | 拼音候选状态类型 |
38
+ | `Candidate` | interface | 拼音候选项类型 |
39
+ | `HandwritingRecognizer` | interface | 手写识别器通用接口 |
40
+ | `RecognizerInitOptions` | interface | 手写识别初始化参数 |
41
+ | `TextInputElement` | type | 文本输入元素类型别名 |
42
+ | `KeyboardPosition` | interface | 键盘定位结果类型 |
43
+ | `KeyRepeatOptions` | interface | 长按连发参数类型 |
44
+ | `KeyRepeater` | interface | 长按连发实例类型 |
45
+ | `CanvasDrawerOptions` | interface | 手写画布配置类型 |
46
+
47
+ ## 快速示例
48
+
49
+ ```ts
50
+ import {
51
+ createKeyRepeater,
52
+ delToInputElement,
53
+ setKeyboardConfig,
54
+ writeToInputElement,
55
+ } from '@tdh-keyboard/core'
56
+
57
+ setKeyboardConfig({
58
+ defaultMode: 'zh',
59
+ position: 'bottom',
60
+ })
61
+
62
+ const input = document.querySelector('input')
63
+ const repeater = createKeyRepeater({
64
+ delay: 400,
65
+ interval: 60,
66
+ })
67
+
68
+ if (input) {
69
+ writeToInputElement(input, '你')
70
+ }
71
+
72
+ const backspaceButton = document.querySelector('#backspace')
73
+
74
+ backspaceButton?.addEventListener('mousedown', () => {
75
+ if (input) {
76
+ repeater.start(() => delToInputElement(input))
77
+ }
78
+ })
79
+
80
+ backspaceButton?.addEventListener('mouseup', () => {
81
+ repeater.stop()
82
+ })
83
+ ```
84
+
85
+ ## API 说明
86
+
87
+ ### 配置与服务注册
88
+
89
+ #### `setKeyboardConfig(config)`
90
+
91
+ ```ts
92
+ function setKeyboardConfig(config: KeyboardConfig): void
93
+ ```
94
+
95
+ 设置全局键盘配置。
96
+
97
+ - 作用:统一设置默认输入模式、定位方式、是否启用手写等行为
98
+ - 入参:`KeyboardConfig`,支持只传一部分字段
99
+ - 行为:会和当前配置合并,不是整体覆盖
100
+ - 适用场景:应用初始化时设置默认行为
101
+
102
+ 常用配置项:
103
+
104
+ - `defaultMode`:默认键盘模式,可选 `'en' | 'zh' | 'hand' | 'num' | 'symbol'`
105
+ - `enableHandwriting`:是否启用手写模式
106
+ - `position`:键盘定位方式,可选 `'static' | 'float' | 'bottom'`
107
+ - `floatMarginTop`:浮动模式下键盘与输入框的垂直间距
108
+ - `disableWhenNoFocus`:没有聚焦输入框时是否禁用键盘
109
+ - `manual`:是否改为手动打开模式
110
+ - `numKeys`:数字键盘的行布局
111
+ - `wasmDir`:默认拼音引擎使用的 RIME 资源目录
112
+
113
+ 示例:
114
+
115
+ ```ts
116
+ import { setKeyboardConfig } from '@tdh-keyboard/core'
117
+
118
+ setKeyboardConfig({
119
+ defaultMode: 'zh',
120
+ enableHandwriting: true,
121
+ position: 'float',
122
+ floatMarginTop: 12,
123
+ })
124
+ ```
125
+
126
+ #### `getKeyboardConfig()`
127
+
128
+ ```ts
129
+ function getKeyboardConfig(): KeyboardConfig
130
+ ```
131
+
132
+ 读取当前全局键盘配置。
133
+
134
+ - 作用:获取当前生效的配置快照
135
+ - 返回:`KeyboardConfig`
136
+ - 适用场景:组件初始化时读取全局默认配置
137
+
138
+ #### `registerPinyinEngine(engine)`
139
+
140
+ ```ts
141
+ function registerPinyinEngine(engine: PinyinEngine): void
142
+ ```
143
+
144
+ 注册全局拼音引擎实例。
145
+
146
+ - 作用:让上层键盘组件复用你提供的拼音引擎,而不是内部默认实现
147
+ - 入参:实现了 `PinyinEngine` 接口的实例
148
+ - 适用场景:接入自定义拼音服务,或者把拼音逻辑放到 Worker 中运行
149
+
150
+ #### `getPinyinEngine()`
151
+
152
+ ```ts
153
+ function getPinyinEngine(): PinyinEngine | null
154
+ ```
155
+
156
+ 获取当前已注册的拼音引擎。
157
+
158
+ - 返回:`PinyinEngine | null`
159
+ - 返回 `null` 的情况:还没有调用过 `registerPinyinEngine`
160
+
161
+ #### `registerHandwritingRecognizer(recognizer)`
162
+
163
+ ```ts
164
+ function registerHandwritingRecognizer(recognizer: HandwritingRecognizer): void
165
+ ```
166
+
167
+ 注册全局手写识别器实例。
168
+
169
+ - 作用:把手写识别逻辑注入到键盘组件中
170
+ - 入参:实现了 `HandwritingRecognizer` 接口的实例
171
+ - 适用场景:接入本地模型、远程识别接口,或你自己的识别实现
172
+
173
+ #### `getHandwritingRecognizer()`
174
+
175
+ ```ts
176
+ function getHandwritingRecognizer(): HandwritingRecognizer | null
177
+ ```
178
+
179
+ 获取当前已注册的手写识别器。
180
+
181
+ - 返回:`HandwritingRecognizer | null`
182
+ - 返回 `null` 的情况:还没有注册识别器
183
+
184
+ ### 输入框操作
185
+
186
+ #### `isInputElement(el)`
187
+
188
+ ```ts
189
+ function isInputElement(el?: Element | null): el is TextInputElement
190
+ ```
191
+
192
+ 判断一个 DOM 元素是否为可写入的文本输入元素。
193
+
194
+ - 支持:`input`、`textarea`
195
+ - 不支持:`checkbox`、`radio`、`file`、`date`、`time` 等非文本类 `input`
196
+ - 返回:类型守卫,返回 `true` 时可当作 `HTMLInputElement | HTMLTextAreaElement` 使用
197
+
198
+ #### `getInputElement()`
199
+
200
+ ```ts
201
+ function getInputElement(): TextInputElement
202
+ ```
203
+
204
+ 获取当前页面上正在聚焦的输入框。
205
+
206
+ - 作用:直接从 `document.activeElement` 中取当前激活输入框
207
+ - 返回:`HTMLInputElement | HTMLTextAreaElement`
208
+ - 注意:如果当前没有激活的可输入元素,会抛出异常
209
+
210
+ #### `writeToInputElement(inputElement, text)`
211
+
212
+ ```ts
213
+ function writeToInputElement(inputElement: TextInputElement, text: string): void
214
+ ```
215
+
216
+ 向输入框当前光标位置写入文本。
217
+
218
+ - 作用:模拟键盘输入,把 `text` 插入到光标位置或替换当前选区
219
+ - 行为:写入后会自动更新光标位置,并派发一次 `input` 事件
220
+ - 细节:如果设置了 `maxlength`,超出限制时不会写入
221
+ - 适用场景:自定义虚拟键盘点击字符键时写入文本
222
+
223
+ 示例:
224
+
225
+ ```ts
226
+ import { writeToInputElement } from '@tdh-keyboard/core'
227
+
228
+ const input = document.querySelector('input')
229
+
230
+ if (input) {
231
+ input.focus()
232
+ writeToInputElement(input, 'hello')
233
+ }
234
+ ```
235
+
236
+ #### `delToInputElement(inputElement)`
237
+
238
+ ```ts
239
+ function delToInputElement(inputElement: TextInputElement): void
240
+ ```
241
+
242
+ 删除输入框中的内容。
243
+
244
+ - 作用:模拟退格键
245
+ - 行为:优先删除当前选区;没有选区时删除光标前一个字符
246
+ - 副作用:删除后会更新光标位置,并派发一次 `input` 事件
247
+ - 适用场景:自定义虚拟键盘点击删除键,或长按连续删除
248
+
249
+ #### `moveCursor(inputElement, index)`
250
+
251
+ ```ts
252
+ function moveCursor(inputElement: TextInputElement, index: number): void
253
+ ```
254
+
255
+ 移动输入框光标到指定位置。
256
+
257
+ - 作用:同时设置 `selectionStart` 和 `selectionEnd`
258
+ - 入参:`index` 为目标光标位置
259
+ - 适用场景:插入文本后同步光标,或实现左右移动光标能力
260
+
261
+ ### 键盘定位
262
+
263
+ #### `calculateKeyboardPosition(inputElement, keyboardElement, positionMode, floatMarginTop?)`
264
+
265
+ ```ts
266
+ function calculateKeyboardPosition(
267
+ inputElement: HTMLElement | null,
268
+ keyboardElement: HTMLElement | null,
269
+ positionMode: 'static' | 'float' | 'bottom',
270
+ floatMarginTop?: number,
271
+ ): KeyboardPosition | null
272
+ ```
273
+
274
+ 根据定位模式计算键盘 DOM 的位置。
275
+
276
+ - 返回:`{ top: string, left: string } | null`
277
+ - `static`:不计算位置,直接返回 `null`
278
+ - `bottom`:固定在窗口底部,`left` 为 `0`
279
+ - `float`:根据输入框位置浮动显示,并自动限制在视口范围内
280
+ - `floatMarginTop`:浮动模式下,键盘与输入框底部之间的额外距离
281
+ - 适用场景:浮动键盘、底部固定键盘的定位计算
282
+
283
+ ### 长按连发
284
+
285
+ #### `createKeyRepeater(options?)`
286
+
287
+ ```ts
288
+ function createKeyRepeater(options?: KeyRepeatOptions): KeyRepeater
289
+ ```
290
+
291
+ 创建一个“长按后连续触发”的小工具。
292
+
293
+ - 返回:`KeyRepeater`
294
+ - 用途:实现删除键长按连续删除、方向键长按连续移动等行为
295
+ - 默认行为:
296
+ - `start(action)` 调用时会先立即执行一次 `action`
297
+ - 之后等待 `delay` 毫秒
298
+ - 再按 `interval` 的间隔重复执行 `action`
299
+ - 调用 `stop()` 后停止重复
300
+
301
+ 可选参数:
302
+
303
+ - `delay`:开始重复前的等待时间,默认 `400ms`
304
+ - `interval`:重复执行间隔,默认 `60ms`
305
+
306
+ 返回对象:
307
+
308
+ - `start(action)`:启动连发
309
+ - `stop()`:停止连发并清理定时器
310
+
311
+ 示例:
312
+
313
+ ```ts
314
+ import { createKeyRepeater } from '@tdh-keyboard/core'
315
+
316
+ const repeater = createKeyRepeater({
317
+ delay: 300,
318
+ interval: 50,
319
+ })
320
+
321
+ button.addEventListener('mousedown', () => {
322
+ repeater.start(() => {
323
+ console.log('repeat')
324
+ })
325
+ })
326
+
327
+ button.addEventListener('mouseup', () => {
328
+ repeater.stop()
329
+ })
330
+ ```
331
+
332
+ ### 手写画布
333
+
334
+ #### `new CanvasDrawer(canvas, options?)`
335
+
336
+ ```ts
337
+ new CanvasDrawer(canvas: HTMLCanvasElement, options?: CanvasDrawerOptions)
338
+ ```
339
+
340
+ 创建一个手写画布控制器。
341
+
342
+ - 作用:接管一个 `canvas`,自动处理鼠标和触摸绘制
343
+ - 能力:记录笔迹、绘制网格、在停止书写后自动清空
344
+ - 适用场景:手写输入面板
345
+
346
+ 可选参数:
347
+
348
+ - `onDrawEnd`:每次一笔书写结束后的回调
349
+ - `clearDelay`:停止书写后自动清空的延迟,默认 `1000ms`
350
+
351
+ 示例:
352
+
353
+ ```ts
354
+ import { CanvasDrawer } from '@tdh-keyboard/core'
355
+
356
+ const canvas = document.querySelector('canvas')
357
+
358
+ if (canvas) {
359
+ const drawer = new CanvasDrawer(canvas, {
360
+ onDrawEnd() {
361
+ console.log(drawer.getStrokeData())
362
+ },
363
+ clearDelay: 1500,
364
+ })
365
+ }
366
+ ```
367
+
368
+ 常用实例方法:
369
+
370
+ #### `canvasDrawer.clearCanvas()`
371
+
372
+ ```ts
373
+ canvasDrawer.clearCanvas(): void
374
+ ```
375
+
376
+ 清空画布并重绘辅助网格,同时清空当前笔迹数据。
377
+
378
+ #### `canvasDrawer.getStrokeData()`
379
+
380
+ ```ts
381
+ canvasDrawer.getStrokeData(): ReadonlyArray<number>
382
+ ```
383
+
384
+ 获取当前笔迹数据。
385
+
386
+ - 返回:只读数组 `ReadonlyArray<number>`
387
+ - 数据格式:`[x1, y1, c1, x2, y2, c2, ...]`
388
+ - 其中 `c` 为笔画结束标记,`1` 表示该点是当前笔画最后一点
389
+
390
+ #### `canvasDrawer.startClearTimer()`
391
+
392
+ ```ts
393
+ canvasDrawer.startClearTimer(): void
394
+ ```
395
+
396
+ 启动自动清空计时器,常用于一笔书写结束后延迟清屏。
397
+
398
+ #### `canvasDrawer.resetClearTimer()`
399
+
400
+ ```ts
401
+ canvasDrawer.resetClearTimer(): void
402
+ ```
403
+
404
+ 取消当前自动清空计时器。
405
+
406
+ #### `canvasDrawer.destroy()`
407
+
408
+ ```ts
409
+ canvasDrawer.destroy(): void
410
+ ```
411
+
412
+ 销毁画布控制器。
413
+
414
+ - 作用:移除已绑定的鼠标和触摸事件,并清理定时器
415
+ - 适用场景:组件卸载时释放资源
416
+
417
+ #### `canvasDrawer.getCanvas()`
418
+
419
+ ```ts
420
+ canvasDrawer.getCanvas(): HTMLCanvasElement
421
+ ```
422
+
423
+ 返回构造时传入的 `canvas` 元素。
424
+
425
+ #### `canvasDrawer.getContext()`
426
+
427
+ ```ts
428
+ canvasDrawer.getContext(): CanvasRenderingContext2D
429
+ ```
430
+
431
+ 返回内部使用的 `CanvasRenderingContext2D`。
432
+
433
+ ## 类型接口
434
+
435
+ ### `KeyboardConfig`
436
+
437
+ 键盘全局配置类型,用于 `setKeyboardConfig()`。
438
+
439
+ | 字段 | 类型 | 说明 |
440
+ | --- | --- | --- |
441
+ | `defaultMode` | `'en' \| 'zh' \| 'hand' \| 'num' \| 'symbol'` | 默认键盘模式 |
442
+ | `enableHandwriting` | `boolean` | 是否启用手写输入 |
443
+ | `position` | `'static' \| 'float' \| 'bottom'` | 键盘定位方式 |
444
+ | `floatMarginTop` | `number` | 浮动模式的顶部间距 |
445
+ | `disableWhenNoFocus` | `boolean` | 无聚焦输入框时是否禁用键盘 |
446
+ | `manual` | `boolean` | 是否手动控制打开关闭 |
447
+ | `numKeys` | `string[][]` | 数字键盘布局 |
448
+ | `wasmDir` | `string` | 默认 RIME 资源目录 |
449
+
450
+ ### `PinyinEngine`
451
+
452
+ 拼音引擎的通用接口,供自定义实现接入。
453
+
454
+ - `initialize()`:初始化引擎资源
455
+ - `processInput(pinyin)`:处理完整拼音串,返回候选状态
456
+ - `pickCandidate(index)`:按候选索引选词
457
+ - `clearInput()`:清空当前拼音组合
458
+ - `setSimplified?(simplified)`:可选,实现简繁切换
459
+ - `destroy()`:销毁引擎
460
+
461
+ ```ts
462
+ import type { PinyinEngine } from '@tdh-keyboard/core'
463
+
464
+ class CustomPinyinEngine implements PinyinEngine {
465
+ async initialize() {}
466
+ async processInput(pinyin: string) {
467
+ return {
468
+ committed: null,
469
+ preeditHead: '',
470
+ preeditBody: pinyin,
471
+ preeditTail: '',
472
+ cursorPos: pinyin.length,
473
+ candidates: [],
474
+ pageNo: 0,
475
+ isLastPage: true,
476
+ highlightedIndex: 0,
477
+ selectLabels: [],
478
+ }
479
+ }
480
+ async pickCandidate() {
481
+ throw new Error('Not implemented')
482
+ }
483
+ async clearInput() {}
484
+ async destroy() {}
485
+ }
486
+ ```
487
+
488
+ ### `PinyinState`
489
+
490
+ 拼音候选状态。
491
+
492
+ | 字段 | 类型 | 说明 |
493
+ | --- | --- | --- |
494
+ | `committed` | `string \| null` | 已提交文本 |
495
+ | `preeditHead` | `string` | 光标前的预编辑文本 |
496
+ | `preeditBody` | `string` | 当前高亮的预编辑文本 |
497
+ | `preeditTail` | `string` | 光标后的预编辑文本 |
498
+ | `cursorPos` | `number` | 预编辑中的光标位置 |
499
+ | `candidates` | `Candidate[]` | 当前候选项列表 |
500
+ | `pageNo` | `number` | 当前页码,从 `0` 开始 |
501
+ | `isLastPage` | `boolean` | 是否最后一页 |
502
+ | `highlightedIndex` | `number` | 当前高亮候选索引 |
503
+ | `selectLabels` | `string[]` | 候选快捷选择标签 |
504
+
505
+ ### `Candidate`
506
+
507
+ 拼音候选项类型。
508
+
509
+ | 字段 | 类型 | 说明 |
510
+ | --- | --- | --- |
511
+ | `text` | `string` | 候选文本 |
512
+ | `comment` | `string` | 候选注释 |
513
+
514
+ ### `HandwritingRecognizer`
515
+
516
+ 手写识别器的通用接口,供自定义识别服务实现。
517
+
518
+ - `initialize(options?)`:初始化识别器
519
+ - `recognize(strokeData)`:识别笔迹,返回候选文字列表
520
+ - `close()`:释放识别器资源
521
+
522
+ ```ts
523
+ import type { HandwritingRecognizer } from '@tdh-keyboard/core'
524
+
525
+ class CustomRecognizer implements HandwritingRecognizer {
526
+ async initialize() {
527
+ return true
528
+ }
529
+ async recognize(strokeData: number[]) {
530
+ return strokeData.length ? ['中'] : []
531
+ }
532
+ async close() {}
533
+ }
534
+ ```
535
+
536
+ ### `RecognizerInitOptions`
537
+
538
+ 手写识别初始化参数。
539
+
540
+ | 字段 | 类型 | 说明 |
541
+ | --- | --- | --- |
542
+ | `onProgress` | `(progress: number) => void` | 初始化进度回调,范围 `0` 到 `1` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdh-keyboard/core",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "中文虚拟键盘组件库核心包",
5
5
  "author": "tdh",
6
6
  "license": "Apache 2.0",
@@ -21,7 +21,8 @@
21
21
  "module": "dist/index.mjs",
22
22
  "types": "dist/index.d.ts",
23
23
  "files": [
24
- "dist"
24
+ "dist",
25
+ "README.md"
25
26
  ],
26
27
  "devDependencies": {
27
28
  "tsdown": "^0.21.2",